Files
zig/src/link/Wasm/ZigObject.zig
Luuk de Gram f6896ef218 wasm: create linking objects in correct module
CodeGen will create linking objects such as symbols, function types, etc
in ZigObject, rather than in the linker driver where the final result
will be stored. They will end up in the linker driver module during
the `flush` phase instead.

This must mean we must call functions such as `addOrGetFuncType` in the
correct namespace or else it will be created in the incorrect list and
therefore return incorrect indexes.
2024-02-29 15:23:03 +01:00

1174 lines
48 KiB
Zig

//! 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.
path: []const u8,
/// 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.ArrayListUnmanaged(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,
/// 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,
/// Initializes the `ZigObject` with initial symbols.
pub fn init(zig_object: *ZigObject, wasm_file: *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_file);
// TODO: Initialize debug information when we reimplement Dwarf support.
}
fn createStackPointer(zig_object: *ZigObject, wasm_file: *Wasm) !void {
const gpa = wasm_file.base.comp.gpa;
const sym_index = try zig_object.getGlobalSymbol(gpa, "__stack_pointer");
const sym = zig_object.symbol(sym_index);
sym.index = zig_object.imported_globals_count;
sym.tag = .global;
const is_wasm32 = wasm_file.base.comp.root_mod.resolved_target.result.cpu.arch == .wasm32;
try zig_object.imports.putNoClobber(gpa, sym_index, .{
.name = sym.name,
.module_name = try zig_object.string_table.insert(gpa, wasm_file.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;
}
fn symbol(zig_object: *const ZigObject, index: u32) *Symbol {
return &zig_object.symbols.items[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, 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);
if (zig_object.dwarf) |*dwarf| {
dwarf.deinit();
}
gpa.free(zig_object.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) !u32 {
try zig_object.symbols.ensureUnusedCapacity(gpa, 1);
const sym: 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] = sym;
return index;
}
const index = @as(u32, @intCast(zig_object.symbols.items.len));
zig_object.symbols.appendAssumeCapacity(sym);
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(wasm_file, 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 zig_object.addOrUpdateImport(wasm_file, 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 zig_object.finishUpdateDecl(wasm_file, 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(wasm_file, 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 sym = zig_object.symbol(atom.getSymbolIndex().?);
const full_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
sym.name = try zig_object.string_table.insert(gpa, full_name);
sym.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 sym_index = try zig_object.allocateSymbol(gpa);
gop.value_ptr.* = try wasm_file.createAtom(sym_index);
const mod = wasm_file.base.comp.module.?;
const decl = mod.declPtr(decl_index);
const full_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
const sym = zig_object.symbol(sym_index);
sym.name = try zig_object.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(wasm_file, 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(wasm_file, 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(wasm_file, 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 sym_index = try zig_object.allocateSymbol(gpa);
const atom_index = try wasm_file.createAtom(sym_index);
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[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) |sym| {
return sym;
}
// 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.comp.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 sym = zig_object.symbol(sym_index);
sym.* = .{
.name = sym_name,
.tag = .data,
.flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL),
.index = 0,
.virtual_address = undefined,
};
// TODO: can we remove this?
// sym.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 sym: *Symbol = &zig_object.symbols.items[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) |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, gpa: std.mem.Allocator, name: []const u8) !u32 {
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.*;
}
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: 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] = 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 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 sym = loc.getSymbol(wasm_file);
std.debug.assert(zig_object.global_syms.remove(sym.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(wasm_file, 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(wasm_file, 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 sym = sym_loc.getSymbol(wasm_file);
sym.setGlobal(true);
sym.setUndefined(false);
sym.index = atom_sym.index;
sym.tag = atom_sym.tag;
sym.name = atom_sym.name;
switch (exp.opts.linkage) {
.Internal => {
sym.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
sym.setFlag(.WASM_SYM_BINDING_WEAK);
},
.Weak => {
sym.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;
}
}
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 });
}
/// 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_file: *Wasm, index: *?u32, name: []const u8) !Atom.Index {
const gpa = wasm_file.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_file.createAtom(sym_index);
const atom = wasm_file.getAtomPtr(atom_index);
zig_object.symbols.items[sym_index] = .{
.tag = .section,
.name = try zig_object.string_table.put(gpa, 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, mod: *Module, decl_index: InternPool.DeclIndex) !void {
if (zig_object.dwarf) |*dw| {
const decl = mod.declPtr(decl_index);
const decl_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
log.debug("updateDeclLineNumber {s}{*}", .{ decl_name, decl });
try dw.updateDeclLineNumber(mod, 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, decl_index: InternPool.DeclIndex, func_type: std.wasm.Type) !u32 {
const atom_index = zig_object.decls.get(decl_index).?;
const index = try zig_object.putOrGetFuncType(gpa, func_type);
try zig_object.atom_types.put(gpa, atom_index, index);
return index;
}
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 Dwarf = @import("../Dwarf.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();