diff options
| author | Luuk de Gram <luuk@degram.dev> | 2024-01-13 17:48:21 +0100 |
|---|---|---|
| committer | Luuk de Gram <luuk@degram.dev> | 2024-02-29 15:22:58 +0100 |
| commit | ba0e84a411074fe661b7df14edb2595267edcd30 (patch) | |
| tree | ce303dc35caa8dacf481836e7e9fd2c428814be3 /src | |
| parent | 5c0766b6c8f1aea18815206e0698953a35384a21 (diff) | |
| download | zig-ba0e84a411074fe661b7df14edb2595267edcd30.tar.gz zig-ba0e84a411074fe661b7df14edb2595267edcd30.zip | |
wasm: move Zig module-linkage to ZigObject
Rather than specializing the linker-driver to be able to handle objects
generated by a ZCU, we store all data in-memory in ZigObject. ZigObject
acts more like a regular object file which will allow us to treat it
as us. This will make linking much more simple, but will also reduce
the complexity of incremental-linking as we can simply update ZigObject
and relink it.
Diffstat (limited to 'src')
| -rw-r--r-- | src/link/Wasm/ZigObject.zig | 998 |
1 files changed, 998 insertions, 0 deletions
diff --git a/src/link/Wasm/ZigObject.zig b/src/link/Wasm/ZigObject.zig new file mode 100644 index 0000000000..275593f348 --- /dev/null +++ b/src/link/Wasm/ZigObject.zig @@ -0,0 +1,998 @@ +//! 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. +//! Think about this as fake in-memory Object file for the Zig module. + +/// List of all `Decl` that are currently alive. +/// Each index maps to the corresponding `Atom.Index`. +decls: std.AutoHashMapUnmanaged(InternPool.DeclIndex, Atom.Index) = .{}, +/// List of function type signatures for this Zig module. +func_types: std.ArrayListUnmanaged(std.wasm.Type) = .{}, +/// Map of symbol locations, represented by its `types.Import`. +imports: std.AutoHashMapUnmanaged(u32, types.Import) = .{}, +/// List of WebAssembly globals. +globals: std.ArrayListUnmanaged(std.wasm.Global) = .{}, +/// Mapping between an `Atom` and its type index representing the Wasm +/// type of the function signature. +atom_types: std.AutoHashMapUnmanaged(Atom.Index, u32) = .{}, +/// List of all symbols generated by Zig code. +symbols: std.ArrayListUnmanaged(Symbol) = .{}, +/// Map from symbol name offset to their index into the `symbols` list. +global_syms: std.AutoHashMapUnmanaged(u32, u32) = .{}, +/// List of symbol indexes which are free to be used. +symbols_free_list: std.ArrayListUnmanaged(u32) = .{}, +/// Extra metadata about the linking section, such as alignment of segments and their name. +segment_info: std.ArrayListUnmanage(types.Segment) = &.{}, +/// File encapsulated string table, used to deduplicate strings within the generated file. +string_table: StringTable = .{}, +/// Map for storing anonymous declarations. Each anonymous decl maps to its Atom's index. +anon_decls: std.AutoArrayHashMapUnmanaged(InternPool.Index, Atom.Index) = .{}, +/// 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: ?u32 = 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: u32, + +/// 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, gpa: std.mem.Allocator) void { + for (zig_object.segment_info.values()) |segment_info| { + gpa.free(segment_info.name); + } + + // For decls and anon decls we free the memory of its atoms. + // The memory of atoms parsed from object files is managed by + // the object file itself, and therefore we can skip those. + { + var it = zig_object.decls.valueIterator(); + while (it.next()) |atom_index_ptr| { + const atom = zig_object.getAtomPtr(atom_index_ptr.*); + for (atom.locals.items) |local_index| { + const local_atom = zig_object.getAtomPtr(local_index); + local_atom.deinit(gpa); + } + atom.deinit(gpa); + } + } + { + for (zig_object.anon_decls.values()) |atom_index| { + const atom = zig_object.getAtomPtr(atom_index); + for (atom.locals.items) |local_index| { + const local_atom = zig_object.getAtomPtr(local_index); + local_atom.deinit(gpa); + } + atom.deinit(gpa); + } + } + zig_object.decls.deinit(gpa); + zig_object.anon_decls.deinit(gpa); + zig_object.symbols.deinit(gpa); + zig_object.symbols_free_list.deinit(gpa); + zig_object.segment_info.deinit(gpa); + + zig_object.string_table.deinit(gpa); + 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) !u32 { + try zig_object.symbols.ensureUnusedCapacity(gpa, 1); + const symbol: Symbol = .{ + .name = std.math.maxInt(u32), // 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[index] = symbol; + return index; + } + const index = @as(u32, @intCast(zig_object.symbols.items.len)); + zig_object.symbols.appendAssumeCapacity(symbol); + return index; +} + +// Generate code for the Decl, storing it in memory to be later written to +// the file on flush(). +pub fn updateDecl(zig_object: *ZigObject, wasm_file: *Wasm, mod: *Module, decl_index: InternPool.DeclIndex) !void { + const decl = mod.declPtr(decl_index); + if (decl.val.getFunction(mod)) |_| { + return; + } else if (decl.val.getExternFunc(mod)) |_| { + return; + } + + const gpa = wasm_file.base.comp.gpa; + const atom_index = try zig_object.getOrCreateAtomForDecl(decl_index); + const atom = wasm_file.getAtomPtr(atom_index); + atom.clear(); + + if (decl.isExtern(mod)) { + const variable = decl.getOwnedVariable(mod).?; + const name = mod.intern_pool.stringToSlice(decl.name); + const lib_name = mod.intern_pool.stringToSliceUnwrap(variable.lib_name); + return wasm_file.addOrUpdateImport(name, atom.sym_index, lib_name, null); + } + const val = if (decl.val.getVariable(mod)) |variable| Value.fromInterned(variable.init) else decl.val; + + var code_writer = std.ArrayList(u8).init(gpa); + defer code_writer.deinit(); + + const res = try codegen.generateSymbol( + &wasm_file.base, + decl.srcLoc(mod), + .{ .ty = decl.ty, .val = val }, + &code_writer, + .none, + .{ .parent_atom_index = atom.sym_index }, + ); + + const code = switch (res) { + .ok => code_writer.items, + .fail => |em| { + decl.analysis = .codegen_failure; + try mod.failed_decls.put(mod.gpa, decl_index, em); + return; + }, + }; + + return wasm_file.finishUpdateDecl(decl_index, code, .data); +} + +pub fn updateFunc(zig_object: *ZigObject, wasm_file: *Wasm, mod: *Module, func_index: InternPool.Index, air: Air, liveness: Liveness) !void { + const gpa = wasm_file.base.comp.gpa; + const func = mod.funcInfo(func_index); + const decl_index = func.owner_decl; + const decl = mod.declPtr(decl_index); + const atom_index = try zig_object.getOrCreateAtomForDecl(decl_index); + const atom = wasm_file.getAtomPtr(atom_index); + atom.clear(); + + var code_writer = std.ArrayList(u8).init(gpa); + defer code_writer.deinit(); + const result = try codegen.generateFunction( + &wasm_file.base, + decl.srcLoc(mod), + func_index, + air, + liveness, + &code_writer, + .none, + ); + + const code = switch (result) { + .ok => code_writer.items, + .fail => |em| { + decl.analysis = .codegen_failure; + try mod.failed_decls.put(mod.gpa, decl_index, em); + return; + }, + }; + + return zig_object.finishUpdateDecl(wasm_file, decl_index, code, .function); +} + +fn finishUpdateDecl(zig_object: *ZigObject, wasm_file: *Wasm, decl_index: InternPool.DeclIndex, code: []const u8, symbol_tag: Symbol.Tag) !void { + const gpa = wasm_file.base.comp.gpa; + const mod = wasm_file.base.comp.module.?; + const decl = mod.declPtr(decl_index); + const atom_index = zig_object.decls.get(decl_index).?; + const atom = wasm_file.getAtomPtr(atom_index); + const symbol = &zig_object.symbols.items[atom.sym_index]; + const full_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod)); + symbol.name = try zig_object.string_table.insert(gpa, full_name); + symbol.tag = symbol_tag; + try atom.code.appendSlice(gpa, code); + try wasm_file.resolved_symbols.put(gpa, atom.symbolLoc(), {}); + + atom.size = @intCast(code.len); + if (code.len == 0) return; + atom.alignment = decl.getAlignment(mod); +} + +/// For a given `InternPool.DeclIndex` 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 getOrCreateAtomForDecl(zig_object: *ZigObject, wasm_file: *Wasm, decl_index: InternPool.DeclIndex) !Atom.Index { + const gpa = wasm_file.base.comp.gpa; + const gop = try zig_object.decls.getOrPut(gpa, decl_index); + if (!gop.found_existing) { + const atom_index = try wasm_file.createAtom(); + gop.value_ptr.* = atom_index; + const atom = wasm_file.getAtom(atom_index); + const symbol = atom.symbolLoc().getSymbol(wasm_file); + const mod = wasm_file.base.comp.module.?; + const decl = mod.declPtr(decl_index); + const full_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod)); + symbol.name = try wasm_file.string_table.insert(gpa, full_name); + } + return gop.value_ptr.*; +} + +pub fn lowerAnonDecl( + zig_object: *ZigObject, + wasm_file: *Wasm, + decl_val: InternPool.Index, + explicit_alignment: InternPool.Alignment, + src_loc: Module.SrcLoc, +) !codegen.Result { + const gpa = wasm_file.base.comp.gpa; + const gop = try zig_object.anon_decls.getOrPut(gpa, decl_val); + if (!gop.found_existing) { + const mod = wasm_file.base.comp.module.?; + const ty = Type.fromInterned(mod.intern_pool.typeOf(decl_val)); + const tv: TypedValue = .{ .ty = ty, .val = Value.fromInterned(decl_val) }; + var name_buf: [32]u8 = undefined; + const name = std.fmt.bufPrint(&name_buf, "__anon_{d}", .{ + @intFromEnum(decl_val), + }) catch unreachable; + + switch (try zig_object.lowerConst(name, tv, src_loc)) { + .ok => |atom_index| zig_object.anon_decls.values()[gop.index] = atom_index, + .fail => |em| return .{ .fail = em }, + } + } + + const atom = wasm_file.getAtomPtr(zig_object.anon_decls.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 .ok; +} + +/// Lowers a constant typed value to a local symbol and atom. +/// Returns the symbol index of the local +/// The given `decl` is the parent decl whom owns the constant. +pub fn lowerUnnamedConst(zig_object: *ZigObject, wasm_file: *Wasm, tv: TypedValue, decl_index: InternPool.DeclIndex) !u32 { + const gpa = wasm_file.base.comp.gpa; + const mod = wasm_file.base.comp.module.?; + std.debug.assert(tv.ty.zigTypeTag(mod) != .Fn); // cannot create local symbols for functions + const decl = mod.declPtr(decl_index); + + const parent_atom_index = try zig_object.getOrCreateAtomForDecl(decl_index); + const parent_atom = wasm_file.getAtom(parent_atom_index); + const local_index = parent_atom.locals.items.len; + const fqn = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod)); + const name = try std.fmt.allocPrintZ(gpa, "__unnamed_{s}_{d}", .{ + fqn, local_index, + }); + defer gpa.free(name); + + switch (try zig_object.lowerConst(name, tv, decl.srcLoc(mod))) { + .ok => |atom_index| { + try wasm_file.getAtomPtr(parent_atom_index).locals.append(gpa, atom_index); + return wasm_file.getAtom(atom_index).getSymbolIndex().?; + }, + .fail => |em| { + decl.analysis = .codegen_failure; + try mod.failed_decls.put(mod.gpa, decl_index, em); + return error.CodegenFail; + }, + } +} + +const LowerConstResult = union(enum) { + ok: Atom.Index, + fail: *Module.ErrorMsg, +}; + +fn lowerConst(zig_object: *ZigObject, wasm_file: *Wasm, name: []const u8, tv: TypedValue, src_loc: Module.SrcLoc) !LowerConstResult { + const gpa = wasm_file.base.comp.gpa; + const mod = wasm_file.base.comp.module.?; + + // Create and initialize a new local symbol and atom + const atom_index = try wasm_file.createAtom(); + var value_bytes = std.ArrayList(u8).init(gpa); + defer value_bytes.deinit(); + + const code = code: { + const atom = wasm_file.getAtomPtr(atom_index); + atom.alignment = tv.ty.abiAlignment(mod); + zig_object.symbols.items[atom.sym_index] = .{ + .name = try zig_object.string_table.insert(gpa, name), + .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL), + .tag = .data, + .index = undefined, + .virtual_address = undefined, + }; + + const result = try codegen.generateSymbol( + &wasm_file.base, + src_loc, + tv, + &value_bytes, + .none, + .{ + .parent_atom_index = atom.sym_index, + .addend = null, + }, + ); + break :code switch (result) { + .ok => value_bytes.items, + .fail => |em| { + return .{ .fail = em }; + }, + }; + }; + + const atom = wasm_file.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_file: *Wasm) !u32 { + if (zig_object.error_table_symbol) |symbol| { + return 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_file.base.gpa; + const sym_index = try zig_object.allocateSymbol(gpa); + const atom_index = try wasm_file.createAtom(sym_index); + const atom = wasm_file.getAtomPtr(atom_index); + const slice_ty = Type.slice_const_u8_sentinel_0; + const mod = wasm_file.base.comp.module.?; + atom.alignment = slice_ty.abiAlignment(mod); + + const sym_name = try zig_object.string_table.insert(gpa, "__zig_err_name_table"); + const symbol = &zig_object.symbols.items[sym_index]; + symbol.* = .{ + .name = sym_name, + .tag = .data, + .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL), + .index = 0, + .virtual_address = undefined, + }; + symbol.mark(); + + log.debug("Error name table was created with symbol index: ({d})", .{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_file: *Wasm) !void { + const symbol_index = zig_object.error_table_symbol orelse return; + const gpa = wasm_file.base.comp.gpa; + const atom_index = wasm_file.symbol_atom.get(.{ .file = null, .index = symbol_index }).?; + + // 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_file.createAtom(names_sym_index); + const names_atom = wasm_file.getAtomPtr(names_atom_index); + names_atom.alignment = .@"1"; + const sym_name = try zig_object.string_table.insert(gpa, "__zig_err_names"); + const names_symbol = &zig_object.symbols.items[names_sym_index]; + names_symbol.* = .{ + .name = sym_name, + .tag = .data, + .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL), + .index = 0, + .virtual_address = undefined, + }; + names_symbol.mark(); + + log.debug("Populating error names", .{}); + + // Addend for each relocation to the table + var addend: u32 = 0; + const mod = wasm_file.base.comp.module.?; + for (mod.global_error_set.keys()) |error_name_nts| { + const atom = wasm_file.getAtomPtr(atom_index); + + const error_name = mod.intern_pool.stringToSlice(error_name_nts); + const len = @as(u32, @intCast(error_name.len + 1)); // names are 0-termianted + + const slice_ty = Type.slice_const_u8_sentinel_0; + 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 = names_atom.sym_index, + .relocation_type = .R_WASM_MEMORY_ADDR_I32, + .offset = offset, + .addend = @as(i32, @intCast(addend)), + }); + atom.size += @as(u32, @intCast(slice_ty.abiSize(mod))); + 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); + names_atom.code.appendAssumeCapacity(0); + + log.debug("Populated error name: '{s}'", .{error_name}); + } + names_atom.size = addend; + + // link the atoms with the rest of the binary so they can be allocated + // and relocations will be performed. + try wasm_file.parseAtom(atom_index, .{ .data = .read_only }); + try wasm_file.parseAtom(names_atom_index, .{ .data = .read_only }); +} + +/// 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_file: *Wasm, + /// Name of the import + name: []const u8, + /// Symbol index that is external + symbol_index: u32, + /// 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_file.base.comp.gpa; + std.debug.assert(symbol_index != 0); + // 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 = lib_name != null and + !std.mem.eql(u8, lib_name.?, "c"); + const full_name = if (mangle_name) full_name: { + break :full_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 zig_object.string_table.insert(gpa, full_name); + const symbol: *Symbol = &zig_object.symbols.items[symbol_index]; + symbol.setUndefined(true); + symbol.setGlobal(true); + symbol.name = decl_name_index; + if (mangle_name) { + // we specified a specific name for the symbol that does not match the import name + symbol.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) |l_name| blk: { + break :blk l_name; + } else wasm_file.host_name; + if (!gop.found_existing) { + gop.value_ptr.* = .{ + .module_name = try zig_object.string_table.insert(gpa, module_name), + .name = try zig_object.string_table.insert(gpa, name), + .kind = .{ .function = ty_index }, + }; + zig_object.imported_functions_count += 1; + } + } +} + +/// 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, wasm_file: *Wasm, name: []const u8) !u32 { + const gpa = wasm_file.base.comp.gpa; + const name_index = try zig_object.string_table.insert(gpa, name); + const gop = try zig_object.global_syms.getOrPut(gpa, name_index); + if (gop.found_existing) { + return gop.value_ptr.index; + } + + var symbol: Symbol = .{ + .name = name_index, + .flags = 0, + .index = undefined, // index to type will be set after merging function symbols + .tag = .function, + .virtual_address = undefined, + }; + symbol.setGlobal(true); + symbol.setUndefined(true); + + const sym_index = if (zig_object.symbol.popOrNull()) |index| index else blk: { + const index: u32 = @intCast(zig_object.symbols.items.len); + try zig_object.symbols.ensureUnusedCapacity(gpa, 1); + zig_object.symbols.items.len += 1; + break :blk index; + }; + zig_object.symbols.items[sym_index] = symbol; + gop.value_ptr.* = .{ .index = sym_index, .file = null }; + 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 getDeclVAddr( + zig_object: *ZigObject, + wasm_file: *Wasm, + decl_index: InternPool.DeclIndex, + reloc_info: link.File.RelocInfo, +) !u64 { + const target = wasm_file.base.comp.root_mod.resolved_target.result; + const gpa = wasm_file.base.comp.gpa; + const mod = wasm_file.base.comp.module.?; + const decl = mod.declPtr(decl_index); + + const target_atom_index = try zig_object.getOrCreateAtomForDecl(wasm_file, decl_index); + const target_symbol_index = wasm_file.getAtom(target_atom_index).sym_index; + + std.debug.assert(reloc_info.parent_atom_index != 0); + const atom_index = wasm_file.symbol_atom.get(.{ .file = null, .index = reloc_info.parent_atom_index }).?; + const atom = wasm_file.getAtomPtr(atom_index); + const is_wasm32 = target.cpu.arch == .wasm32; + if (decl.ty.zigTypeTag(mod) == .Fn) { + 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 getAnonDeclVAddr( + zig_object: *ZigObject, + wasm_file: *Wasm, + decl_val: InternPool.Index, + reloc_info: link.File.RelocInfo, +) !u64 { + const gpa = wasm_file.base.comp.gpa; + const target = wasm_file.base.comp.root_mod.resolved_target.result; + const atom_index = zig_object.anon_decls.get(decl_val).?; + const target_symbol_index = wasm_file.getAtom(atom_index).getSymbolIndex().?; + + const parent_atom_index = wasm_file.symbol_atom.get(.{ .file = null, .index = reloc_info.parent_atom_index }).?; + const parent_atom = wasm_file.getAtomPtr(parent_atom_index); + const is_wasm32 = target.cpu.arch == .wasm32; + const mod = wasm_file.base.comp.module.?; + const ty = Type.fromInterned(mod.intern_pool.typeOf(decl_val)); + if (ty.zigTypeTag(mod) == .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 deleteDeclExport( + zig_object: *ZigObject, + wasm_file: *Wasm, + decl_index: InternPool.DeclIndex, +) void { + const atom_index = zig_object.decls.get(decl_index) orelse return; + const sym_index = wasm_file.getAtom(atom_index).sym_index; + const loc: Wasm.SymbolLoc = .{ .file = null, .index = sym_index }; + const symbol = loc.getSymbol(wasm_file); + std.debug.assert(zig_object.global_syms.remove(symbol.name)); +} + +pub fn updateExports( + zig_object: *ZigObject, + wasm_file: *Wasm, + mod: *Module, + exported: Module.Exported, + exports: []const *Module.Export, +) !void { + const decl_index = switch (exported) { + .decl_index => |i| i, + .value => |val| { + _ = val; + @panic("TODO: implement Wasm linker code for exporting a constant value"); + }, + }; + const decl = mod.declPtr(decl_index); + const atom_index = try zig_object.getOrCreateAtomForDecl(decl_index); + const atom = wasm_file.getAtom(atom_index); + const atom_sym = atom.symbolLoc().getSymbol(wasm_file).*; + const gpa = mod.gpa; + + for (exports) |exp| { + if (mod.intern_pool.stringToSliceUnwrap(exp.opts.section)) |section| { + try mod.failed_exports.putNoClobber(gpa, exp, try Module.ErrorMsg.create( + gpa, + decl.srcLoc(mod), + "Unimplemented: ExportOptions.section '{s}'", + .{section}, + )); + continue; + } + + const exported_decl_index = switch (exp.exported) { + .value => { + try mod.failed_exports.putNoClobber(gpa, exp, try Module.ErrorMsg.create( + gpa, + decl.srcLoc(mod), + "Unimplemented: exporting a named constant value", + .{}, + )); + continue; + }, + .decl_index => |i| i, + }; + const exported_atom_index = try zig_object.getOrCreateAtomForDecl(exported_decl_index); + const exported_atom = wasm_file.getAtom(exported_atom_index); + // const export_name = try zig_object.string_table.put(gpa, mod.intern_pool.stringToSlice(exp.opts.name)); + const sym_loc = exported_atom.symbolLoc(); + const symbol = sym_loc.getSymbol(wasm_file); + symbol.setGlobal(true); + symbol.setUndefined(false); + symbol.index = atom_sym.index; + symbol.tag = atom_sym.tag; + symbol.name = atom_sym.name; + + switch (exp.opts.linkage) { + .Internal => { + symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); + symbol.setFlag(.WASM_SYM_BINDING_WEAK); + }, + .Weak => { + symbol.setFlag(.WASM_SYM_BINDING_WEAK); + }, + .Strong => {}, // symbols are strong by default + .LinkOnce => { + try mod.failed_exports.putNoClobber(gpa, exp, try Module.ErrorMsg.create( + gpa, + decl.srcLoc(mod), + "Unimplemented: LinkOnce", + .{}, + )); + continue; + }, + } + + // TODO: Revisit this + // if (zig_object.global_syms.get(export_name)) |existing_loc| { + // if (existing_loc.index == atom.sym_index) continue; + // const existing_sym: Symbol = existing_loc.getSymbol(wasm_file).*; + + // if (!existing_sym.isUndefined()) blk: { + // if (symbol.isWeak()) { + // try wasm_file.discarded.put(gpa, existing_loc, sym_loc); + // continue; // to-be-exported symbol is weak, so we keep the existing symbol + // } + + // // new symbol is not weak while existing is, replace existing symbol + // if (existing_sym.isWeak()) { + // break :blk; + // } + // // When both the to-be-exported symbol and the already existing symbol + // // are strong symbols, we have a linker error. + // // In the other case we replace one with the other. + // try mod.failed_exports.put(gpa, exp, try Module.ErrorMsg.create( + // gpa, + // decl.srcLoc(mod), + // \\LinkError: symbol '{}' defined multiple times + // \\ first definition in '{s}' + // \\ next definition in '{s}' + // , + // .{ exp.opts.name.fmt(&mod.intern_pool), wasm_file.name, wasm_file.name }, + // )); + // continue; + // } + + // // in this case the existing symbol must be replaced either because it's weak or undefined. + // try wasm.discarded.put(gpa, existing_loc, sym_loc); + // _ = wasm.imports.remove(existing_loc); + // _ = wasm.undefs.swapRemove(existing_sym.name); + // } + + // // Ensure the symbol will be exported using the given name + // if (!mod.intern_pool.stringEqlSlice(exp.opts.name, sym_loc.getName(wasm))) { + // try wasm.export_names.put(gpa, sym_loc, export_name); + // } + + // try wasm.globals.put( + // gpa, + // export_name, + // sym_loc, + // ); + } +} + +pub fn freeDecl(zig_object: *ZigObject, wasm_file: *Wasm, decl_index: InternPool.DeclIndex) void { + const gpa = wasm_file.base.comp.gpa; + const mod = wasm_file.base.comp.module.?; + const decl = mod.declPtr(decl_index); + const atom_index = zig_object.decls.get(decl_index).?; + const atom = wasm_file.getAtomPtr(atom_index); + zig_object.symbols_free_list.append(gpa, atom.sym_index) catch {}; + _ = zig_object.decls.remove(decl_index); + zig_object.symbols.items[atom.sym_index].tag = .dead; + for (atom.locals.items) |local_atom_index| { + const local_atom = wasm_file.getAtom(local_atom_index); + const local_symbol = &zig_object.symbols.items[local_atom.sym_index]; + local_symbol.tag = .dead; // also for any local symbol + zig_object.symbols_free_list.append(gpa, local_atom.sym_index) catch {}; + std.denug.assert(wasm_file.symbol_atom.remove(local_atom.symbolLoc())); + } + + if (decl.isExtern(mod)) { + _ = zig_object.imports.remove(atom.getSymbolIndex().?); + } + _ = wasm_file.symbol_atom.remove(atom.symbolLoc()); + + // if (wasm.dwarf) |*dwarf| { + // dwarf.freeDecl(decl_index); + // } + + if (atom.next) |next_atom_index| { + const next_atom = wasm_file.getAtomPtr(next_atom_index); + next_atom.prev = atom.prev; + atom.next = null; + } + if (atom.prev) |prev_index| { + const prev_atom = wasm_file.getAtomPtr(prev_index); + prev_atom.next = atom.next; + atom.prev = null; + } +} + +pub 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; +} + +/// Kind represents the type of an Atom, which is only +/// used to parse a decl into an Atom to define in which section +/// or segment it should be placed. +const Kind = union(enum) { + /// Represents the segment the data symbol should + /// be inserted into. + /// TODO: Add TLS segments + data: enum { + read_only, + uninitialized, + initialized, + }, + function: void, + + /// Returns the segment name the data kind represents. + /// Asserts `kind` has its active tag set to `data`. + fn segmentName(kind: Kind) []const u8 { + switch (kind.data) { + .read_only => return ".rodata.", + .uninitialized => return ".bss.", + .initialized => return ".data.", + } + } +}; + +/// Parses an Atom and inserts its metadata into the corresponding sections. +pub fn parseAtom(zig_object: *ZigObject, wasm_file: *Wasm, atom_index: Atom.Index, kind: Kind) !void { + // TODO: Revisit + _ = zig_object; + _ = wasm_file; + _ = atom_index; + _ = kind; + // const comp = wasm.base.comp; + // const gpa = comp.gpa; + // const shared_memory = comp.config.shared_memory; + // const import_memory = comp.config.import_memory; + // const atom = wasm.getAtomPtr(atom_index); + // const symbol = (SymbolLoc{ .file = null, .index = atom.sym_index }).getSymbol(wasm); + // const do_garbage_collect = wasm.base.gc_sections; + + // if (symbol.isDead() and do_garbage_collect) { + // // Prevent unreferenced symbols from being parsed. + // return; + // } + + // const final_index: u32 = switch (kind) { + // .function => result: { + // const index: u32 = @intCast(wasm.functions.count() + wasm.imported_functions_count); + // const type_index = wasm.atom_types.get(atom_index).?; + // try wasm.functions.putNoClobber( + // gpa, + // .{ .file = null, .index = index }, + // .{ .func = .{ .type_index = type_index }, .sym_index = atom.sym_index }, + // ); + // symbol.tag = .function; + // symbol.index = index; + + // if (wasm.code_section_index == null) { + // wasm.code_section_index = @intCast(wasm.segments.items.len); + // try wasm.segments.append(gpa, .{ + // .alignment = atom.alignment, + // .size = atom.size, + // .offset = 0, + // .flags = 0, + // }); + // } + + // break :result wasm.code_section_index.?; + // }, + // .data => result: { + // const segment_name = try std.mem.concat(gpa, u8, &.{ + // kind.segmentName(), + // wasm.string_table.get(symbol.name), + // }); + // errdefer gpa.free(segment_name); + // const segment_info: types.Segment = .{ + // .name = segment_name, + // .alignment = atom.alignment, + // .flags = 0, + // }; + // symbol.tag = .data; + + // // when creating an object file, or importing memory and the data belongs in the .bss segment + // // we set the entire region of it to zeroes. + // // We do not have to do this when exporting the memory (the default) because the runtime + // // will do it for us, and we do not emit the bss segment at all. + // if ((wasm.base.comp.config.output_mode == .Obj or import_memory) and kind.data == .uninitialized) { + // @memset(atom.code.items, 0); + // } + + // const should_merge = wasm.base.comp.config.output_mode != .Obj; + // const gop = try wasm.data_segments.getOrPut(gpa, segment_info.outputName(should_merge)); + // if (gop.found_existing) { + // const index = gop.value_ptr.*; + // wasm.segments.items[index].size += atom.size; + + // symbol.index = @intCast(wasm.segment_info.getIndex(index).?); + // // segment info already exists, so free its memory + // gpa.free(segment_name); + // break :result index; + // } else { + // const index: u32 = @intCast(wasm.segments.items.len); + // var flags: u32 = 0; + // if (shared_memory) { + // flags |= @intFromEnum(Segment.Flag.WASM_DATA_SEGMENT_IS_PASSIVE); + // } + // try wasm.segments.append(gpa, .{ + // .alignment = atom.alignment, + // .size = 0, + // .offset = 0, + // .flags = flags, + // }); + // gop.value_ptr.* = index; + + // const info_index: u32 = @intCast(wasm.segment_info.count()); + // try wasm.segment_info.put(gpa, index, segment_info); + // symbol.index = info_index; + // break :result index; + // } + // }, + // }; + + // const segment: *Segment = &wasm.segments.items[final_index]; + // segment.alignment = segment.alignment.max(atom.alignment); + + // try wasm.appendAtomAtIndex(final_index, atom_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_file: *Wasm) !void { + const gpa = wasm_file.base.comp.gpa; + const loc = zig_object.findGlobalSymbol("__zig_errors_len") orelse return; + + const errors_len = wasm_file.base.comp.module.?.global_error_set.count(); + // overwrite existing atom if it already exists (maybe the error set has increased) + // if not, allcoate a new atom. + const atom_index = if (wasm_file.symbol_atom.get(loc)) |index| blk: { + const atom = wasm_file.getAtomPtr(index); + if (atom.next) |next_atom_index| { + const next_atom = wasm_file.getAtomPtr(next_atom_index); + next_atom.prev = atom.prev; + atom.next = null; + } + if (atom.prev) |prev_index| { + const prev_atom = wasm_file.getAtomPtr(prev_index); + prev_atom.next = atom.next; + atom.prev = null; + } + atom.deinit(gpa); + break :blk index; + } else new_atom: { + const atom_index: Atom.Index = @intCast(wasm_file.managed_atoms.items.len); + try wasm_file.symbol_atom.put(gpa, loc, atom_index); + try wasm_file.managed_atoms.append(gpa, undefined); + break :new_atom atom_index; + }; + const atom = wasm_file.getAtomPtr(atom_index); + atom.* = Atom.empty; + atom.sym_index = loc.index; + atom.size = 2; + try atom.code.writer(gpa).writeInt(u16, @intCast(errors_len), .little); + + // try wasm.parseAtom(atom_index, .{ .data = .read_only }); +} + +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 types = @import("types.zig"); + +const Air = @import("../../Air.zig"); +const Atom = @import("Atom.zig"); +const InternPool = @import("../../InternPool.zig"); +const Liveness = @import("../../Liveness.zig"); +const Module = @import("../../Module.zig"); +const StringTable = @import("../StringTable.zig"); +const Symbol = @import("Symbol.zig"); +const Type = @import("../../type.zig").Type; +const TypedValue = @import("../../TypedValue.zig"); +const Value = @import("../../value.zig").Value; +const Wasm = @import("../Wasm.zig"); +const ZigObject = @This(); |
