//! ZigObject encapsulates the state of the incrementally compiled Zig module. //! It stores the associated input local and global symbols, allocated atoms, //! and any relocations that may have been emitted. /// For error reporting purposes only. path: Path, /// Map of all `Nav` that are currently alive. /// Each index maps to the corresponding `NavInfo`. navs: std.AutoHashMapUnmanaged(InternPool.Nav.Index, NavInfo) = .empty, /// List of function type signatures for this Zig module. func_types: std.ArrayListUnmanaged(std.wasm.Type) = .empty, /// List of `std.wasm.Func`. Each entry contains the function signature, /// rather than the actual body. functions: std.ArrayListUnmanaged(std.wasm.Func) = .empty, /// List of indexes pointing to an entry within the `functions` list which has been removed. functions_free_list: std.ArrayListUnmanaged(u32) = .empty, /// Map of symbol locations, represented by its `Wasm.Import`. imports: std.AutoHashMapUnmanaged(Symbol.Index, Wasm.Import) = .empty, /// List of WebAssembly globals. globals: std.ArrayListUnmanaged(std.wasm.Global) = .empty, /// Mapping between an `Atom` and its type index representing the Wasm /// type of the function signature. atom_types: std.AutoHashMapUnmanaged(Atom.Index, u32) = .empty, /// List of all symbols generated by Zig code. symbols: std.ArrayListUnmanaged(Symbol) = .empty, /// Map from symbol name to their index into the `symbols` list. global_syms: std.AutoHashMapUnmanaged(Wasm.String, Symbol.Index) = .empty, /// List of symbol indexes which are free to be used. symbols_free_list: std.ArrayListUnmanaged(Symbol.Index) = .empty, /// Extra metadata about the linking section, such as alignment of segments and their name. segment_info: std.ArrayListUnmanaged(Wasm.NamedSegment) = .empty, /// List of indexes which contain a free slot in the `segment_info` list. segment_free_list: std.ArrayListUnmanaged(u32) = .empty, /// Map for storing anonymous declarations. Each anonymous decl maps to its Atom's index. uavs: std.AutoArrayHashMapUnmanaged(InternPool.Index, Atom.Index) = .empty, /// List of atom indexes of functions that are generated by the backend. synthetic_functions: std.ArrayListUnmanaged(Atom.Index) = .empty, /// Represents the symbol index of the error name table /// When this is `null`, no code references an error using runtime `@errorName`. /// During initializion, a symbol with corresponding atom will be created that is /// used to perform relocations to the pointer of this table. /// The actual table is populated during `flush`. error_table_symbol: Symbol.Index = .null, /// Atom index of the table of symbol names. This is stored so we can clean up the atom. error_names_atom: Atom.Index = .null, /// Amount of functions in the `import` sections. imported_functions_count: u32 = 0, /// Amount of globals in the `import` section. imported_globals_count: u32 = 0, /// Symbol index representing the stack pointer. This will be set upon initializion /// of a new `ZigObject`. Codegen will make calls into this to create relocations for /// this symbol each time the stack pointer is moved. stack_pointer_sym: Symbol.Index, /// Debug information for the Zig module. dwarf: ?Dwarf = null, // Debug section atoms. These are only set when the current compilation // unit contains Zig code. The lifetime of these atoms are extended // until the end of the compiler's lifetime. Meaning they're not freed // during `flush()` in incremental-mode. debug_info_atom: ?Atom.Index = null, debug_line_atom: ?Atom.Index = null, debug_loc_atom: ?Atom.Index = null, debug_ranges_atom: ?Atom.Index = null, debug_abbrev_atom: ?Atom.Index = null, debug_str_atom: ?Atom.Index = null, debug_pubnames_atom: ?Atom.Index = null, debug_pubtypes_atom: ?Atom.Index = null, /// The index of the segment representing the custom '.debug_info' section. debug_info_index: ?u32 = null, /// The index of the segment representing the custom '.debug_line' section. debug_line_index: ?u32 = null, /// The index of the segment representing the custom '.debug_loc' section. debug_loc_index: ?u32 = null, /// The index of the segment representing the custom '.debug_ranges' section. debug_ranges_index: ?u32 = null, /// The index of the segment representing the custom '.debug_pubnames' section. debug_pubnames_index: ?u32 = null, /// The index of the segment representing the custom '.debug_pubtypes' section. debug_pubtypes_index: ?u32 = null, /// The index of the segment representing the custom '.debug_pubtypes' section. debug_str_index: ?u32 = null, /// The index of the segment representing the custom '.debug_pubtypes' section. debug_abbrev_index: ?u32 = null, const NavInfo = struct { atom: Atom.Index = .null, exports: std.ArrayListUnmanaged(Symbol.Index) = .empty, fn @"export"(ni: NavInfo, zo: *const ZigObject, name: Wasm.String) ?Symbol.Index { for (ni.exports.items) |sym_index| { if (zo.symbol(sym_index).name == name) return sym_index; } return null; } fn appendExport(ni: *NavInfo, gpa: std.mem.Allocator, sym_index: Symbol.Index) !void { return ni.exports.append(gpa, sym_index); } fn deleteExport(ni: *NavInfo, sym_index: Symbol.Index) void { for (ni.exports.items, 0..) |idx, index| { if (idx == sym_index) { _ = ni.exports.swapRemove(index); return; } } unreachable; // invalid sym_index } }; /// Initializes the `ZigObject` with initial symbols. pub fn init(zig_object: *ZigObject, wasm: *Wasm) !void { // Initialize an undefined global with the name __stack_pointer. Codegen will use // this to generate relocations when moving the stack pointer. This symbol will be // resolved automatically by the final linking stage. try zig_object.createStackPointer(wasm); // TODO: Initialize debug information when we reimplement Dwarf support. } fn createStackPointer(zig_object: *ZigObject, wasm: *Wasm) !void { const gpa = wasm.base.comp.gpa; const sym_index = try zig_object.getGlobalSymbol(gpa, wasm.preloaded_strings.__stack_pointer); const sym = zig_object.symbol(sym_index); sym.index = zig_object.imported_globals_count; sym.tag = .global; const is_wasm32 = wasm.base.comp.root_mod.resolved_target.result.cpu.arch == .wasm32; try zig_object.imports.putNoClobber(gpa, sym_index, .{ .name = sym.name, .module_name = wasm.host_name, .kind = .{ .global = .{ .valtype = if (is_wasm32) .i32 else .i64, .mutable = true } }, }); zig_object.imported_globals_count += 1; zig_object.stack_pointer_sym = sym_index; } pub fn symbol(zig_object: *const ZigObject, index: Symbol.Index) *Symbol { return &zig_object.symbols.items[@intFromEnum(index)]; } /// Frees and invalidates all memory of the incrementally compiled Zig module. /// It is illegal behavior to access the `ZigObject` after calling `deinit`. pub fn deinit(zig_object: *ZigObject, wasm: *Wasm) void { const gpa = wasm.base.comp.gpa; for (zig_object.segment_info.items) |segment_info| { gpa.free(segment_info.name); } { var it = zig_object.navs.valueIterator(); while (it.next()) |nav_info| { const atom = wasm.getAtomPtr(nav_info.atom); for (atom.locals.items) |local_index| { const local_atom = wasm.getAtomPtr(local_index); local_atom.deinit(gpa); } atom.deinit(gpa); nav_info.exports.deinit(gpa); } } { for (zig_object.uavs.values()) |atom_index| { const atom = wasm.getAtomPtr(atom_index); for (atom.locals.items) |local_index| { const local_atom = wasm.getAtomPtr(local_index); local_atom.deinit(gpa); } atom.deinit(gpa); } } if (zig_object.global_syms.get(wasm.preloaded_strings.__zig_errors_len)) |sym_index| { const atom_index = wasm.symbol_atom.get(.{ .file = .zig_object, .index = sym_index }).?; wasm.getAtomPtr(atom_index).deinit(gpa); } if (wasm.symbol_atom.get(.{ .file = .zig_object, .index = zig_object.error_table_symbol })) |atom_index| { const atom = wasm.getAtomPtr(atom_index); atom.deinit(gpa); } for (zig_object.synthetic_functions.items) |atom_index| { const atom = wasm.getAtomPtr(atom_index); atom.deinit(gpa); } zig_object.synthetic_functions.deinit(gpa); for (zig_object.func_types.items) |*ty| { ty.deinit(gpa); } if (zig_object.error_names_atom != .null) { const atom = wasm.getAtomPtr(zig_object.error_names_atom); atom.deinit(gpa); } zig_object.global_syms.deinit(gpa); zig_object.func_types.deinit(gpa); zig_object.atom_types.deinit(gpa); zig_object.functions.deinit(gpa); zig_object.imports.deinit(gpa); zig_object.navs.deinit(gpa); zig_object.uavs.deinit(gpa); zig_object.symbols.deinit(gpa); zig_object.symbols_free_list.deinit(gpa); zig_object.segment_info.deinit(gpa); zig_object.segment_free_list.deinit(gpa); if (zig_object.dwarf) |*dwarf| { dwarf.deinit(); } gpa.free(zig_object.path.sub_path); zig_object.* = undefined; } /// Allocates a new symbol and returns its index. /// Will re-use slots when a symbol was freed at an earlier stage. pub fn allocateSymbol(zig_object: *ZigObject, gpa: std.mem.Allocator) !Symbol.Index { try zig_object.symbols.ensureUnusedCapacity(gpa, 1); const sym: Symbol = .{ .name = undefined, // will be set after updateDecl as well as during atom creation for decls .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL), .tag = .undefined, // will be set after updateDecl .index = std.math.maxInt(u32), // will be set during atom parsing .virtual_address = std.math.maxInt(u32), // will be set during atom allocation }; if (zig_object.symbols_free_list.popOrNull()) |index| { zig_object.symbols.items[@intFromEnum(index)] = sym; return index; } const index: Symbol.Index = @enumFromInt(zig_object.symbols.items.len); zig_object.symbols.appendAssumeCapacity(sym); return index; } // Generate code for the `Nav`, storing it in memory to be later written to // the file on flush(). pub fn updateNav( zig_object: *ZigObject, wasm: *Wasm, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index, ) !void { const zcu = pt.zcu; const ip = &zcu.intern_pool; const nav = ip.getNav(nav_index); const nav_val = zcu.navValue(nav_index); const is_extern, const lib_name, const nav_init = switch (ip.indexToKey(nav_val.toIntern())) { .variable => |variable| .{ false, variable.lib_name, Value.fromInterned(variable.init) }, .func => return, .@"extern" => |@"extern"| if (ip.isFunctionType(nav.typeOf(ip))) return else .{ true, @"extern".lib_name, nav_val }, else => .{ false, .none, nav_val }, }; if (nav_init.typeOf(zcu).hasRuntimeBits(zcu)) { const gpa = wasm.base.comp.gpa; const atom_index = try zig_object.getOrCreateAtomForNav(wasm, pt, nav_index); const atom = wasm.getAtomPtr(atom_index); atom.clear(); if (is_extern) return zig_object.addOrUpdateImport(wasm, nav.name.toSlice(ip), atom.sym_index, lib_name.toSlice(ip), null); var code_writer = std.ArrayList(u8).init(gpa); defer code_writer.deinit(); const res = try codegen.generateSymbol( &wasm.base, pt, zcu.navSrcLoc(nav_index), nav_init, &code_writer, .{ .atom_index = @intFromEnum(atom.sym_index) }, ); const code = switch (res) { .ok => code_writer.items, .fail => |em| { try zcu.failed_codegen.put(zcu.gpa, nav_index, em); return; }, }; try zig_object.finishUpdateNav(wasm, pt, nav_index, code); } } pub fn updateFunc( zig_object: *ZigObject, wasm: *Wasm, pt: Zcu.PerThread, func_index: InternPool.Index, air: Air, liveness: Liveness, ) !void { const zcu = pt.zcu; const gpa = zcu.gpa; const func = pt.zcu.funcInfo(func_index); const atom_index = try zig_object.getOrCreateAtomForNav(wasm, pt, func.owner_nav); const atom = wasm.getAtomPtr(atom_index); atom.clear(); var code_writer = std.ArrayList(u8).init(gpa); defer code_writer.deinit(); const result = try codegen.generateFunction( &wasm.base, pt, zcu.navSrcLoc(func.owner_nav), func_index, air, liveness, &code_writer, .none, ); const code = switch (result) { .ok => code_writer.items, .fail => |em| { try pt.zcu.failed_codegen.put(gpa, func.owner_nav, em); return; }, }; return zig_object.finishUpdateNav(wasm, pt, func.owner_nav, code); } fn finishUpdateNav( zig_object: *ZigObject, wasm: *Wasm, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index, code: []const u8, ) !void { const zcu = pt.zcu; const ip = &zcu.intern_pool; const gpa = zcu.gpa; const nav = ip.getNav(nav_index); const nav_val = zcu.navValue(nav_index); const nav_info = zig_object.navs.get(nav_index).?; const atom_index = nav_info.atom; const atom = wasm.getAtomPtr(atom_index); const sym = zig_object.symbol(atom.sym_index); sym.name = try wasm.internString(nav.fqn.toSlice(ip)); try atom.code.appendSlice(gpa, code); atom.size = @intCast(code.len); if (ip.isFunctionType(nav.typeOf(ip))) { sym.index = try zig_object.appendFunction(gpa, .{ .type_index = zig_object.atom_types.get(atom_index).? }); sym.tag = .function; } else { const is_const, const nav_init = switch (ip.indexToKey(nav_val.toIntern())) { .variable => |variable| .{ false, variable.init }, .@"extern" => |@"extern"| .{ @"extern".is_const, .none }, else => .{ true, nav_val.toIntern() }, }; const segment_name = name: { if (is_const) break :name ".rodata."; if (nav_init != .none and Value.fromInterned(nav_init).isUndefDeep(zcu)) { break :name switch (zcu.navFileScope(nav_index).mod.optimize_mode) { .Debug, .ReleaseSafe => ".data.", .ReleaseFast, .ReleaseSmall => ".bss.", }; } // when the decl is all zeroes, we store the atom in the bss segment, // in all other cases it will be in the data segment. for (atom.code.items) |byte| { if (byte != 0) break :name ".data."; } break :name ".bss."; }; if ((wasm.base.isObject() or wasm.base.comp.config.import_memory) and std.mem.startsWith(u8, segment_name, ".bss")) { @memset(atom.code.items, 0); } // Will be freed upon freeing of decl or after cleanup of Wasm binary. const full_segment_name = try std.mem.concat(gpa, u8, &.{ segment_name, nav.fqn.toSlice(ip), }); errdefer gpa.free(full_segment_name); sym.tag = .data; sym.index = try zig_object.createDataSegment(gpa, full_segment_name, pt.navAlignment(nav_index)); } if (code.len == 0) return; atom.alignment = pt.navAlignment(nav_index); } /// Creates and initializes a new segment in the 'Data' section. /// Reuses free slots in the list of segments and returns the index. fn createDataSegment( zig_object: *ZigObject, gpa: std.mem.Allocator, name: []const u8, alignment: InternPool.Alignment, ) !u32 { const segment_index: u32 = if (zig_object.segment_free_list.popOrNull()) |index| index else index: { const idx: u32 = @intCast(zig_object.segment_info.items.len); _ = try zig_object.segment_info.addOne(gpa); break :index idx; }; zig_object.segment_info.items[segment_index] = .{ .alignment = alignment, .flags = 0, .name = name, }; return segment_index; } /// For a given `InternPool.Nav.Index` returns its corresponding `Atom.Index`. /// When the index was not found, a new `Atom` will be created, and its index will be returned. /// The newly created Atom is empty with default fields as specified by `Atom.empty`. pub fn getOrCreateAtomForNav( zig_object: *ZigObject, wasm: *Wasm, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index, ) !Atom.Index { const ip = &pt.zcu.intern_pool; const gpa = pt.zcu.gpa; const gop = try zig_object.navs.getOrPut(gpa, nav_index); if (!gop.found_existing) { const sym_index = try zig_object.allocateSymbol(gpa); gop.value_ptr.* = .{ .atom = try wasm.createAtom(sym_index, .zig_object) }; const nav = ip.getNav(nav_index); const sym = zig_object.symbol(sym_index); sym.name = try wasm.internString(nav.fqn.toSlice(ip)); } return gop.value_ptr.atom; } pub fn lowerUav( zig_object: *ZigObject, wasm: *Wasm, pt: Zcu.PerThread, uav: InternPool.Index, explicit_alignment: InternPool.Alignment, src_loc: Zcu.LazySrcLoc, ) !codegen.GenResult { const gpa = wasm.base.comp.gpa; const gop = try zig_object.uavs.getOrPut(gpa, uav); if (!gop.found_existing) { var name_buf: [32]u8 = undefined; const name = std.fmt.bufPrint(&name_buf, "__anon_{d}", .{ @intFromEnum(uav), }) catch unreachable; switch (try zig_object.lowerConst(wasm, pt, name, Value.fromInterned(uav), src_loc)) { .ok => |atom_index| zig_object.uavs.values()[gop.index] = atom_index, .fail => |em| return .{ .fail = em }, } } const atom = wasm.getAtomPtr(zig_object.uavs.values()[gop.index]); atom.alignment = switch (atom.alignment) { .none => explicit_alignment, else => switch (explicit_alignment) { .none => atom.alignment, else => atom.alignment.maxStrict(explicit_alignment), }, }; return .{ .mcv = .{ .load_symbol = @intFromEnum(atom.sym_index) } }; } const LowerConstResult = union(enum) { ok: Atom.Index, fail: *Zcu.ErrorMsg, }; fn lowerConst( zig_object: *ZigObject, wasm: *Wasm, pt: Zcu.PerThread, name: []const u8, val: Value, src_loc: Zcu.LazySrcLoc, ) !LowerConstResult { const gpa = wasm.base.comp.gpa; const zcu = wasm.base.comp.zcu.?; const ty = val.typeOf(zcu); // Create and initialize a new local symbol and atom const sym_index = try zig_object.allocateSymbol(gpa); const atom_index = try wasm.createAtom(sym_index, .zig_object); var value_bytes = std.ArrayList(u8).init(gpa); defer value_bytes.deinit(); const code = code: { const atom = wasm.getAtomPtr(atom_index); atom.alignment = ty.abiAlignment(zcu); const segment_name = try std.mem.concat(gpa, u8, &.{ ".rodata.", name }); errdefer gpa.free(segment_name); zig_object.symbol(sym_index).* = .{ .name = try wasm.internString(name), .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL), .tag = .data, .index = try zig_object.createDataSegment( gpa, segment_name, ty.abiAlignment(zcu), ), .virtual_address = undefined, }; const result = try codegen.generateSymbol( &wasm.base, pt, src_loc, val, &value_bytes, .{ .atom_index = @intFromEnum(atom.sym_index) }, ); break :code switch (result) { .ok => value_bytes.items, .fail => |em| { return .{ .fail = em }; }, }; }; const atom = wasm.getAtomPtr(atom_index); atom.size = @intCast(code.len); try atom.code.appendSlice(gpa, code); return .{ .ok = atom_index }; } /// Returns the symbol index of the error name table. /// /// When the symbol does not yet exist, it will create a new one instead. pub fn getErrorTableSymbol(zig_object: *ZigObject, wasm: *Wasm, pt: Zcu.PerThread) !Symbol.Index { if (zig_object.error_table_symbol != .null) { return zig_object.error_table_symbol; } // no error was referenced yet, so create a new symbol and atom for it // and then return said symbol's index. The final table will be populated // during `flush` when we know all possible error names. const gpa = wasm.base.comp.gpa; const sym_index = try zig_object.allocateSymbol(gpa); const atom_index = try wasm.createAtom(sym_index, .zig_object); const atom = wasm.getAtomPtr(atom_index); const slice_ty = Type.slice_const_u8_sentinel_0; atom.alignment = slice_ty.abiAlignment(pt.zcu); const segment_name = try gpa.dupe(u8, ".rodata.__zig_err_name_table"); const sym = zig_object.symbol(sym_index); sym.* = .{ .name = wasm.preloaded_strings.__zig_err_name_table, .tag = .data, .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL), .index = try zig_object.createDataSegment(gpa, segment_name, atom.alignment), .virtual_address = undefined, }; log.debug("Error name table was created with symbol index: ({d})", .{@intFromEnum(sym_index)}); zig_object.error_table_symbol = sym_index; return sym_index; } /// Populates the error name table, when `error_table_symbol` is not null. /// /// This creates a table that consists of pointers and length to each error name. /// The table is what is being pointed to within the runtime bodies that are generated. fn populateErrorNameTable(zig_object: *ZigObject, wasm: *Wasm, tid: Zcu.PerThread.Id) !void { if (zig_object.error_table_symbol == .null) return; const gpa = wasm.base.comp.gpa; const atom_index = wasm.symbol_atom.get(.{ .file = .zig_object, .index = zig_object.error_table_symbol }).?; // Rather than creating a symbol for each individual error name, // we create a symbol for the entire region of error names. We then calculate // the pointers into the list using addends which are appended to the relocation. const names_sym_index = try zig_object.allocateSymbol(gpa); const names_atom_index = try wasm.createAtom(names_sym_index, .zig_object); const names_atom = wasm.getAtomPtr(names_atom_index); names_atom.alignment = .@"1"; const segment_name = try gpa.dupe(u8, ".rodata.__zig_err_names"); const names_symbol = zig_object.symbol(names_sym_index); names_symbol.* = .{ .name = wasm.preloaded_strings.__zig_err_names, .tag = .data, .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL), .index = try zig_object.createDataSegment(gpa, segment_name, names_atom.alignment), .virtual_address = undefined, }; log.debug("Populating error names", .{}); // Addend for each relocation to the table var addend: u32 = 0; const pt: Zcu.PerThread = .activate(wasm.base.comp.zcu.?, tid); defer pt.deactivate(); const slice_ty = Type.slice_const_u8_sentinel_0; const atom = wasm.getAtomPtr(atom_index); { // TODO: remove this unreachable entry try atom.code.appendNTimes(gpa, 0, 4); try atom.code.writer(gpa).writeInt(u32, 0, .little); atom.size += @intCast(slice_ty.abiSize(pt.zcu)); addend += 1; try names_atom.code.append(gpa, 0); } const ip = &pt.zcu.intern_pool; for (ip.global_error_set.getNamesFromMainThread()) |error_name| { const error_name_slice = error_name.toSlice(ip); const len: u32 = @intCast(error_name_slice.len + 1); // names are 0-terminated const offset = @as(u32, @intCast(atom.code.items.len)); // first we create the data for the slice of the name try atom.code.appendNTimes(gpa, 0, 4); // ptr to name, will be relocated try atom.code.writer(gpa).writeInt(u32, len - 1, .little); // create relocation to the error name try atom.relocs.append(gpa, .{ .index = @intFromEnum(names_atom.sym_index), .relocation_type = .R_WASM_MEMORY_ADDR_I32, .offset = offset, .addend = @intCast(addend), }); atom.size += @intCast(slice_ty.abiSize(pt.zcu)); addend += len; // as we updated the error name table, we now store the actual name within the names atom try names_atom.code.ensureUnusedCapacity(gpa, len); names_atom.code.appendSliceAssumeCapacity(error_name_slice[0..len]); log.debug("Populated error name: '{}'", .{error_name.fmt(ip)}); } names_atom.size = addend; zig_object.error_names_atom = names_atom_index; } /// Either creates a new import, or updates one if existing. /// When `type_index` is non-null, we assume an external function. /// In all other cases, a data-symbol will be created instead. pub fn addOrUpdateImport( zig_object: *ZigObject, wasm: *Wasm, /// Name of the import name: []const u8, /// Symbol index that is external symbol_index: Symbol.Index, /// Optional library name (i.e. `extern "c" fn foo() void` lib_name: ?[:0]const u8, /// The index of the type that represents the function signature /// when the extern is a function. When this is null, a data-symbol /// is asserted instead. type_index: ?u32, ) !void { const gpa = wasm.base.comp.gpa; std.debug.assert(symbol_index != .null); // For the import name, we use the decl's name, rather than the fully qualified name // Also mangle the name when the lib name is set and not equal to "C" so imports with the same // name but different module can be resolved correctly. const mangle_name = if (lib_name) |n| !std.mem.eql(u8, n, "c") else false; const full_name = if (mangle_name) try std.fmt.allocPrint(gpa, "{s}|{s}", .{ name, lib_name.? }) else name; defer if (mangle_name) gpa.free(full_name); const decl_name_index = try wasm.internString(full_name); const sym: *Symbol = &zig_object.symbols.items[@intFromEnum(symbol_index)]; sym.setUndefined(true); sym.setGlobal(true); sym.name = decl_name_index; if (mangle_name) { // we specified a specific name for the symbol that does not match the import name sym.setFlag(.WASM_SYM_EXPLICIT_NAME); } if (type_index) |ty_index| { const gop = try zig_object.imports.getOrPut(gpa, symbol_index); const module_name = if (lib_name) |n| try wasm.internString(n) else wasm.host_name; if (!gop.found_existing) zig_object.imported_functions_count += 1; gop.value_ptr.* = .{ .module_name = module_name, .name = try wasm.internString(name), .kind = .{ .function = ty_index }, }; sym.tag = .function; } else { sym.tag = .data; } } /// Returns the symbol index from a symbol of which its flag is set global, /// such as an exported or imported symbol. /// If the symbol does not yet exist, creates a new one symbol instead /// and then returns the index to it. pub fn getGlobalSymbol(zig_object: *ZigObject, gpa: std.mem.Allocator, name_index: Wasm.String) !Symbol.Index { const gop = try zig_object.global_syms.getOrPut(gpa, name_index); if (gop.found_existing) { return gop.value_ptr.*; } var sym: Symbol = .{ .name = name_index, .flags = 0, .index = undefined, // index to type will be set after merging symbols .tag = .function, .virtual_address = std.math.maxInt(u32), }; sym.setGlobal(true); sym.setUndefined(true); const sym_index = if (zig_object.symbols_free_list.popOrNull()) |index| index else blk: { const index: Symbol.Index = @enumFromInt(zig_object.symbols.items.len); try zig_object.symbols.ensureUnusedCapacity(gpa, 1); zig_object.symbols.items.len += 1; break :blk index; }; zig_object.symbol(sym_index).* = sym; gop.value_ptr.* = sym_index; return sym_index; } /// For a given decl, find the given symbol index's atom, and create a relocation for the type. /// Returns the given pointer address pub fn getNavVAddr( zig_object: *ZigObject, wasm: *Wasm, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index, reloc_info: link.File.RelocInfo, ) !u64 { const zcu = pt.zcu; const ip = &zcu.intern_pool; const gpa = zcu.gpa; const nav = ip.getNav(nav_index); const target = &zcu.navFileScope(nav_index).mod.resolved_target.result; const target_atom_index = try zig_object.getOrCreateAtomForNav(wasm, pt, nav_index); const target_atom = wasm.getAtom(target_atom_index); const target_symbol_index = @intFromEnum(target_atom.sym_index); switch (ip.indexToKey(nav.status.resolved.val)) { .@"extern" => |@"extern"| try zig_object.addOrUpdateImport( wasm, nav.name.toSlice(ip), target_atom.sym_index, @"extern".lib_name.toSlice(ip), null, ), else => {}, } std.debug.assert(reloc_info.parent.atom_index != 0); const atom_index = wasm.symbol_atom.get(.{ .file = .zig_object, .index = @enumFromInt(reloc_info.parent.atom_index), }).?; const atom = wasm.getAtomPtr(atom_index); const is_wasm32 = target.cpu.arch == .wasm32; if (ip.isFunctionType(ip.getNav(nav_index).typeOf(ip))) { std.debug.assert(reloc_info.addend == 0); // addend not allowed for function relocations try atom.relocs.append(gpa, .{ .index = target_symbol_index, .offset = @intCast(reloc_info.offset), .relocation_type = if (is_wasm32) .R_WASM_TABLE_INDEX_I32 else .R_WASM_TABLE_INDEX_I64, }); } else { try atom.relocs.append(gpa, .{ .index = target_symbol_index, .offset = @intCast(reloc_info.offset), .relocation_type = if (is_wasm32) .R_WASM_MEMORY_ADDR_I32 else .R_WASM_MEMORY_ADDR_I64, .addend = @intCast(reloc_info.addend), }); } // we do not know the final address at this point, // as atom allocation will determine the address and relocations // will calculate and rewrite this. Therefore, we simply return the symbol index // that was targeted. return target_symbol_index; } pub fn getUavVAddr( zig_object: *ZigObject, wasm: *Wasm, uav: InternPool.Index, reloc_info: link.File.RelocInfo, ) !u64 { const gpa = wasm.base.comp.gpa; const target = wasm.base.comp.root_mod.resolved_target.result; const atom_index = zig_object.uavs.get(uav).?; const target_symbol_index = @intFromEnum(wasm.getAtom(atom_index).sym_index); const parent_atom_index = wasm.symbol_atom.get(.{ .file = .zig_object, .index = @enumFromInt(reloc_info.parent.atom_index), }).?; const parent_atom = wasm.getAtomPtr(parent_atom_index); const is_wasm32 = target.cpu.arch == .wasm32; const zcu = wasm.base.comp.zcu.?; const ty = Type.fromInterned(zcu.intern_pool.typeOf(uav)); if (ty.zigTypeTag(zcu) == .@"fn") { std.debug.assert(reloc_info.addend == 0); // addend not allowed for function relocations try parent_atom.relocs.append(gpa, .{ .index = target_symbol_index, .offset = @intCast(reloc_info.offset), .relocation_type = if (is_wasm32) .R_WASM_TABLE_INDEX_I32 else .R_WASM_TABLE_INDEX_I64, }); } else { try parent_atom.relocs.append(gpa, .{ .index = target_symbol_index, .offset = @intCast(reloc_info.offset), .relocation_type = if (is_wasm32) .R_WASM_MEMORY_ADDR_I32 else .R_WASM_MEMORY_ADDR_I64, .addend = @intCast(reloc_info.addend), }); } // we do not know the final address at this point, // as atom allocation will determine the address and relocations // will calculate and rewrite this. Therefore, we simply return the symbol index // that was targeted. return target_symbol_index; } pub fn deleteExport( zig_object: *ZigObject, wasm: *Wasm, exported: Zcu.Exported, name: InternPool.NullTerminatedString, ) void { const zcu = wasm.base.comp.zcu.?; const nav_index = switch (exported) { .nav => |nav_index| nav_index, .uav => @panic("TODO: implement Wasm linker code for exporting a constant value"), }; const nav_info = zig_object.navs.getPtr(nav_index) orelse return; const name_interned = wasm.getExistingString(name.toSlice(&zcu.intern_pool)).?; if (nav_info.@"export"(zig_object, name_interned)) |sym_index| { const sym = zig_object.symbol(sym_index); nav_info.deleteExport(sym_index); std.debug.assert(zig_object.global_syms.remove(sym.name)); std.debug.assert(wasm.symbol_atom.remove(.{ .file = .zig_object, .index = sym_index })); zig_object.symbols_free_list.append(wasm.base.comp.gpa, sym_index) catch {}; sym.tag = .dead; } } pub fn updateExports( zig_object: *ZigObject, wasm: *Wasm, pt: Zcu.PerThread, exported: Zcu.Exported, export_indices: []const u32, ) !void { const zcu = pt.zcu; const ip = &zcu.intern_pool; const nav_index = switch (exported) { .nav => |nav| nav, .uav => |uav| { _ = uav; @panic("TODO: implement Wasm linker code for exporting a constant value"); }, }; const nav = ip.getNav(nav_index); const atom_index = try zig_object.getOrCreateAtomForNav(wasm, pt, nav_index); const nav_info = zig_object.navs.getPtr(nav_index).?; const atom = wasm.getAtom(atom_index); const atom_sym = wasm.symbolLocSymbol(atom.symbolLoc()).*; const gpa = zcu.gpa; log.debug("Updating exports for decl '{}'", .{nav.name.fmt(ip)}); for (export_indices) |export_idx| { const exp = zcu.all_exports.items[export_idx]; if (exp.opts.section.toSlice(ip)) |section| { try zcu.failed_exports.putNoClobber(gpa, export_idx, try Zcu.ErrorMsg.create( gpa, zcu.navSrcLoc(nav_index), "Unimplemented: ExportOptions.section '{s}'", .{section}, )); continue; } const export_name = try wasm.internString(exp.opts.name.toSlice(ip)); const sym_index = if (nav_info.@"export"(zig_object, export_name)) |idx| idx else index: { const sym_index = try zig_object.allocateSymbol(gpa); try nav_info.appendExport(gpa, sym_index); break :index sym_index; }; const sym = zig_object.symbol(sym_index); sym.setGlobal(true); sym.setUndefined(false); sym.index = atom_sym.index; sym.tag = atom_sym.tag; sym.name = export_name; switch (exp.opts.linkage) { .internal => { sym.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); }, .weak => { sym.setFlag(.WASM_SYM_BINDING_WEAK); }, .strong => {}, // symbols are strong by default .link_once => { try zcu.failed_exports.putNoClobber(gpa, export_idx, try Zcu.ErrorMsg.create( gpa, zcu.navSrcLoc(nav_index), "Unimplemented: LinkOnce", .{}, )); continue; }, } if (exp.opts.visibility == .hidden) { sym.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); } log.debug(" with name '{s}' - {}", .{ wasm.stringSlice(export_name), sym }); try zig_object.global_syms.put(gpa, export_name, sym_index); try wasm.symbol_atom.put(gpa, .{ .file = .zig_object, .index = sym_index }, atom_index); } } pub fn freeNav(zig_object: *ZigObject, wasm: *Wasm, nav_index: InternPool.Nav.Index) void { const gpa = wasm.base.comp.gpa; const zcu = wasm.base.comp.zcu.?; const ip = &zcu.intern_pool; const nav_info = zig_object.navs.getPtr(nav_index).?; const atom_index = nav_info.atom; const atom = wasm.getAtomPtr(atom_index); zig_object.symbols_free_list.append(gpa, atom.sym_index) catch {}; for (nav_info.exports.items) |exp_sym_index| { const exp_sym = zig_object.symbol(exp_sym_index); exp_sym.tag = .dead; zig_object.symbols_free_list.append(exp_sym_index) catch {}; } nav_info.exports.deinit(gpa); std.debug.assert(zig_object.navs.remove(nav_index)); const sym = &zig_object.symbols.items[atom.sym_index]; for (atom.locals.items) |local_atom_index| { const local_atom = wasm.getAtom(local_atom_index); const local_symbol = &zig_object.symbols.items[local_atom.sym_index]; std.debug.assert(local_symbol.tag == .data); zig_object.symbols_free_list.append(gpa, local_atom.sym_index) catch {}; std.debug.assert(wasm.symbol_atom.remove(local_atom.symbolLoc())); local_symbol.tag = .dead; // also for any local symbol const segment = &zig_object.segment_info.items[local_atom.sym_index]; gpa.free(segment.name); segment.name = &.{}; // Ensure no accidental double free } const nav_val = zcu.navValue(nav_index).toIntern(); if (ip.indexToKey(nav_val) == .@"extern") { std.debug.assert(zig_object.imports.remove(atom.sym_index)); } std.debug.assert(wasm.symbol_atom.remove(atom.symbolLoc())); // if (wasm.dwarf) |*dwarf| { // dwarf.freeDecl(decl_index); // } atom.prev = null; sym.tag = .dead; if (sym.isGlobal()) { std.debug.assert(zig_object.global_syms.remove(atom.sym_index)); } if (ip.isFunctionType(ip.typeOf(nav_val))) { zig_object.functions_free_list.append(gpa, sym.index) catch {}; std.debug.assert(zig_object.atom_types.remove(atom_index)); } else { zig_object.segment_free_list.append(gpa, sym.index) catch {}; const segment = &zig_object.segment_info.items[sym.index]; gpa.free(segment.name); segment.name = &.{}; // Prevent accidental double free } } fn getTypeIndex(zig_object: *const ZigObject, func_type: std.wasm.Type) ?u32 { var index: u32 = 0; while (index < zig_object.func_types.items.len) : (index += 1) { if (zig_object.func_types.items[index].eql(func_type)) return index; } return null; } /// Searches for a matching function signature. When no matching signature is found, /// a new entry will be made. The value returned is the index of the type within `wasm.func_types`. pub fn putOrGetFuncType(zig_object: *ZigObject, gpa: std.mem.Allocator, func_type: std.wasm.Type) !u32 { if (zig_object.getTypeIndex(func_type)) |index| { return index; } // functype does not exist. const index: u32 = @intCast(zig_object.func_types.items.len); const params = try gpa.dupe(std.wasm.Valtype, func_type.params); errdefer gpa.free(params); const returns = try gpa.dupe(std.wasm.Valtype, func_type.returns); errdefer gpa.free(returns); try zig_object.func_types.append(gpa, .{ .params = params, .returns = returns, }); return index; } /// Generates an atom containing the global error set' size. /// This will only be generated if the symbol exists. fn setupErrorsLen(zig_object: *ZigObject, wasm: *Wasm) !void { const gpa = wasm.base.comp.gpa; const sym_index = zig_object.global_syms.get(wasm.preloaded_strings.__zig_errors_len) orelse return; const errors_len = 1 + wasm.base.comp.zcu.?.intern_pool.global_error_set.getNamesFromMainThread().len; // overwrite existing atom if it already exists (maybe the error set has increased) // if not, allocate a new atom. const atom_index = if (wasm.symbol_atom.get(.{ .file = .zig_object, .index = sym_index })) |index| blk: { const atom = wasm.getAtomPtr(index); atom.prev = .null; atom.deinit(gpa); break :blk index; } else idx: { // We found a call to __zig_errors_len so make the symbol a local symbol // and define it, so the final binary or resulting object file will not attempt // to resolve it. const sym = zig_object.symbol(sym_index); sym.setGlobal(false); sym.setUndefined(false); sym.tag = .data; const segment_name = try gpa.dupe(u8, ".rodata.__zig_errors_len"); sym.index = try zig_object.createDataSegment(gpa, segment_name, .@"2"); break :idx try wasm.createAtom(sym_index, .zig_object); }; const atom = wasm.getAtomPtr(atom_index); atom.code.clearRetainingCapacity(); atom.sym_index = sym_index; atom.size = 2; atom.alignment = .@"2"; try atom.code.writer(gpa).writeInt(u16, @intCast(errors_len), .little); } /// Initializes symbols and atoms for the debug sections /// Initialization is only done when compiling Zig code. /// When Zig is invoked as a linker instead, the atoms /// and symbols come from the object files instead. pub fn initDebugSections(zig_object: *ZigObject) !void { if (zig_object.dwarf == null) return; // not compiling Zig code, so no need to pre-initialize debug sections std.debug.assert(zig_object.debug_info_index == null); // this will create an Atom and set the index for us. zig_object.debug_info_atom = try zig_object.createDebugSectionForIndex(&zig_object.debug_info_index, ".debug_info"); zig_object.debug_line_atom = try zig_object.createDebugSectionForIndex(&zig_object.debug_line_index, ".debug_line"); zig_object.debug_loc_atom = try zig_object.createDebugSectionForIndex(&zig_object.debug_loc_index, ".debug_loc"); zig_object.debug_abbrev_atom = try zig_object.createDebugSectionForIndex(&zig_object.debug_abbrev_index, ".debug_abbrev"); zig_object.debug_ranges_atom = try zig_object.createDebugSectionForIndex(&zig_object.debug_ranges_index, ".debug_ranges"); zig_object.debug_str_atom = try zig_object.createDebugSectionForIndex(&zig_object.debug_str_index, ".debug_str"); zig_object.debug_pubnames_atom = try zig_object.createDebugSectionForIndex(&zig_object.debug_pubnames_index, ".debug_pubnames"); zig_object.debug_pubtypes_atom = try zig_object.createDebugSectionForIndex(&zig_object.debug_pubtypes_index, ".debug_pubtypes"); } /// From a given index variable, creates a new debug section. /// This initializes the index, appends a new segment, /// and finally, creates a managed `Atom`. pub fn createDebugSectionForIndex(zig_object: *ZigObject, wasm: *Wasm, index: *?u32, name: []const u8) !Atom.Index { const gpa = wasm.base.comp.gpa; const new_index: u32 = @intCast(zig_object.segments.items.len); index.* = new_index; try zig_object.appendDummySegment(); const sym_index = try zig_object.allocateSymbol(gpa); const atom_index = try wasm.createAtom(sym_index, .zig_object); const atom = wasm.getAtomPtr(atom_index); zig_object.symbols.items[sym_index] = .{ .tag = .section, .name = try wasm.internString(name), .index = 0, .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL), }; atom.alignment = .@"1"; // debug sections are always 1-byte-aligned return atom_index; } pub fn updateDeclLineNumber( zig_object: *ZigObject, pt: Zcu.PerThread, decl_index: InternPool.DeclIndex, ) !void { if (zig_object.dwarf) |*dw| { const decl = pt.zcu.declPtr(decl_index); log.debug("updateDeclLineNumber {}{*}", .{ decl.fqn.fmt(&pt.zcu.intern_pool), decl }); try dw.updateDeclLineNumber(pt.zcu, decl_index); } } /// Allocates debug atoms into their respective debug sections /// to merge them with maybe-existing debug atoms from object files. fn allocateDebugAtoms(zig_object: *ZigObject) !void { if (zig_object.dwarf == null) return; const allocAtom = struct { fn f(ctx: *ZigObject, maybe_index: *?u32, atom_index: Atom.Index) !void { const index = maybe_index.* orelse idx: { const index = @as(u32, @intCast(ctx.segments.items.len)); try ctx.appendDummySegment(); maybe_index.* = index; break :idx index; }; const atom = ctx.getAtomPtr(atom_index); atom.size = @as(u32, @intCast(atom.code.items.len)); ctx.symbols.items[atom.sym_index].index = index; try ctx.appendAtomAtIndex(index, atom_index); } }.f; try allocAtom(zig_object, &zig_object.debug_info_index, zig_object.debug_info_atom.?); try allocAtom(zig_object, &zig_object.debug_line_index, zig_object.debug_line_atom.?); try allocAtom(zig_object, &zig_object.debug_loc_index, zig_object.debug_loc_atom.?); try allocAtom(zig_object, &zig_object.debug_str_index, zig_object.debug_str_atom.?); try allocAtom(zig_object, &zig_object.debug_ranges_index, zig_object.debug_ranges_atom.?); try allocAtom(zig_object, &zig_object.debug_abbrev_index, zig_object.debug_abbrev_atom.?); try allocAtom(zig_object, &zig_object.debug_pubnames_index, zig_object.debug_pubnames_atom.?); try allocAtom(zig_object, &zig_object.debug_pubtypes_index, zig_object.debug_pubtypes_atom.?); } /// For the given `decl_index`, stores the corresponding type representing the function signature. /// Asserts declaration has an associated `Atom`. /// Returns the index into the list of types. pub fn storeDeclType(zig_object: *ZigObject, gpa: std.mem.Allocator, nav_index: InternPool.Nav.Index, func_type: std.wasm.Type) !u32 { const nav_info = zig_object.navs.get(nav_index).?; const index = try zig_object.putOrGetFuncType(gpa, func_type); try zig_object.atom_types.put(gpa, nav_info.atom, index); return index; } /// The symbols in ZigObject are already represented by an atom as we need to store its data. /// So rather than creating a new Atom and returning its index, we use this opportunity to scan /// its relocations and create any GOT symbols or function table indexes it may require. pub fn parseSymbolIntoAtom(zig_object: *ZigObject, wasm: *Wasm, index: Symbol.Index) !Atom.Index { const gpa = wasm.base.comp.gpa; const loc: Wasm.SymbolLoc = .{ .file = .zig_object, .index = index }; const atom_index = wasm.symbol_atom.get(loc).?; const final_index = try wasm.getMatchingSegment(.zig_object, index); try wasm.appendAtomAtIndex(final_index, atom_index); const atom = wasm.getAtom(atom_index); for (atom.relocs.items) |reloc| { const reloc_index: Symbol.Index = @enumFromInt(reloc.index); switch (reloc.relocation_type) { .R_WASM_TABLE_INDEX_I32, .R_WASM_TABLE_INDEX_I64, .R_WASM_TABLE_INDEX_SLEB, .R_WASM_TABLE_INDEX_SLEB64, => { try wasm.function_table.put(gpa, .{ .file = .zig_object, .index = reloc_index, }, 0); }, .R_WASM_GLOBAL_INDEX_I32, .R_WASM_GLOBAL_INDEX_LEB, => { const sym = zig_object.symbol(reloc_index); if (sym.tag != .global) { try wasm.got_symbols.append(gpa, .{ .file = .zig_object, .index = reloc_index, }); } }, else => {}, } } return atom_index; } /// Creates a new Wasm function with a given symbol name and body. /// Returns the symbol index of the new function. pub fn createFunction( zig_object: *ZigObject, wasm: *Wasm, symbol_name: []const u8, func_ty: std.wasm.Type, function_body: *std.ArrayList(u8), relocations: *std.ArrayList(Wasm.Relocation), ) !Symbol.Index { const gpa = wasm.base.comp.gpa; const sym_index = try zig_object.allocateSymbol(gpa); const sym = zig_object.symbol(sym_index); sym.tag = .function; sym.name = try wasm.internString(symbol_name); const type_index = try zig_object.putOrGetFuncType(gpa, func_ty); sym.index = try zig_object.appendFunction(gpa, .{ .type_index = type_index }); const atom_index = try wasm.createAtom(sym_index, .zig_object); const atom = wasm.getAtomPtr(atom_index); atom.size = @intCast(function_body.items.len); atom.code = function_body.moveToUnmanaged(); atom.relocs = relocations.moveToUnmanaged(); try zig_object.synthetic_functions.append(gpa, atom_index); return sym_index; } /// Appends a new `std.wasm.Func` to the list of functions and returns its index. fn appendFunction(zig_object: *ZigObject, gpa: std.mem.Allocator, func: std.wasm.Func) !u32 { const index: u32 = if (zig_object.functions_free_list.popOrNull()) |idx| idx else idx: { const len: u32 = @intCast(zig_object.functions.items.len); _ = try zig_object.functions.addOne(gpa); break :idx len; }; zig_object.functions.items[index] = func; return index; } pub fn flushModule(zig_object: *ZigObject, wasm: *Wasm, tid: Zcu.PerThread.Id) !void { try zig_object.populateErrorNameTable(wasm, tid); try zig_object.setupErrorsLen(wasm); } const build_options = @import("build_options"); const builtin = @import("builtin"); const codegen = @import("../../codegen.zig"); const link = @import("../../link.zig"); const log = std.log.scoped(.zig_object); const std = @import("std"); const Path = std.Build.Cache.Path; const Air = @import("../../Air.zig"); const Atom = Wasm.Atom; const Dwarf = @import("../Dwarf.zig"); const InternPool = @import("../../InternPool.zig"); const Liveness = @import("../../Liveness.zig"); const Zcu = @import("../../Zcu.zig"); const Symbol = @import("Symbol.zig"); const Type = @import("../../Type.zig"); const Value = @import("../../Value.zig"); const Wasm = @import("../Wasm.zig"); const AnalUnit = InternPool.AnalUnit; const ZigObject = @This();