diff --git a/CMakeLists.txt b/CMakeLists.txt index 47bd0da030..bc2ee4dde7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -643,9 +643,9 @@ set(ZIG_STAGE2_SOURCES src/link/StringTable.zig src/link/Wasm.zig src/link/Wasm/Archive.zig + src/link/Wasm/Flush.zig src/link/Wasm/Object.zig src/link/Wasm/Symbol.zig - src/link/Wasm/ZigObject.zig src/link/aarch64.zig src/link/riscv.zig src/link/table_section.zig diff --git a/lib/std/Build/Step/CheckObject.zig b/lib/std/Build/Step/CheckObject.zig index 00ea5013de..d3632f6b87 100644 --- a/lib/std/Build/Step/CheckObject.zig +++ b/lib/std/Build/Step/CheckObject.zig @@ -2682,7 +2682,7 @@ const WasmDumper = struct { else => unreachable, } const end_opcode = try std.leb.readUleb128(u8, reader); - if (end_opcode != std.wasm.opcode(.end)) { + if (end_opcode != @intFromEnum(std.wasm.Opcode.end)) { return step.fail("expected 'end' opcode in init expression", .{}); } } diff --git a/lib/std/wasm.zig b/lib/std/wasm.zig index 8996a174f1..7764d40aff 100644 --- a/lib/std/wasm.zig +++ b/lib/std/wasm.zig @@ -4,8 +4,6 @@ const std = @import("std.zig"); const testing = std.testing; -// TODO: Add support for multi-byte ops (e.g. table operations) - /// Wasm instruction opcodes /// /// All instructions are defined as per spec: @@ -195,27 +193,6 @@ pub const Opcode = enum(u8) { _, }; -/// Returns the integer value of an `Opcode`. Used by the Zig compiler -/// to write instructions to the wasm binary file -pub fn opcode(op: Opcode) u8 { - return @intFromEnum(op); -} - -test "opcodes" { - // Ensure our opcodes values remain intact as certain values are skipped due to them being reserved - const i32_const = opcode(.i32_const); - const end = opcode(.end); - const drop = opcode(.drop); - const local_get = opcode(.local_get); - const i64_extend32_s = opcode(.i64_extend32_s); - - try testing.expectEqual(@as(u16, 0x41), i32_const); - try testing.expectEqual(@as(u16, 0x0B), end); - try testing.expectEqual(@as(u16, 0x1A), drop); - try testing.expectEqual(@as(u16, 0x20), local_get); - try testing.expectEqual(@as(u16, 0xC4), i64_extend32_s); -} - /// Opcodes that require a prefix `0xFC`. /// Each opcode represents a varuint32, meaning /// they are encoded as leb128 in binary. @@ -241,12 +218,6 @@ pub const MiscOpcode = enum(u32) { _, }; -/// Returns the integer value of an `MiscOpcode`. Used by the Zig compiler -/// to write instructions to the wasm binary file -pub fn miscOpcode(op: MiscOpcode) u32 { - return @intFromEnum(op); -} - /// Simd opcodes that require a prefix `0xFD`. /// Each opcode represents a varuint32, meaning /// they are encoded as leb128 in binary. @@ -512,12 +483,6 @@ pub const SimdOpcode = enum(u32) { f32x4_relaxed_dot_bf16x8_add_f32x4 = 0x114, }; -/// Returns the integer value of an `SimdOpcode`. Used by the Zig compiler -/// to write instructions to the wasm binary file -pub fn simdOpcode(op: SimdOpcode) u32 { - return @intFromEnum(op); -} - /// Atomic opcodes that require a prefix `0xFE`. /// Each opcode represents a varuint32, meaning /// they are encoded as leb128 in binary. @@ -592,12 +557,6 @@ pub const AtomicsOpcode = enum(u32) { i64_atomic_rmw32_cmpxchg_u = 0x4E, }; -/// Returns the integer value of an `AtomicsOpcode`. Used by the Zig compiler -/// to write instructions to the wasm binary file -pub fn atomicsOpcode(op: AtomicsOpcode) u32 { - return @intFromEnum(op); -} - /// Enum representing all Wasm value types as per spec: /// https://webassembly.github.io/spec/core/binary/types.html pub const Valtype = enum(u8) { @@ -608,11 +567,6 @@ pub const Valtype = enum(u8) { v128 = 0x7B, }; -/// Returns the integer value of a `Valtype` -pub fn valtype(value: Valtype) u8 { - return @intFromEnum(value); -} - /// Reference types, where the funcref references to a function regardless of its type /// and ref references an object from the embedder. pub const RefType = enum(u8) { @@ -620,41 +574,17 @@ pub const RefType = enum(u8) { externref = 0x6F, }; -/// Returns the integer value of a `Reftype` -pub fn reftype(value: RefType) u8 { - return @intFromEnum(value); -} - -test "valtypes" { - const _i32 = valtype(.i32); - const _i64 = valtype(.i64); - const _f32 = valtype(.f32); - const _f64 = valtype(.f64); - - try testing.expectEqual(@as(u8, 0x7F), _i32); - try testing.expectEqual(@as(u8, 0x7E), _i64); - try testing.expectEqual(@as(u8, 0x7D), _f32); - try testing.expectEqual(@as(u8, 0x7C), _f64); -} - /// Limits classify the size range of resizeable storage associated with memory types and table types. pub const Limits = struct { - flags: u8, + flags: Flags, min: u32, max: u32, - pub const Flags = enum(u8) { - WASM_LIMITS_FLAG_HAS_MAX = 0x1, - WASM_LIMITS_FLAG_IS_SHARED = 0x2, + pub const Flags = packed struct(u8) { + has_max: bool, + is_shared: bool, + reserved: u6 = 0, }; - - pub fn hasFlag(limits: Limits, flag: Flags) bool { - return limits.flags & @intFromEnum(flag) != 0; - } - - pub fn setFlag(limits: *Limits, flag: Flags) void { - limits.flags |= @intFromEnum(flag); - } }; /// Initialization expressions are used to set the initial value on an object @@ -667,18 +597,6 @@ pub const InitExpression = union(enum) { global_get: u32, }; -/// Represents a function entry, holding the index to its type -pub const Func = struct { - type_index: u32, -}; - -/// Tables are used to hold pointers to opaque objects. -/// This can either by any function, or an object from the host. -pub const Table = struct { - limits: Limits, - reftype: RefType, -}; - /// Describes the layout of the memory where `min` represents /// the minimal amount of pages, and the optional `max` represents /// the max pages. When `null` will allow the host to determine the @@ -687,88 +605,6 @@ pub const Memory = struct { limits: Limits, }; -/// Represents the type of a `Global` or an imported global. -pub const GlobalType = struct { - valtype: Valtype, - mutable: bool, -}; - -pub const Global = struct { - global_type: GlobalType, - init: InitExpression, -}; - -/// Notates an object to be exported from wasm -/// to the host. -pub const Export = struct { - name: []const u8, - kind: ExternalKind, - index: u32, -}; - -/// Element describes the layout of the table that can -/// be found at `table_index` -pub const Element = struct { - table_index: u32, - offset: InitExpression, - func_indexes: []const u32, -}; - -/// Imports are used to import objects from the host -pub const Import = struct { - module_name: []const u8, - name: []const u8, - kind: Kind, - - pub const Kind = union(ExternalKind) { - function: u32, - table: Table, - memory: Limits, - global: GlobalType, - }; -}; - -/// `Type` represents a function signature type containing both -/// a slice of parameters as well as a slice of return values. -pub const Type = struct { - params: []const Valtype, - returns: []const Valtype, - - pub fn format(self: Type, comptime fmt: []const u8, opt: std.fmt.FormatOptions, writer: anytype) !void { - if (fmt.len != 0) std.fmt.invalidFmtError(fmt, self); - _ = opt; - try writer.writeByte('('); - for (self.params, 0..) |param, i| { - try writer.print("{s}", .{@tagName(param)}); - if (i + 1 != self.params.len) { - try writer.writeAll(", "); - } - } - try writer.writeAll(") -> "); - if (self.returns.len == 0) { - try writer.writeAll("nil"); - } else { - for (self.returns, 0..) |return_ty, i| { - try writer.print("{s}", .{@tagName(return_ty)}); - if (i + 1 != self.returns.len) { - try writer.writeAll(", "); - } - } - } - } - - pub fn eql(self: Type, other: Type) bool { - return std.mem.eql(Valtype, self.params, other.params) and - std.mem.eql(Valtype, self.returns, other.returns); - } - - pub fn deinit(self: *Type, gpa: std.mem.Allocator) void { - gpa.free(self.params); - gpa.free(self.returns); - self.* = undefined; - } -}; - /// Wasm module sections as per spec: /// https://webassembly.github.io/spec/core/binary/modules.html pub const Section = enum(u8) { @@ -788,11 +624,6 @@ pub const Section = enum(u8) { _, }; -/// Returns the integer value of a given `Section` -pub fn section(val: Section) u8 { - return @intFromEnum(val); -} - /// The kind of the type when importing or exporting to/from the host environment. /// https://webassembly.github.io/spec/core/syntax/modules.html pub const ExternalKind = enum(u8) { @@ -802,11 +633,6 @@ pub const ExternalKind = enum(u8) { global, }; -/// Returns the integer value of a given `ExternalKind` -pub fn externalKind(val: ExternalKind) u8 { - return @intFromEnum(val); -} - /// Defines the enum values for each subsection id for the "Names" custom section /// as described by: /// https://webassembly.github.io/spec/core/appendix/custom.html?highlight=name#name-section diff --git a/src/Compilation.zig b/src/Compilation.zig index 7ec0640256..d0681faad6 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2438,9 +2438,8 @@ fn flush( if (comp.bin_file) |lf| { // This is needed before reading the error flags. lf.flush(arena, tid, prog_node) catch |err| switch (err) { - error.FlushFailure, error.LinkFailure => {}, // error reported through link_diags.flags - error.LLDReportedFailure => {}, // error reported via lockAndParseLldStderr - else => |e| return e, + error.LinkFailure => {}, // Already reported. + error.OutOfMemory => return error.OutOfMemory, }; } diff --git a/src/Zcu.zig b/src/Zcu.zig index 338c4c5528..6dcc7b6af6 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -524,6 +524,15 @@ pub const Export = struct { section: InternPool.OptionalNullTerminatedString = .none, visibility: std.builtin.SymbolVisibility = .default, }; + + /// Index into `all_exports`. + pub const Index = enum(u32) { + _, + + pub fn ptr(i: Index, zcu: *const Zcu) *Export { + return &zcu.all_exports.items[@intFromEnum(i)]; + } + }; }; pub const Reference = struct { diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 5de00a96cd..94838ee617 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -1722,22 +1722,19 @@ pub fn linkerUpdateFunc(pt: Zcu.PerThread, func_index: InternPool.Index, air: Ai // Correcting this failure will involve changing a type this function // depends on, hence triggering re-analysis of this function, so this // interacts correctly with incremental compilation. - // TODO: do we need to mark this failure anywhere? I don't think so, since compilation - // will fail due to the type error anyway. } else if (comp.bin_file) |lf| { lf.updateFunc(pt, func_index, air, liveness) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, - error.AnalysisFail => { - assert(zcu.failed_codegen.contains(nav_index)); - }, - else => { - try zcu.failed_codegen.putNoClobber(gpa, nav_index, try Zcu.ErrorMsg.create( + error.CodegenFail => assert(zcu.failed_codegen.contains(nav_index)), + error.LinkFailure => assert(comp.link_diags.hasErrors()), + error.Overflow => { + try zcu.failed_codegen.putNoClobber(nav_index, try Zcu.ErrorMsg.create( gpa, zcu.navSrcLoc(nav_index), "unable to codegen: {s}", .{@errorName(err)}, )); - try zcu.retryable_failures.append(zcu.gpa, AnalUnit.wrap(.{ .func = func_index })); + // Not a retryable failure. }, }; } else if (zcu.llvm_object) |llvm_object| { @@ -3100,6 +3097,7 @@ pub fn populateTestFunctions( pub fn linkerUpdateNav(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) error{OutOfMemory}!void { const zcu = pt.zcu; const comp = zcu.comp; + const gpa = zcu.gpa; const ip = &zcu.intern_pool; const nav = zcu.intern_pool.getNav(nav_index); @@ -3113,26 +3111,16 @@ pub fn linkerUpdateNav(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) error } else if (comp.bin_file) |lf| { lf.updateNav(pt, nav_index) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, - error.AnalysisFail => { - assert(zcu.failed_codegen.contains(nav_index)); - }, - else => { - const gpa = zcu.gpa; - try zcu.failed_codegen.ensureUnusedCapacity(gpa, 1); - zcu.failed_codegen.putAssumeCapacityNoClobber(nav_index, try Zcu.ErrorMsg.create( + error.CodegenFail => assert(zcu.failed_codegen.contains(nav_index)), + error.LinkFailure => assert(comp.link_diags.hasErrors()), + error.Overflow => { + try zcu.failed_codegen.putNoClobber(nav_index, try Zcu.ErrorMsg.create( gpa, zcu.navSrcLoc(nav_index), "unable to codegen: {s}", .{@errorName(err)}, )); - if (nav.analysis != null) { - try zcu.retryable_failures.append(zcu.gpa, .wrap(.{ .nav_val = nav_index })); - } else { - // TODO: we don't have a way to indicate that this failure is retryable! - // Since these are really rare, we could as a cop-out retry the whole build next update. - // But perhaps we can do better... - @panic("TODO: retryable failure codegenning non-declaration Nav"); - } + // Not a retryable failure. }, }; } else if (zcu.llvm_object) |llvm_object| { diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index 8fd27d4bb7..557f7f17f6 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -167,7 +167,7 @@ const DbgInfoReloc = struct { name: [:0]const u8, mcv: MCValue, - fn genDbgInfo(reloc: DbgInfoReloc, function: Self) !void { + fn genDbgInfo(reloc: DbgInfoReloc, function: Self) CodeGenError!void { switch (reloc.tag) { .arg, .dbg_arg_inline, @@ -181,7 +181,7 @@ const DbgInfoReloc = struct { } } - fn genArgDbgInfo(reloc: DbgInfoReloc, function: Self) !void { + fn genArgDbgInfo(reloc: DbgInfoReloc, function: Self) CodeGenError!void { switch (function.debug_output) { .dwarf => |dw| { const loc: link.File.Dwarf.Loc = switch (reloc.mcv) { @@ -209,7 +209,7 @@ const DbgInfoReloc = struct { } } - fn genVarDbgInfo(reloc: DbgInfoReloc, function: Self) !void { + fn genVarDbgInfo(reloc: DbgInfoReloc, function: Self) CodeGenError!void { switch (function.debug_output) { .dwarf => |dwarf| { const loc: link.File.Dwarf.Loc = switch (reloc.mcv) { @@ -395,13 +395,13 @@ pub fn generate( try reloc.genDbgInfo(function); } - var mir = Mir{ + var mir: Mir = .{ .instructions = function.mir_instructions.toOwnedSlice(), .extra = try function.mir_extra.toOwnedSlice(gpa), }; defer mir.deinit(gpa); - var emit = Emit{ + var emit: Emit = .{ .mir = mir, .bin_file = lf, .debug_output = debug_output, @@ -723,7 +723,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .cmp_gt => try self.airCmp(inst, .gt), .cmp_neq => try self.airCmp(inst, .neq), - .cmp_vector => try self.airCmpVector(inst), + .cmp_vector => try self.airCmpVector(inst), .cmp_lt_errors_len => try self.airCmpLtErrorsLen(inst), .alloc => try self.airAlloc(inst), @@ -744,7 +744,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .fpext => try self.airFpext(inst), .intcast => try self.airIntCast(inst), .trunc => try self.airTrunc(inst), - .int_from_bool => try self.airIntFromBool(inst), + .int_from_bool => try self.airIntFromBool(inst), .is_non_null => try self.airIsNonNull(inst), .is_non_null_ptr => try self.airIsNonNullPtr(inst), .is_null => try self.airIsNull(inst), @@ -756,7 +756,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .load => try self.airLoad(inst), .loop => try self.airLoop(inst), .not => try self.airNot(inst), - .int_from_ptr => try self.airIntFromPtr(inst), + .int_from_ptr => try self.airIntFromPtr(inst), .ret => try self.airRet(inst), .ret_safe => try self.airRet(inst), // TODO .ret_load => try self.airRetLoad(inst), @@ -765,8 +765,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .struct_field_ptr=> try self.airStructFieldPtr(inst), .struct_field_val=> try self.airStructFieldVal(inst), .array_to_slice => try self.airArrayToSlice(inst), - .float_from_int => try self.airFloatFromInt(inst), - .int_from_float => try self.airIntFromFloat(inst), + .float_from_int => try self.airFloatFromInt(inst), + .int_from_float => try self.airIntFromFloat(inst), .cmpxchg_strong => try self.airCmpxchg(inst), .cmpxchg_weak => try self.airCmpxchg(inst), .atomic_rmw => try self.airAtomicRmw(inst), @@ -1107,7 +1107,7 @@ fn spillCompareFlagsIfOccupied(self: *Self) !void { /// Copies a value to a register without tracking the register. The register is not considered /// allocated. A second call to `copyToTmpRegister` may return the same register. /// This can have a side effect of spilling instructions to the stack to free up a register. -fn copyToTmpRegister(self: *Self, ty: Type, mcv: MCValue) !Register { +fn copyToTmpRegister(self: *Self, ty: Type, mcv: MCValue) InnerError!Register { const raw_reg = try self.register_manager.allocReg(null, gp); const reg = self.registerAlias(raw_reg, ty); try self.genSetReg(ty, reg, mcv); @@ -1125,12 +1125,12 @@ fn copyToNewRegister(self: *Self, reg_owner: Air.Inst.Index, mcv: MCValue) !MCVa return MCValue{ .register = reg }; } -fn airAlloc(self: *Self, inst: Air.Inst.Index) !void { +fn airAlloc(self: *Self, inst: Air.Inst.Index) InnerError!void { const stack_offset = try self.allocMemPtr(inst); return self.finishAir(inst, .{ .ptr_stack_offset = stack_offset }, .{ .none, .none, .none }); } -fn airRetPtr(self: *Self, inst: Air.Inst.Index) !void { +fn airRetPtr(self: *Self, inst: Air.Inst.Index) InnerError!void { const pt = self.pt; const zcu = pt.zcu; const result: MCValue = switch (self.ret_mcv) { @@ -1152,19 +1152,19 @@ fn airRetPtr(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ .none, .none, .none }); } -fn airFptrunc(self: *Self, inst: Air.Inst.Index) !void { +fn airFptrunc(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airFptrunc for {}", .{self.target.cpu.arch}); return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } -fn airFpext(self: *Self, inst: Air.Inst.Index) !void { +fn airFpext(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airFpext for {}", .{self.target.cpu.arch}); return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } -fn airIntCast(self: *Self, inst: Air.Inst.Index) !void { +fn airIntCast(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; if (self.liveness.isUnused(inst)) return self.finishAir(inst, .dead, .{ ty_op.operand, .none, .none }); @@ -1293,7 +1293,7 @@ fn trunc( } } -fn airTrunc(self: *Self, inst: Air.Inst.Index) !void { +fn airTrunc(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const operand = try self.resolveInst(ty_op.operand); const operand_ty = self.typeOf(ty_op.operand); @@ -1306,14 +1306,14 @@ fn airTrunc(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } -fn airIntFromBool(self: *Self, inst: Air.Inst.Index) !void { +fn airIntFromBool(self: *Self, inst: Air.Inst.Index) InnerError!void { const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op; const operand = try self.resolveInst(un_op); const result: MCValue = if (self.liveness.isUnused(inst)) .dead else operand; return self.finishAir(inst, result, .{ un_op, .none, .none }); } -fn airNot(self: *Self, inst: Air.Inst.Index) !void { +fn airNot(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const pt = self.pt; const zcu = pt.zcu; @@ -1484,7 +1484,7 @@ fn minMax( } } -fn airMinMax(self: *Self, inst: Air.Inst.Index) !void { +fn airMinMax(self: *Self, inst: Air.Inst.Index) InnerError!void { const tag = self.air.instructions.items(.tag)[@intFromEnum(inst)]; const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; const lhs_ty = self.typeOf(bin_op.lhs); @@ -1502,7 +1502,7 @@ fn airMinMax(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); } -fn airSlice(self: *Self, inst: Air.Inst.Index) !void { +fn airSlice(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { @@ -2440,7 +2440,7 @@ fn ptrArithmetic( } } -fn airBinOp(self: *Self, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void { +fn airBinOp(self: *Self, inst: Air.Inst.Index, tag: Air.Inst.Tag) InnerError!void { const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; const lhs_ty = self.typeOf(bin_op.lhs); const rhs_ty = self.typeOf(bin_op.rhs); @@ -2490,7 +2490,7 @@ fn airBinOp(self: *Self, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void { return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); } -fn airPtrArithmetic(self: *Self, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void { +fn airPtrArithmetic(self: *Self, inst: Air.Inst.Index, tag: Air.Inst.Tag) InnerError!void { const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data; const lhs_ty = self.typeOf(bin_op.lhs); @@ -2505,25 +2505,25 @@ fn airPtrArithmetic(self: *Self, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); } -fn airAddSat(self: *Self, inst: Air.Inst.Index) !void { +fn airAddSat(self: *Self, inst: Air.Inst.Index) InnerError!void { const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement add_sat for {}", .{self.target.cpu.arch}); return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); } -fn airSubSat(self: *Self, inst: Air.Inst.Index) !void { +fn airSubSat(self: *Self, inst: Air.Inst.Index) InnerError!void { const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement sub_sat for {}", .{self.target.cpu.arch}); return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); } -fn airMulSat(self: *Self, inst: Air.Inst.Index) !void { +fn airMulSat(self: *Self, inst: Air.Inst.Index) InnerError!void { const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement mul_sat for {}", .{self.target.cpu.arch}); return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); } -fn airOverflow(self: *Self, inst: Air.Inst.Index) !void { +fn airOverflow(self: *Self, inst: Air.Inst.Index) InnerError!void { const tag = self.air.instructions.items(.tag)[@intFromEnum(inst)]; const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; const extra = self.air.extraData(Air.Bin, ty_pl.payload).data; @@ -2536,9 +2536,9 @@ fn airOverflow(self: *Self, inst: Air.Inst.Index) !void { const rhs_ty = self.typeOf(extra.rhs); const tuple_ty = self.typeOfIndex(inst); - const tuple_size = @as(u32, @intCast(tuple_ty.abiSize(zcu))); + const tuple_size: u32 = @intCast(tuple_ty.abiSize(zcu)); const tuple_align = tuple_ty.abiAlignment(zcu); - const overflow_bit_offset = @as(u32, @intCast(tuple_ty.structFieldOffset(1, zcu))); + const overflow_bit_offset: u32 = @intCast(tuple_ty.structFieldOffset(1, zcu)); switch (lhs_ty.zigTypeTag(zcu)) { .vector => return self.fail("TODO implement add_with_overflow/sub_with_overflow for vectors", .{}), @@ -2652,7 +2652,7 @@ fn airOverflow(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, .none }); } -fn airMulWithOverflow(self: *Self, inst: Air.Inst.Index) !void { +fn airMulWithOverflow(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; const extra = self.air.extraData(Air.Bin, ty_pl.payload).data; if (self.liveness.isUnused(inst)) return self.finishAir(inst, .dead, .{ extra.lhs, extra.rhs, .none }); @@ -2876,7 +2876,7 @@ fn airMulWithOverflow(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, .none }); } -fn airShlWithOverflow(self: *Self, inst: Air.Inst.Index) !void { +fn airShlWithOverflow(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; const extra = self.air.extraData(Air.Bin, ty_pl.payload).data; if (self.liveness.isUnused(inst)) return self.finishAir(inst, .dead, .{ extra.lhs, extra.rhs, .none }); @@ -3012,13 +3012,13 @@ fn airShlWithOverflow(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, .none }); } -fn airShlSat(self: *Self, inst: Air.Inst.Index) !void { +fn airShlSat(self: *Self, inst: Air.Inst.Index) InnerError!void { const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement shl_sat for {}", .{self.target.cpu.arch}); return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); } -fn airOptionalPayload(self: *Self, inst: Air.Inst.Index) !void { +fn airOptionalPayload(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { const optional_ty = self.typeOf(ty_op.operand); @@ -3055,13 +3055,13 @@ fn optionalPayload(self: *Self, inst: Air.Inst.Index, mcv: MCValue, optional_ty: } } -fn airOptionalPayloadPtr(self: *Self, inst: Air.Inst.Index) !void { +fn airOptionalPayloadPtr(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement .optional_payload_ptr for {}", .{self.target.cpu.arch}); return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } -fn airOptionalPayloadPtrSet(self: *Self, inst: Air.Inst.Index) !void { +fn airOptionalPayloadPtrSet(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement .optional_payload_ptr_set for {}", .{self.target.cpu.arch}); return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); @@ -3137,7 +3137,7 @@ fn errUnionErr( } } -fn airUnwrapErrErr(self: *Self, inst: Air.Inst.Index) !void { +fn airUnwrapErrErr(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { const error_union_bind: ReadArg.Bind = .{ .inst = ty_op.operand }; @@ -3218,7 +3218,7 @@ fn errUnionPayload( } } -fn airUnwrapErrPayload(self: *Self, inst: Air.Inst.Index) !void { +fn airUnwrapErrPayload(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { const error_union_bind: ReadArg.Bind = .{ .inst = ty_op.operand }; @@ -3230,26 +3230,26 @@ fn airUnwrapErrPayload(self: *Self, inst: Air.Inst.Index) !void { } // *(E!T) -> E -fn airUnwrapErrErrPtr(self: *Self, inst: Air.Inst.Index) !void { +fn airUnwrapErrErrPtr(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement unwrap error union error ptr for {}", .{self.target.cpu.arch}); return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } // *(E!T) -> *T -fn airUnwrapErrPayloadPtr(self: *Self, inst: Air.Inst.Index) !void { +fn airUnwrapErrPayloadPtr(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement unwrap error union payload ptr for {}", .{self.target.cpu.arch}); return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } -fn airErrUnionPayloadPtrSet(self: *Self, inst: Air.Inst.Index) !void { +fn airErrUnionPayloadPtrSet(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement .errunion_payload_ptr_set for {}", .{self.target.cpu.arch}); return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } -fn airErrReturnTrace(self: *Self, inst: Air.Inst.Index) !void { +fn airErrReturnTrace(self: *Self, inst: Air.Inst.Index) InnerError!void { const result: MCValue = if (self.liveness.isUnused(inst)) .dead else @@ -3257,17 +3257,17 @@ fn airErrReturnTrace(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ .none, .none, .none }); } -fn airSetErrReturnTrace(self: *Self, inst: Air.Inst.Index) !void { +fn airSetErrReturnTrace(self: *Self, inst: Air.Inst.Index) InnerError!void { _ = inst; return self.fail("TODO implement airSetErrReturnTrace for {}", .{self.target.cpu.arch}); } -fn airSaveErrReturnTraceIndex(self: *Self, inst: Air.Inst.Index) !void { +fn airSaveErrReturnTraceIndex(self: *Self, inst: Air.Inst.Index) InnerError!void { _ = inst; return self.fail("TODO implement airSaveErrReturnTraceIndex for {}", .{self.target.cpu.arch}); } -fn airWrapOptional(self: *Self, inst: Air.Inst.Index) !void { +fn airWrapOptional(self: *Self, inst: Air.Inst.Index) InnerError!void { const pt = self.pt; const zcu = pt.zcu; const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; @@ -3313,7 +3313,7 @@ fn airWrapOptional(self: *Self, inst: Air.Inst.Index) !void { } /// T to E!T -fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) !void { +fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!void { const pt = self.pt; const zcu = pt.zcu; const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; @@ -3338,7 +3338,7 @@ fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) !void { } /// E to E!T -fn airWrapErrUnionErr(self: *Self, inst: Air.Inst.Index) !void { +fn airWrapErrUnionErr(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { const pt = self.pt; @@ -3379,7 +3379,7 @@ fn slicePtr(mcv: MCValue) MCValue { } } -fn airSlicePtr(self: *Self, inst: Air.Inst.Index) !void { +fn airSlicePtr(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { const mcv = try self.resolveInst(ty_op.operand); @@ -3388,7 +3388,7 @@ fn airSlicePtr(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } -fn airSliceLen(self: *Self, inst: Air.Inst.Index) !void { +fn airSliceLen(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { const ptr_bits = 64; @@ -3412,7 +3412,7 @@ fn airSliceLen(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } -fn airPtrSliceLenPtr(self: *Self, inst: Air.Inst.Index) !void { +fn airPtrSliceLenPtr(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { const ptr_bits = 64; @@ -3429,7 +3429,7 @@ fn airPtrSliceLenPtr(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } -fn airPtrSlicePtrPtr(self: *Self, inst: Air.Inst.Index) !void { +fn airPtrSlicePtrPtr(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { const mcv = try self.resolveInst(ty_op.operand); @@ -3444,7 +3444,7 @@ fn airPtrSlicePtrPtr(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } -fn airSliceElemVal(self: *Self, inst: Air.Inst.Index) !void { +fn airSliceElemVal(self: *Self, inst: Air.Inst.Index) InnerError!void { const pt = self.pt; const zcu = pt.zcu; const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; @@ -3487,7 +3487,7 @@ fn ptrElemVal( } } -fn airSliceElemPtr(self: *Self, inst: Air.Inst.Index) !void { +fn airSliceElemPtr(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; const extra = self.air.extraData(Air.Bin, ty_pl.payload).data; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { @@ -3506,13 +3506,13 @@ fn airSliceElemPtr(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, .none }); } -fn airArrayElemVal(self: *Self, inst: Air.Inst.Index) !void { +fn airArrayElemVal(self: *Self, inst: Air.Inst.Index) InnerError!void { const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement array_elem_val for {}", .{self.target.cpu.arch}); return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); } -fn airPtrElemVal(self: *Self, inst: Air.Inst.Index) !void { +fn airPtrElemVal(self: *Self, inst: Air.Inst.Index) InnerError!void { const pt = self.pt; const zcu = pt.zcu; const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; @@ -3526,7 +3526,7 @@ fn airPtrElemVal(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); } -fn airPtrElemPtr(self: *Self, inst: Air.Inst.Index) !void { +fn airPtrElemPtr(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; const extra = self.air.extraData(Air.Bin, ty_pl.payload).data; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { @@ -3542,55 +3542,55 @@ fn airPtrElemPtr(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, .none }); } -fn airSetUnionTag(self: *Self, inst: Air.Inst.Index) !void { +fn airSetUnionTag(self: *Self, inst: Air.Inst.Index) InnerError!void { const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; _ = bin_op; return self.fail("TODO implement airSetUnionTag for {}", .{self.target.cpu.arch}); } -fn airGetUnionTag(self: *Self, inst: Air.Inst.Index) !void { +fn airGetUnionTag(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airGetUnionTag for {}", .{self.target.cpu.arch}); return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } -fn airClz(self: *Self, inst: Air.Inst.Index) !void { +fn airClz(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airClz for {}", .{self.target.cpu.arch}); return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } -fn airCtz(self: *Self, inst: Air.Inst.Index) !void { +fn airCtz(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airCtz for {}", .{self.target.cpu.arch}); return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } -fn airPopcount(self: *Self, inst: Air.Inst.Index) !void { +fn airPopcount(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airPopcount for {}", .{self.target.cpu.arch}); return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } -fn airAbs(self: *Self, inst: Air.Inst.Index) !void { +fn airAbs(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airAbs for {}", .{self.target.cpu.arch}); return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } -fn airByteSwap(self: *Self, inst: Air.Inst.Index) !void { +fn airByteSwap(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airByteSwap for {}", .{self.target.cpu.arch}); return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } -fn airBitReverse(self: *Self, inst: Air.Inst.Index) !void { +fn airBitReverse(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airBitReverse for {}", .{self.target.cpu.arch}); return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } -fn airUnaryMath(self: *Self, inst: Air.Inst.Index) !void { +fn airUnaryMath(self: *Self, inst: Air.Inst.Index) InnerError!void { const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead @@ -3885,7 +3885,7 @@ fn genInlineMemsetCode( // end: } -fn airLoad(self: *Self, inst: Air.Inst.Index) !void { +fn airLoad(self: *Self, inst: Air.Inst.Index) InnerError!void { const pt = self.pt; const zcu = pt.zcu; const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; @@ -4086,7 +4086,7 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type } } -fn airStore(self: *Self, inst: Air.Inst.Index, safety: bool) !void { +fn airStore(self: *Self, inst: Air.Inst.Index, safety: bool) InnerError!void { if (safety) { // TODO if the value is undef, write 0xaa bytes to dest } else { @@ -4103,14 +4103,14 @@ fn airStore(self: *Self, inst: Air.Inst.Index, safety: bool) !void { return self.finishAir(inst, .dead, .{ bin_op.lhs, bin_op.rhs, .none }); } -fn airStructFieldPtr(self: *Self, inst: Air.Inst.Index) !void { +fn airStructFieldPtr(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; const extra = self.air.extraData(Air.StructField, ty_pl.payload).data; const result = try self.structFieldPtr(inst, extra.struct_operand, extra.field_index); return self.finishAir(inst, result, .{ extra.struct_operand, .none, .none }); } -fn airStructFieldPtrIndex(self: *Self, inst: Air.Inst.Index, index: u8) !void { +fn airStructFieldPtrIndex(self: *Self, inst: Air.Inst.Index, index: u8) InnerError!void { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const result = try self.structFieldPtr(inst, ty_op.operand, index); return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); @@ -4138,7 +4138,7 @@ fn structFieldPtr(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, inde }; } -fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void { +fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; const extra = self.air.extraData(Air.StructField, ty_pl.payload).data; const operand = extra.struct_operand; @@ -4194,7 +4194,7 @@ fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ extra.struct_operand, .none, .none }); } -fn airFieldParentPtr(self: *Self, inst: Air.Inst.Index) !void { +fn airFieldParentPtr(self: *Self, inst: Air.Inst.Index) InnerError!void { const pt = self.pt; const zcu = pt.zcu; const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; @@ -4218,7 +4218,7 @@ fn airFieldParentPtr(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ extra.field_ptr, .none, .none }); } -fn airArg(self: *Self, inst: Air.Inst.Index) !void { +fn airArg(self: *Self, inst: Air.Inst.Index) InnerError!void { // skip zero-bit arguments as they don't have a corresponding arg instruction var arg_index = self.arg_index; while (self.args[arg_index] == .none) arg_index += 1; @@ -4238,7 +4238,7 @@ fn airArg(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ .none, .none, .none }); } -fn airTrap(self: *Self) !void { +fn airTrap(self: *Self) InnerError!void { _ = try self.addInst(.{ .tag = .brk, .data = .{ .imm16 = 0x0001 }, @@ -4246,7 +4246,7 @@ fn airTrap(self: *Self) !void { return self.finishAirBookkeeping(); } -fn airBreakpoint(self: *Self) !void { +fn airBreakpoint(self: *Self) InnerError!void { _ = try self.addInst(.{ .tag = .brk, .data = .{ .imm16 = 0xf000 }, @@ -4254,17 +4254,17 @@ fn airBreakpoint(self: *Self) !void { return self.finishAirBookkeeping(); } -fn airRetAddr(self: *Self, inst: Air.Inst.Index) !void { +fn airRetAddr(self: *Self, inst: Air.Inst.Index) InnerError!void { const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airRetAddr for aarch64", .{}); return self.finishAir(inst, result, .{ .none, .none, .none }); } -fn airFrameAddress(self: *Self, inst: Air.Inst.Index) !void { +fn airFrameAddress(self: *Self, inst: Air.Inst.Index) InnerError!void { const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airFrameAddress for aarch64", .{}); return self.finishAir(inst, result, .{ .none, .none, .none }); } -fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier) !void { +fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier) InnerError!void { if (modifier == .always_tail) return self.fail("TODO implement tail calls for aarch64", .{}); const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; const callee = pl_op.operand; @@ -4422,7 +4422,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier return bt.finishAir(result); } -fn airRet(self: *Self, inst: Air.Inst.Index) !void { +fn airRet(self: *Self, inst: Air.Inst.Index) InnerError!void { const pt = self.pt; const zcu = pt.zcu; const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op; @@ -4455,7 +4455,7 @@ fn airRet(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, .dead, .{ un_op, .none, .none }); } -fn airRetLoad(self: *Self, inst: Air.Inst.Index) !void { +fn airRetLoad(self: *Self, inst: Air.Inst.Index) InnerError!void { const pt = self.pt; const zcu = pt.zcu; const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op; @@ -4499,7 +4499,7 @@ fn airRetLoad(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, .dead, .{ un_op, .none, .none }); } -fn airCmp(self: *Self, inst: Air.Inst.Index, op: math.CompareOperator) !void { +fn airCmp(self: *Self, inst: Air.Inst.Index, op: math.CompareOperator) InnerError!void { const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; const lhs_ty = self.typeOf(bin_op.lhs); @@ -4597,12 +4597,12 @@ fn cmp( } } -fn airCmpVector(self: *Self, inst: Air.Inst.Index) !void { +fn airCmpVector(self: *Self, inst: Air.Inst.Index) InnerError!void { _ = inst; return self.fail("TODO implement airCmpVector for {}", .{self.target.cpu.arch}); } -fn airCmpLtErrorsLen(self: *Self, inst: Air.Inst.Index) !void { +fn airCmpLtErrorsLen(self: *Self, inst: Air.Inst.Index) InnerError!void { const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op; const operand = try self.resolveInst(un_op); _ = operand; @@ -4610,7 +4610,7 @@ fn airCmpLtErrorsLen(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ un_op, .none, .none }); } -fn airDbgStmt(self: *Self, inst: Air.Inst.Index) !void { +fn airDbgStmt(self: *Self, inst: Air.Inst.Index) InnerError!void { const dbg_stmt = self.air.instructions.items(.data)[@intFromEnum(inst)].dbg_stmt; _ = try self.addInst(.{ @@ -4624,7 +4624,7 @@ fn airDbgStmt(self: *Self, inst: Air.Inst.Index) !void { return self.finishAirBookkeeping(); } -fn airDbgInlineBlock(self: *Self, inst: Air.Inst.Index) !void { +fn airDbgInlineBlock(self: *Self, inst: Air.Inst.Index) InnerError!void { const pt = self.pt; const zcu = pt.zcu; const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; @@ -4635,7 +4635,7 @@ fn airDbgInlineBlock(self: *Self, inst: Air.Inst.Index) !void { try self.lowerBlock(inst, @ptrCast(self.air.extra[extra.end..][0..extra.data.body_len])); } -fn airDbgVar(self: *Self, inst: Air.Inst.Index) !void { +fn airDbgVar(self: *Self, inst: Air.Inst.Index) InnerError!void { const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; const operand = pl_op.operand; const tag = self.air.instructions.items(.tag)[@intFromEnum(inst)]; @@ -4686,7 +4686,7 @@ fn condBr(self: *Self, condition: MCValue) !Mir.Inst.Index { } } -fn airCondBr(self: *Self, inst: Air.Inst.Index) !void { +fn airCondBr(self: *Self, inst: Air.Inst.Index) InnerError!void { const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; const cond = try self.resolveInst(pl_op.operand); const extra = self.air.extraData(Air.CondBr, pl_op.payload); @@ -4919,7 +4919,7 @@ fn isNonErr( } } -fn airIsNull(self: *Self, inst: Air.Inst.Index) !void { +fn airIsNull(self: *Self, inst: Air.Inst.Index) InnerError!void { const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { const operand = try self.resolveInst(un_op); @@ -4930,7 +4930,7 @@ fn airIsNull(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ un_op, .none, .none }); } -fn airIsNullPtr(self: *Self, inst: Air.Inst.Index) !void { +fn airIsNullPtr(self: *Self, inst: Air.Inst.Index) InnerError!void { const pt = self.pt; const zcu = pt.zcu; const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op; @@ -4947,7 +4947,7 @@ fn airIsNullPtr(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ un_op, .none, .none }); } -fn airIsNonNull(self: *Self, inst: Air.Inst.Index) !void { +fn airIsNonNull(self: *Self, inst: Air.Inst.Index) InnerError!void { const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { const operand = try self.resolveInst(un_op); @@ -4958,7 +4958,7 @@ fn airIsNonNull(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ un_op, .none, .none }); } -fn airIsNonNullPtr(self: *Self, inst: Air.Inst.Index) !void { +fn airIsNonNullPtr(self: *Self, inst: Air.Inst.Index) InnerError!void { const pt = self.pt; const zcu = pt.zcu; const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op; @@ -4975,7 +4975,7 @@ fn airIsNonNullPtr(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ un_op, .none, .none }); } -fn airIsErr(self: *Self, inst: Air.Inst.Index) !void { +fn airIsErr(self: *Self, inst: Air.Inst.Index) InnerError!void { const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { const error_union_bind: ReadArg.Bind = .{ .inst = un_op }; @@ -4986,7 +4986,7 @@ fn airIsErr(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ un_op, .none, .none }); } -fn airIsErrPtr(self: *Self, inst: Air.Inst.Index) !void { +fn airIsErrPtr(self: *Self, inst: Air.Inst.Index) InnerError!void { const pt = self.pt; const zcu = pt.zcu; const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op; @@ -5003,7 +5003,7 @@ fn airIsErrPtr(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ un_op, .none, .none }); } -fn airIsNonErr(self: *Self, inst: Air.Inst.Index) !void { +fn airIsNonErr(self: *Self, inst: Air.Inst.Index) InnerError!void { const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { const error_union_bind: ReadArg.Bind = .{ .inst = un_op }; @@ -5014,7 +5014,7 @@ fn airIsNonErr(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ un_op, .none, .none }); } -fn airIsNonErrPtr(self: *Self, inst: Air.Inst.Index) !void { +fn airIsNonErrPtr(self: *Self, inst: Air.Inst.Index) InnerError!void { const pt = self.pt; const zcu = pt.zcu; const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op; @@ -5031,7 +5031,7 @@ fn airIsNonErrPtr(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ un_op, .none, .none }); } -fn airLoop(self: *Self, inst: Air.Inst.Index) !void { +fn airLoop(self: *Self, inst: Air.Inst.Index) InnerError!void { // A loop is a setup to be able to jump back to the beginning. const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; const loop = self.air.extraData(Air.Block, ty_pl.payload); @@ -5052,7 +5052,7 @@ fn jump(self: *Self, inst: Mir.Inst.Index) !void { }); } -fn airBlock(self: *Self, inst: Air.Inst.Index) !void { +fn airBlock(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; const extra = self.air.extraData(Air.Block, ty_pl.payload); try self.lowerBlock(inst, @ptrCast(self.air.extra[extra.end..][0..extra.data.body_len])); @@ -5090,7 +5090,7 @@ fn lowerBlock(self: *Self, inst: Air.Inst.Index, body: []const Air.Inst.Index) ! return self.finishAir(inst, result, .{ .none, .none, .none }); } -fn airSwitch(self: *Self, inst: Air.Inst.Index) !void { +fn airSwitch(self: *Self, inst: Air.Inst.Index) InnerError!void { const switch_br = self.air.unwrapSwitch(inst); const condition_ty = self.typeOf(switch_br.operand); const liveness = try self.liveness.getSwitchBr( @@ -5224,7 +5224,7 @@ fn performReloc(self: *Self, inst: Mir.Inst.Index) !void { } } -fn airBr(self: *Self, inst: Air.Inst.Index) !void { +fn airBr(self: *Self, inst: Air.Inst.Index) InnerError!void { const branch = self.air.instructions.items(.data)[@intFromEnum(inst)].br; try self.br(branch.block_inst, branch.operand); return self.finishAir(inst, .dead, .{ branch.operand, .none, .none }); @@ -5268,7 +5268,7 @@ fn brVoid(self: *Self, block: Air.Inst.Index) !void { })); } -fn airAsm(self: *Self, inst: Air.Inst.Index) !void { +fn airAsm(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; const extra = self.air.extraData(Air.Asm, ty_pl.payload); const is_volatile = @as(u1, @truncate(extra.data.flags >> 31)) != 0; @@ -5601,7 +5601,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void .tag = .ldr_ptr_stack, .data = .{ .load_store_stack = .{ .rt = reg, - .offset = @as(u32, @intCast(off)), + .offset = @intCast(off), } }, }); }, @@ -5617,13 +5617,13 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void .immediate => |x| { _ = try self.addInst(.{ .tag = .movz, - .data = .{ .r_imm16_sh = .{ .rd = reg, .imm16 = @as(u16, @truncate(x)) } }, + .data = .{ .r_imm16_sh = .{ .rd = reg, .imm16 = @truncate(x) } }, }); if (x & 0x0000_0000_ffff_0000 != 0) { _ = try self.addInst(.{ .tag = .movk, - .data = .{ .r_imm16_sh = .{ .rd = reg, .imm16 = @as(u16, @truncate(x >> 16)), .hw = 1 } }, + .data = .{ .r_imm16_sh = .{ .rd = reg, .imm16 = @truncate(x >> 16), .hw = 1 } }, }); } @@ -5631,13 +5631,13 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void if (x & 0x0000_ffff_0000_0000 != 0) { _ = try self.addInst(.{ .tag = .movk, - .data = .{ .r_imm16_sh = .{ .rd = reg, .imm16 = @as(u16, @truncate(x >> 32)), .hw = 2 } }, + .data = .{ .r_imm16_sh = .{ .rd = reg, .imm16 = @truncate(x >> 32), .hw = 2 } }, }); } if (x & 0xffff_0000_0000_0000 != 0) { _ = try self.addInst(.{ .tag = .movk, - .data = .{ .r_imm16_sh = .{ .rd = reg, .imm16 = @as(u16, @truncate(x >> 48)), .hw = 3 } }, + .data = .{ .r_imm16_sh = .{ .rd = reg, .imm16 = @truncate(x >> 48), .hw = 3 } }, }); } } @@ -5709,7 +5709,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void .tag = tag, .data = .{ .load_store_stack = .{ .rt = reg, - .offset = @as(u32, @intCast(off)), + .offset = @intCast(off), } }, }); }, @@ -5733,7 +5733,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void .tag = tag, .data = .{ .load_store_stack = .{ .rt = reg, - .offset = @as(u32, @intCast(off)), + .offset = @intCast(off), } }, }); }, @@ -5918,13 +5918,13 @@ fn genSetStackArgument(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) I } } -fn airIntFromPtr(self: *Self, inst: Air.Inst.Index) !void { +fn airIntFromPtr(self: *Self, inst: Air.Inst.Index) InnerError!void { const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op; const result = try self.resolveInst(un_op); return self.finishAir(inst, result, .{ un_op, .none, .none }); } -fn airBitCast(self: *Self, inst: Air.Inst.Index) !void { +fn airBitCast(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const result = if (self.liveness.isUnused(inst)) .dead else result: { const operand = try self.resolveInst(ty_op.operand); @@ -5945,7 +5945,7 @@ fn airBitCast(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } -fn airArrayToSlice(self: *Self, inst: Air.Inst.Index) !void { +fn airArrayToSlice(self: *Self, inst: Air.Inst.Index) InnerError!void { const pt = self.pt; const zcu = pt.zcu; const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; @@ -5963,7 +5963,7 @@ fn airArrayToSlice(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } -fn airFloatFromInt(self: *Self, inst: Air.Inst.Index) !void { +fn airFloatFromInt(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airFloatFromInt for {}", .{ self.target.cpu.arch, @@ -5971,7 +5971,7 @@ fn airFloatFromInt(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } -fn airIntFromFloat(self: *Self, inst: Air.Inst.Index) !void { +fn airIntFromFloat(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airIntFromFloat for {}", .{ self.target.cpu.arch, @@ -5979,7 +5979,7 @@ fn airIntFromFloat(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } -fn airCmpxchg(self: *Self, inst: Air.Inst.Index) !void { +fn airCmpxchg(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; const extra = self.air.extraData(Air.Block, ty_pl.payload); _ = extra; @@ -5989,23 +5989,23 @@ fn airCmpxchg(self: *Self, inst: Air.Inst.Index) !void { }); } -fn airAtomicRmw(self: *Self, inst: Air.Inst.Index) !void { +fn airAtomicRmw(self: *Self, inst: Air.Inst.Index) InnerError!void { _ = inst; return self.fail("TODO implement airCmpxchg for {}", .{self.target.cpu.arch}); } -fn airAtomicLoad(self: *Self, inst: Air.Inst.Index) !void { +fn airAtomicLoad(self: *Self, inst: Air.Inst.Index) InnerError!void { _ = inst; return self.fail("TODO implement airAtomicLoad for {}", .{self.target.cpu.arch}); } -fn airAtomicStore(self: *Self, inst: Air.Inst.Index, order: std.builtin.AtomicOrder) !void { +fn airAtomicStore(self: *Self, inst: Air.Inst.Index, order: std.builtin.AtomicOrder) InnerError!void { _ = inst; _ = order; return self.fail("TODO implement airAtomicStore for {}", .{self.target.cpu.arch}); } -fn airMemset(self: *Self, inst: Air.Inst.Index, safety: bool) !void { +fn airMemset(self: *Self, inst: Air.Inst.Index, safety: bool) InnerError!void { _ = inst; if (safety) { // TODO if the value is undef, write 0xaa bytes to dest @@ -6015,12 +6015,12 @@ fn airMemset(self: *Self, inst: Air.Inst.Index, safety: bool) !void { return self.fail("TODO implement airMemset for {}", .{self.target.cpu.arch}); } -fn airMemcpy(self: *Self, inst: Air.Inst.Index) !void { +fn airMemcpy(self: *Self, inst: Air.Inst.Index) InnerError!void { _ = inst; return self.fail("TODO implement airMemcpy for {}", .{self.target.cpu.arch}); } -fn airTagName(self: *Self, inst: Air.Inst.Index) !void { +fn airTagName(self: *Self, inst: Air.Inst.Index) InnerError!void { const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op; const operand = try self.resolveInst(un_op); const result: MCValue = if (self.liveness.isUnused(inst)) .dead else { @@ -6030,7 +6030,7 @@ fn airTagName(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ un_op, .none, .none }); } -fn airErrorName(self: *Self, inst: Air.Inst.Index) !void { +fn airErrorName(self: *Self, inst: Air.Inst.Index) InnerError!void { const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op; const operand = try self.resolveInst(un_op); const result: MCValue = if (self.liveness.isUnused(inst)) .dead else { @@ -6040,33 +6040,33 @@ fn airErrorName(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ un_op, .none, .none }); } -fn airSplat(self: *Self, inst: Air.Inst.Index) !void { +fn airSplat(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airSplat for {}", .{self.target.cpu.arch}); return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } -fn airSelect(self: *Self, inst: Air.Inst.Index) !void { +fn airSelect(self: *Self, inst: Air.Inst.Index) InnerError!void { const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; const extra = self.air.extraData(Air.Bin, pl_op.payload).data; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airSelect for {}", .{self.target.cpu.arch}); return self.finishAir(inst, result, .{ pl_op.operand, extra.lhs, extra.rhs }); } -fn airShuffle(self: *Self, inst: Air.Inst.Index) !void { +fn airShuffle(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; const extra = self.air.extraData(Air.Shuffle, ty_pl.payload).data; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airShuffle for {}", .{self.target.cpu.arch}); return self.finishAir(inst, result, .{ extra.a, extra.b, .none }); } -fn airReduce(self: *Self, inst: Air.Inst.Index) !void { +fn airReduce(self: *Self, inst: Air.Inst.Index) InnerError!void { const reduce = self.air.instructions.items(.data)[@intFromEnum(inst)].reduce; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airReduce for aarch64", .{}); return self.finishAir(inst, result, .{ reduce.operand, .none, .none }); } -fn airAggregateInit(self: *Self, inst: Air.Inst.Index) !void { +fn airAggregateInit(self: *Self, inst: Air.Inst.Index) InnerError!void { const pt = self.pt; const zcu = pt.zcu; const vector_ty = self.typeOfIndex(inst); @@ -6090,19 +6090,19 @@ fn airAggregateInit(self: *Self, inst: Air.Inst.Index) !void { return bt.finishAir(result); } -fn airUnionInit(self: *Self, inst: Air.Inst.Index) !void { +fn airUnionInit(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; const extra = self.air.extraData(Air.UnionInit, ty_pl.payload).data; _ = extra; return self.fail("TODO implement airUnionInit for aarch64", .{}); } -fn airPrefetch(self: *Self, inst: Air.Inst.Index) !void { +fn airPrefetch(self: *Self, inst: Air.Inst.Index) InnerError!void { const prefetch = self.air.instructions.items(.data)[@intFromEnum(inst)].prefetch; return self.finishAir(inst, MCValue.dead, .{ prefetch.ptr, .none, .none }); } -fn airMulAdd(self: *Self, inst: Air.Inst.Index) !void { +fn airMulAdd(self: *Self, inst: Air.Inst.Index) InnerError!void { const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; const extra = self.air.extraData(Air.Bin, pl_op.payload).data; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else { @@ -6111,7 +6111,7 @@ fn airMulAdd(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, pl_op.operand }); } -fn airTry(self: *Self, inst: Air.Inst.Index) !void { +fn airTry(self: *Self, inst: Air.Inst.Index) InnerError!void { const pt = self.pt; const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; const extra = self.air.extraData(Air.Try, pl_op.payload); @@ -6139,7 +6139,7 @@ fn airTry(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ pl_op.operand, .none, .none }); } -fn airTryPtr(self: *Self, inst: Air.Inst.Index) !void { +fn airTryPtr(self: *Self, inst: Air.Inst.Index) InnerError!void { const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; const extra = self.air.extraData(Air.TryPtr, ty_pl.payload); const body = self.air.extra[extra.end..][0..extra.data.body_len]; diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index 065f4a047d..3d3a821668 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -245,7 +245,7 @@ const DbgInfoReloc = struct { name: [:0]const u8, mcv: MCValue, - fn genDbgInfo(reloc: DbgInfoReloc, function: Self) !void { + fn genDbgInfo(reloc: DbgInfoReloc, function: Self) CodeGenError!void { switch (reloc.tag) { .arg, .dbg_arg_inline, @@ -259,7 +259,7 @@ const DbgInfoReloc = struct { } } - fn genArgDbgInfo(reloc: DbgInfoReloc, function: Self) !void { + fn genArgDbgInfo(reloc: DbgInfoReloc, function: Self) CodeGenError!void { switch (function.debug_output) { .dwarf => |dw| { const loc: link.File.Dwarf.Loc = switch (reloc.mcv) { @@ -287,7 +287,7 @@ const DbgInfoReloc = struct { } } - fn genVarDbgInfo(reloc: DbgInfoReloc, function: Self) !void { + fn genVarDbgInfo(reloc: DbgInfoReloc, function: Self) CodeGenError!void { switch (function.debug_output) { .dwarf => |dw| { const loc: link.File.Dwarf.Loc = switch (reloc.mcv) { diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 49961042bc..e3febb4451 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -6,7 +6,6 @@ const assert = std.debug.assert; const testing = std.testing; const leb = std.leb; const mem = std.mem; -const wasm = std.wasm; const log = std.log.scoped(.codegen); const codegen = @import("../../codegen.zig"); @@ -55,22 +54,19 @@ const WValue = union(enum) { float32: f32, /// A constant 64bit float value float64: f64, - /// A value that represents a pointer to the data section - /// Note: The value contains the symbol index, rather than the actual address - /// as we use this to perform the relocation. - memory: u32, + /// A value that represents a pointer to the data section. + memory: InternPool.Index, /// A value that represents a parent pointer and an offset /// from that pointer. i.e. when slicing with constant values. memory_offset: struct { - /// The symbol of the parent pointer - pointer: u32, + pointer: InternPool.Index, /// Offset will be set as addend when relocating offset: u32, }, /// Represents a function pointer /// In wasm function pointers are indexes into a function table, /// rather than an address in the data section. - function_index: u32, + function_index: InternPool.Index, /// Offset from the bottom of the virtual stack, with the offset /// pointing to where the value lives. stack_offset: struct { @@ -119,7 +115,7 @@ const WValue = union(enum) { if (local_value < reserved + 2) return; // reserved locals may never be re-used. Also accounts for 2 stack locals. const index = local_value - reserved; - const valtype = @as(wasm.Valtype, @enumFromInt(gen.locals.items[index])); + const valtype: std.wasm.Valtype = @enumFromInt(gen.locals.items[index]); switch (valtype) { .i32 => gen.free_locals_i32.append(gen.gpa, local_value) catch return, // It's ok to fail any of those, a new local can be allocated instead .i64 => gen.free_locals_i64.append(gen.gpa, local_value) catch return, @@ -132,8 +128,6 @@ const WValue = union(enum) { } }; -/// Wasm ops, but without input/output/signedness information -/// Used for `buildOpcode` const Op = enum { @"unreachable", nop, @@ -200,70 +194,42 @@ const Op = enum { extend, }; -/// Contains the settings needed to create an `Opcode` using `buildOpcode`. -/// -/// The fields correspond to the opcode name. Here is an example -/// i32_trunc_f32_s -/// ^ ^ ^ ^ -/// | | | | -/// valtype1 | | | -/// = .i32 | | | -/// | | | -/// op | | -/// = .trunc | | -/// | | -/// valtype2 | -/// = .f32 | -/// | -/// width | -/// = null | -/// | -/// signed -/// = true -/// -/// There can be missing fields, here are some more examples: -/// i64_load8_u -/// --> .{ .valtype1 = .i64, .op = .load, .width = 8, signed = false } -/// i32_mul -/// --> .{ .valtype1 = .i32, .op = .trunc } -/// nop -/// --> .{ .op = .nop } const OpcodeBuildArguments = struct { /// First valtype in the opcode (usually represents the type of the output) - valtype1: ?wasm.Valtype = null, + valtype1: ?std.wasm.Valtype = null, /// The operation (e.g. call, unreachable, div, min, sqrt, etc.) op: Op, /// Width of the operation (e.g. 8 for i32_load8_s, 16 for i64_extend16_i32_s) width: ?u8 = null, /// Second valtype in the opcode name (usually represents the type of the input) - valtype2: ?wasm.Valtype = null, + valtype2: ?std.wasm.Valtype = null, /// Signedness of the op signedness: ?std.builtin.Signedness = null, }; -/// Helper function that builds an Opcode given the arguments needed -fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode { +/// TODO: deprecated, should be split up per tag. +fn buildOpcode(args: OpcodeBuildArguments) std.wasm.Opcode { switch (args.op) { - .@"unreachable" => return .@"unreachable", - .nop => return .nop, - .block => return .block, - .loop => return .loop, - .@"if" => return .@"if", - .@"else" => return .@"else", - .end => return .end, - .br => return .br, - .br_if => return .br_if, - .br_table => return .br_table, - .@"return" => return .@"return", - .call => return .call, - .call_indirect => return .call_indirect, - .drop => return .drop, - .select => return .select, - .local_get => return .local_get, - .local_set => return .local_set, - .local_tee => return .local_tee, - .global_get => return .global_get, - .global_set => return .global_set, + .@"unreachable" => unreachable, + .nop => unreachable, + .block => unreachable, + .loop => unreachable, + .@"if" => unreachable, + .@"else" => unreachable, + .end => unreachable, + .br => unreachable, + .br_if => unreachable, + .br_table => unreachable, + .@"return" => unreachable, + .call => unreachable, + .call_indirect => unreachable, + .drop => unreachable, + .select => unreachable, + .local_get => unreachable, + .local_set => unreachable, + .local_tee => unreachable, + .global_get => unreachable, + .global_set => unreachable, .load => if (args.width) |width| switch (width) { 8 => switch (args.valtype1.?) { @@ -626,11 +592,11 @@ test "Wasm - buildOpcode" { const i64_extend32_s = buildOpcode(.{ .op = .extend, .valtype1 = .i64, .width = 32, .signedness = .signed }); const f64_reinterpret_i64 = buildOpcode(.{ .op = .reinterpret, .valtype1 = .f64, .valtype2 = .i64 }); - try testing.expectEqual(@as(wasm.Opcode, .i32_const), i32_const); - try testing.expectEqual(@as(wasm.Opcode, .end), end); - try testing.expectEqual(@as(wasm.Opcode, .local_get), local_get); - try testing.expectEqual(@as(wasm.Opcode, .i64_extend32_s), i64_extend32_s); - try testing.expectEqual(@as(wasm.Opcode, .f64_reinterpret_i64), f64_reinterpret_i64); + try testing.expectEqual(@as(std.wasm.Opcode, .i32_const), i32_const); + try testing.expectEqual(@as(std.wasm.Opcode, .end), end); + try testing.expectEqual(@as(std.wasm.Opcode, .local_get), local_get); + try testing.expectEqual(@as(std.wasm.Opcode, .i64_extend32_s), i64_extend32_s); + try testing.expectEqual(@as(std.wasm.Opcode, .f64_reinterpret_i64), f64_reinterpret_i64); } /// Hashmap to store generated `WValue` for each `Air.Inst.Ref` @@ -806,13 +772,7 @@ fn resolveInst(func: *CodeGen, ref: Air.Inst.Ref) InnerError!WValue { // In the other cases, we will simply lower the constant to a value that fits // into a single local (such as a pointer, integer, bool, etc). const result: WValue = if (isByRef(ty, pt, func.target.*)) - switch (try func.bin_file.lowerUav(pt, val.toIntern(), .none, func.src_loc)) { - .mcv => |mcv| .{ .memory = mcv.load_symbol }, - .fail => |err_msg| { - func.err_msg = err_msg; - return error.CodegenFail; - }, - } + .{ .memory = val.toIntern() } else try func.lowerConstant(val, ty); @@ -919,7 +879,7 @@ fn addTag(func: *CodeGen, tag: Mir.Inst.Tag) error{OutOfMemory}!void { try func.addInst(.{ .tag = tag, .data = .{ .tag = {} } }); } -fn addExtended(func: *CodeGen, opcode: wasm.MiscOpcode) error{OutOfMemory}!void { +fn addExtended(func: *CodeGen, opcode: std.wasm.MiscOpcode) error{OutOfMemory}!void { const extra_index = @as(u32, @intCast(func.mir_extra.items.len)); try func.mir_extra.append(func.gpa, @intFromEnum(opcode)); try func.addInst(.{ .tag = .misc_prefix, .data = .{ .payload = extra_index } }); @@ -929,6 +889,10 @@ fn addLabel(func: *CodeGen, tag: Mir.Inst.Tag, label: u32) error{OutOfMemory}!vo try func.addInst(.{ .tag = tag, .data = .{ .label = label } }); } +fn addCallTagName(func: *CodeGen, ip_index: InternPool.Index) error{OutOfMemory}!void { + try func.addInst(.{ .tag = .call_tag_name, .data = .{ .ip_index = ip_index } }); +} + /// Accepts an unsigned 32bit integer rather than a signed integer to /// prevent us from having to bitcast multiple times as most values /// within codegen are represented as unsigned rather than signed. @@ -950,7 +914,7 @@ fn addImm128(func: *CodeGen, index: u32) error{OutOfMemory}!void { const extra_index = @as(u32, @intCast(func.mir_extra.items.len)); // tag + 128bit value try func.mir_extra.ensureUnusedCapacity(func.gpa, 5); - func.mir_extra.appendAssumeCapacity(std.wasm.simdOpcode(.v128_const)); + func.mir_extra.appendAssumeCapacity(@intFromEnum(std.wasm.SimdOpcode.v128_const)); func.mir_extra.appendSliceAssumeCapacity(@alignCast(mem.bytesAsSlice(u32, &simd_values))); try func.addInst(.{ .tag = .simd_prefix, .data = .{ .payload = extra_index } }); } @@ -968,15 +932,15 @@ fn addMemArg(func: *CodeGen, tag: Mir.Inst.Tag, mem_arg: Mir.MemArg) error{OutOf /// Inserts an instruction from the 'atomics' feature which accesses wasm's linear memory dependent on the /// given `tag`. -fn addAtomicMemArg(func: *CodeGen, tag: wasm.AtomicsOpcode, mem_arg: Mir.MemArg) error{OutOfMemory}!void { - const extra_index = try func.addExtra(@as(struct { val: u32 }, .{ .val = wasm.atomicsOpcode(tag) })); +fn addAtomicMemArg(func: *CodeGen, tag: std.wasm.AtomicsOpcode, mem_arg: Mir.MemArg) error{OutOfMemory}!void { + const extra_index = try func.addExtra(@as(struct { val: u32 }, .{ .val = @intFromEnum(tag) })); _ = try func.addExtra(mem_arg); try func.addInst(.{ .tag = .atomics_prefix, .data = .{ .payload = extra_index } }); } /// Helper function to emit atomic mir opcodes. -fn addAtomicTag(func: *CodeGen, tag: wasm.AtomicsOpcode) error{OutOfMemory}!void { - const extra_index = try func.addExtra(@as(struct { val: u32 }, .{ .val = wasm.atomicsOpcode(tag) })); +fn addAtomicTag(func: *CodeGen, tag: std.wasm.AtomicsOpcode) error{OutOfMemory}!void { + const extra_index = try func.addExtra(@as(struct { val: u32 }, .{ .val = @intFromEnum(tag) })); try func.addInst(.{ .tag = .atomics_prefix, .data = .{ .payload = extra_index } }); } @@ -1003,7 +967,7 @@ fn addExtraAssumeCapacity(func: *CodeGen, extra: anytype) error{OutOfMemory}!u32 } /// Using a given `Type`, returns the corresponding valtype for .auto callconv -fn typeToValtype(ty: Type, pt: Zcu.PerThread, target: std.Target) wasm.Valtype { +fn typeToValtype(ty: Type, pt: Zcu.PerThread, target: std.Target) std.wasm.Valtype { const zcu = pt.zcu; const ip = &zcu.intern_pool; return switch (ty.zigTypeTag(zcu)) { @@ -1044,7 +1008,7 @@ fn typeToValtype(ty: Type, pt: Zcu.PerThread, target: std.Target) wasm.Valtype { /// Using a given `Type`, returns the byte representation of its wasm value type fn genValtype(ty: Type, pt: Zcu.PerThread, target: std.Target) u8 { - return wasm.valtype(typeToValtype(ty, pt, target)); + return @intFromEnum(typeToValtype(ty, pt, target)); } /// Using a given `Type`, returns the corresponding wasm value type @@ -1052,7 +1016,7 @@ fn genValtype(ty: Type, pt: Zcu.PerThread, target: std.Target) u8 { /// with no return type fn genBlockType(ty: Type, pt: Zcu.PerThread, target: std.Target) u8 { return switch (ty.ip_index) { - .void_type, .noreturn_type => wasm.block_empty, + .void_type, .noreturn_type => std.wasm.block_empty, else => genValtype(ty, pt, target), }; } @@ -1141,35 +1105,34 @@ fn ensureAllocLocal(func: *CodeGen, ty: Type) InnerError!WValue { return .{ .local = .{ .value = initial_index, .references = 1 } }; } -/// Generates a `wasm.Type` from a given function type. -/// Memory is owned by the caller. fn genFunctype( - gpa: Allocator, + wasm: *link.File.Wasm, cc: std.builtin.CallingConvention, params: []const InternPool.Index, return_type: Type, pt: Zcu.PerThread, target: std.Target, -) !wasm.Type { +) !link.File.Wasm.FunctionType.Index { const zcu = pt.zcu; - var temp_params = std.ArrayList(wasm.Valtype).init(gpa); - defer temp_params.deinit(); - var returns = std.ArrayList(wasm.Valtype).init(gpa); - defer returns.deinit(); + const gpa = zcu.gpa; + var temp_params: std.ArrayListUnmanaged(std.wasm.Valtype) = .empty; + defer temp_params.deinit(gpa); + var returns: std.ArrayListUnmanaged(std.wasm.Valtype) = .empty; + defer returns.deinit(gpa); if (firstParamSRet(cc, return_type, pt, target)) { - try temp_params.append(.i32); // memory address is always a 32-bit handle + try temp_params.append(gpa, .i32); // memory address is always a 32-bit handle } else if (return_type.hasRuntimeBitsIgnoreComptime(zcu)) { if (cc == .wasm_watc) { const res_classes = abi.classifyType(return_type, zcu); assert(res_classes[0] == .direct and res_classes[1] == .none); const scalar_type = abi.scalarType(return_type, zcu); - try returns.append(typeToValtype(scalar_type, pt, target)); + try returns.append(gpa, typeToValtype(scalar_type, pt, target)); } else { - try returns.append(typeToValtype(return_type, pt, target)); + try returns.append(gpa, typeToValtype(return_type, pt, target)); } } else if (return_type.isError(zcu)) { - try returns.append(.i32); + try returns.append(gpa, .i32); } // param types @@ -1183,24 +1146,24 @@ fn genFunctype( if (param_classes[1] == .none) { if (param_classes[0] == .direct) { const scalar_type = abi.scalarType(param_type, zcu); - try temp_params.append(typeToValtype(scalar_type, pt, target)); + try temp_params.append(gpa, typeToValtype(scalar_type, pt, target)); } else { - try temp_params.append(typeToValtype(param_type, pt, target)); + try temp_params.append(gpa, typeToValtype(param_type, pt, target)); } } else { // i128/f128 - try temp_params.append(.i64); - try temp_params.append(.i64); + try temp_params.append(gpa, .i64); + try temp_params.append(gpa, .i64); } }, - else => try temp_params.append(typeToValtype(param_type, pt, target)), + else => try temp_params.append(gpa, typeToValtype(param_type, pt, target)), } } - return wasm.Type{ - .params = try temp_params.toOwnedSlice(), - .returns = try returns.toOwnedSlice(), - }; + return wasm.addFuncType(.{ + .params = try wasm.internValtypeList(temp_params.items), + .returns = try wasm.internValtypeList(returns.items), + }); } pub fn generate( @@ -1244,14 +1207,13 @@ pub fn generate( } fn genFunc(func: *CodeGen) InnerError!void { + const wasm = func.bin_file; const pt = func.pt; const zcu = pt.zcu; const ip = &zcu.intern_pool; const fn_ty = zcu.navValue(func.owner_nav).typeOf(zcu); const fn_info = zcu.typeToFunc(fn_ty).?; - var func_type = try genFunctype(func.gpa, fn_info.cc, fn_info.param_types.get(ip), Type.fromInterned(fn_info.return_type), pt, func.target.*); - defer func_type.deinit(func.gpa); - _ = try func.bin_file.storeNavType(func.owner_nav, func_type); + const fn_ty_index = try genFunctype(wasm, fn_info.cc, fn_info.param_types.get(ip), Type.fromInterned(fn_info.return_type), pt, func.target.*); var cc_result = try func.resolveCallingConventionValues(fn_ty); defer cc_result.deinit(func.gpa); @@ -1273,7 +1235,8 @@ fn genFunc(func: *CodeGen) InnerError!void { // In case we have a return value, but the last instruction is a noreturn (such as a while loop) // we emit an unreachable instruction to tell the stack validator that part will never be reached. - if (func_type.returns.len != 0 and func.air.instructions.len > 0) { + const returns = fn_ty_index.ptr(wasm).returns.slice(wasm); + if (returns.len != 0 and func.air.instructions.len > 0) { const inst: Air.Inst.Index = @enumFromInt(func.air.instructions.len - 1); const last_inst_ty = func.typeOfIndex(inst); if (!last_inst_ty.hasRuntimeBitsIgnoreComptime(zcu) or last_inst_ty.isNoReturn(zcu)) { @@ -1291,7 +1254,7 @@ fn genFunc(func: *CodeGen) InnerError!void { var prologue = std.ArrayList(Mir.Inst).init(func.gpa); defer prologue.deinit(); - const sp = @intFromEnum(func.bin_file.zig_object.?.stack_pointer_sym); + const sp = @intFromEnum(wasm.zig_object.?.stack_pointer_sym); // load stack pointer try prologue.append(.{ .tag = .global_get, .data = .{ .label = sp } }); // store stack pointer so we can restore it when we return from the function @@ -1328,7 +1291,7 @@ fn genFunc(func: *CodeGen) InnerError!void { var emit: Emit = .{ .mir = mir, - .bin_file = func.bin_file, + .bin_file = wasm, .code = func.code, .locals = func.locals.items, .owner_nav = func.owner_nav, @@ -1643,8 +1606,8 @@ fn memcpy(func: *CodeGen, dst: WValue, src: WValue, len: WValue) !void { try func.addLabel(.local_set, offset.local.value); // outer block to jump to when loop is done - try func.startBlock(.block, wasm.block_empty); - try func.startBlock(.loop, wasm.block_empty); + try func.startBlock(.block, std.wasm.block_empty); + try func.startBlock(.loop, std.wasm.block_empty); // loop condition (offset == length -> break) { @@ -1792,7 +1755,7 @@ const SimdStoreStrategy = enum { /// features are enabled, the function will return `.direct`. This would allow to store /// it using a instruction, rather than an unrolled version. fn determineSimdStoreStrategy(ty: Type, zcu: *Zcu, target: std.Target) SimdStoreStrategy { - std.debug.assert(ty.zigTypeTag(zcu) == .vector); + assert(ty.zigTypeTag(zcu) == .vector); if (ty.bitSize(zcu) != 128) return .unrolled; const hasFeature = std.Target.wasm.featureSetHas; const features = target.cpu.features; @@ -2186,10 +2149,11 @@ fn airRetLoad(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { } fn airCall(func: *CodeGen, inst: Air.Inst.Index, modifier: std.builtin.CallModifier) InnerError!void { + const wasm = func.bin_file; if (modifier == .always_tail) return func.fail("TODO implement tail calls for wasm", .{}); const pl_op = func.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; const extra = func.air.extraData(Air.Call, pl_op.payload); - const args = @as([]const Air.Inst.Ref, @ptrCast(func.air.extra[extra.end..][0..extra.data.args_len])); + const args: []const Air.Inst.Ref = @ptrCast(func.air.extra[extra.end..][0..extra.data.args_len]); const ty = func.typeOf(pl_op.operand); const pt = func.pt; @@ -2208,43 +2172,14 @@ fn airCall(func: *CodeGen, inst: Air.Inst.Index, modifier: std.builtin.CallModif const func_val = (try func.air.value(pl_op.operand, pt)) orelse break :blk null; switch (ip.indexToKey(func_val.toIntern())) { - .func => |function| { - _ = try func.bin_file.getOrCreateAtomForNav(pt, function.owner_nav); - break :blk function.owner_nav; - }, - .@"extern" => |@"extern"| { - const ext_nav = ip.getNav(@"extern".owner_nav); - const ext_info = zcu.typeToFunc(Type.fromInterned(@"extern".ty)).?; - var func_type = try genFunctype( - func.gpa, - ext_info.cc, - ext_info.param_types.get(ip), - Type.fromInterned(ext_info.return_type), - pt, - func.target.*, - ); - defer func_type.deinit(func.gpa); - const atom_index = try func.bin_file.getOrCreateAtomForNav(pt, @"extern".owner_nav); - const atom = func.bin_file.getAtomPtr(atom_index); - const type_index = try func.bin_file.storeNavType(@"extern".owner_nav, func_type); - try func.bin_file.addOrUpdateImport( - ext_nav.name.toSlice(ip), - atom.sym_index, - @"extern".lib_name.toSlice(ip), - type_index, - ); - break :blk @"extern".owner_nav; - }, + inline .func, .@"extern" => |x| break :blk x.owner_nav, .ptr => |ptr| if (ptr.byte_offset == 0) switch (ptr.base_addr) { - .nav => |nav| { - _ = try func.bin_file.getOrCreateAtomForNav(pt, nav); - break :blk nav; - }, + .nav => |nav| break :blk nav, else => {}, }, else => {}, } - return func.fail("Expected a function, but instead found '{s}'", .{@tagName(ip.indexToKey(func_val.toIntern()))}); + return func.fail("unable to lower callee to a function index", .{}); }; const sret: WValue = if (first_param_sret) blk: { @@ -2262,21 +2197,17 @@ fn airCall(func: *CodeGen, inst: Air.Inst.Index, modifier: std.builtin.CallModif try func.lowerArg(zcu.typeToFunc(fn_ty).?.cc, arg_ty, arg_val); } - if (callee) |direct| { - const atom_index = func.bin_file.zig_object.?.navs.get(direct).?.atom; - try func.addLabel(.call, @intFromEnum(func.bin_file.getAtom(atom_index).sym_index)); + if (callee) |nav_index| { + try func.addNav(.call_nav, nav_index); } else { // in this case we call a function pointer // so load its value onto the stack - std.debug.assert(ty.zigTypeTag(zcu) == .pointer); + assert(ty.zigTypeTag(zcu) == .pointer); const operand = try func.resolveInst(pl_op.operand); try func.emitWValue(operand); - var fn_type = try genFunctype(func.gpa, fn_info.cc, fn_info.param_types.get(ip), Type.fromInterned(fn_info.return_type), pt, func.target.*); - defer fn_type.deinit(func.gpa); - - const fn_type_index = try func.bin_file.zig_object.?.putOrGetFuncType(func.gpa, fn_type); - try func.addLabel(.call_indirect, fn_type_index); + const fn_type_index = try genFunctype(wasm, fn_info.cc, fn_info.param_types.get(ip), Type.fromInterned(fn_info.return_type), pt, func.target.*); + try func.addLabel(.call_indirect, @intFromEnum(fn_type_index)); } const result_value = result_value: { @@ -2418,7 +2349,7 @@ fn store(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerE const extra_index: u32 = @intCast(func.mir_extra.items.len); // stores as := opcode, offset, alignment (opcode::memarg) try func.mir_extra.appendSlice(func.gpa, &[_]u32{ - std.wasm.simdOpcode(.v128_store), + @intFromEnum(std.wasm.SimdOpcode.v128_store), offset + lhs.offset(), @intCast(ty.abiAlignment(zcu).toByteUnits() orelse 0), }); @@ -2533,7 +2464,7 @@ fn load(func: *CodeGen, operand: WValue, ty: Type, offset: u32) InnerError!WValu const extra_index = @as(u32, @intCast(func.mir_extra.items.len)); // stores as := opcode, offset, alignment (opcode::memarg) try func.mir_extra.appendSlice(func.gpa, &[_]u32{ - std.wasm.simdOpcode(.v128_load), + @intFromEnum(std.wasm.SimdOpcode.v128_load), offset + operand.offset(), @intCast(ty.abiAlignment(zcu).toByteUnits().?), }); @@ -2664,7 +2595,7 @@ fn binOp(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type, op: Op) InnerError! } } - const opcode: wasm.Opcode = buildOpcode(.{ + const opcode: std.wasm.Opcode = buildOpcode(.{ .op = op, .valtype1 = typeToValtype(ty, pt, func.target.*), .signedness = if (ty.isSignedInt(zcu)) .signed else .unsigned, @@ -2988,7 +2919,7 @@ fn floatNeg(func: *CodeGen, ty: Type, arg: WValue) InnerError!WValue { }, 32, 64 => { try func.emitWValue(arg); - const val_type: wasm.Valtype = if (float_bits == 32) .f32 else .f64; + const val_type: std.wasm.Valtype = if (float_bits == 32) .f32 else .f64; const opcode = buildOpcode(.{ .op = .neg, .valtype1 = val_type }); try func.addTag(Mir.Inst.Tag.fromOpcode(opcode)); return .stack; @@ -3197,20 +3128,14 @@ fn lowerUavRef( return .{ .imm32 = 0xaaaaaaaa }; } - const decl_align = zcu.intern_pool.indexToKey(uav.orig_ty).ptr_type.flags.alignment; - const res = try func.bin_file.lowerUav(pt, uav.val, decl_align, func.src_loc); - const target_sym_index = switch (res) { - .mcv => |mcv| mcv.load_symbol, - .fail => |err_msg| { - func.err_msg = err_msg; - return error.CodegenFail; - }, - }; - if (is_fn_body) { - return .{ .function_index = target_sym_index }; - } else if (offset == 0) { - return .{ .memory = target_sym_index }; - } else return .{ .memory_offset = .{ .pointer = target_sym_index, .offset = offset } }; + return if (is_fn_body) .{ + .function_index = uav.val, + } else if (offset == 0) .{ + .memory = uav.val, + } else .{ .memory_offset = .{ + .pointer = uav.val, + .offset = offset, + } }; } fn lowerNavRef(func: *CodeGen, nav_index: InternPool.Nav.Index, offset: u32) InnerError!WValue { @@ -3334,13 +3259,7 @@ fn lowerConstant(func: *CodeGen, val: Value, ty: Type) InnerError!WValue { .f64 => |f64_val| return .{ .float64 = f64_val }, else => unreachable, }, - .slice => switch (try func.bin_file.lowerUav(pt, val.toIntern(), .none, func.src_loc)) { - .mcv => |mcv| return .{ .memory = mcv.load_symbol }, - .fail => |err_msg| { - func.err_msg = err_msg; - return error.CodegenFail; - }, - }, + .slice => return .{ .memory = val.toIntern() }, .ptr => return func.lowerPtr(val.toIntern(), 0), .opt => if (ty.optionalReprIsPayload(zcu)) { const pl_ty = ty.optionalChild(zcu); @@ -3489,12 +3408,12 @@ fn lowerBlock(func: *CodeGen, inst: Air.Inst.Index, block_ty: Type, body: []cons const wasm_block_ty = genBlockType(block_ty, pt, func.target.*); // if wasm_block_ty is non-empty, we create a register to store the temporary value - const block_result: WValue = if (wasm_block_ty != wasm.block_empty) blk: { + const block_result: WValue = if (wasm_block_ty != std.wasm.block_empty) blk: { const ty: Type = if (isByRef(block_ty, pt, func.target.*)) Type.u32 else block_ty; break :blk try func.ensureAllocLocal(ty); // make sure it's a clean local as it may never get overwritten } else .none; - try func.startBlock(.block, wasm.block_empty); + try func.startBlock(.block, std.wasm.block_empty); // Here we set the current block idx, so breaks know the depth to jump // to when breaking out. try func.blocks.putNoClobber(func.gpa, inst, .{ @@ -3512,7 +3431,7 @@ fn lowerBlock(func: *CodeGen, inst: Air.Inst.Index, block_ty: Type, body: []cons } /// appends a new wasm block to the code section and increases the `block_depth` by 1 -fn startBlock(func: *CodeGen, block_tag: wasm.Opcode, valtype: u8) !void { +fn startBlock(func: *CodeGen, block_tag: std.wasm.Opcode, valtype: u8) !void { func.block_depth += 1; try func.addInst(.{ .tag = Mir.Inst.Tag.fromOpcode(block_tag), @@ -3533,7 +3452,7 @@ fn airLoop(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { // result type of loop is always 'noreturn', meaning we can always // emit the wasm type 'block_empty'. - try func.startBlock(.loop, wasm.block_empty); + try func.startBlock(.loop, std.wasm.block_empty); try func.loops.putNoClobber(func.gpa, inst, func.block_depth); defer assert(func.loops.remove(inst)); @@ -3553,7 +3472,7 @@ fn airCondBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { const liveness_condbr = func.liveness.getCondBr(inst); // result type is always noreturn, so use `block_empty` as type. - try func.startBlock(.block, wasm.block_empty); + try func.startBlock(.block, std.wasm.block_empty); // emit the conditional value try func.emitWValue(condition); @@ -3632,7 +3551,7 @@ fn cmp(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type, op: std.math.CompareO try func.lowerToStack(lhs); try func.lowerToStack(rhs); - const opcode: wasm.Opcode = buildOpcode(.{ + const opcode: std.wasm.Opcode = buildOpcode(.{ .valtype1 = typeToValtype(ty, pt, func.target.*), .op = switch (op) { .lt => .lt, @@ -3674,7 +3593,7 @@ fn cmpFloat(func: *CodeGen, ty: Type, lhs: WValue, rhs: WValue, cmp_op: std.math 32, 64 => { try func.emitWValue(lhs); try func.emitWValue(rhs); - const val_type: wasm.Valtype = if (float_bits == 32) .f32 else .f64; + const val_type: std.wasm.Valtype = if (float_bits == 32) .f32 else .f64; const opcode = buildOpcode(.{ .op = op, .valtype1 = val_type }); try func.addTag(Mir.Inst.Tag.fromOpcode(opcode)); return .stack; @@ -4053,7 +3972,7 @@ fn airSwitchBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { const pt = func.pt; const zcu = pt.zcu; // result type is always 'noreturn' - const blocktype = wasm.block_empty; + const blocktype = std.wasm.block_empty; const switch_br = func.air.unwrapSwitch(inst); const target = try func.resolveInst(switch_br.operand); const target_ty = func.typeOf(switch_br.operand); @@ -4245,7 +4164,7 @@ fn airSwitchBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { return func.finishAir(inst, .none, &.{}); } -fn airIsErr(func: *CodeGen, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!void { +fn airIsErr(func: *CodeGen, inst: Air.Inst.Index, opcode: std.wasm.Opcode) InnerError!void { const pt = func.pt; const zcu = pt.zcu; const un_op = func.air.instructions.items(.data)[@intFromEnum(inst)].un_op; @@ -4467,7 +4386,7 @@ fn intcast(func: *CodeGen, operand: WValue, given: Type, wanted: Type) InnerErro } else return func.load(operand, wanted, 0); } -fn airIsNull(func: *CodeGen, inst: Air.Inst.Index, opcode: wasm.Opcode, op_kind: enum { value, ptr }) InnerError!void { +fn airIsNull(func: *CodeGen, inst: Air.Inst.Index, opcode: std.wasm.Opcode, op_kind: enum { value, ptr }) InnerError!void { const pt = func.pt; const zcu = pt.zcu; const un_op = func.air.instructions.items(.data)[@intFromEnum(inst)].un_op; @@ -4481,7 +4400,7 @@ fn airIsNull(func: *CodeGen, inst: Air.Inst.Index, opcode: wasm.Opcode, op_kind: /// For a given type and operand, checks if it's considered `null`. /// NOTE: Leaves the result on the stack -fn isNull(func: *CodeGen, operand: WValue, optional_ty: Type, opcode: wasm.Opcode) InnerError!WValue { +fn isNull(func: *CodeGen, operand: WValue, optional_ty: Type, opcode: std.wasm.Opcode) InnerError!WValue { const pt = func.pt; const zcu = pt.zcu; try func.emitWValue(operand); @@ -4967,8 +4886,8 @@ fn memset(func: *CodeGen, elem_ty: Type, ptr: WValue, len: WValue, value: WValue try func.addLabel(.local_set, end_ptr.local.value); // outer block to jump to when loop is done - try func.startBlock(.block, wasm.block_empty); - try func.startBlock(.loop, wasm.block_empty); + try func.startBlock(.block, std.wasm.block_empty); + try func.startBlock(.loop, std.wasm.block_empty); // check for condition for loop end try func.emitWValue(new_ptr); @@ -5022,11 +4941,11 @@ fn airArrayElemVal(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { try func.addTag(.i32_mul); try func.addTag(.i32_add); } else { - std.debug.assert(array_ty.zigTypeTag(zcu) == .vector); + assert(array_ty.zigTypeTag(zcu) == .vector); switch (index) { inline .imm32, .imm64 => |lane| { - const opcode: wasm.SimdOpcode = switch (elem_ty.bitSize(zcu)) { + const opcode: std.wasm.SimdOpcode = switch (elem_ty.bitSize(zcu)) { 8 => if (elem_ty.isSignedInt(zcu)) .i8x16_extract_lane_s else .i8x16_extract_lane_u, 16 => if (elem_ty.isSignedInt(zcu)) .i16x8_extract_lane_s else .i16x8_extract_lane_u, 32 => if (elem_ty.isInt(zcu)) .i32x4_extract_lane else .f32x4_extract_lane, @@ -5034,7 +4953,7 @@ fn airArrayElemVal(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { else => unreachable, }; - var operands = [_]u32{ std.wasm.simdOpcode(opcode), @as(u8, @intCast(lane)) }; + var operands = [_]u32{ @intFromEnum(opcode), @as(u8, @intCast(lane)) }; try func.emitWValue(array); @@ -5171,10 +5090,10 @@ fn airSplat(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { // the scalar value onto the stack. .stack_offset, .memory, .memory_offset => { const opcode = switch (elem_ty.bitSize(zcu)) { - 8 => std.wasm.simdOpcode(.v128_load8_splat), - 16 => std.wasm.simdOpcode(.v128_load16_splat), - 32 => std.wasm.simdOpcode(.v128_load32_splat), - 64 => std.wasm.simdOpcode(.v128_load64_splat), + 8 => @intFromEnum(std.wasm.SimdOpcode.v128_load8_splat), + 16 => @intFromEnum(std.wasm.SimdOpcode.v128_load16_splat), + 32 => @intFromEnum(std.wasm.SimdOpcode.v128_load32_splat), + 64 => @intFromEnum(std.wasm.SimdOpcode.v128_load64_splat), else => break :blk, // Cannot make use of simd-instructions }; try func.emitWValue(operand); @@ -5191,10 +5110,10 @@ fn airSplat(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { }, .local => { const opcode = switch (elem_ty.bitSize(zcu)) { - 8 => std.wasm.simdOpcode(.i8x16_splat), - 16 => std.wasm.simdOpcode(.i16x8_splat), - 32 => if (elem_ty.isInt(zcu)) std.wasm.simdOpcode(.i32x4_splat) else std.wasm.simdOpcode(.f32x4_splat), - 64 => if (elem_ty.isInt(zcu)) std.wasm.simdOpcode(.i64x2_splat) else std.wasm.simdOpcode(.f64x2_splat), + 8 => @intFromEnum(std.wasm.SimdOpcode.i8x16_splat), + 16 => @intFromEnum(std.wasm.SimdOpcode.i16x8_splat), + 32 => if (elem_ty.isInt(zcu)) @intFromEnum(std.wasm.SimdOpcode.i32x4_splat) else @intFromEnum(std.wasm.SimdOpcode.f32x4_splat), + 64 => if (elem_ty.isInt(zcu)) @intFromEnum(std.wasm.SimdOpcode.i64x2_splat) else @intFromEnum(std.wasm.SimdOpcode.f64x2_splat), else => break :blk, // Cannot make use of simd-instructions }; try func.emitWValue(operand); @@ -5267,7 +5186,7 @@ fn airShuffle(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { return func.finishAir(inst, result, &.{ extra.a, extra.b }); } else { var operands = [_]u32{ - std.wasm.simdOpcode(.i8x16_shuffle), + @intFromEnum(std.wasm.SimdOpcode.i8x16_shuffle), } ++ [1]u32{undefined} ** 4; var lanes = mem.asBytes(operands[1..]); @@ -5538,7 +5457,7 @@ fn cmpOptionals(func: *CodeGen, lhs: WValue, rhs: WValue, operand_ty: Type, op: var result = try func.ensureAllocLocal(Type.i32); defer result.free(func); - try func.startBlock(.block, wasm.block_empty); + try func.startBlock(.block, std.wasm.block_empty); _ = try func.isNull(lhs, operand_ty, .i32_eq); _ = try func.isNull(rhs, operand_ty, .i32_eq); try func.addTag(.i32_ne); // inverse so we can exit early @@ -5678,7 +5597,7 @@ fn fpext(func: *CodeGen, operand: WValue, given: Type, wanted: Type) InnerError! Type.f32, &.{operand}, ); - std.debug.assert(f32_result == .stack); + assert(f32_result == .stack); if (wanted_bits == 64) { try func.addTag(.f64_promote_f32); @@ -6557,7 +6476,7 @@ fn lowerTry( if (!err_union_ty.errorUnionSet(zcu).errorSetIsEmpty(zcu)) { // Block we can jump out of when error is not set - try func.startBlock(.block, wasm.block_empty); + try func.startBlock(.block, std.wasm.block_empty); // check if the error tag is set for the error union. try func.emitWValue(err_union); @@ -7162,17 +7081,13 @@ fn callIntrinsic( args: []const WValue, ) InnerError!WValue { assert(param_types.len == args.len); - const symbol_index = func.bin_file.getGlobalSymbol(name, null) catch |err| { - return func.fail("Could not find or create global symbol '{s}'", .{@errorName(err)}); - }; - - // Always pass over C-ABI + const wasm = func.bin_file; const pt = func.pt; const zcu = pt.zcu; - var func_type = try genFunctype(func.gpa, .{ .wasm_watc = .{} }, param_types, return_type, pt, func.target.*); - defer func_type.deinit(func.gpa); - const func_type_index = try func.bin_file.zig_object.?.putOrGetFuncType(func.gpa, func_type); - try func.bin_file.addOrUpdateImport(name, symbol_index, null, func_type_index); + const func_type_index = try genFunctype(wasm, .{ .wasm_watc = .{} }, param_types, return_type, pt, func.target.*); + const func_index = wasm.getOutputFunction(try wasm.internString(name), func_type_index); + + // Always pass over C-ABI const want_sret_param = firstParamSRet(.{ .wasm_watc = .{} }, return_type, pt, func.target.*); // if we want return as first param, we allocate a pointer to stack, @@ -7191,7 +7106,7 @@ fn callIntrinsic( } // Actually call our intrinsic - try func.addLabel(.call, @intFromEnum(symbol_index)); + try func.addLabel(.call_func, func_index); if (!return_type.hasRuntimeBitsIgnoreComptime(zcu)) { return .none; @@ -7210,177 +7125,14 @@ fn airTagName(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { const operand = try func.resolveInst(un_op); const enum_ty = func.typeOf(un_op); - const func_sym_index = try func.getTagNameFunction(enum_ty); - const result_ptr = try func.allocStack(func.typeOfIndex(inst)); try func.lowerToStack(result_ptr); try func.emitWValue(operand); - try func.addLabel(.call, func_sym_index); + try func.addCallTagName(enum_ty.toIntern()); return func.finishAir(inst, result_ptr, &.{un_op}); } -fn getTagNameFunction(func: *CodeGen, enum_ty: Type) InnerError!u32 { - const pt = func.pt; - const zcu = pt.zcu; - const ip = &zcu.intern_pool; - - var arena_allocator = std.heap.ArenaAllocator.init(func.gpa); - defer arena_allocator.deinit(); - const arena = arena_allocator.allocator(); - - const func_name = try std.fmt.allocPrintZ(arena, "__zig_tag_name_{}", .{ip.loadEnumType(enum_ty.toIntern()).name.fmt(ip)}); - - // check if we already generated code for this. - if (func.bin_file.findGlobalSymbol(func_name)) |loc| { - return @intFromEnum(loc.index); - } - - const int_tag_ty = enum_ty.intTagType(zcu); - - if (int_tag_ty.bitSize(zcu) > 64) { - return func.fail("TODO: Implement @tagName for enums with tag size larger than 64 bits", .{}); - } - - var relocs = std.ArrayList(link.File.Wasm.Relocation).init(func.gpa); - defer relocs.deinit(); - - var body_list = std.ArrayList(u8).init(func.gpa); - defer body_list.deinit(); - var writer = body_list.writer(); - - // The locals of the function body (always 0) - try leb.writeUleb128(writer, @as(u32, 0)); - - // outer block - try writer.writeByte(std.wasm.opcode(.block)); - try writer.writeByte(std.wasm.block_empty); - - // TODO: Make switch implementation generic so we can use a jump table for this when the tags are not sparse. - // generate an if-else chain for each tag value as well as constant. - const tag_names = enum_ty.enumFields(zcu); - for (0..tag_names.len) |tag_index| { - const tag_name = tag_names.get(ip)[tag_index]; - const tag_name_len = tag_name.length(ip); - // for each tag name, create an unnamed const, - // and then get a pointer to its value. - const name_ty = try pt.arrayType(.{ - .len = tag_name_len, - .child = .u8_type, - .sentinel = .zero_u8, - }); - const name_val = try pt.intern(.{ .aggregate = .{ - .ty = name_ty.toIntern(), - .storage = .{ .bytes = tag_name.toString() }, - } }); - const tag_sym_index = switch (try func.bin_file.lowerUav(pt, name_val, .none, func.src_loc)) { - .mcv => |mcv| mcv.load_symbol, - .fail => |err_msg| { - func.err_msg = err_msg; - return error.CodegenFail; - }, - }; - - // block for this if case - try writer.writeByte(std.wasm.opcode(.block)); - try writer.writeByte(std.wasm.block_empty); - - // get actual tag value (stored in 2nd parameter); - try writer.writeByte(std.wasm.opcode(.local_get)); - try leb.writeUleb128(writer, @as(u32, 1)); - - const tag_val = try pt.enumValueFieldIndex(enum_ty, @intCast(tag_index)); - const tag_value = try func.lowerConstant(tag_val, enum_ty); - - switch (tag_value) { - .imm32 => |value| { - try writer.writeByte(std.wasm.opcode(.i32_const)); - try leb.writeIleb128(writer, @as(i32, @bitCast(value))); - try writer.writeByte(std.wasm.opcode(.i32_ne)); - }, - .imm64 => |value| { - try writer.writeByte(std.wasm.opcode(.i64_const)); - try leb.writeIleb128(writer, @as(i64, @bitCast(value))); - try writer.writeByte(std.wasm.opcode(.i64_ne)); - }, - else => unreachable, - } - // if they're not equal, break out of current branch - try writer.writeByte(std.wasm.opcode(.br_if)); - try leb.writeUleb128(writer, @as(u32, 0)); - - // store the address of the tagname in the pointer field of the slice - // get the address twice so we can also store the length. - try writer.writeByte(std.wasm.opcode(.local_get)); - try leb.writeUleb128(writer, @as(u32, 0)); - try writer.writeByte(std.wasm.opcode(.local_get)); - try leb.writeUleb128(writer, @as(u32, 0)); - - // get address of tagname and emit a relocation to it - if (func.arch() == .wasm32) { - const encoded_alignment = @ctz(@as(u32, 4)); - try writer.writeByte(std.wasm.opcode(.i32_const)); - try relocs.append(.{ - .relocation_type = .R_WASM_MEMORY_ADDR_LEB, - .offset = @as(u32, @intCast(body_list.items.len)), - .index = tag_sym_index, - }); - try writer.writeAll(&[_]u8{0} ** 5); // will be relocated - - // store pointer - try writer.writeByte(std.wasm.opcode(.i32_store)); - try leb.writeUleb128(writer, encoded_alignment); - try leb.writeUleb128(writer, @as(u32, 0)); - - // store length - try writer.writeByte(std.wasm.opcode(.i32_const)); - try leb.writeUleb128(writer, @as(u32, @intCast(tag_name_len))); - try writer.writeByte(std.wasm.opcode(.i32_store)); - try leb.writeUleb128(writer, encoded_alignment); - try leb.writeUleb128(writer, @as(u32, 4)); - } else { - const encoded_alignment = @ctz(@as(u32, 8)); - try writer.writeByte(std.wasm.opcode(.i64_const)); - try relocs.append(.{ - .relocation_type = .R_WASM_MEMORY_ADDR_LEB64, - .offset = @as(u32, @intCast(body_list.items.len)), - .index = tag_sym_index, - }); - try writer.writeAll(&[_]u8{0} ** 10); // will be relocated - - // store pointer - try writer.writeByte(std.wasm.opcode(.i64_store)); - try leb.writeUleb128(writer, encoded_alignment); - try leb.writeUleb128(writer, @as(u32, 0)); - - // store length - try writer.writeByte(std.wasm.opcode(.i64_const)); - try leb.writeUleb128(writer, @as(u64, @intCast(tag_name_len))); - try writer.writeByte(std.wasm.opcode(.i64_store)); - try leb.writeUleb128(writer, encoded_alignment); - try leb.writeUleb128(writer, @as(u32, 8)); - } - - // break outside blocks - try writer.writeByte(std.wasm.opcode(.br)); - try leb.writeUleb128(writer, @as(u32, 1)); - - // end the block for this case - try writer.writeByte(std.wasm.opcode(.end)); - } - - try writer.writeByte(std.wasm.opcode(.@"unreachable")); // tag value does not have a name - // finish outer block - try writer.writeByte(std.wasm.opcode(.end)); - // finish function body - try writer.writeByte(std.wasm.opcode(.end)); - - const slice_ty = Type.slice_const_u8_sentinel_0; - const func_type = try genFunctype(arena, .Unspecified, &.{int_tag_ty.ip_index}, slice_ty, pt, func.target.*); - const sym_index = try func.bin_file.createFunction(func_name, func_type, &body_list, &relocs); - return @intFromEnum(sym_index); -} - fn airErrorSetHasValue(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { const pt = func.pt; const zcu = pt.zcu; @@ -7418,11 +7170,11 @@ fn airErrorSetHasValue(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { } // start block for 'true' branch - try func.startBlock(.block, wasm.block_empty); + try func.startBlock(.block, std.wasm.block_empty); // start block for 'false' branch - try func.startBlock(.block, wasm.block_empty); + try func.startBlock(.block, std.wasm.block_empty); // block for the jump table itself - try func.startBlock(.block, wasm.block_empty); + try func.startBlock(.block, std.wasm.block_empty); // lower operand to determine jump table target try func.emitWValue(operand); @@ -7549,7 +7301,7 @@ fn airAtomicLoad(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { const ty = func.typeOfIndex(inst); if (func.useAtomicFeature()) { - const tag: wasm.AtomicsOpcode = switch (ty.abiSize(pt.zcu)) { + const tag: std.wasm.AtomicsOpcode = switch (ty.abiSize(pt.zcu)) { 1 => .i32_atomic_load8_u, 2 => .i32_atomic_load16_u, 4 => .i32_atomic_load, @@ -7589,7 +7341,7 @@ fn airAtomicRmw(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { const value = try tmp.toLocal(func, ty); // create a loop to cmpxchg the new value - try func.startBlock(.loop, wasm.block_empty); + try func.startBlock(.loop, std.wasm.block_empty); try func.emitWValue(ptr); try func.emitWValue(value); @@ -7639,7 +7391,7 @@ fn airAtomicRmw(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { else => { try func.emitWValue(ptr); try func.emitWValue(operand); - const tag: wasm.AtomicsOpcode = switch (ty.abiSize(zcu)) { + const tag: std.wasm.AtomicsOpcode = switch (ty.abiSize(zcu)) { 1 => switch (op) { .Xchg => .i32_atomic_rmw8_xchg_u, .Add => .i32_atomic_rmw8_add_u, @@ -7754,7 +7506,7 @@ fn airAtomicStore(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { const ty = ptr_ty.childType(zcu); if (func.useAtomicFeature()) { - const tag: wasm.AtomicsOpcode = switch (ty.abiSize(zcu)) { + const tag: std.wasm.AtomicsOpcode = switch (ty.abiSize(zcu)) { 1 => .i32_atomic_store8, 2 => .i32_atomic_store16, 4 => .i32_atomic_store, diff --git a/src/arch/wasm/Emit.zig b/src/arch/wasm/Emit.zig index cd744cd53e..13c227d53d 100644 --- a/src/arch/wasm/Emit.zig +++ b/src/arch/wasm/Emit.zig @@ -3,12 +3,13 @@ const Emit = @This(); const std = @import("std"); +const leb128 = std.leb; + const Mir = @import("Mir.zig"); const link = @import("../../link.zig"); const Zcu = @import("../../Zcu.zig"); const InternPool = @import("../../InternPool.zig"); const codegen = @import("../../codegen.zig"); -const leb128 = std.leb; /// Contains our list of instructions mir: Mir, @@ -254,7 +255,8 @@ fn offset(self: Emit) u32 { fn fail(emit: *Emit, comptime format: []const u8, args: anytype) InnerError { @branchHint(.cold); std.debug.assert(emit.error_msg == null); - const comp = emit.bin_file.base.comp; + const wasm = emit.bin_file; + const comp = wasm.base.comp; const zcu = comp.zcu.?; const gpa = comp.gpa; emit.error_msg = try Zcu.ErrorMsg.create(gpa, zcu.navSrcLoc(emit.owner_nav), format, args); @@ -287,7 +289,7 @@ fn emitBrTable(emit: *Emit, inst: Mir.Inst.Index) !void { const labels = emit.mir.extra[extra.end..][0..extra.data.length]; const writer = emit.code.writer(); - try emit.code.append(std.wasm.opcode(.br_table)); + try emit.code.append(@intFromEnum(std.wasm.Opcode.br_table)); try leb128.writeUleb128(writer, extra.data.length - 1); // Default label is not part of length/depth for (labels) |label| { try leb128.writeUleb128(writer, label); @@ -301,7 +303,8 @@ fn emitLabel(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) !void { } fn emitGlobal(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) !void { - const comp = emit.bin_file.base.comp; + const wasm = emit.bin_file; + const comp = wasm.base.comp; const gpa = comp.gpa; const label = emit.mir.instructions.items(.data)[inst].label; try emit.code.append(@intFromEnum(tag)); @@ -310,38 +313,38 @@ fn emitGlobal(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) !void { const global_offset = emit.offset(); try emit.code.appendSlice(&buf); - const atom_index = emit.bin_file.zig_object.?.navs.get(emit.owner_nav).?.atom; - const atom = emit.bin_file.getAtomPtr(atom_index); - try atom.relocs.append(gpa, .{ + const zo = wasm.zig_object.?; + try zo.relocs.append(gpa, .{ + .nav_index = emit.nav_index, .index = label, .offset = global_offset, - .relocation_type = .R_WASM_GLOBAL_INDEX_LEB, + .tag = .GLOBAL_INDEX_LEB, }); } fn emitImm32(emit: *Emit, inst: Mir.Inst.Index) !void { const value: i32 = emit.mir.instructions.items(.data)[inst].imm32; - try emit.code.append(std.wasm.opcode(.i32_const)); + try emit.code.append(@intFromEnum(std.wasm.Opcode.i32_const)); try leb128.writeIleb128(emit.code.writer(), value); } fn emitImm64(emit: *Emit, inst: Mir.Inst.Index) !void { const extra_index = emit.mir.instructions.items(.data)[inst].payload; const value = emit.mir.extraData(Mir.Imm64, extra_index); - try emit.code.append(std.wasm.opcode(.i64_const)); + try emit.code.append(@intFromEnum(std.wasm.Opcode.i64_const)); try leb128.writeIleb128(emit.code.writer(), @as(i64, @bitCast(value.data.toU64()))); } fn emitFloat32(emit: *Emit, inst: Mir.Inst.Index) !void { const value: f32 = emit.mir.instructions.items(.data)[inst].float32; - try emit.code.append(std.wasm.opcode(.f32_const)); + try emit.code.append(@intFromEnum(std.wasm.Opcode.f32_const)); try emit.code.writer().writeInt(u32, @bitCast(value), .little); } fn emitFloat64(emit: *Emit, inst: Mir.Inst.Index) !void { const extra_index = emit.mir.instructions.items(.data)[inst].payload; const value = emit.mir.extraData(Mir.Float64, extra_index); - try emit.code.append(std.wasm.opcode(.f64_const)); + try emit.code.append(@intFromEnum(std.wasm.Opcode.f64_const)); try emit.code.writer().writeInt(u64, value.data.toU64(), .little); } @@ -360,105 +363,99 @@ fn encodeMemArg(mem_arg: Mir.MemArg, writer: anytype) !void { } fn emitCall(emit: *Emit, inst: Mir.Inst.Index) !void { - const comp = emit.bin_file.base.comp; + const wasm = emit.bin_file; + const comp = wasm.base.comp; const gpa = comp.gpa; const label = emit.mir.instructions.items(.data)[inst].label; - try emit.code.append(std.wasm.opcode(.call)); + try emit.code.append(@intFromEnum(std.wasm.Opcode.call)); const call_offset = emit.offset(); var buf: [5]u8 = undefined; leb128.writeUnsignedFixed(5, &buf, label); try emit.code.appendSlice(&buf); - if (label != 0) { - const atom_index = emit.bin_file.zig_object.?.navs.get(emit.owner_nav).?.atom; - const atom = emit.bin_file.getAtomPtr(atom_index); - try atom.relocs.append(gpa, .{ - .offset = call_offset, - .index = label, - .relocation_type = .R_WASM_FUNCTION_INDEX_LEB, - }); - } + const zo = wasm.zig_object.?; + try zo.relocs.append(gpa, .{ + .offset = call_offset, + .index = label, + .tag = .FUNCTION_INDEX_LEB, + }); } fn emitCallIndirect(emit: *Emit, inst: Mir.Inst.Index) !void { + const wasm = emit.bin_file; const type_index = emit.mir.instructions.items(.data)[inst].label; - try emit.code.append(std.wasm.opcode(.call_indirect)); + try emit.code.append(@intFromEnum(std.wasm.Opcode.call_indirect)); // NOTE: If we remove unused function types in the future for incremental // linking, we must also emit a relocation for this `type_index` const call_offset = emit.offset(); var buf: [5]u8 = undefined; leb128.writeUnsignedFixed(5, &buf, type_index); try emit.code.appendSlice(&buf); - if (type_index != 0) { - const atom_index = emit.bin_file.zig_object.?.navs.get(emit.owner_nav).?.atom; - const atom = emit.bin_file.getAtomPtr(atom_index); - try atom.relocs.append(emit.bin_file.base.comp.gpa, .{ - .offset = call_offset, - .index = type_index, - .relocation_type = .R_WASM_TYPE_INDEX_LEB, - }); - } + + const zo = wasm.zig_object.?; + try zo.relocs.append(wasm.base.comp.gpa, .{ + .offset = call_offset, + .index = type_index, + .tag = .TYPE_INDEX_LEB, + }); + try leb128.writeUleb128(emit.code.writer(), @as(u32, 0)); // TODO: Emit relocation for table index } fn emitFunctionIndex(emit: *Emit, inst: Mir.Inst.Index) !void { - const comp = emit.bin_file.base.comp; + const wasm = emit.bin_file; + const comp = wasm.base.comp; const gpa = comp.gpa; const symbol_index = emit.mir.instructions.items(.data)[inst].label; - try emit.code.append(std.wasm.opcode(.i32_const)); + try emit.code.append(@intFromEnum(std.wasm.Opcode.i32_const)); const index_offset = emit.offset(); var buf: [5]u8 = undefined; leb128.writeUnsignedFixed(5, &buf, symbol_index); try emit.code.appendSlice(&buf); - if (symbol_index != 0) { - const atom_index = emit.bin_file.zig_object.?.navs.get(emit.owner_nav).?.atom; - const atom = emit.bin_file.getAtomPtr(atom_index); - try atom.relocs.append(gpa, .{ - .offset = index_offset, - .index = symbol_index, - .relocation_type = .R_WASM_TABLE_INDEX_SLEB, - }); - } + const zo = wasm.zig_object.?; + try zo.relocs.append(gpa, .{ + .offset = index_offset, + .index = symbol_index, + .tag = .TABLE_INDEX_SLEB, + }); } fn emitMemAddress(emit: *Emit, inst: Mir.Inst.Index) !void { + const wasm = emit.bin_file; const extra_index = emit.mir.instructions.items(.data)[inst].payload; const mem = emit.mir.extraData(Mir.Memory, extra_index).data; const mem_offset = emit.offset() + 1; - const comp = emit.bin_file.base.comp; + const comp = wasm.base.comp; const gpa = comp.gpa; const target = comp.root_mod.resolved_target.result; const is_wasm32 = target.cpu.arch == .wasm32; if (is_wasm32) { - try emit.code.append(std.wasm.opcode(.i32_const)); + try emit.code.append(@intFromEnum(std.wasm.Opcode.i32_const)); var buf: [5]u8 = undefined; leb128.writeUnsignedFixed(5, &buf, mem.pointer); try emit.code.appendSlice(&buf); } else { - try emit.code.append(std.wasm.opcode(.i64_const)); + try emit.code.append(@intFromEnum(std.wasm.Opcode.i64_const)); var buf: [10]u8 = undefined; leb128.writeUnsignedFixed(10, &buf, mem.pointer); try emit.code.appendSlice(&buf); } - if (mem.pointer != 0) { - const atom_index = emit.bin_file.zig_object.?.navs.get(emit.owner_nav).?.atom; - const atom = emit.bin_file.getAtomPtr(atom_index); - try atom.relocs.append(gpa, .{ - .offset = mem_offset, - .index = mem.pointer, - .relocation_type = if (is_wasm32) .R_WASM_MEMORY_ADDR_LEB else .R_WASM_MEMORY_ADDR_LEB64, - .addend = @as(i32, @intCast(mem.offset)), - }); - } + const zo = wasm.zig_object.?; + try zo.relocs.append(gpa, .{ + .offset = mem_offset, + .index = mem.pointer, + .tag = if (is_wasm32) .MEMORY_ADDR_LEB else .MEMORY_ADDR_LEB64, + .addend = @as(i32, @intCast(mem.offset)), + }); } fn emitExtended(emit: *Emit, inst: Mir.Inst.Index) !void { const extra_index = emit.mir.instructions.items(.data)[inst].payload; const opcode = emit.mir.extra[extra_index]; const writer = emit.code.writer(); - try emit.code.append(std.wasm.opcode(.misc_prefix)); + try emit.code.append(@intFromEnum(std.wasm.Opcode.misc_prefix)); try leb128.writeUleb128(writer, opcode); switch (@as(std.wasm.MiscOpcode, @enumFromInt(opcode))) { // bulk-memory opcodes @@ -497,7 +494,7 @@ fn emitSimd(emit: *Emit, inst: Mir.Inst.Index) !void { const extra_index = emit.mir.instructions.items(.data)[inst].payload; const opcode = emit.mir.extra[extra_index]; const writer = emit.code.writer(); - try emit.code.append(std.wasm.opcode(.simd_prefix)); + try emit.code.append(@intFromEnum(std.wasm.Opcode.simd_prefix)); try leb128.writeUleb128(writer, opcode); switch (@as(std.wasm.SimdOpcode, @enumFromInt(opcode))) { .v128_store, @@ -548,7 +545,7 @@ fn emitAtomic(emit: *Emit, inst: Mir.Inst.Index) !void { const extra_index = emit.mir.instructions.items(.data)[inst].payload; const opcode = emit.mir.extra[extra_index]; const writer = emit.code.writer(); - try emit.code.append(std.wasm.opcode(.atomics_prefix)); + try emit.code.append(@intFromEnum(std.wasm.Opcode.atomics_prefix)); try leb128.writeUleb128(writer, opcode); switch (@as(std.wasm.AtomicsOpcode, @enumFromInt(opcode))) { .i32_atomic_load, diff --git a/src/arch/wasm/Mir.zig b/src/arch/wasm/Mir.zig index 2d4f624b22..3a7ae67534 100644 --- a/src/arch/wasm/Mir.zig +++ b/src/arch/wasm/Mir.zig @@ -7,6 +7,8 @@ //! and known jump labels for blocks. const Mir = @This(); +const InternPool = @import("../../InternPool.zig"); +const Wasm = @import("../../link/Wasm.zig"); const std = @import("std"); @@ -78,22 +80,23 @@ pub const Inst = struct { /// /// Uses `nop` @"return" = 0x0F, - /// Calls a function by its index - /// - /// Uses `label` - call = 0x10, + /// Calls a function using `nav_index`. + call_nav, + /// Calls a function using `func_index`. + call_func, /// Calls a function pointer by its function signature /// and index into the function table. /// /// Uses `label` call_indirect = 0x11, + /// Calls a function by its index. + /// + /// The function is the auto-generated tag name function for the type + /// provided in `ip_index`. + call_tag_name, /// Contains a symbol to a function pointer /// uses `label` - /// - /// Note: This uses `0x16` as value which is reserved by the WebAssembly - /// specification but unused, meaning we must update this if the specification were to - /// use this value. - function_index = 0x16, + function_index, /// Pops three values from the stack and pushes /// the first or second value dependent on the third value. /// Uses `tag` @@ -580,6 +583,10 @@ pub const Inst = struct { /// /// Used by e.g. `br_table` payload: u32, + + ip_index: InternPool.Index, + nav_index: InternPool.Nav.Index, + func_index: Wasm.FunctionIndex, }; }; diff --git a/src/codegen.zig b/src/codegen.zig index a70fc8642d..8ab6fb33b6 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -25,18 +25,19 @@ const Alignment = InternPool.Alignment; const dev = @import("dev.zig"); pub const Result = union(enum) { - /// The `code` parameter passed to `generateSymbol` has the value ok. + /// The `code` parameter passed to `generateSymbol` has the value. ok, - /// There was a codegen error. fail: *ErrorMsg, }; pub const CodeGenError = error{ OutOfMemory, + /// Compiler was asked to operate on a number larger than supported. Overflow, + /// Indicates the error is already stored in Zcu `failed_codegen`. CodegenFail, -} || link.File.UpdateDebugInfoError; +}; fn devFeatureForBackend(comptime backend: std.builtin.CompilerBackend) dev.Feature { comptime assert(mem.startsWith(u8, @tagName(backend), "stage2_")); @@ -49,7 +50,6 @@ fn importBackend(comptime backend: std.builtin.CompilerBackend) type { .stage2_arm => @import("arch/arm/CodeGen.zig"), .stage2_riscv64 => @import("arch/riscv64/CodeGen.zig"), .stage2_sparc64 => @import("arch/sparc64/CodeGen.zig"), - .stage2_wasm => @import("arch/wasm/CodeGen.zig"), .stage2_x86_64 => @import("arch/x86_64/CodeGen.zig"), else => unreachable, }; @@ -74,7 +74,6 @@ pub fn generateFunction( .stage2_arm, .stage2_riscv64, .stage2_sparc64, - .stage2_wasm, .stage2_x86_64, => |backend| { dev.check(devFeatureForBackend(backend)); @@ -96,9 +95,7 @@ pub fn generateLazyFunction( const target = zcu.fileByIndex(file).mod.resolved_target.result; switch (target_util.zigBackend(target, false)) { else => unreachable, - inline .stage2_x86_64, - .stage2_riscv64, - => |backend| { + inline .stage2_x86_64, .stage2_riscv64 => |backend| { dev.check(devFeatureForBackend(backend)); return importBackend(backend).generateLazy(lf, pt, src_loc, lazy_sym, code, debug_output); }, @@ -694,6 +691,7 @@ fn lowerUavRef( offset: u64, ) CodeGenError!Result { const zcu = pt.zcu; + const gpa = zcu.gpa; const ip = &zcu.intern_pool; const target = lf.comp.root_mod.resolved_target.result; @@ -704,7 +702,7 @@ fn lowerUavRef( const is_fn_body = uav_ty.zigTypeTag(zcu) == .@"fn"; if (!is_fn_body and !uav_ty.hasRuntimeBits(zcu)) { try code.appendNTimes(0xaa, ptr_width_bytes); - return Result.ok; + return .ok; } const uav_align = ip.indexToKey(uav.orig_ty).ptr_type.flags.alignment; @@ -714,6 +712,26 @@ fn lowerUavRef( .fail => |em| return .{ .fail = em }, } + switch (lf.tag) { + .c => unreachable, + .spirv => unreachable, + .nvptx => unreachable, + .wasm => { + dev.check(link.File.Tag.wasm.devFeature()); + const wasm = lf.cast(.wasm).?; + assert(reloc_parent == .none); + try wasm.relocations.append(gpa, .{ + .tag = .uav_index, + .addend = @intCast(offset), + .offset = @intCast(code.items.len), + .pointee = .{ .uav_index = uav.val }, + }); + try code.appendNTimes(0, ptr_width_bytes); + return .ok; + }, + else => {}, + } + const vaddr = try lf.getUavVAddr(uav_val, .{ .parent = reloc_parent, .offset = code.items.len, @@ -741,31 +759,52 @@ fn lowerNavRef( ) CodeGenError!Result { _ = src_loc; const zcu = pt.zcu; + const gpa = zcu.gpa; const ip = &zcu.intern_pool; const target = zcu.navFileScope(nav_index).mod.resolved_target.result; - const ptr_width = target.ptrBitWidth(); + const ptr_width_bytes = @divExact(target.ptrBitWidth(), 8); const nav_ty = Type.fromInterned(ip.getNav(nav_index).typeOf(ip)); const is_fn_body = nav_ty.zigTypeTag(zcu) == .@"fn"; if (!is_fn_body and !nav_ty.hasRuntimeBits(zcu)) { - try code.appendNTimes(0xaa, @divExact(ptr_width, 8)); + try code.appendNTimes(0xaa, ptr_width_bytes); return Result.ok; } + switch (lf.tag) { + .c => unreachable, + .spirv => unreachable, + .nvptx => unreachable, + .wasm => { + dev.check(link.File.Tag.wasm.devFeature()); + const wasm = lf.cast(.wasm).?; + assert(reloc_parent == .none); + try wasm.relocations.append(gpa, .{ + .tag = .nav_index, + .addend = @intCast(offset), + .offset = @intCast(code.items.len), + .pointee = .{ .nav_index = nav_index }, + }); + try code.appendNTimes(0, ptr_width_bytes); + return .ok; + }, + else => {}, + } + const vaddr = try lf.getNavVAddr(pt, nav_index, .{ .parent = reloc_parent, .offset = code.items.len, .addend = @intCast(offset), }); const endian = target.cpu.arch.endian(); - switch (ptr_width) { - 16 => mem.writeInt(u16, try code.addManyAsArray(2), @intCast(vaddr), endian), - 32 => mem.writeInt(u32, try code.addManyAsArray(4), @intCast(vaddr), endian), - 64 => mem.writeInt(u64, try code.addManyAsArray(8), vaddr, endian), + switch (ptr_width_bytes) { + 2 => mem.writeInt(u16, try code.addManyAsArray(2), @intCast(vaddr), endian), + 4 => mem.writeInt(u32, try code.addManyAsArray(4), @intCast(vaddr), endian), + 8 => mem.writeInt(u64, try code.addManyAsArray(8), vaddr, endian), else => unreachable, } - return Result.ok; + return .ok; } /// Helper struct to denote that the value is in memory but requires a linker relocation fixup: diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 492506e9a6..aad6dad78b 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1059,9 +1059,10 @@ pub const Object = struct { lto: Compilation.Config.LtoMode, }; - pub fn emit(o: *Object, options: EmitOptions) !void { + pub fn emit(o: *Object, options: EmitOptions) error{ LinkFailure, OutOfMemory }!void { const zcu = o.pt.zcu; const comp = zcu.comp; + const diags = &comp.link_diags; { try o.genErrorNameTable(); @@ -1223,27 +1224,30 @@ pub const Object = struct { o.builder.clearAndFree(); if (options.pre_bc_path) |path| { - var file = try std.fs.cwd().createFile(path, .{}); + var file = std.fs.cwd().createFile(path, .{}) catch |err| + return diags.fail("failed to create '{s}': {s}", .{ path, @errorName(err) }); defer file.close(); const ptr: [*]const u8 = @ptrCast(bitcode.ptr); - try file.writeAll(ptr[0..(bitcode.len * 4)]); + file.writeAll(ptr[0..(bitcode.len * 4)]) catch |err| + return diags.fail("failed to write to '{s}': {s}", .{ path, @errorName(err) }); } if (options.asm_path == null and options.bin_path == null and options.post_ir_path == null and options.post_bc_path == null) return; if (options.post_bc_path) |path| { - var file = try std.fs.cwd().createFileZ(path, .{}); + var file = std.fs.cwd().createFileZ(path, .{}) catch |err| + return diags.fail("failed to create '{s}': {s}", .{ path, @errorName(err) }); defer file.close(); const ptr: [*]const u8 = @ptrCast(bitcode.ptr); - try file.writeAll(ptr[0..(bitcode.len * 4)]); + file.writeAll(ptr[0..(bitcode.len * 4)]) catch |err| + return diags.fail("failed to write to '{s}': {s}", .{ path, @errorName(err) }); } if (!build_options.have_llvm or !comp.config.use_lib_llvm) { - log.err("emitting without libllvm not implemented", .{}); - return error.FailedToEmit; + return diags.fail("emitting without libllvm not implemented", .{}); } initializeLLVMTarget(comp.root_mod.resolved_target.result.cpu.arch); @@ -1263,8 +1267,7 @@ pub const Object = struct { var module: *llvm.Module = undefined; if (context.parseBitcodeInContext2(bitcode_memory_buffer, &module).toBool() or context.getBrokenDebugInfo()) { - log.err("Failed to parse bitcode", .{}); - return error.FailedToEmit; + return diags.fail("Failed to parse bitcode", .{}); } break :emit .{ context, module }; }; @@ -1274,12 +1277,7 @@ pub const Object = struct { var error_message: [*:0]const u8 = undefined; if (llvm.Target.getFromTriple(target_triple_sentinel, &target, &error_message).toBool()) { defer llvm.disposeMessage(error_message); - - log.err("LLVM failed to parse '{s}': {s}", .{ - target_triple_sentinel, - error_message, - }); - @panic("Invalid LLVM triple"); + return diags.fail("LLVM failed to parse '{s}': {s}", .{ target_triple_sentinel, error_message }); } const optimize_mode = comp.root_mod.optimize_mode; @@ -1374,10 +1372,9 @@ pub const Object = struct { if (options.asm_path != null and options.bin_path != null) { if (target_machine.emitToFile(module, &error_message, &lowered_options)) { defer llvm.disposeMessage(error_message); - log.err("LLVM failed to emit bin={s} ir={s}: {s}", .{ + return diags.fail("LLVM failed to emit bin={s} ir={s}: {s}", .{ emit_bin_msg, post_llvm_ir_msg, error_message, }); - return error.FailedToEmit; } lowered_options.bin_filename = null; lowered_options.llvm_ir_filename = null; @@ -1386,11 +1383,9 @@ pub const Object = struct { lowered_options.asm_filename = options.asm_path; if (target_machine.emitToFile(module, &error_message, &lowered_options)) { defer llvm.disposeMessage(error_message); - log.err("LLVM failed to emit asm={s} bin={s} ir={s} bc={s}: {s}", .{ - emit_asm_msg, emit_bin_msg, post_llvm_ir_msg, post_llvm_bc_msg, - error_message, + return diags.fail("LLVM failed to emit asm={s} bin={s} ir={s} bc={s}: {s}", .{ + emit_asm_msg, emit_bin_msg, post_llvm_ir_msg, post_llvm_bc_msg, error_message, }); - return error.FailedToEmit; } } @@ -1967,11 +1962,6 @@ pub const Object = struct { } } - pub fn freeDecl(self: *Object, decl_index: InternPool.DeclIndex) void { - const global = self.decl_map.get(decl_index) orelse return; - global.delete(&self.builder); - } - fn getDebugFile(o: *Object, file_index: Zcu.File.Index) Allocator.Error!Builder.Metadata { const gpa = o.gpa; const gop = try o.debug_file_map.getOrPut(gpa, file_index); diff --git a/src/link.zig b/src/link.zig index fd1ef7ce33..3a3063fa2c 100644 --- a/src/link.zig +++ b/src/link.zig @@ -633,42 +633,15 @@ pub const File = struct { pub const FlushDebugInfoError = Dwarf.FlushError; pub const UpdateNavError = error{ - OutOfMemory, Overflow, - Underflow, - FileTooBig, - InputOutput, - FilesOpenedWithWrongFlags, - IsDir, - NoSpaceLeft, - Unseekable, - PermissionDenied, - SwapFile, - CorruptedData, - SystemResources, - OperationAborted, - BrokenPipe, - ConnectionResetByPeer, - ConnectionTimedOut, - SocketNotConnected, - NotOpenForReading, - WouldBlock, - Canceled, - AccessDenied, - Unexpected, - DiskQuota, - NotOpenForWriting, - AnalysisFail, + OutOfMemory, + /// Indicates the error is already reported and stored in + /// `failed_codegen` on the Zcu. CodegenFail, - EmitFail, - NameTooLong, - CurrentWorkingDirectoryUnlinked, - LockViolation, - NetNameDeleted, - DeviceBusy, - InvalidArgument, - HotSwapUnavailableOnHostOperatingSystem, - } || UpdateDebugInfoError; + /// Indicates the error is already reported and stored in `link_diags` + /// on the Compilation. + LinkFailure, + }; /// Called from within CodeGen to retrieve the symbol index of a global symbol. /// If no symbol exists yet with this name, a new undefined global symbol will @@ -771,83 +744,11 @@ pub const File = struct { } } - /// TODO audit this error set. most of these should be collapsed into one error, - /// and Diags.Flags should be updated to convey the meaning to the user. pub const FlushError = error{ - CacheCheckFailed, - CurrentWorkingDirectoryUnlinked, - DivisionByZero, - DllImportLibraryNotFound, - ExpectedFuncType, - FailedToEmit, - FileSystem, - FilesOpenedWithWrongFlags, - /// Deprecated. Use `LinkFailure` instead. - /// Formerly used to indicate an error will be present in `Compilation.link_errors`. - FlushFailure, /// Indicates an error will be present in `Compilation.link_errors`. LinkFailure, - FunctionSignatureMismatch, - GlobalTypeMismatch, - HotSwapUnavailableOnHostOperatingSystem, - InvalidCharacter, - InvalidEntryKind, - InvalidFeatureSet, - InvalidFormat, - InvalidIndex, - InvalidInitFunc, - InvalidMagicByte, - InvalidWasmVersion, - LLDCrashed, - LLDReportedFailure, - LLD_LinkingIsTODO_ForSpirV, - LibCInstallationMissingCrtDir, - LibCInstallationNotAvailable, - LinkingWithoutZigSourceUnimplemented, - MalformedArchive, - MalformedDwarf, - MalformedSection, - MemoryTooBig, - MemoryTooSmall, - MissAlignment, - MissingEndForBody, - MissingEndForExpression, - MissingSymbol, - MissingTableSymbols, - ModuleNameMismatch, - NoObjectsToLink, - NotObjectFile, - NotSupported, OutOfMemory, - Overflow, - PermissionDenied, - StreamTooLong, - SwapFile, - SymbolCollision, - SymbolMismatchingType, - TODOImplementPlan9Objs, - TODOImplementWritingLibFiles, - UnableToSpawnSelf, - UnableToSpawnWasm, - UnableToWriteArchive, - UndefinedLocal, - UndefinedSymbol, - Underflow, - UnexpectedRemainder, - UnexpectedTable, - UnexpectedValue, - UnknownFeature, - UnrecognizedVolume, - Unseekable, - UnsupportedCpuArchitecture, - UnsupportedVersion, - UnexpectedEndOfFile, - } || - fs.File.WriteFileError || - fs.File.OpenError || - std.process.Child.SpawnError || - fs.Dir.CopyFileError || - FlushDebugInfoError; + }; /// Commit pending changes and write headers. Takes into account final output mode /// and `use_lld`, not only `effectiveOutputMode`. @@ -864,7 +765,12 @@ pub const File = struct { assert(comp.c_object_table.count() == 1); const the_key = comp.c_object_table.keys()[0]; const cached_pp_file_path = the_key.status.success.object_path; - try cached_pp_file_path.root_dir.handle.copyFile(cached_pp_file_path.sub_path, emit.root_dir.handle, emit.sub_path, .{}); + cached_pp_file_path.root_dir.handle.copyFile(cached_pp_file_path.sub_path, emit.root_dir.handle, emit.sub_path, .{}) catch |err| { + const diags = &base.comp.link_diags; + return diags.fail("failed to copy '{'}' to '{'}': {s}", .{ + @as(Path, cached_pp_file_path), @as(Path, emit), @errorName(err), + }); + }; return; } @@ -893,16 +799,6 @@ pub const File = struct { } } - /// Called when a Decl is deleted from the Zcu. - pub fn freeDecl(base: *File, decl_index: InternPool.DeclIndex) void { - switch (base.tag) { - inline else => |tag| { - dev.check(tag.devFeature()); - @as(*tag.Type(), @fieldParentPtr("base", base)).freeDecl(decl_index); - }, - } - } - pub const UpdateExportsError = error{ OutOfMemory, AnalysisFail, @@ -932,6 +828,7 @@ pub const File = struct { addend: u32, pub const Parent = union(enum) { + none, atom_index: u32, debug_output: DebugInfoOutput, }; @@ -948,6 +845,7 @@ pub const File = struct { .c => unreachable, .spirv => unreachable, .nvptx => unreachable, + .wasm => unreachable, inline else => |tag| { dev.check(tag.devFeature()); return @as(*tag.Type(), @fieldParentPtr("base", base)).getNavVAddr(pt, nav_index, reloc_info); @@ -966,6 +864,7 @@ pub const File = struct { .c => unreachable, .spirv => unreachable, .nvptx => unreachable, + .wasm => unreachable, inline else => |tag| { dev.check(tag.devFeature()); return @as(*tag.Type(), @fieldParentPtr("base", base)).lowerUav(pt, decl_val, decl_align, src_loc); @@ -978,6 +877,7 @@ pub const File = struct { .c => unreachable, .spirv => unreachable, .nvptx => unreachable, + .wasm => unreachable, inline else => |tag| { dev.check(tag.devFeature()); return @as(*tag.Type(), @fieldParentPtr("base", base)).getUavVAddr(decl_val, reloc_info); @@ -1099,6 +999,26 @@ pub const File = struct { } } + /// Called when all linker inputs have been sent via `loadInput`. After + /// this, `loadInput` will not be called anymore. + pub fn prelink(base: *File) FlushError!void { + const use_lld = build_options.have_llvm and base.comp.config.use_lld; + if (use_lld) return; + + // In this case, an object file is created by the LLVM backend, so + // there is no prelink phase. The Zig code is linked as a standard + // object along with the others. + if (base.zcu_object_sub_path != null) return; + + switch (base.tag) { + inline .wasm => |tag| { + dev.check(tag.devFeature()); + return @as(*tag.Type(), @fieldParentPtr("base", base)).prelink(); + }, + else => {}, + } + } + pub fn linkAsArchive(base: *File, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) FlushError!void { dev.check(.lld_linker); diff --git a/src/link/C.zig b/src/link/C.zig index 4df5b824bd..dd42af1c9c 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -175,21 +175,13 @@ pub fn deinit(self: *C) void { self.lazy_code_buf.deinit(gpa); } -pub fn freeDecl(self: *C, decl_index: InternPool.DeclIndex) void { - const gpa = self.base.comp.gpa; - if (self.decl_table.fetchSwapRemove(decl_index)) |kv| { - var decl_block = kv.value; - decl_block.deinit(gpa); - } -} - pub fn updateFunc( self: *C, pt: Zcu.PerThread, func_index: InternPool.Index, air: Air, liveness: Liveness, -) !void { +) link.File.UpdateNavError!void { const zcu = pt.zcu; const gpa = zcu.gpa; const func = zcu.funcInfo(func_index); @@ -313,7 +305,7 @@ fn updateUav(self: *C, pt: Zcu.PerThread, i: usize) !void { }; } -pub fn updateNav(self: *C, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !void { +pub fn updateNav(self: *C, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) link.File.UpdateNavError!void { const tracy = trace(@src()); defer tracy.end(); @@ -390,7 +382,7 @@ pub fn updateLineNumber(self: *C, pt: Zcu.PerThread, ti_id: InternPool.TrackedIn _ = ti_id; } -pub fn flush(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) !void { +pub fn flush(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { return self.flushModule(arena, tid, prog_node); } @@ -409,7 +401,7 @@ fn abiDefines(self: *C, target: std.Target) !std.ArrayList(u8) { return defines; } -pub fn flushModule(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) !void { +pub fn flushModule(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { _ = arena; // Has the same lifetime as the call to Compilation.update. const tracy = trace(@src()); @@ -419,6 +411,7 @@ pub fn flushModule(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: defer sub_prog_node.end(); const comp = self.base.comp; + const diags = &comp.link_diags; const gpa = comp.gpa; const zcu = self.base.comp.zcu.?; const ip = &zcu.intern_pool; @@ -554,8 +547,10 @@ pub fn flushModule(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: }, self.getString(av_block.code)); const file = self.base.file.?; - try file.setEndPos(f.file_size); - try file.pwritevAll(f.all_buffers.items, 0); + file.setEndPos(f.file_size) catch |err| return diags.fail("failed to allocate file: {s}", .{@errorName(err)}); + file.pwritevAll(f.all_buffers.items, 0) catch |err| return diags.fail("failed to write to '{'}': {s}", .{ + self.base.emit, @errorName(err), + }); } const Flush = struct { diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 64df9b82f8..03bb4c6f20 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -408,7 +408,7 @@ pub fn createEmpty( max_file_offset = header.pointer_to_raw_data + header.size_of_raw_data; } } - try coff.base.file.?.pwriteAll(&[_]u8{0}, max_file_offset); + try coff.pwriteAll(&[_]u8{0}, max_file_offset); } return coff; @@ -858,7 +858,7 @@ fn writeAtom(coff: *Coff, atom_index: Atom.Index, code: []u8) !void { } coff.resolveRelocs(atom_index, relocs.items, code, coff.image_base); - try coff.base.file.?.pwriteAll(code, file_offset); + try coff.pwriteAll(code, file_offset); // Now we can mark the relocs as resolved. while (relocs.popOrNull()) |reloc| { @@ -891,7 +891,7 @@ fn writeOffsetTableEntry(coff: *Coff, index: usize) !void { const sect_id = coff.got_section_index.?; if (coff.got_table_count_dirty) { - const needed_size = @as(u32, @intCast(coff.got_table.entries.items.len * coff.ptr_width.size())); + const needed_size: u32 = @intCast(coff.got_table.entries.items.len * coff.ptr_width.size()); try coff.growSection(sect_id, needed_size); coff.got_table_count_dirty = false; } @@ -908,13 +908,13 @@ fn writeOffsetTableEntry(coff: *Coff, index: usize) !void { switch (coff.ptr_width) { .p32 => { var buf: [4]u8 = undefined; - mem.writeInt(u32, &buf, @as(u32, @intCast(entry_value + coff.image_base)), .little); - try coff.base.file.?.pwriteAll(&buf, file_offset); + mem.writeInt(u32, &buf, @intCast(entry_value + coff.image_base), .little); + try coff.pwriteAll(&buf, file_offset); }, .p64 => { var buf: [8]u8 = undefined; mem.writeInt(u64, &buf, entry_value + coff.image_base, .little); - try coff.base.file.?.pwriteAll(&buf, file_offset); + try coff.pwriteAll(&buf, file_offset); }, } @@ -1093,7 +1093,13 @@ fn freeAtom(coff: *Coff, atom_index: Atom.Index) void { coff.getAtomPtr(atom_index).sym_index = 0; } -pub fn updateFunc(coff: *Coff, pt: Zcu.PerThread, func_index: InternPool.Index, air: Air, liveness: Liveness) !void { +pub fn updateFunc( + coff: *Coff, + pt: Zcu.PerThread, + func_index: InternPool.Index, + air: Air, + liveness: Liveness, +) link.File.UpdateNavError!void { if (build_options.skip_non_native and builtin.object_format != .coff) { @panic("Attempted to compile for object format that was disabled by build configuration"); } @@ -1106,8 +1112,9 @@ pub fn updateFunc(coff: *Coff, pt: Zcu.PerThread, func_index: InternPool.Index, const zcu = pt.zcu; const gpa = zcu.gpa; const func = zcu.funcInfo(func_index); + const nav_index = func.owner_nav; - const atom_index = try coff.getOrCreateAtomForNav(func.owner_nav); + const atom_index = try coff.getOrCreateAtomForNav(nav_index); coff.freeRelocations(atom_index); coff.navs.getPtr(func.owner_nav).?.section = coff.text_section_index.?; @@ -1115,25 +1122,38 @@ pub fn updateFunc(coff: *Coff, pt: Zcu.PerThread, func_index: InternPool.Index, var code_buffer = std.ArrayList(u8).init(gpa); defer code_buffer.deinit(); - const res = try codegen.generateFunction( + const res = codegen.generateFunction( &coff.base, pt, - zcu.navSrcLoc(func.owner_nav), + zcu.navSrcLoc(nav_index), func_index, air, liveness, &code_buffer, .none, - ); + ) catch |err| switch (err) { + error.CodegenFail => return error.CodegenFail, + error.OutOfMemory => return error.OutOfMemory, + else => |e| { + try zcu.failed_codegen.putNoClobber(gpa, nav_index, try Zcu.ErrorMsg.create( + gpa, + zcu.navSrcLoc(nav_index), + "unable to codegen: {s}", + .{@errorName(e)}, + )); + try zcu.retryable_failures.append(zcu.gpa, AnalUnit.wrap(.{ .func = func_index })); + return error.CodegenFail; + }, + }; const code = switch (res) { .ok => code_buffer.items, .fail => |em| { - try zcu.failed_codegen.put(zcu.gpa, func.owner_nav, em); + try zcu.failed_codegen.put(zcu.gpa, nav_index, em); return; }, }; - try coff.updateNavCode(pt, func.owner_nav, code, .FUNCTION); + try coff.updateNavCode(pt, nav_index, code, .FUNCTION); // Exports will be updated by `Zcu.processExports` after the update. } @@ -1258,9 +1278,11 @@ fn updateLazySymbolAtom( sym: link.File.LazySymbol, atom_index: Atom.Index, section_index: u16, -) !void { +) link.File.FlushError!void { const zcu = pt.zcu; - const gpa = zcu.gpa; + const comp = coff.base.comp; + const gpa = comp.gpa; + const diags = &comp.link_diags; var required_alignment: InternPool.Alignment = .none; var code_buffer = std.ArrayList(u8).init(gpa); @@ -1276,7 +1298,7 @@ fn updateLazySymbolAtom( const local_sym_index = atom.getSymbolIndex().?; const src = Type.fromInterned(sym.ty).srcLocOrNull(zcu) orelse Zcu.LazySrcLoc.unneeded; - const res = try codegen.generateLazySymbol( + const res = codegen.generateLazySymbol( &coff.base, pt, src, @@ -1285,7 +1307,10 @@ fn updateLazySymbolAtom( &code_buffer, .none, .{ .atom_index = local_sym_index }, - ); + ) catch |err| switch (err) { + error.CodegenFail => return error.LinkFailure, + else => |e| return diags.fail("failed to generate lazy symbol: {s}", .{@errorName(e)}), + }; const code = switch (res) { .ok => code_buffer.items, .fail => |em| { @@ -1387,7 +1412,7 @@ fn updateNavCode( nav_index: InternPool.Nav.Index, code: []u8, complex_type: coff_util.ComplexType, -) !void { +) link.File.UpdateNavError!void { const zcu = pt.zcu; const ip = &zcu.intern_pool; const nav = ip.getNav(nav_index); @@ -1405,12 +1430,12 @@ fn updateNavCode( const atom = coff.getAtom(atom_index); const sym_index = atom.getSymbolIndex().?; const sect_index = nav_metadata.section; - const code_len = @as(u32, @intCast(code.len)); + const code_len: u32 = @intCast(code.len); if (atom.size != 0) { const sym = atom.getSymbolPtr(coff); try coff.setSymbolName(sym, nav.fqn.toSlice(ip)); - sym.section_number = @as(coff_util.SectionNumber, @enumFromInt(sect_index + 1)); + sym.section_number = @enumFromInt(sect_index + 1); sym.type = .{ .complex_type = complex_type, .base_type = .NULL }; const capacity = atom.capacity(coff); @@ -1434,7 +1459,7 @@ fn updateNavCode( } else { const sym = atom.getSymbolPtr(coff); try coff.setSymbolName(sym, nav.fqn.toSlice(ip)); - sym.section_number = @as(coff_util.SectionNumber, @enumFromInt(sect_index + 1)); + sym.section_number = @enumFromInt(sect_index + 1); sym.type = .{ .complex_type = complex_type, .base_type = .NULL }; const vaddr = try coff.allocateAtom(atom_index, code_len, @intCast(required_alignment.toByteUnits() orelse 0)); @@ -1453,7 +1478,6 @@ pub fn freeNav(coff: *Coff, nav_index: InternPool.NavIndex) void { if (coff.llvm_object) |llvm_object| return llvm_object.freeNav(nav_index); const gpa = coff.base.comp.gpa; - log.debug("freeDecl 0x{x}", .{nav_index}); if (coff.decls.fetchOrderedRemove(nav_index)) |const_kv| { var kv = const_kv; @@ -1674,9 +1698,10 @@ pub fn flush(coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: st if (use_lld) { return coff.linkWithLLD(arena, tid, prog_node); } + const diags = &comp.link_diags; switch (comp.config.output_mode) { .Exe, .Obj => return coff.flushModule(arena, tid, prog_node), - .Lib => return error.TODOImplementWritingLibFiles, + .Lib => return diags.fail("writing lib files not yet implemented for COFF", .{}), } } @@ -2224,7 +2249,7 @@ pub fn flushModule(coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_no defer sub_prog_node.end(); const pt: Zcu.PerThread = .activate( - comp.zcu orelse return error.LinkingWithoutZigSourceUnimplemented, + comp.zcu orelse return diags.fail("linking without zig source is not yet implemented", .{}), tid, ); defer pt.deactivate(); @@ -2232,24 +2257,18 @@ pub fn flushModule(coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_no if (coff.lazy_syms.getPtr(.anyerror_type)) |metadata| { // Most lazy symbols can be updated on first use, but // anyerror needs to wait for everything to be flushed. - if (metadata.text_state != .unused) coff.updateLazySymbolAtom( + if (metadata.text_state != .unused) try coff.updateLazySymbolAtom( pt, .{ .kind = .code, .ty = .anyerror_type }, metadata.text_atom, coff.text_section_index.?, - ) catch |err| return switch (err) { - error.CodegenFail => error.FlushFailure, - else => |e| e, - }; - if (metadata.rdata_state != .unused) coff.updateLazySymbolAtom( + ); + if (metadata.rdata_state != .unused) try coff.updateLazySymbolAtom( pt, .{ .kind = .const_data, .ty = .anyerror_type }, metadata.rdata_atom, coff.rdata_section_index.?, - ) catch |err| return switch (err) { - error.CodegenFail => error.FlushFailure, - else => |e| e, - }; + ); } for (coff.lazy_syms.values()) |*metadata| { if (metadata.text_state != .unused) metadata.text_state = .flushed; @@ -2594,7 +2613,7 @@ fn writeBaseRelocations(coff: *Coff) !void { const needed_size = @as(u32, @intCast(buffer.items.len)); try coff.growSection(coff.reloc_section_index.?, needed_size); - try coff.base.file.?.pwriteAll(buffer.items, header.pointer_to_raw_data); + try coff.pwriteAll(buffer.items, header.pointer_to_raw_data); coff.data_directories[@intFromEnum(coff_util.DirectoryEntry.BASERELOC)] = .{ .virtual_address = header.virtual_address, @@ -2727,7 +2746,7 @@ fn writeImportTables(coff: *Coff) !void { assert(dll_names_offset == needed_size); - try coff.base.file.?.pwriteAll(buffer.items, header.pointer_to_raw_data); + try coff.pwriteAll(buffer.items, header.pointer_to_raw_data); coff.data_directories[@intFromEnum(coff_util.DirectoryEntry.IMPORT)] = .{ .virtual_address = header.virtual_address + iat_size, @@ -2741,20 +2760,22 @@ fn writeImportTables(coff: *Coff) !void { coff.imports_count_dirty = false; } -fn writeStrtab(coff: *Coff) !void { +fn writeStrtab(coff: *Coff) link.File.FlushError!void { if (coff.strtab_offset == null) return; + const comp = coff.base.comp; + const gpa = comp.gpa; + const diags = &comp.link_diags; const allocated_size = coff.allocatedSize(coff.strtab_offset.?); - const needed_size = @as(u32, @intCast(coff.strtab.buffer.items.len)); + const needed_size: u32 = @intCast(coff.strtab.buffer.items.len); if (needed_size > allocated_size) { coff.strtab_offset = null; - coff.strtab_offset = @as(u32, @intCast(coff.findFreeSpace(needed_size, @alignOf(u32)))); + coff.strtab_offset = @intCast(coff.findFreeSpace(needed_size, @alignOf(u32))); } log.debug("writing strtab from 0x{x} to 0x{x}", .{ coff.strtab_offset.?, coff.strtab_offset.? + needed_size }); - const gpa = coff.base.comp.gpa; var buffer = std.ArrayList(u8).init(gpa); defer buffer.deinit(); try buffer.ensureTotalCapacityPrecise(needed_size); @@ -2763,17 +2784,19 @@ fn writeStrtab(coff: *Coff) !void { // we write the length of the strtab to a temporary buffer that goes to file. mem.writeInt(u32, buffer.items[0..4], @as(u32, @intCast(coff.strtab.buffer.items.len)), .little); - try coff.base.file.?.pwriteAll(buffer.items, coff.strtab_offset.?); + coff.pwriteAll(buffer.items, coff.strtab_offset.?) catch |err| { + return diags.fail("failed to write: {s}", .{@errorName(err)}); + }; } fn writeSectionHeaders(coff: *Coff) !void { const offset = coff.getSectionHeadersOffset(); - try coff.base.file.?.pwriteAll(mem.sliceAsBytes(coff.sections.items(.header)), offset); + try coff.pwriteAll(mem.sliceAsBytes(coff.sections.items(.header)), offset); } fn writeDataDirectoriesHeaders(coff: *Coff) !void { const offset = coff.getDataDirectoryHeadersOffset(); - try coff.base.file.?.pwriteAll(mem.sliceAsBytes(&coff.data_directories), offset); + try coff.pwriteAll(mem.sliceAsBytes(&coff.data_directories), offset); } fn writeHeader(coff: *Coff) !void { @@ -2913,7 +2936,7 @@ fn writeHeader(coff: *Coff) !void { }, } - try coff.base.file.?.pwriteAll(buffer.items, 0); + try coff.pwriteAll(buffer.items, 0); } pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { @@ -3710,6 +3733,14 @@ const ImportTable = struct { const ImportIndex = u32; }; +fn pwriteAll(coff: *Coff, bytes: []const u8, offset: u64) error{LinkFailure}!void { + const comp = coff.base.comp; + const diags = &comp.link_diags; + coff.base.file.?.pwriteAll(bytes, offset) catch |err| { + return diags.fail("failed to write: {s}", .{@errorName(err)}); + }; +} + const Coff = @This(); const std = @import("std"); diff --git a/src/link/Dwarf.zig b/src/link/Dwarf.zig index 2aa04a5efd..bdd5814ce1 100644 --- a/src/link/Dwarf.zig +++ b/src/link/Dwarf.zig @@ -21,20 +21,10 @@ debug_rnglists: DebugRngLists, debug_str: StringSection, pub const UpdateError = error{ + /// Indicates the error is already reported on `failed_codegen` in the Zcu. CodegenFail, - ReinterpretDeclRef, - Unimplemented, OutOfMemory, - EndOfStream, - Overflow, - Underflow, - UnexpectedEndOfFile, -} || - std.fs.File.OpenError || - std.fs.File.SetEndPosError || - std.fs.File.CopyRangeError || - std.fs.File.PReadError || - std.fs.File.PWriteError; +}; pub const FlushError = UpdateError || diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 716a1ee59c..327d8e5e34 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -842,12 +842,12 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod .Exe => {}, } - if (diags.hasErrors()) return error.FlushFailure; + if (diags.hasErrors()) return error.LinkFailure; // If we haven't already, create a linker-generated input file comprising of // linker-defined synthetic symbols only such as `_DYNAMIC`, etc. if (self.linker_defined_index == null) { - const index = @as(File.Index, @intCast(try self.files.addOne(gpa))); + const index: File.Index = @intCast(try self.files.addOne(gpa)); self.files.set(index, .{ .linker_defined = .{ .index = index } }); self.linker_defined_index = index; const object = self.linkerDefinedPtr().?; @@ -878,7 +878,7 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod } self.checkDuplicates() catch |err| switch (err) { - error.HasDuplicates => return error.FlushFailure, + error.HasDuplicates => return error.LinkFailure, else => |e| return e, }; @@ -956,14 +956,14 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod error.RelocFailure, error.RelaxFailure => has_reloc_errors = true, error.UnsupportedCpuArch => { try self.reportUnsupportedCpuArch(); - return error.FlushFailure; + return error.LinkFailure; }, else => |e| return e, }; try self.base.file.?.pwriteAll(code, file_offset); } - if (has_reloc_errors) return error.FlushFailure; + if (has_reloc_errors) return error.LinkFailure; } try self.writePhdrTable(); @@ -972,10 +972,10 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod try self.writeMergeSections(); self.writeSyntheticSections() catch |err| switch (err) { - error.RelocFailure => return error.FlushFailure, + error.RelocFailure => return error.LinkFailure, error.UnsupportedCpuArch => { try self.reportUnsupportedCpuArch(); - return error.FlushFailure; + return error.LinkFailure; }, else => |e| return e, }; @@ -989,7 +989,7 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod try self.writeElfHeader(); } - if (diags.hasErrors()) return error.FlushFailure; + if (diags.hasErrors()) return error.LinkFailure; } fn dumpArgvInit(self: *Elf, arena: Allocator) !void { @@ -1389,7 +1389,7 @@ fn scanRelocs(self: *Elf) !void { error.RelaxFailure => unreachable, error.UnsupportedCpuArch => { try self.reportUnsupportedCpuArch(); - return error.FlushFailure; + return error.LinkFailure; }, error.RelocFailure => has_reloc_errors = true, else => |e| return e, @@ -1400,7 +1400,7 @@ fn scanRelocs(self: *Elf) !void { error.RelaxFailure => unreachable, error.UnsupportedCpuArch => { try self.reportUnsupportedCpuArch(); - return error.FlushFailure; + return error.LinkFailure; }, error.RelocFailure => has_reloc_errors = true, else => |e| return e, @@ -1409,7 +1409,7 @@ fn scanRelocs(self: *Elf) !void { try self.reportUndefinedSymbols(&undefs); - if (has_reloc_errors) return error.FlushFailure; + if (has_reloc_errors) return error.LinkFailure; if (self.zigObjectPtr()) |zo| { try zo.asFile().createSymbolIndirection(self); @@ -2327,7 +2327,13 @@ pub fn freeNav(self: *Elf, nav: InternPool.Nav.Index) void { return self.zigObjectPtr().?.freeNav(self, nav); } -pub fn updateFunc(self: *Elf, pt: Zcu.PerThread, func_index: InternPool.Index, air: Air, liveness: Liveness) !void { +pub fn updateFunc( + self: *Elf, + pt: Zcu.PerThread, + func_index: InternPool.Index, + air: Air, + liveness: Liveness, +) link.File.UpdateNavError!void { if (build_options.skip_non_native and builtin.object_format != .elf) { @panic("Attempted to compile for object format that was disabled by build configuration"); } @@ -2426,7 +2432,7 @@ pub fn addCommentString(self: *Elf) !void { self.comment_merge_section_index = msec_index; } -pub fn resolveMergeSections(self: *Elf) !void { +pub fn resolveMergeSections(self: *Elf) link.File.FlushError!void { const tracy = trace(@src()); defer tracy.end(); @@ -2441,7 +2447,7 @@ pub fn resolveMergeSections(self: *Elf) !void { }; } - if (has_errors) return error.FlushFailure; + if (has_errors) return error.LinkFailure; for (self.objects.items) |index| { const object = self.file(index).?.object; @@ -3658,7 +3664,7 @@ fn writeAtoms(self: *Elf) !void { atom_list.write(&buffer, &undefs, self) catch |err| switch (err) { error.UnsupportedCpuArch => { try self.reportUnsupportedCpuArch(); - return error.FlushFailure; + return error.LinkFailure; }, error.RelocFailure, error.RelaxFailure => has_reloc_errors = true, else => |e| return e, @@ -3666,7 +3672,7 @@ fn writeAtoms(self: *Elf) !void { } try self.reportUndefinedSymbols(&undefs); - if (has_reloc_errors) return error.FlushFailure; + if (has_reloc_errors) return error.LinkFailure; if (self.requiresThunks()) { for (self.thunks.items) |th| { diff --git a/src/link/Elf/ZigObject.zig b/src/link/Elf/ZigObject.zig index 73ea628864..41387434d3 100644 --- a/src/link/Elf/ZigObject.zig +++ b/src/link/Elf/ZigObject.zig @@ -264,7 +264,7 @@ pub fn deinit(self: *ZigObject, allocator: Allocator) void { } } -pub fn flush(self: *ZigObject, elf_file: *Elf, tid: Zcu.PerThread.Id) !void { +pub fn flush(self: *ZigObject, elf_file: *Elf, tid: Zcu.PerThread.Id) link.File.FlushError!void { // Handle any lazy symbols that were emitted by incremental compilation. if (self.lazy_syms.getPtr(.anyerror_type)) |metadata| { const pt: Zcu.PerThread = .activate(elf_file.base.comp.zcu.?, tid); @@ -278,7 +278,7 @@ pub fn flush(self: *ZigObject, elf_file: *Elf, tid: Zcu.PerThread.Id) !void { .{ .kind = .code, .ty = .anyerror_type }, metadata.text_symbol_index, ) catch |err| return switch (err) { - error.CodegenFail => error.FlushFailure, + error.CodegenFail => error.LinkFailure, else => |e| e, }; if (metadata.rodata_state != .unused) self.updateLazySymbol( @@ -287,7 +287,7 @@ pub fn flush(self: *ZigObject, elf_file: *Elf, tid: Zcu.PerThread.Id) !void { .{ .kind = .const_data, .ty = .anyerror_type }, metadata.rodata_symbol_index, ) catch |err| return switch (err) { - error.CodegenFail => error.FlushFailure, + error.CodegenFail => error.LinkFailure, else => |e| e, }; } @@ -933,6 +933,7 @@ pub fn getNavVAddr( const this_sym = self.symbol(this_sym_index); const vaddr = this_sym.address(.{}, elf_file); switch (reloc_info.parent) { + .none => unreachable, .atom_index => |atom_index| { const parent_atom = self.symbol(atom_index).atom(elf_file).?; const r_type = relocation.encode(.abs, elf_file.getTarget().cpu.arch); @@ -965,6 +966,7 @@ pub fn getUavVAddr( const sym = self.symbol(sym_index); const vaddr = sym.address(.{}, elf_file); switch (reloc_info.parent) { + .none => unreachable, .atom_index => |atom_index| { const parent_atom = self.symbol(atom_index).atom(elf_file).?; const r_type = relocation.encode(.abs, elf_file.getTarget().cpu.arch); @@ -1408,7 +1410,7 @@ pub fn updateFunc( func_index: InternPool.Index, air: Air, liveness: Liveness, -) !void { +) link.File.UpdateNavError!void { const tracy = trace(@src()); defer tracy.end(); @@ -1615,7 +1617,7 @@ fn updateLazySymbol( pt: Zcu.PerThread, sym: link.File.LazySymbol, symbol_index: Symbol.Index, -) !void { +) link.File.FlushError!void { const zcu = pt.zcu; const gpa = zcu.gpa; diff --git a/src/link/Elf/relocatable.zig b/src/link/Elf/relocatable.zig index 3035c33790..83bfe6d1f7 100644 --- a/src/link/Elf/relocatable.zig +++ b/src/link/Elf/relocatable.zig @@ -2,7 +2,7 @@ pub fn flushStaticLib(elf_file: *Elf, comp: *Compilation) link.File.FlushError!v const gpa = comp.gpa; const diags = &comp.link_diags; - if (diags.hasErrors()) return error.FlushFailure; + if (diags.hasErrors()) return error.LinkFailure; // First, we flush relocatable object file generated with our backends. if (elf_file.zigObjectPtr()) |zig_object| { @@ -127,13 +127,13 @@ pub fn flushStaticLib(elf_file: *Elf, comp: *Compilation) link.File.FlushError!v try elf_file.base.file.?.setEndPos(total_size); try elf_file.base.file.?.pwriteAll(buffer.items, 0); - if (diags.hasErrors()) return error.FlushFailure; + if (diags.hasErrors()) return error.LinkFailure; } pub fn flushObject(elf_file: *Elf, comp: *Compilation) link.File.FlushError!void { const diags = &comp.link_diags; - if (diags.hasErrors()) return error.FlushFailure; + if (diags.hasErrors()) return error.LinkFailure; // Now, we are ready to resolve the symbols across all input files. // We will first resolve the files in the ZigObject, next in the parsed @@ -179,7 +179,7 @@ pub fn flushObject(elf_file: *Elf, comp: *Compilation) link.File.FlushError!void try elf_file.writeShdrTable(); try elf_file.writeElfHeader(); - if (diags.hasErrors()) return error.FlushFailure; + if (diags.hasErrors()) return error.LinkFailure; } fn claimUnresolved(elf_file: *Elf) void { @@ -259,7 +259,7 @@ fn initComdatGroups(elf_file: *Elf) !void { } } -fn updateSectionSizes(elf_file: *Elf) !void { +fn updateSectionSizes(elf_file: *Elf) link.File.FlushError!void { const slice = elf_file.sections.slice(); for (slice.items(.atom_list_2)) |*atom_list| { if (atom_list.atoms.keys().len == 0) continue; diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 3a3710aed8..3878aa25b9 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -481,7 +481,7 @@ pub fn flushModule(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_n } }; - if (diags.hasErrors()) return error.FlushFailure; + if (diags.hasErrors()) return error.LinkFailure; { const index = @as(File.Index, @intCast(try self.files.addOne(gpa))); @@ -501,7 +501,7 @@ pub fn flushModule(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_n } self.checkDuplicates() catch |err| switch (err) { - error.HasDuplicates => return error.FlushFailure, + error.HasDuplicates => return error.LinkFailure, else => |e| return diags.fail("failed to check for duplicate symbol definitions: {s}", .{@errorName(e)}), }; @@ -516,7 +516,7 @@ pub fn flushModule(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_n self.claimUnresolved(); self.scanRelocs() catch |err| switch (err) { - error.HasUndefinedSymbols => return error.FlushFailure, + error.HasUndefinedSymbols => return error.LinkFailure, else => |e| return diags.fail("failed to scan relocations: {s}", .{@errorName(e)}), }; @@ -543,7 +543,7 @@ pub fn flushModule(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_n if (self.getZigObject()) |zo| { zo.resolveRelocs(self) catch |err| switch (err) { - error.ResolveFailed => return error.FlushFailure, + error.ResolveFailed => return error.LinkFailure, else => |e| return e, }; } @@ -2998,7 +2998,13 @@ pub fn writeCodeSignature(self: *MachO, code_sig: *CodeSignature) !void { try self.base.file.?.pwriteAll(buffer.items, offset); } -pub fn updateFunc(self: *MachO, pt: Zcu.PerThread, func_index: InternPool.Index, air: Air, liveness: Liveness) !void { +pub fn updateFunc( + self: *MachO, + pt: Zcu.PerThread, + func_index: InternPool.Index, + air: Air, + liveness: Liveness, +) link.File.UpdateNavError!void { if (build_options.skip_non_native and builtin.object_format != .macho) { @panic("Attempted to compile for object format that was disabled by build configuration"); } @@ -3006,7 +3012,7 @@ pub fn updateFunc(self: *MachO, pt: Zcu.PerThread, func_index: InternPool.Index, return self.getZigObject().?.updateFunc(self, pt, func_index, air, liveness); } -pub fn updateNav(self: *MachO, pt: Zcu.PerThread, nav: InternPool.Nav.Index) !void { +pub fn updateNav(self: *MachO, pt: Zcu.PerThread, nav: InternPool.Nav.Index) link.File.UpdateNavError!void { if (build_options.skip_non_native and builtin.object_format != .macho) { @panic("Attempted to compile for object format that was disabled by build configuration"); } diff --git a/src/link/MachO/ZigObject.zig b/src/link/MachO/ZigObject.zig index fb5a1255ca..4f94c48a98 100644 --- a/src/link/MachO/ZigObject.zig +++ b/src/link/MachO/ZigObject.zig @@ -560,7 +560,7 @@ pub fn flushModule(self: *ZigObject, macho_file: *MachO, tid: Zcu.PerThread.Id) .{ .kind = .code, .ty = .anyerror_type }, metadata.text_symbol_index, ) catch |err| return switch (err) { - error.CodegenFail => error.FlushFailure, + error.CodegenFail => error.LinkFailure, else => |e| e, }; if (metadata.const_state != .unused) self.updateLazySymbol( @@ -569,7 +569,7 @@ pub fn flushModule(self: *ZigObject, macho_file: *MachO, tid: Zcu.PerThread.Id) .{ .kind = .const_data, .ty = .anyerror_type }, metadata.const_symbol_index, ) catch |err| return switch (err) { - error.CodegenFail => error.FlushFailure, + error.CodegenFail => error.LinkFailure, else => |e| e, }; } diff --git a/src/link/MachO/relocatable.zig b/src/link/MachO/relocatable.zig index 497969ab90..b8e05e333a 100644 --- a/src/link/MachO/relocatable.zig +++ b/src/link/MachO/relocatable.zig @@ -33,11 +33,11 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Pat diags.addParseError(link_input.path().?, "failed to read input file: {s}", .{@errorName(err)}); } - if (diags.hasErrors()) return error.FlushFailure; + if (diags.hasErrors()) return error.LinkFailure; try macho_file.parseInputFiles(); - if (diags.hasErrors()) return error.FlushFailure; + if (diags.hasErrors()) return error.LinkFailure; try macho_file.resolveSymbols(); try macho_file.dedupLiterals(); @@ -93,11 +93,11 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ? diags.addParseError(link_input.path().?, "failed to read input file: {s}", .{@errorName(err)}); } - if (diags.hasErrors()) return error.FlushFailure; + if (diags.hasErrors()) return error.LinkFailure; try parseInputFilesAr(macho_file); - if (diags.hasErrors()) return error.FlushFailure; + if (diags.hasErrors()) return error.LinkFailure; // First, we flush relocatable object file generated with our backends. if (macho_file.getZigObject()) |zo| { @@ -218,7 +218,7 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ? try macho_file.base.file.?.setEndPos(total_size); try macho_file.base.file.?.pwriteAll(buffer.items, 0); - if (diags.hasErrors()) return error.FlushFailure; + if (diags.hasErrors()) return error.LinkFailure; } fn parseInputFilesAr(macho_file: *MachO) !void { diff --git a/src/link/NvPtx.zig b/src/link/NvPtx.zig index 199b13a6c6..84fc015552 100644 --- a/src/link/NvPtx.zig +++ b/src/link/NvPtx.zig @@ -82,11 +82,17 @@ pub fn deinit(self: *NvPtx) void { self.llvm_object.deinit(); } -pub fn updateFunc(self: *NvPtx, pt: Zcu.PerThread, func_index: InternPool.Index, air: Air, liveness: Liveness) !void { +pub fn updateFunc( + self: *NvPtx, + pt: Zcu.PerThread, + func_index: InternPool.Index, + air: Air, + liveness: Liveness, +) link.File.UpdateNavError!void { try self.llvm_object.updateFunc(pt, func_index, air, liveness); } -pub fn updateNav(self: *NvPtx, pt: Zcu.PerThread, nav: InternPool.Nav.Index) !void { +pub fn updateNav(self: *NvPtx, pt: Zcu.PerThread, nav: InternPool.Nav.Index) link.File.UpdateNavError!void { return self.llvm_object.updateNav(pt, nav); } @@ -102,10 +108,6 @@ pub fn updateExports( return self.llvm_object.updateExports(pt, exported, export_indices); } -pub fn freeDecl(self: *NvPtx, decl_index: InternPool.DeclIndex) void { - return self.llvm_object.freeDecl(decl_index); -} - pub fn flush(self: *NvPtx, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { return self.flushModule(arena, tid, prog_node); } diff --git a/src/link/Plan9.zig b/src/link/Plan9.zig index 31aac2486e..1330c876ea 100644 --- a/src/link/Plan9.zig +++ b/src/link/Plan9.zig @@ -385,7 +385,13 @@ fn addPathComponents(self: *Plan9, path: []const u8, a: *std.ArrayList(u8)) !voi } } -pub fn updateFunc(self: *Plan9, pt: Zcu.PerThread, func_index: InternPool.Index, air: Air, liveness: Liveness) !void { +pub fn updateFunc( + self: *Plan9, + pt: Zcu.PerThread, + func_index: InternPool.Index, + air: Air, + liveness: Liveness, +) link.File.UpdateNavError!void { if (build_options.skip_non_native and builtin.object_format != .plan9) { @panic("Attempted to compile for object format that was disabled by build configuration"); } @@ -437,7 +443,7 @@ pub fn updateFunc(self: *Plan9, pt: Zcu.PerThread, func_index: InternPool.Index, return self.updateFinish(pt, func.owner_nav); } -pub fn updateNav(self: *Plan9, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !void { +pub fn updateNav(self: *Plan9, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) link.File.UpdateNavError!void { const zcu = pt.zcu; const gpa = zcu.gpa; const ip = &zcu.intern_pool; @@ -619,7 +625,7 @@ pub fn flushModule(self: *Plan9, arena: Allocator, tid: Zcu.PerThread.Id, prog_n .{ .kind = .code, .ty = .anyerror_type }, metadata.text_atom, ) catch |err| return switch (err) { - error.CodegenFail => error.FlushFailure, + error.CodegenFail => error.LinkFailure, else => |e| e, }; if (metadata.rodata_state != .unused) self.updateLazySymbolAtom( @@ -627,7 +633,7 @@ pub fn flushModule(self: *Plan9, arena: Allocator, tid: Zcu.PerThread.Id, prog_n .{ .kind = .const_data, .ty = .anyerror_type }, metadata.rodata_atom, ) catch |err| return switch (err) { - error.CodegenFail => error.FlushFailure, + error.CodegenFail => error.LinkFailure, else => |e| e, }; } @@ -947,50 +953,6 @@ fn addNavExports( } } -pub fn freeDecl(self: *Plan9, decl_index: InternPool.DeclIndex) void { - const gpa = self.base.comp.gpa; - // TODO audit the lifetimes of decls table entries. It's possible to get - // freeDecl without any updateDecl in between. - const zcu = self.base.comp.zcu.?; - const decl = zcu.declPtr(decl_index); - const is_fn = decl.val.isFuncBody(zcu); - if (is_fn) { - const symidx_and_submap = self.fn_decl_table.get(decl.getFileScope(zcu)).?; - var submap = symidx_and_submap.functions; - if (submap.fetchSwapRemove(decl_index)) |removed_entry| { - gpa.free(removed_entry.value.code); - gpa.free(removed_entry.value.lineinfo); - } - if (submap.count() == 0) { - self.syms.items[symidx_and_submap.sym_index] = aout.Sym.undefined_symbol; - self.syms_index_free_list.append(gpa, symidx_and_submap.sym_index) catch {}; - submap.deinit(gpa); - } - } else { - if (self.data_decl_table.fetchSwapRemove(decl_index)) |removed_entry| { - gpa.free(removed_entry.value); - } - } - if (self.decls.fetchRemove(decl_index)) |const_kv| { - var kv = const_kv; - const atom = self.getAtom(kv.value.index); - if (atom.got_index) |i| { - // TODO: if this catch {} is triggered, an assertion in flushModule will be triggered, because got_index_free_list will have the wrong length - self.got_index_free_list.append(gpa, i) catch {}; - } - if (atom.sym_index) |i| { - self.syms_index_free_list.append(gpa, i) catch {}; - self.syms.items[i] = aout.Sym.undefined_symbol; - } - kv.value.exports.deinit(gpa); - } - { - const atom_index = self.decls.get(decl_index).?.index; - const relocs = self.relocs.getPtr(atom_index) orelse return; - relocs.clearAndFree(gpa); - assert(self.relocs.remove(atom_index)); - } -} fn createAtom(self: *Plan9) !Atom.Index { const gpa = self.base.comp.gpa; const index = @as(Atom.Index, @intCast(self.atoms.items.len)); diff --git a/src/link/SpirV.zig b/src/link/SpirV.zig index b1b8945963..531e544f5a 100644 --- a/src/link/SpirV.zig +++ b/src/link/SpirV.zig @@ -122,7 +122,13 @@ pub fn deinit(self: *SpirV) void { self.object.deinit(); } -pub fn updateFunc(self: *SpirV, pt: Zcu.PerThread, func_index: InternPool.Index, air: Air, liveness: Liveness) !void { +pub fn updateFunc( + self: *SpirV, + pt: Zcu.PerThread, + func_index: InternPool.Index, + air: Air, + liveness: Liveness, +) link.File.UpdateNavError!void { if (build_options.skip_non_native) { @panic("Attempted to compile for architecture that was disabled by build configuration"); } @@ -134,7 +140,7 @@ pub fn updateFunc(self: *SpirV, pt: Zcu.PerThread, func_index: InternPool.Index, try self.object.updateFunc(pt, func_index, air, liveness); } -pub fn updateNav(self: *SpirV, pt: Zcu.PerThread, nav: InternPool.Nav.Index) !void { +pub fn updateNav(self: *SpirV, pt: Zcu.PerThread, nav: InternPool.Nav.Index) link.File.UpdateNavError!void { if (build_options.skip_non_native) { @panic("Attempted to compile for architecture that was disabled by build configuration"); } @@ -196,11 +202,6 @@ pub fn updateExports( // TODO: Export regular functions, variables, etc using Linkage attributes. } -pub fn freeDecl(self: *SpirV, decl_index: InternPool.DeclIndex) void { - _ = self; - _ = decl_index; -} - pub fn flush(self: *SpirV, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { return self.flushModule(arena, tid, prog_node); } @@ -266,7 +267,7 @@ pub fn flushModule(self: *SpirV, arena: Allocator, tid: Zcu.PerThread.Id, prog_n error.OutOfMemory => return error.OutOfMemory, else => |other| { log.err("error while linking: {s}", .{@errorName(other)}); - return error.FlushFailure; + return error.LinkFailure; }, }; diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 7bb1d8c476..23d4b0e8dd 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -1,44 +1,49 @@ const Wasm = @This(); -const build_options = @import("build_options"); +const Archive = @import("Wasm/Archive.zig"); +const Object = @import("Wasm/Object.zig"); +const Flush = @import("Wasm/Flush.zig"); const builtin = @import("builtin"); const native_endian = builtin.cpu.arch.endian(); +const build_options = @import("build_options"); + const std = @import("std"); const Allocator = std.mem.Allocator; const Cache = std.Build.Cache; const Path = Cache.Path; const assert = std.debug.assert; const fs = std.fs; -const gc_log = std.log.scoped(.gc); const leb = std.leb; const log = std.log.scoped(.link); const mem = std.mem; const Air = @import("../Air.zig"); -const Archive = @import("Wasm/Archive.zig"); const CodeGen = @import("../arch/wasm/CodeGen.zig"); const Compilation = @import("../Compilation.zig"); const Dwarf = @import("Dwarf.zig"); const InternPool = @import("../InternPool.zig"); const Liveness = @import("../Liveness.zig"); const LlvmObject = @import("../codegen/llvm.zig").Object; -const Object = @import("Wasm/Object.zig"); -const Symbol = @import("Wasm/Symbol.zig"); -const Type = @import("../Type.zig"); -const Value = @import("../Value.zig"); const Zcu = @import("../Zcu.zig"); -const ZigObject = @import("Wasm/ZigObject.zig"); const codegen = @import("../codegen.zig"); const dev = @import("../dev.zig"); const link = @import("../link.zig"); const lldMain = @import("../main.zig").lldMain; const trace = @import("../tracy.zig").trace; const wasi_libc = @import("../wasi_libc.zig"); +const Value = @import("../Value.zig"); base: link.File, /// Null-terminated strings, indexes have type String and string_table provides /// lookup. +/// +/// There are a couple of sites that add things here without adding +/// corresponding string_table entries. For such cases, when implementing +/// serialization/deserialization, they should be adjusted to prefix that data +/// with a null byte so that deserialization does not attempt to create +/// string_table entries for them. Alternately those sites could be moved to +/// use a different byte array for this purpose. string_bytes: std.ArrayListUnmanaged(u8), /// Omitted when serializing linker state. string_table: String.Table, @@ -62,118 +67,637 @@ export_table: bool, name: []const u8, /// If this is not null, an object file is created by LLVM and linked with LLD afterwards. llvm_object: ?LlvmObject.Ptr = null, -zig_object: ?*ZigObject, /// List of relocatable files to be linked into the final binary. objects: std.ArrayListUnmanaged(Object) = .{}, + +func_types: std.AutoArrayHashMapUnmanaged(FunctionType, void) = .empty, +/// Provides a mapping of both imports and provided functions to symbol name. +/// Local functions may be unnamed. +object_function_imports: std.AutoArrayHashMapUnmanaged(String, FunctionImport) = .empty, +/// All functions for all objects. +object_functions: std.ArrayListUnmanaged(Function) = .empty, + +/// Provides a mapping of both imports and provided globals to symbol name. +/// Local globals may be unnamed. +object_global_imports: std.AutoArrayHashMapUnmanaged(String, GlobalImport) = .empty, +/// All globals for all objects. +object_globals: std.ArrayListUnmanaged(Global) = .empty, + +/// All table imports for all objects. +object_table_imports: std.ArrayListUnmanaged(TableImport) = .empty, +/// All parsed table sections for all objects. +object_tables: std.ArrayListUnmanaged(Table) = .empty, + +/// All memory imports for all objects. +object_memory_imports: std.ArrayListUnmanaged(MemoryImport) = .empty, +/// All parsed memory sections for all objects. +object_memories: std.ArrayListUnmanaged(std.wasm.Memory) = .empty, + +/// List of initialization functions. These must be called in order of priority +/// by the (synthetic) __wasm_call_ctors function. +object_init_funcs: std.ArrayListUnmanaged(InitFunc) = .empty, +/// All relocations from all objects concatenated. `relocs_start` marks the end +/// point of object relocations and start point of Zcu relocations. +relocations: std.MultiArrayList(Relocation) = .empty, + +/// Non-synthetic section that can essentially be mem-cpy'd into place after performing relocations. +object_data_segments: std.ArrayListUnmanaged(DataSegment) = .empty, +/// Non-synthetic section that can essentially be mem-cpy'd into place after performing relocations. +object_custom_segments: std.AutoArrayHashMapUnmanaged(ObjectSectionIndex, CustomSegment) = .empty, + +/// All comdat information for all objects. +object_comdats: std.ArrayListUnmanaged(Comdat) = .empty, +/// A table that maps the relocations to be performed where the key represents +/// the section (across all objects) that the slice of relocations applies to. +object_relocations_table: std.AutoArrayHashMapUnmanaged(ObjectSectionIndex, Relocation.Slice) = .empty, +/// Incremented across all objects in order to enable calculation of `ObjectSectionIndex` values. +object_total_sections: u32 = 0, +/// All comdat symbols from all objects concatenated. +object_comdat_symbols: std.MultiArrayList(Comdat.Symbol) = .empty, + /// When importing objects from the host environment, a name must be supplied. /// LLVM uses "env" by default when none is given. This would be a good default for Zig /// to support existing code. /// TODO: Allow setting this through a flag? host_name: String, -/// List of symbols generated by the linker. -synthetic_symbols: std.ArrayListUnmanaged(Symbol) = .empty, -/// Maps atoms to their segment index -atoms: std.AutoHashMapUnmanaged(Segment.Index, Atom.Index) = .empty, -/// List of all atoms. -managed_atoms: std.ArrayListUnmanaged(Atom) = .empty, -/// The count of imported functions. This number will be appended -/// to the function indexes as their index starts at the lowest non-extern function. -imported_functions_count: u32 = 0, -/// The count of imported wasm globals. This number will be appended -/// to the global indexes when sections are merged. -imported_globals_count: u32 = 0, -/// The count of imported tables. This number will be appended -/// to the table indexes when sections are merged. -imported_tables_count: u32 = 0, -/// Map of symbol locations, represented by its `Import` -imports: std.AutoHashMapUnmanaged(SymbolLoc, Import) = .empty, -/// Represents non-synthetic section entries. -/// Used for code, data and custom sections. -segments: std.ArrayListUnmanaged(Segment) = .empty, -/// Maps a data segment key (such as .rodata) to the index into `segments`. -data_segments: std.StringArrayHashMapUnmanaged(Segment.Index) = .empty, -/// A table of `NamedSegment` which provide meta data -/// about a data symbol such as its name where the key is -/// the segment index, which can be found from `data_segments` -segment_info: std.AutoArrayHashMapUnmanaged(Segment.Index, NamedSegment) = .empty, - -// Output sections -/// Output type section -func_types: std.ArrayListUnmanaged(std.wasm.Type) = .empty, -/// Output function section where the key is the original -/// function index and the value is function. -/// This allows us to map multiple symbols to the same function. -functions: std.AutoArrayHashMapUnmanaged( - struct { - /// `none` in the case of synthetic sections. - file: OptionalObjectId, - index: u32, - }, - struct { - func: std.wasm.Func, - sym_index: Symbol.Index, - }, -) = .{}, -/// Output global section -wasm_globals: std.ArrayListUnmanaged(std.wasm.Global) = .empty, /// Memory section memories: std.wasm.Memory = .{ .limits = .{ .min = 0, .max = undefined, - .flags = 0, + .flags = .{ .has_max = false, .is_shared = false }, } }, -/// Output table section -tables: std.ArrayListUnmanaged(std.wasm.Table) = .empty, -/// Output export section -exports: std.ArrayListUnmanaged(Export) = .empty, -/// List of initialization functions. These must be called in order of priority -/// by the (synthetic) __wasm_call_ctors function. -init_funcs: std.ArrayListUnmanaged(InitFuncLoc) = .empty, -/// Index to a function defining the entry of the wasm file -entry: ?u32 = null, - -/// Indirect function table, used to call function pointers -/// When this is non-zero, we must emit a table entry, -/// as well as an 'elements' section. -/// -/// Note: Key is symbol location, value represents the index into the table -function_table: std.AutoHashMapUnmanaged(SymbolLoc, u32) = .empty, - -/// All archive files that are lazy loaded. -/// e.g. when an undefined symbol references a symbol from the archive. -/// None of this data is serialized to disk because it is trivially reloaded -/// from unchanged archive files on the next start of the compiler process, -/// or if those files have changed, the prelink phase needs to be restarted. -lazy_archives: std.ArrayListUnmanaged(LazyArchive) = .empty, - -/// A map of global names to their symbol location -globals: std.AutoArrayHashMapUnmanaged(String, SymbolLoc) = .empty, -/// The list of GOT symbols and their location -got_symbols: std.ArrayListUnmanaged(SymbolLoc) = .empty, -/// Maps discarded symbols and their positions to the location of the symbol -/// it was resolved to -discarded: std.AutoHashMapUnmanaged(SymbolLoc, SymbolLoc) = .empty, -/// List of all symbol locations which have been resolved by the linker and will be emit -/// into the final binary. -resolved_symbols: std.AutoArrayHashMapUnmanaged(SymbolLoc, void) = .empty, -/// Symbols that remain undefined after symbol resolution. -undefs: std.AutoArrayHashMapUnmanaged(String, SymbolLoc) = .empty, -/// Maps a symbol's location to an atom. This can be used to find meta -/// data of a symbol, such as its size, or its offset to perform a relocation. -/// Undefined (and synthetic) symbols do not have an Atom and therefore cannot be mapped. -symbol_atom: std.AutoHashMapUnmanaged(SymbolLoc, Atom.Index) = .empty, /// `--verbose-link` output. /// Initialized on creation, appended to as inputs are added, printed during `flush`. /// String data is allocated into Compilation arena. dump_argv_list: std.ArrayListUnmanaged([]const u8), -/// Represents the index into `segments` where the 'code' section lives. -code_section_index: Segment.OptionalIndex = .none, -custom_sections: CustomSections, preloaded_strings: PreloadedStrings, +navs: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, Nav) = .empty, +nav_exports: std.AutoArrayHashMapUnmanaged(NavExport, Zcu.Export.Index) = .empty, +uav_exports: std.AutoArrayHashMapUnmanaged(UavExport, Zcu.Export.Index) = .empty, +imports: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, void) = .empty, + +dwarf: ?Dwarf = null, +debug_sections: DebugSections = .{}, + +flush_buffer: Flush = .{}, + +missing_exports_init: []String = &.{}, +entry_resolution: FunctionImport.Resolution = .unresolved, + +/// Empty when outputting an object. +function_exports: std.ArrayListUnmanaged(FunctionIndex) = .empty, +/// Tracks the value at the end of prelink. +function_exports_len: u32 = 0, +global_exports: std.ArrayListUnmanaged(GlobalIndex) = .empty, +/// Tracks the value at the end of prelink. +global_exports_len: u32 = 0, + +/// Ordered list of non-import functions that will appear in the final binary. +/// Empty until prelink. +functions: std.AutoArrayHashMapUnmanaged(FunctionImport.Resolution, void) = .empty, +/// Tracks the value at the end of prelink, at which point `functions` +/// contains only object file functions, and nothing from the Zcu yet. +functions_len: u32 = 0, +/// Immutable after prelink. The undefined functions coming only from all object files. +/// The Zcu must satisfy these. +function_imports_init: []FunctionImportId = &.{}, +/// Initialized as copy of `function_imports_init`; entries are deleted as +/// they are satisfied by the Zcu. +function_imports: std.AutoArrayHashMapUnmanaged(FunctionImportId, void) = .empty, + +/// Ordered list of non-import globals that will appear in the final binary. +/// Empty until prelink. +globals: std.AutoArrayHashMapUnmanaged(GlobalImport.Resolution, void) = .empty, +/// Tracks the value at the end of prelink, at which point `globals` +/// contains only object file globals, and nothing from the Zcu yet. +globals_len: u32 = 0, +global_imports_init: []GlobalImportId = &.{}, +global_imports: std.AutoArrayHashMapUnmanaged(GlobalImportId, void) = .empty, + +/// Ordered list of non-import tables that will appear in the final binary. +/// Empty until prelink. +tables: std.AutoArrayHashMapUnmanaged(TableImport.Resolution, void) = .empty, +table_imports: std.AutoArrayHashMapUnmanaged(ObjectTableImportIndex, void) = .empty, + +any_exports_updated: bool = true, + +/// Index into `functions`. +pub const FunctionIndex = enum(u32) { + _, + + pub fn fromNav(nav_index: InternPool.Nav.Index, wasm: *const Wasm) FunctionIndex { + return @enumFromInt(wasm.functions.getIndex(.pack(wasm, .{ .nav = nav_index })).?); + } +}; + +/// 0. Index into `function_imports` +/// 1. Index into `functions`. +pub const OutputFunctionIndex = enum(u32) { + _, +}; + +/// Index into `globals`. +const GlobalIndex = enum(u32) { + _, + + fn key(index: GlobalIndex, f: *const Flush) *Wasm.GlobalImport.Resolution { + return &f.globals.items[@intFromEnum(index)]; + } +}; + +/// The first N indexes correspond to input objects (`objects`) array. +/// After that, the indexes correspond to the `source_locations` array, +/// representing a location in a Zig source file that can be pinpointed +/// precisely via AST node and token. +pub const SourceLocation = enum(u32) { + /// From the Zig compilation unit but no precise source location. + zig_object_nofile = std.math.maxInt(u32) - 1, + none = std.math.maxInt(u32), + _, +}; + +/// The lower bits of this ABI-match the flags here: +/// https://github.com/WebAssembly/tool-conventions/blob/df8d737539eb8a8f446ba5eab9dc670c40dfb81e/Linking.md#symbol-table-subsection +/// The upper bits are used for nefarious purposes. +pub const SymbolFlags = packed struct(u32) { + binding: Binding = .strong, + /// Indicating that this is a hidden symbol. Hidden symbols are not to be + /// exported when performing the final link, but may be linked to other + /// modules. + visibility_hidden: bool = false, + padding0: u1 = 0, + /// For non-data symbols, this must match whether the symbol is an import + /// or is defined; for data symbols, determines whether a segment is + /// specified. + undefined: bool = false, + /// The symbol is intended to be exported from the wasm module to the host + /// environment. This differs from the visibility flags in that it affects + /// static linking. + exported: bool = false, + /// The symbol uses an explicit symbol name, rather than reusing the name + /// from a wasm import. This allows it to remap imports from foreign + /// WebAssembly modules into local symbols with different names. + explicit_name: bool = false, + /// The symbol is intended to be included in the linker output, regardless + /// of whether it is used by the program. Same meaning as `retain`. + no_strip: bool = false, + /// The symbol resides in thread local storage. + tls: bool = false, + /// The symbol represents an absolute address. This means its offset is + /// relative to the start of the wasm memory as opposed to being relative + /// to a data segment. + absolute: bool = false, + + // Above here matches the tooling conventions ABI. + + padding1: u8 = 0, + /// Zig-specific. Dead things are allowed to be garbage collected. + alive: bool = false, + /// Zig-specific. Segments only. Signals that the segment contains only + /// null terminated strings allowing the linker to perform merging. + strings: bool = false, + /// Zig-specific. This symbol comes from an object that must be included in + /// the final link. + must_link: bool = false, + /// Zig-specific. Data segments only. + is_passive: bool = false, + /// Zig-specific. Data segments only. + alignment: Alignment = .none, + /// Zig-specific. Globals only. + global_type: Global.Type = .zero, + + pub const Binding = enum(u2) { + strong = 0, + /// Indicating that this is a weak symbol. When linking multiple modules + /// defining the same symbol, all weak definitions are discarded if any + /// strong definitions exist; then if multiple weak definitions exist all + /// but one (unspecified) are discarded; and finally it is an error if more + /// than one definition remains. + weak = 1, + /// Indicating that this is a local symbol. Local symbols are not to be + /// exported, or linked to other modules/sections. The names of all + /// non-local symbols must be unique, but the names of local symbols + /// are not considered for uniqueness. A local function or global + /// symbol cannot reference an import. + local = 2, + }; + + pub fn initZigSpecific(flags: *SymbolFlags, must_link: bool, no_strip: bool) void { + flags.alive = false; + flags.strings = false; + flags.must_link = must_link; + flags.no_strip = no_strip; + flags.alignment = .none; + flags.global_type = .zero; + flags.is_passive = false; + } + + pub fn isIncluded(flags: SymbolFlags, is_dynamic: bool) bool { + return flags.exported or + (is_dynamic and !flags.visibility_hidden) or + (flags.no_strip and flags.must_link); + } + + pub fn isExported(flags: SymbolFlags, is_dynamic: bool) bool { + if (flags.undefined or flags.binding == .local) return false; + if (is_dynamic and !flags.visibility_hidden) return true; + return flags.exported; + } + + pub fn requiresImport(flags: SymbolFlags, is_data: bool) bool { + if (is_data) return false; + if (!flags.undefined) return false; + if (flags.binding == .weak) return false; + return true; + } + + /// Returns the name as how it will be output into the final object + /// file or binary. When `merge` is true, this will return the + /// short name. i.e. ".rodata". When false, it returns the entire name instead. + pub fn outputName(flags: SymbolFlags, name: []const u8, merge: bool) []const u8 { + if (flags.tls) return ".tdata"; + if (!merge) return name; + if (mem.startsWith(u8, name, ".rodata.")) return ".rodata"; + if (mem.startsWith(u8, name, ".text.")) return ".text"; + if (mem.startsWith(u8, name, ".data.")) return ".data"; + if (mem.startsWith(u8, name, ".bss.")) return ".bss"; + return name; + } + + /// Masks off the Zig-specific stuff. + pub fn toAbiInteger(flags: SymbolFlags) u32 { + var copy = flags; + copy.initZigSpecific(false, false); + return @bitCast(copy); + } +}; + +pub const Nav = extern struct { + code: DataSegment.Payload, + relocs: Relocation.Slice, + + pub const Code = DataSegment.Payload; + + /// Index into `navs`. + /// Note that swapRemove is sometimes performed on `navs`. + pub const Index = enum(u32) { + _, + + pub fn key(i: @This(), wasm: *const Wasm) *InternPool.Nav.Index { + return &wasm.navs.keys()[@intFromEnum(i)]; + } + + pub fn value(i: @This(), wasm: *const Wasm) *Nav { + return &wasm.navs.values()[@intFromEnum(i)]; + } + }; +}; + +pub const NavExport = extern struct { + name: String, + nav_index: InternPool.Nav.Index, +}; + +pub const UavExport = extern struct { + name: String, + uav_index: InternPool.Index, +}; + +const DebugSections = struct { + abbrev: DebugSection = .{}, + info: DebugSection = .{}, + line: DebugSection = .{}, + loc: DebugSection = .{}, + pubnames: DebugSection = .{}, + pubtypes: DebugSection = .{}, + ranges: DebugSection = .{}, + str: DebugSection = .{}, +}; + +const DebugSection = struct {}; + +pub const FunctionImport = extern struct { + flags: SymbolFlags, + module_name: String, + source_location: SourceLocation, + resolution: Resolution, + type: FunctionType.Index, + + /// Represents a synthetic function, a function from an object, or a + /// function from the Zcu. + pub const Resolution = enum(u32) { + unresolved, + __wasm_apply_global_tls_relocs, + __wasm_call_ctors, + __wasm_init_memory, + __wasm_init_tls, + __zig_error_names, + // Next, index into `object_functions`. + // Next, index into `navs`. + _, + + const first_object_function = @intFromEnum(Resolution.__zig_error_names) + 1; + + pub const Unpacked = union(enum) { + unresolved, + __wasm_apply_global_tls_relocs, + __wasm_call_ctors, + __wasm_init_memory, + __wasm_init_tls, + __zig_error_names, + object_function: ObjectFunctionIndex, + nav: Nav.Index, + }; + + pub fn unpack(r: Resolution, wasm: *const Wasm) Unpacked { + return switch (r) { + .unresolved => .unresolved, + .__wasm_apply_global_tls_relocs => .__wasm_apply_global_tls_relocs, + .__wasm_call_ctors => .__wasm_call_ctors, + .__wasm_init_memory => .__wasm_init_memory, + .__wasm_init_tls => .__wasm_init_tls, + .__zig_error_names => .__zig_error_names, + _ => { + const i: u32 = @intFromEnum(r); + const object_function_index = i - first_object_function; + if (object_function_index < wasm.object_functions.items.len) + return .{ .object_function = @enumFromInt(object_function_index) }; + const nav_index = object_function_index - wasm.object_functions.items.len; + return .{ .nav = @enumFromInt(nav_index) }; + }, + }; + } + + pub fn pack(wasm: *const Wasm, unpacked: Unpacked) Resolution { + return switch (unpacked) { + .unresolved => .unresolved, + .__wasm_apply_global_tls_relocs => .__wasm_apply_global_tls_relocs, + .__wasm_call_ctors => .__wasm_call_ctors, + .__wasm_init_memory => .__wasm_init_memory, + .__wasm_init_tls => .__wasm_init_tls, + .__zig_error_names => .__zig_error_names, + .object_function => |i| @enumFromInt(first_object_function + @intFromEnum(i)), + .nav => |i| @enumFromInt(first_object_function + wasm.object_functions.items.len + @intFromEnum(i)), + }; + } + + pub fn isNavOrUnresolved(r: Resolution, wasm: *const Wasm) bool { + return switch (r.unpack(wasm)) { + .unresolved, .nav => true, + else => false, + }; + } + }; + + /// Index into `object_function_imports`. + pub const Index = enum(u32) { + _, + }; +}; + +pub const Function = extern struct { + flags: SymbolFlags, + /// `none` if this function has no symbol describing it. + name: OptionalString, + type_index: FunctionType.Index, + code: Code, + /// The offset within the section where the data starts. + offset: u32, + section_index: ObjectSectionIndex, + source_location: SourceLocation, + + pub const Code = DataSegment.Payload; +}; + +pub const GlobalImport = extern struct { + flags: SymbolFlags, + module_name: String, + source_location: SourceLocation, + resolution: Resolution, + + /// Represents a synthetic global, or a global from an object. + pub const Resolution = enum(u32) { + unresolved, + __heap_base, + __heap_end, + __stack_pointer, + __tls_align, + __tls_base, + __tls_size, + __zig_error_name_table, + // Next, index into `object_globals`. + // Next, index into `navs`. + _, + }; +}; + +pub const Global = extern struct { + /// `none` if this function has no symbol describing it. + name: OptionalString, + flags: SymbolFlags, + expr: Expr, + + pub const Type = packed struct(u4) { + valtype: Valtype, + mutable: bool, + + pub const zero: Type = @bitCast(@as(u4, 0)); + }; + + pub const Valtype = enum(u3) { + i32, + i64, + f32, + f64, + v128, + + pub fn from(v: std.wasm.Valtype) Valtype { + return switch (v) { + .i32 => .i32, + .i64 => .i64, + .f32 => .f32, + .f64 => .f64, + .v128 => .v128, + }; + } + + pub fn to(v: Valtype) std.wasm.Valtype { + return switch (v) { + .i32 => .i32, + .i64 => .i64, + .f32 => .f32, + .f64 => .f64, + .v128 => .v128, + }; + } + }; +}; + +pub const TableImport = extern struct { + flags: SymbolFlags, + module_name: String, + source_location: SourceLocation, + resolution: Resolution, + + /// Represents a synthetic table, or a table from an object. + pub const Resolution = enum(u32) { + unresolved, + __indirect_function_table, + // Next, index into `object_tables`. + _, + }; +}; + +pub const Table = extern struct { + module_name: String, + name: String, + flags: SymbolFlags, + limits_min: u32, + limits_max: u32, + limits_has_max: bool, + limits_is_shared: bool, + reftype: std.wasm.RefType, + padding: [1]u8 = .{0}, +}; + +/// Uniquely identifies a section across all objects. Each Object has a section_start field. +/// By subtracting that value from this one, the Object section index is obtained. +pub const ObjectSectionIndex = enum(u32) { + _, +}; + +/// Index into `object_function_imports`. +pub const ObjectFunctionImportIndex = enum(u32) { + _, + + pub fn ptr(index: ObjectFunctionImportIndex, wasm: *const Wasm) *FunctionImport { + return &wasm.object_function_imports.items[@intFromEnum(index)]; + } +}; + +/// Index into `object_global_imports`. +pub const ObjectGlobalImportIndex = enum(u32) { + _, +}; + +/// Index into `object_table_imports`. +pub const ObjectTableImportIndex = enum(u32) { + _, +}; + +/// Index into `object_tables`. +pub const ObjectTableIndex = enum(u32) { + _, + + pub fn ptr(index: ObjectTableIndex, wasm: *const Wasm) *Table { + return &wasm.object_tables.items[@intFromEnum(index)]; + } +}; + +/// Index into `global_imports`. +pub const GlobalImportIndex = enum(u32) { + _, +}; + +/// Index into `object_globals`. +pub const ObjectGlobalIndex = enum(u32) { + _, +}; + +/// Index into `object_functions`. +pub const ObjectFunctionIndex = enum(u32) { + _, + + pub fn ptr(index: ObjectFunctionIndex, wasm: *const Wasm) *Function { + return &wasm.object_functions.items[@intFromEnum(index)]; + } + + pub fn toOptional(i: ObjectFunctionIndex) OptionalObjectFunctionIndex { + const result: OptionalObjectFunctionIndex = @enumFromInt(@intFromEnum(i)); + assert(result != .none); + return result; + } +}; + +/// Index into `object_functions`, or null. +pub const OptionalObjectFunctionIndex = enum(u32) { + none = std.math.maxInt(u32), + _, + + pub fn unwrap(i: OptionalObjectFunctionIndex) ?ObjectFunctionIndex { + if (i == .none) return null; + return @enumFromInt(@intFromEnum(i)); + } +}; + +pub const DataSegment = extern struct { + /// `none` if no symbol describes it. + name: OptionalString, + flags: SymbolFlags, + payload: Payload, + /// From the data segment start to the first byte of payload. + segment_offset: u32, + section_index: ObjectSectionIndex, + + pub const Payload = extern struct { + /// Points into string_bytes. No corresponding string_table entry. + off: u32, + /// The size in bytes of the data representing the segment within the section. + len: u32, + + fn slice(p: DataSegment.Payload, wasm: *const Wasm) []const u8 { + return wasm.string_bytes.items[p.off..][0..p.len]; + } + }; + + /// Index into `object_data_segments`. + pub const Index = enum(u32) { + _, + }; +}; + +pub const CustomSegment = extern struct { + payload: Payload, + flags: SymbolFlags, + section_name: String, + + pub const Payload = DataSegment.Payload; +}; + +/// An index into string_bytes where a wasm expression is found. +pub const Expr = enum(u32) { + _, +}; + +pub const FunctionType = extern struct { + params: ValtypeList, + returns: ValtypeList, + + /// Index into func_types + pub const Index = enum(u32) { + _, + + pub fn ptr(i: FunctionType.Index, wasm: *const Wasm) *FunctionType { + return &wasm.func_types.keys()[@intFromEnum(i)]; + } + }; + + pub const format = @compileError("can't format without *Wasm reference"); + + pub fn eql(a: FunctionType, b: FunctionType) bool { + return a.params == b.params and a.returns == b.returns; + } +}; + +/// Represents a function entry, holding the index to its type +pub const Func = extern struct { + type_index: FunctionType.Index, +}; + /// Type reflection is used on the field names to autopopulate each field /// during initialization. const PreloadedStrings = struct { @@ -190,31 +714,14 @@ const PreloadedStrings = struct { __wasm_init_memory: String, __wasm_init_memory_flag: String, __wasm_init_tls: String, - __zig_err_name_table: String, - __zig_err_names: String, + __zig_error_name_table: String, + __zig_error_names: String, __zig_errors_len: String, _initialize: String, _start: String, memory: String, }; -/// Type reflection is used on the field names to autopopulate each inner `name` field. -const CustomSections = struct { - @".debug_info": CustomSection, - @".debug_pubtypes": CustomSection, - @".debug_abbrev": CustomSection, - @".debug_line": CustomSection, - @".debug_str": CustomSection, - @".debug_pubnames": CustomSection, - @".debug_loc": CustomSection, - @".debug_ranges": CustomSection, -}; - -const CustomSection = struct { - name: String, - index: Segment.OptionalIndex, -}; - /// Index into string_bytes pub const String = enum(u32) { _, @@ -246,6 +753,11 @@ pub const String = enum(u32) { } }; + pub fn slice(index: String, wasm: *const Wasm) [:0]const u8 { + const start_slice = wasm.string_bytes.items[@intFromEnum(index)..]; + return start_slice[0..mem.indexOfScalar(u8, start_slice, 0).? :0]; + } + pub fn toOptional(i: String) OptionalString { const result: OptionalString = @enumFromInt(@intFromEnum(i)); assert(result != .none); @@ -261,156 +773,238 @@ pub const OptionalString = enum(u32) { if (i == .none) return null; return @enumFromInt(@intFromEnum(i)); } + + pub fn slice(index: OptionalString, wasm: *const Wasm) ?[:0]const u8 { + return (index.unwrap() orelse return null).slice(wasm); + } }; -/// Index into objects array or the zig object. -pub const ObjectId = enum(u16) { - zig_object = std.math.maxInt(u16) - 1, +/// Stored identically to `String`. The bytes are reinterpreted as +/// `std.wasm.Valtype` elements. +pub const ValtypeList = enum(u32) { _, - pub fn toOptional(i: ObjectId) OptionalObjectId { - const result: OptionalObjectId = @enumFromInt(@intFromEnum(i)); - assert(result != .none); - return result; + pub fn fromString(s: String) ValtypeList { + return @enumFromInt(@intFromEnum(s)); + } + + pub fn slice(index: ValtypeList, wasm: *const Wasm) []const std.wasm.Valtype { + return @bitCast(String.slice(@enumFromInt(@intFromEnum(index)), wasm)); } }; -/// Optional index into objects array or the zig object. -pub const OptionalObjectId = enum(u16) { - zig_object = std.math.maxInt(u16) - 1, - none = std.math.maxInt(u16), +/// 0. Index into `object_function_imports`. +/// 1. Index into `imports`. +pub const FunctionImportId = enum(u32) { _, - - pub fn unwrap(i: OptionalObjectId) ?ObjectId { - if (i == .none) return null; - return @enumFromInt(@intFromEnum(i)); - } }; -/// None of this data is serialized since it can be re-loaded from disk, or if -/// it has been changed, the data must be discarded. -const LazyArchive = struct { - path: Path, - file_contents: []const u8, - archive: Archive, - - fn deinit(la: *LazyArchive, gpa: Allocator) void { - la.archive.deinit(gpa); - gpa.free(la.path.sub_path); - gpa.free(la.file_contents); - la.* = undefined; - } +/// 0. Index into `object_global_imports`. +/// 1. Index into `imports`. +pub const GlobalImportId = enum(u32) { + _, }; -pub const Segment = struct { - alignment: Alignment, - size: u32, +pub const Relocation = struct { + tag: Tag, + /// Offset of the value to rewrite relative to the relevant section's contents. + /// When `offset` is zero, its position is immediately after the id and size of the section. offset: u32, - flags: u32, + pointee: Pointee, + /// Populated only for `MEMORY_ADDR_*`, `FUNCTION_OFFSET_I32` and `SECTION_OFFSET_I32`. + addend: i32, - const Index = enum(u32) { - _, + pub const Pointee = union { + symbol_name: String, + type_index: FunctionType.Index, + section: ObjectSectionIndex, + nav_index: InternPool.Nav.Index, + uav_index: InternPool.Index, + }; - pub fn toOptional(i: Index) OptionalIndex { - const result: OptionalIndex = @enumFromInt(@intFromEnum(i)); - assert(result != .none); - return result; + pub const Slice = extern struct { + /// Index into `relocations`. + off: u32, + len: u32, + + pub fn slice(s: Slice, wasm: *const Wasm) []Relocation { + return wasm.relocations.items[s.off..][0..s.len]; } }; - const OptionalIndex = enum(u32) { - none = std.math.maxInt(u32), - _, + pub const Tag = enum(u8) { + /// Uses `symbol_name`. + FUNCTION_INDEX_LEB = 0, + /// Uses `table_index`. + TABLE_INDEX_SLEB = 1, + /// Uses `table_index`. + TABLE_INDEX_I32 = 2, + MEMORY_ADDR_LEB = 3, + MEMORY_ADDR_SLEB = 4, + MEMORY_ADDR_I32 = 5, + /// Uses `type_index`. + TYPE_INDEX_LEB = 6, + /// Uses `symbol_name`. + GLOBAL_INDEX_LEB = 7, + FUNCTION_OFFSET_I32 = 8, + SECTION_OFFSET_I32 = 9, + TAG_INDEX_LEB = 10, + MEMORY_ADDR_REL_SLEB = 11, + TABLE_INDEX_REL_SLEB = 12, + /// Uses `symbol_name`. + GLOBAL_INDEX_I32 = 13, + MEMORY_ADDR_LEB64 = 14, + MEMORY_ADDR_SLEB64 = 15, + MEMORY_ADDR_I64 = 16, + MEMORY_ADDR_REL_SLEB64 = 17, + /// Uses `table_index`. + TABLE_INDEX_SLEB64 = 18, + /// Uses `table_index`. + TABLE_INDEX_I64 = 19, + TABLE_NUMBER_LEB = 20, + MEMORY_ADDR_TLS_SLEB = 21, + FUNCTION_OFFSET_I64 = 22, + MEMORY_ADDR_LOCREL_I32 = 23, + TABLE_INDEX_REL_SLEB64 = 24, + MEMORY_ADDR_TLS_SLEB64 = 25, + /// Uses `symbol_name`. + FUNCTION_INDEX_I32 = 26, - pub fn unwrap(i: OptionalIndex) ?Index { - if (i == .none) return null; - return @enumFromInt(@intFromEnum(i)); - } + // Above here, the tags correspond to symbol table ABI described in + // https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md + // Below, the tags are compiler-internal. + + /// Uses `nav_index`. 4 or 8 bytes depending on wasm32 or wasm64. + nav_index, + /// Uses `uav_index`. 4 or 8 bytes depending on wasm32 or wasm64. + uav_index, }; - - pub const Flag = enum(u32) { - WASM_DATA_SEGMENT_IS_PASSIVE = 0x01, - WASM_DATA_SEGMENT_HAS_MEMINDEX = 0x02, - }; - - pub fn isPassive(segment: Segment) bool { - return segment.flags & @intFromEnum(Flag.WASM_DATA_SEGMENT_IS_PASSIVE) != 0; - } - - /// For a given segment, determines if it needs passive initialization - fn needsPassiveInitialization(segment: Segment, import_mem: bool, name: []const u8) bool { - if (import_mem and !std.mem.eql(u8, name, ".bss")) { - return true; - } - return segment.isPassive(); - } }; -pub const SymbolLoc = struct { - /// The index of the symbol within the specified file - index: Symbol.Index, - /// The index of the object file where the symbol resides. - file: OptionalObjectId, +pub const MemoryImport = extern struct { + module_name: String, + name: String, + limits_min: u32, + limits_max: u32, + limits_has_max: bool, + limits_is_shared: bool, + padding: [2]u8 = .{ 0, 0 }, }; -/// From a given location, returns the corresponding symbol in the wasm binary -pub fn symbolLocSymbol(wasm: *const Wasm, loc: SymbolLoc) *Symbol { - if (wasm.discarded.get(loc)) |new_loc| { - return symbolLocSymbol(wasm, new_loc); - } - return switch (loc.file) { - .none => &wasm.synthetic_symbols.items[@intFromEnum(loc.index)], - .zig_object => wasm.zig_object.?.symbol(loc.index), - _ => &wasm.objects.items[@intFromEnum(loc.file)].symtable[@intFromEnum(loc.index)], - }; -} +pub const Alignment = InternPool.Alignment; -/// From a given location, returns the name of the symbol. -pub fn symbolLocName(wasm: *const Wasm, loc: SymbolLoc) [:0]const u8 { - return wasm.stringSlice(wasm.symbolLocSymbol(loc).name); -} - -/// From a given symbol location, returns the final location. -/// e.g. when a symbol was resolved and replaced by the symbol -/// in a different file, this will return said location. -/// If the symbol wasn't replaced by another, this will return -/// the given location itwasm. -pub fn symbolLocFinalLoc(wasm: *const Wasm, loc: SymbolLoc) SymbolLoc { - if (wasm.discarded.get(loc)) |new_loc| { - return symbolLocFinalLoc(wasm, new_loc); - } - return loc; -} - -// Contains the location of the function symbol, as well as -/// the priority itself of the initialization function. -pub const InitFuncLoc = struct { - /// object file index in the list of objects. - /// Unlike `SymbolLoc` this cannot be `null` as we never define - /// our own ctors. - file: ObjectId, - /// Symbol index within the corresponding object file. - index: Symbol.Index, - /// The priority in which the constructor must be called. +pub const InitFunc = extern struct { priority: u32, + function_index: ObjectFunctionIndex, - /// From a given `InitFuncLoc` returns the corresponding function symbol - fn getSymbol(loc: InitFuncLoc, wasm: *const Wasm) *Symbol { - return wasm.symbolLocSymbol(getSymbolLoc(loc)); - } - - /// Turns the given `InitFuncLoc` into a `SymbolLoc` - fn getSymbolLoc(loc: InitFuncLoc) SymbolLoc { - return .{ - .file = loc.file.toOptional(), - .index = loc.index, - }; - } - - /// Returns true when `lhs` has a higher priority (e.i. value closer to 0) than `rhs`. - fn lessThan(ctx: void, lhs: InitFuncLoc, rhs: InitFuncLoc) bool { + fn lessThan(ctx: void, lhs: InitFunc, rhs: InitFunc) bool { _ = ctx; - return lhs.priority < rhs.priority; + if (lhs.priority == rhs.priority) { + return @intFromEnum(lhs.function_index) < @intFromEnum(rhs.function_index); + } else { + return lhs.priority < rhs.priority; + } + } +}; + +pub const Comdat = struct { + name: String, + /// Must be zero, no flags are currently defined by the tool-convention. + flags: u32, + symbols: Comdat.Symbol.Slice, + + pub const Symbol = struct { + kind: Comdat.Symbol.Type, + /// Index of the data segment/function/global/event/table within a WASM module. + /// The object must not be an import. + index: u32, + + pub const Slice = struct { + /// Index into Wasm object_comdat_symbols + off: u32, + len: u32, + }; + + pub const Type = enum(u8) { + data = 0, + function = 1, + global = 2, + event = 3, + table = 4, + section = 5, + }; + }; +}; + +/// Stored as a u8 so it can reuse the string table mechanism. +pub const Feature = packed struct(u8) { + prefix: Prefix, + /// Type of the feature, must be unique in the sequence of features. + tag: Tag, + + /// Stored identically to `String`. The bytes are reinterpreted as `Feature` + /// elements. Elements must be sorted before string-interning. + pub const Set = enum(u32) { + _, + + pub fn fromString(s: String) Set { + return @enumFromInt(@intFromEnum(s)); + } + }; + + /// Unlike `std.Target.wasm.Feature` this also contains linker-features such as shared-mem. + /// Additionally the name uses convention matching the wasm binary format. + pub const Tag = enum(u6) { + atomics, + @"bulk-memory", + @"exception-handling", + @"extended-const", + @"half-precision", + multimemory, + multivalue, + @"mutable-globals", + @"nontrapping-fptoint", + @"reference-types", + @"relaxed-simd", + @"sign-ext", + simd128, + @"tail-call", + @"shared-mem", + + pub fn fromCpuFeature(feature: std.Target.wasm.Feature) Tag { + return @enumFromInt(@intFromEnum(feature)); + } + + pub const format = @compileError("use @tagName instead"); + }; + + /// Provides information about the usage of the feature. + pub const Prefix = enum(u2) { + /// Reserved so that a 0-byte Feature is invalid and therefore can be a sentinel. + invalid, + /// '0x2b': Object uses this feature, and the link fails if feature is + /// not in the allowed set. + @"+", + /// '0x2d': Object does not use this feature, and the link fails if + /// this feature is in the allowed set. + @"-", + /// '0x3d': Object uses this feature, and the link fails if this + /// feature is not in the allowed set, or if any object does not use + /// this feature. + @"=", + }; + + pub fn format(feature: Feature, comptime fmt: []const u8, opt: std.fmt.FormatOptions, writer: anytype) !void { + _ = opt; + _ = fmt; + try writer.print("{s} {s}", .{ @tagName(feature.prefix), @tagName(feature.tag) }); + } + + pub fn lessThan(_: void, a: Feature, b: Feature) bool { + assert(a != b); + const a_int: u8 = @bitCast(a); + const b_int: u8 = @bitCast(b); + return a_int < b_int; } }; @@ -431,14 +1025,12 @@ pub fn createEmpty( emit: Path, options: link.File.OpenOptions, ) !*Wasm { - const gpa = comp.gpa; const target = comp.root_mod.resolved_target.result; assert(target.ofmt == .wasm); const use_lld = build_options.have_llvm and comp.config.use_lld; const use_llvm = comp.config.use_llvm; const output_mode = comp.config.output_mode; - const shared_memory = comp.config.shared_memory; const wasi_exec_model = comp.config.wasi_exec_model; // If using LLD to link, this code should produce an object file so that it @@ -458,6 +1050,11 @@ pub fn createEmpty( .comp = comp, .emit = emit, .zcu_object_sub_path = zcu_object_sub_path, + // Garbage collection is so crucial to WebAssembly that we design + // the linker around the assumption that it will be on in the vast + // majority of cases, and therefore express "no garbage collection" + // in terms of setting the no_strip and must_link flags on all + // symbols. .gc_sections = options.gc_sections orelse (output_mode != .Obj), .print_gc_sections = options.print_gc_sections, .stack_size = options.stack_size orelse switch (target.os.tag) { @@ -481,10 +1078,8 @@ pub fn createEmpty( .max_memory = options.max_memory, .entry_name = undefined, - .zig_object = null, .dump_argv_list = .empty, .host_name = undefined, - .custom_sections = undefined, .preloaded_strings = undefined, }; if (use_llvm and comp.config.have_zcu) { @@ -494,13 +1089,6 @@ pub fn createEmpty( wasm.host_name = try wasm.internString("env"); - inline for (@typeInfo(CustomSections).@"struct".fields) |field| { - @field(wasm.custom_sections, field.name) = .{ - .index = .none, - .name = try wasm.internString(field.name), - }; - } - inline for (@typeInfo(PreloadedStrings).@"struct".fields) |field| { @field(wasm.preloaded_strings, field.name) = try wasm.internString(field.name); } @@ -535,181 +1123,9 @@ pub fn createEmpty( }); wasm.name = sub_path; - // create stack pointer symbol - { - const loc = try wasm.createSyntheticSymbol(wasm.preloaded_strings.__stack_pointer, .global); - const symbol = wasm.symbolLocSymbol(loc); - // For object files we will import the stack pointer symbol - if (output_mode == .Obj) { - symbol.setUndefined(true); - symbol.index = @intCast(wasm.imported_globals_count); - wasm.imported_globals_count += 1; - try wasm.imports.putNoClobber(gpa, loc, .{ - .module_name = wasm.host_name, - .name = symbol.name, - .kind = .{ .global = .{ .valtype = .i32, .mutable = true } }, - }); - } else { - symbol.index = @intCast(wasm.imported_globals_count + wasm.wasm_globals.items.len); - symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); - const global = try wasm.wasm_globals.addOne(gpa); - global.* = .{ - .global_type = .{ - .valtype = .i32, - .mutable = true, - }, - .init = .{ .i32_const = 0 }, - }; - } - } - - // create indirect function pointer symbol - { - const loc = try wasm.createSyntheticSymbol(wasm.preloaded_strings.__indirect_function_table, .table); - const symbol = wasm.symbolLocSymbol(loc); - const table: std.wasm.Table = .{ - .limits = .{ .flags = 0, .min = 0, .max = undefined }, // will be overwritten during `mapFunctionTable` - .reftype = .funcref, - }; - if (output_mode == .Obj or options.import_table) { - symbol.setUndefined(true); - symbol.index = @intCast(wasm.imported_tables_count); - wasm.imported_tables_count += 1; - try wasm.imports.put(gpa, loc, .{ - .module_name = wasm.host_name, - .name = symbol.name, - .kind = .{ .table = table }, - }); - } else { - symbol.index = @as(u32, @intCast(wasm.imported_tables_count + wasm.tables.items.len)); - try wasm.tables.append(gpa, table); - if (wasm.export_table) { - symbol.setFlag(.WASM_SYM_EXPORTED); - } else { - symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); - } - } - } - - // create __wasm_call_ctors - { - const loc = try wasm.createSyntheticSymbol(wasm.preloaded_strings.__wasm_call_ctors, .function); - const symbol = wasm.symbolLocSymbol(loc); - symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); - // we do not know the function index until after we merged all sections. - // Therefore we set `symbol.index` and create its corresponding references - // at the end during `initializeCallCtorsFunction`. - } - - // shared-memory symbols for TLS support - if (shared_memory) { - { - const loc = try wasm.createSyntheticSymbol(wasm.preloaded_strings.__tls_base, .global); - const symbol = wasm.symbolLocSymbol(loc); - symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); - symbol.index = @intCast(wasm.imported_globals_count + wasm.wasm_globals.items.len); - symbol.mark(); - try wasm.wasm_globals.append(gpa, .{ - .global_type = .{ .valtype = .i32, .mutable = true }, - .init = .{ .i32_const = undefined }, - }); - } - { - const loc = try wasm.createSyntheticSymbol(wasm.preloaded_strings.__tls_size, .global); - const symbol = wasm.symbolLocSymbol(loc); - symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); - symbol.index = @intCast(wasm.imported_globals_count + wasm.wasm_globals.items.len); - symbol.mark(); - try wasm.wasm_globals.append(gpa, .{ - .global_type = .{ .valtype = .i32, .mutable = false }, - .init = .{ .i32_const = undefined }, - }); - } - { - const loc = try wasm.createSyntheticSymbol(wasm.preloaded_strings.__tls_align, .global); - const symbol = wasm.symbolLocSymbol(loc); - symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); - symbol.index = @intCast(wasm.imported_globals_count + wasm.wasm_globals.items.len); - symbol.mark(); - try wasm.wasm_globals.append(gpa, .{ - .global_type = .{ .valtype = .i32, .mutable = false }, - .init = .{ .i32_const = undefined }, - }); - } - { - const loc = try wasm.createSyntheticSymbol(wasm.preloaded_strings.__wasm_init_tls, .function); - const symbol = wasm.symbolLocSymbol(loc); - symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); - } - } - - if (comp.zcu) |zcu| { - if (!use_llvm) { - const zig_object = try arena.create(ZigObject); - wasm.zig_object = zig_object; - zig_object.* = .{ - .path = .{ - .root_dir = std.Build.Cache.Directory.cwd(), - .sub_path = try std.fmt.allocPrint(gpa, "{s}.o", .{fs.path.stem(zcu.main_mod.root_src_path)}), - }, - .stack_pointer_sym = .null, - }; - try zig_object.init(wasm); - } - } - return wasm; } -pub fn getTypeIndex(wasm: *const Wasm, func_type: std.wasm.Type) ?u32 { - var index: u32 = 0; - while (index < wasm.func_types.items.len) : (index += 1) { - if (wasm.func_types.items[index].eql(func_type)) return index; - } - return null; -} - -/// 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( - 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 { - return wasm.zig_object.?.addOrUpdateImport(wasm, name, symbol_index, lib_name, type_index); -} - -/// For a given name, creates a new global synthetic symbol. -/// Leaves index undefined and the default flags (0). -fn createSyntheticSymbol(wasm: *Wasm, name: String, tag: Symbol.Tag) !SymbolLoc { - return wasm.createSyntheticSymbolOffset(name, tag); -} - -fn createSyntheticSymbolOffset(wasm: *Wasm, name_offset: String, tag: Symbol.Tag) !SymbolLoc { - const sym_index: Symbol.Index = @enumFromInt(wasm.synthetic_symbols.items.len); - const loc: SymbolLoc = .{ .index = sym_index, .file = .none }; - const gpa = wasm.base.comp.gpa; - try wasm.synthetic_symbols.append(gpa, .{ - .name = name_offset, - .flags = 0, - .tag = tag, - .index = undefined, - .virtual_address = undefined, - }); - try wasm.resolved_symbols.putNoClobber(gpa, loc, {}); - try wasm.globals.put(gpa, name_offset, loc); - return loc; -} - fn openParseObjectReportingFailure(wasm: *Wasm, path: Path) void { const diags = &wasm.base.comp.link_diags; const obj = link.openObject(path, false, false) catch |err| { @@ -725,8 +1141,11 @@ fn openParseObjectReportingFailure(wasm: *Wasm, path: Path) void { } fn parseObject(wasm: *Wasm, obj: link.Input.Object) !void { - defer obj.file.close(); const gpa = wasm.base.comp.gpa; + const gc_sections = wasm.base.gc_sections; + + defer obj.file.close(); + try wasm.objects.ensureUnusedCapacity(gpa, 1); const stat = try obj.file.stat(); const size = std.math.cast(usize, stat.size) orelse return error.FileTooBig; @@ -737,33 +1156,16 @@ fn parseObject(wasm: *Wasm, obj: link.Input.Object) !void { const n = try obj.file.preadAll(file_contents, 0); if (n != file_contents.len) return error.UnexpectedEndOfFile; - wasm.objects.appendAssumeCapacity(try Object.create(wasm, file_contents, obj.path, null)); -} + var ss: Object.ScratchSpace = .{}; + defer ss.deinit(gpa); -/// Creates a new empty `Atom` and returns its `Atom.Index` -pub fn createAtom(wasm: *Wasm, sym_index: Symbol.Index, object_index: OptionalObjectId) !Atom.Index { - const gpa = wasm.base.comp.gpa; - const index: Atom.Index = @enumFromInt(wasm.managed_atoms.items.len); - const atom = try wasm.managed_atoms.addOne(gpa); - atom.* = .{ - .file = object_index, - .sym_index = sym_index, - }; - try wasm.symbol_atom.putNoClobber(gpa, atom.symbolLoc(), index); - - return index; -} - -pub fn getAtom(wasm: *const Wasm, index: Atom.Index) Atom { - return wasm.managed_atoms.items[@intFromEnum(index)]; -} - -pub fn getAtomPtr(wasm: *Wasm, index: Atom.Index) *Atom { - return &wasm.managed_atoms.items[@intFromEnum(index)]; + const object = try Object.parse(wasm, file_contents, obj.path, null, wasm.host_name, &ss, obj.must_link, gc_sections); + wasm.objects.appendAssumeCapacity(object); } fn parseArchive(wasm: *Wasm, obj: link.Input.Object) !void { const gpa = wasm.base.comp.gpa; + const gc_sections = wasm.base.gc_sections; defer obj.file.close(); @@ -771,28 +1173,12 @@ fn parseArchive(wasm: *Wasm, obj: link.Input.Object) !void { const size = std.math.cast(usize, stat.size) orelse return error.FileTooBig; const file_contents = try gpa.alloc(u8, size); - var keep_file_contents = false; - defer if (!keep_file_contents) gpa.free(file_contents); + defer gpa.free(file_contents); const n = try obj.file.preadAll(file_contents, 0); if (n != file_contents.len) return error.UnexpectedEndOfFile; var archive = try Archive.parse(gpa, file_contents); - - if (!obj.must_link) { - errdefer archive.deinit(gpa); - try wasm.lazy_archives.append(gpa, .{ - .path = .{ - .root_dir = obj.path.root_dir, - .sub_path = try gpa.dupe(u8, obj.path.sub_path), - }, - .file_contents = file_contents, - .archive = archive, - }); - keep_file_contents = true; - return; - } - defer archive.deinit(gpa); // In this case we must force link all embedded object files within the archive @@ -806,700 +1192,14 @@ fn parseArchive(wasm: *Wasm, obj: link.Input.Object) !void { } } + var ss: Object.ScratchSpace = .{}; + defer ss.deinit(gpa); + + try wasm.objects.ensureUnusedCapacity(gpa, offsets.count()); for (offsets.keys()) |file_offset| { - const object = try archive.parseObject(wasm, file_contents[file_offset..], obj.path); - try wasm.objects.append(gpa, object); - } -} - -fn requiresTLSReloc(wasm: *const Wasm) bool { - for (wasm.got_symbols.items) |loc| { - if (wasm.symbolLocSymbol(loc).isTLS()) { - return true; - } - } - return false; -} - -fn objectPath(wasm: *const Wasm, object_id: ObjectId) Path { - const obj = wasm.objectById(object_id) orelse return wasm.zig_object.?.path; - return obj.path; -} - -fn objectSymbols(wasm: *const Wasm, object_id: ObjectId) []const Symbol { - const obj = wasm.objectById(object_id) orelse return wasm.zig_object.?.symbols.items; - return obj.symtable; -} - -fn objectSymbol(wasm: *const Wasm, object_id: ObjectId, index: Symbol.Index) *Symbol { - const obj = wasm.objectById(object_id) orelse return &wasm.zig_object.?.symbols.items[@intFromEnum(index)]; - return &obj.symtable[@intFromEnum(index)]; -} - -fn objectFunction(wasm: *const Wasm, object_id: ObjectId, sym_index: Symbol.Index) std.wasm.Func { - const obj = wasm.objectById(object_id) orelse { - const zo = wasm.zig_object.?; - const sym = zo.symbols.items[@intFromEnum(sym_index)]; - return zo.functions.items[sym.index]; - }; - const sym = obj.symtable[@intFromEnum(sym_index)]; - return obj.functions[sym.index - obj.imported_functions_count]; -} - -fn objectImportedFunctions(wasm: *const Wasm, object_id: ObjectId) u32 { - const obj = wasm.objectById(object_id) orelse return wasm.zig_object.?.imported_functions_count; - return obj.imported_functions_count; -} - -fn objectGlobals(wasm: *const Wasm, object_id: ObjectId) []const std.wasm.Global { - const obj = wasm.objectById(object_id) orelse return wasm.zig_object.?.globals.items; - return obj.globals; -} - -fn objectFuncTypes(wasm: *const Wasm, object_id: ObjectId) []const std.wasm.Type { - const obj = wasm.objectById(object_id) orelse return wasm.zig_object.?.func_types.items; - return obj.func_types; -} - -fn objectSegmentInfo(wasm: *const Wasm, object_id: ObjectId) []const NamedSegment { - const obj = wasm.objectById(object_id) orelse return wasm.zig_object.?.segment_info.items; - return obj.segment_info; -} - -/// For a given symbol index, find its corresponding import. -/// Asserts import exists. -fn objectImport(wasm: *const Wasm, object_id: ObjectId, symbol_index: Symbol.Index) Import { - const obj = wasm.objectById(object_id) orelse return wasm.zig_object.?.imports.get(symbol_index).?; - return obj.findImport(obj.symtable[@intFromEnum(symbol_index)]); -} - -/// Returns the object element pointer, or null if it is the ZigObject. -fn objectById(wasm: *const Wasm, object_id: ObjectId) ?*Object { - if (object_id == .zig_object) return null; - return &wasm.objects.items[@intFromEnum(object_id)]; -} - -fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void { - const gpa = wasm.base.comp.gpa; - const diags = &wasm.base.comp.link_diags; - const obj_path = objectPath(wasm, object_id); - log.debug("Resolving symbols in object: '{'}'", .{obj_path}); - const symbols = objectSymbols(wasm, object_id); - - for (symbols, 0..) |symbol, i| { - const sym_index: Symbol.Index = @enumFromInt(i); - const location: SymbolLoc = .{ - .file = object_id.toOptional(), - .index = sym_index, - }; - if (symbol.name == wasm.preloaded_strings.__indirect_function_table) continue; - - if (symbol.isLocal()) { - if (symbol.isUndefined()) { - diags.addParseError(obj_path, "local symbol '{s}' references import", .{ - wasm.stringSlice(symbol.name), - }); - } - try wasm.resolved_symbols.putNoClobber(gpa, location, {}); - continue; - } - - const maybe_existing = try wasm.globals.getOrPut(gpa, symbol.name); - if (!maybe_existing.found_existing) { - maybe_existing.value_ptr.* = location; - try wasm.resolved_symbols.putNoClobber(gpa, location, {}); - - if (symbol.isUndefined()) { - try wasm.undefs.putNoClobber(gpa, symbol.name, location); - } - continue; - } - - const existing_loc = maybe_existing.value_ptr.*; - const existing_sym: *Symbol = wasm.symbolLocSymbol(existing_loc); - const existing_file_path: Path = if (existing_loc.file.unwrap()) |id| objectPath(wasm, id) else .{ - .root_dir = std.Build.Cache.Directory.cwd(), - .sub_path = wasm.name, - }; - - if (!existing_sym.isUndefined()) outer: { - if (!symbol.isUndefined()) inner: { - if (symbol.isWeak()) { - break :inner; // ignore the new symbol (discard it) - } - if (existing_sym.isWeak()) { - break :outer; // existing is weak, while new one isn't. Replace it. - } - // both are defined and weak, we have a symbol collision. - var err = try diags.addErrorWithNotes(2); - try err.addMsg("symbol '{s}' defined multiple times", .{wasm.stringSlice(symbol.name)}); - try err.addNote("first definition in '{'}'", .{existing_file_path}); - try err.addNote("next definition in '{'}'", .{obj_path}); - } - - try wasm.discarded.put(gpa, location, existing_loc); - continue; // Do not overwrite defined symbols with undefined symbols - } - - if (symbol.tag != existing_sym.tag) { - var err = try diags.addErrorWithNotes(2); - try err.addMsg("symbol '{s}' mismatching types '{s}' and '{s}'", .{ - wasm.stringSlice(symbol.name), @tagName(symbol.tag), @tagName(existing_sym.tag), - }); - try err.addNote("first definition in '{'}'", .{existing_file_path}); - try err.addNote("next definition in '{'}'", .{obj_path}); - } - - if (existing_sym.isUndefined() and symbol.isUndefined()) { - // only verify module/import name for function symbols - if (symbol.tag == .function) { - const existing_name = if (existing_loc.file.unwrap()) |existing_obj_id| - objectImport(wasm, existing_obj_id, existing_loc.index).module_name - else - wasm.imports.get(existing_loc).?.module_name; - - const module_name = objectImport(wasm, object_id, sym_index).module_name; - if (existing_name != module_name) { - var err = try diags.addErrorWithNotes(2); - try err.addMsg("symbol '{s}' module name mismatch. Expected '{s}', but found '{s}'", .{ - wasm.stringSlice(symbol.name), - wasm.stringSlice(existing_name), - wasm.stringSlice(module_name), - }); - try err.addNote("first definition in '{'}'", .{existing_file_path}); - try err.addNote("next definition in '{'}'", .{obj_path}); - } - } - - // both undefined so skip overwriting existing symbol and discard the new symbol - try wasm.discarded.put(gpa, location, existing_loc); - continue; - } - - if (existing_sym.tag == .global) { - const existing_ty = wasm.getGlobalType(existing_loc); - const new_ty = wasm.getGlobalType(location); - if (existing_ty.mutable != new_ty.mutable or existing_ty.valtype != new_ty.valtype) { - var err = try diags.addErrorWithNotes(2); - try err.addMsg("symbol '{s}' mismatching global types", .{wasm.stringSlice(symbol.name)}); - try err.addNote("first definition in '{'}'", .{existing_file_path}); - try err.addNote("next definition in '{'}'", .{obj_path}); - } - } - - if (existing_sym.tag == .function) { - const existing_ty = wasm.getFunctionSignature(existing_loc); - const new_ty = wasm.getFunctionSignature(location); - if (!existing_ty.eql(new_ty)) { - var err = try diags.addErrorWithNotes(3); - try err.addMsg("symbol '{s}' mismatching function signatures.", .{wasm.stringSlice(symbol.name)}); - try err.addNote("expected signature {}, but found signature {}", .{ existing_ty, new_ty }); - try err.addNote("first definition in '{'}'", .{existing_file_path}); - try err.addNote("next definition in '{'}'", .{obj_path}); - } - } - - // when both symbols are weak, we skip overwriting unless the existing - // symbol is weak and the new one isn't, in which case we *do* overwrite it. - if (existing_sym.isWeak() and symbol.isWeak()) blk: { - if (existing_sym.isUndefined() and !symbol.isUndefined()) break :blk; - try wasm.discarded.put(gpa, location, existing_loc); - continue; - } - - // simply overwrite with the new symbol - log.debug("Overwriting symbol '{s}'", .{wasm.stringSlice(symbol.name)}); - log.debug(" old definition in '{'}'", .{existing_file_path}); - log.debug(" new definition in '{'}'", .{obj_path}); - try wasm.discarded.putNoClobber(gpa, existing_loc, location); - maybe_existing.value_ptr.* = location; - try wasm.globals.put(gpa, symbol.name, location); - try wasm.resolved_symbols.put(gpa, location, {}); - assert(wasm.resolved_symbols.swapRemove(existing_loc)); - if (existing_sym.isUndefined()) { - _ = wasm.undefs.swapRemove(symbol.name); - } - } -} - -fn resolveSymbolsInArchives(wasm: *Wasm) !void { - if (wasm.lazy_archives.items.len == 0) return; - const gpa = wasm.base.comp.gpa; - const diags = &wasm.base.comp.link_diags; - - log.debug("Resolving symbols in lazy_archives", .{}); - var index: u32 = 0; - undef_loop: while (index < wasm.undefs.count()) { - const sym_name_index = wasm.undefs.keys()[index]; - - for (wasm.lazy_archives.items) |lazy_archive| { - const sym_name = wasm.stringSlice(sym_name_index); - log.debug("Detected symbol '{s}' in archive '{'}', parsing objects..", .{ - sym_name, lazy_archive.path, - }); - const offset = lazy_archive.archive.toc.get(sym_name) orelse continue; // symbol does not exist in this archive - - // Symbol is found in unparsed object file within current archive. - // Parse object and and resolve symbols again before we check remaining - // undefined symbols. - const file_contents = lazy_archive.file_contents[offset.items[0]..]; - const object = lazy_archive.archive.parseObject(wasm, file_contents, lazy_archive.path) catch |err| { - // TODO this fails to include information to identify which object failed - return diags.failParse(lazy_archive.path, "failed to parse object in archive: {s}", .{@errorName(err)}); - }; - try wasm.objects.append(gpa, object); - try wasm.resolveSymbolsInObject(@enumFromInt(wasm.objects.items.len - 1)); - - // continue loop for any remaining undefined symbols that still exist - // after resolving last object file - continue :undef_loop; - } - index += 1; - } -} - -/// Writes an unsigned 32-bit integer as a LEB128-encoded 'i32.const' value. -fn writeI32Const(writer: anytype, val: u32) !void { - try writer.writeByte(std.wasm.opcode(.i32_const)); - try leb.writeIleb128(writer, @as(i32, @bitCast(val))); -} - -fn setupInitMemoryFunction(wasm: *Wasm) !void { - const comp = wasm.base.comp; - const gpa = comp.gpa; - const shared_memory = comp.config.shared_memory; - const import_memory = comp.config.import_memory; - - // Passive segments are used to avoid memory being reinitialized on each - // thread's instantiation. These passive segments are initialized and - // dropped in __wasm_init_memory, which is registered as the start function - // We also initialize bss segments (using memory.fill) as part of this - // function. - if (!wasm.hasPassiveInitializationSegments()) { - return; - } - const sym_loc = try wasm.createSyntheticSymbol(wasm.preloaded_strings.__wasm_init_memory, .function); - wasm.symbolLocSymbol(sym_loc).mark(); - - const flag_address: u32 = if (shared_memory) address: { - // when we have passive initialization segments and shared memory - // `setupMemory` will create this symbol and set its virtual address. - const loc = wasm.globals.get(wasm.preloaded_strings.__wasm_init_memory_flag).?; - break :address wasm.symbolLocSymbol(loc).virtual_address; - } else 0; - - var function_body = std.ArrayList(u8).init(gpa); - defer function_body.deinit(); - const writer = function_body.writer(); - - // we have 0 locals - try leb.writeUleb128(writer, @as(u32, 0)); - - if (shared_memory) { - // destination blocks - // based on values we jump to corresponding label - try writer.writeByte(std.wasm.opcode(.block)); // $drop - try writer.writeByte(std.wasm.block_empty); // block type - - try writer.writeByte(std.wasm.opcode(.block)); // $wait - try writer.writeByte(std.wasm.block_empty); // block type - - try writer.writeByte(std.wasm.opcode(.block)); // $init - try writer.writeByte(std.wasm.block_empty); // block type - - // atomically check - try writeI32Const(writer, flag_address); - try writeI32Const(writer, 0); - try writeI32Const(writer, 1); - try writer.writeByte(std.wasm.opcode(.atomics_prefix)); - try leb.writeUleb128(writer, std.wasm.atomicsOpcode(.i32_atomic_rmw_cmpxchg)); - try leb.writeUleb128(writer, @as(u32, 2)); // alignment - try leb.writeUleb128(writer, @as(u32, 0)); // offset - - // based on the value from the atomic check, jump to the label. - try writer.writeByte(std.wasm.opcode(.br_table)); - try leb.writeUleb128(writer, @as(u32, 2)); // length of the table (we have 3 blocks but because of the mandatory default the length is 2). - try leb.writeUleb128(writer, @as(u32, 0)); // $init - try leb.writeUleb128(writer, @as(u32, 1)); // $wait - try leb.writeUleb128(writer, @as(u32, 2)); // $drop - try writer.writeByte(std.wasm.opcode(.end)); - } - - for (wasm.data_segments.keys(), wasm.data_segments.values(), 0..) |key, value, segment_index_usize| { - const segment_index: u32 = @intCast(segment_index_usize); - const segment = wasm.segmentPtr(value); - if (segment.needsPassiveInitialization(import_memory, key)) { - // For passive BSS segments we can simple issue a memory.fill(0). - // For non-BSS segments we do a memory.init. Both these - // instructions take as their first argument the destination - // address. - try writeI32Const(writer, segment.offset); - - if (shared_memory and std.mem.eql(u8, key, ".tdata")) { - // When we initialize the TLS segment we also set the `__tls_base` - // global. This allows the runtime to use this static copy of the - // TLS data for the first/main thread. - try writeI32Const(writer, segment.offset); - try writer.writeByte(std.wasm.opcode(.global_set)); - const loc = wasm.globals.get(wasm.preloaded_strings.__tls_base).?; - try leb.writeUleb128(writer, wasm.symbolLocSymbol(loc).index); - } - - try writeI32Const(writer, 0); - try writeI32Const(writer, segment.size); - try writer.writeByte(std.wasm.opcode(.misc_prefix)); - if (std.mem.eql(u8, key, ".bss")) { - // fill bss segment with zeroes - try leb.writeUleb128(writer, std.wasm.miscOpcode(.memory_fill)); - } else { - // initialize the segment - try leb.writeUleb128(writer, std.wasm.miscOpcode(.memory_init)); - try leb.writeUleb128(writer, segment_index); - } - try writer.writeByte(0); // memory index immediate - } - } - - if (shared_memory) { - // we set the init memory flag to value '2' - try writeI32Const(writer, flag_address); - try writeI32Const(writer, 2); - try writer.writeByte(std.wasm.opcode(.atomics_prefix)); - try leb.writeUleb128(writer, std.wasm.atomicsOpcode(.i32_atomic_store)); - try leb.writeUleb128(writer, @as(u32, 2)); // alignment - try leb.writeUleb128(writer, @as(u32, 0)); // offset - - // notify any waiters for segment initialization completion - try writeI32Const(writer, flag_address); - try writer.writeByte(std.wasm.opcode(.i32_const)); - try leb.writeIleb128(writer, @as(i32, -1)); // number of waiters - try writer.writeByte(std.wasm.opcode(.atomics_prefix)); - try leb.writeUleb128(writer, std.wasm.atomicsOpcode(.memory_atomic_notify)); - try leb.writeUleb128(writer, @as(u32, 2)); // alignment - try leb.writeUleb128(writer, @as(u32, 0)); // offset - try writer.writeByte(std.wasm.opcode(.drop)); - - // branch and drop segments - try writer.writeByte(std.wasm.opcode(.br)); - try leb.writeUleb128(writer, @as(u32, 1)); - - // wait for thread to initialize memory segments - try writer.writeByte(std.wasm.opcode(.end)); // end $wait - try writeI32Const(writer, flag_address); - try writeI32Const(writer, 1); // expected flag value - try writer.writeByte(std.wasm.opcode(.i64_const)); - try leb.writeIleb128(writer, @as(i64, -1)); // timeout - try writer.writeByte(std.wasm.opcode(.atomics_prefix)); - try leb.writeUleb128(writer, std.wasm.atomicsOpcode(.memory_atomic_wait32)); - try leb.writeUleb128(writer, @as(u32, 2)); // alignment - try leb.writeUleb128(writer, @as(u32, 0)); // offset - try writer.writeByte(std.wasm.opcode(.drop)); - - try writer.writeByte(std.wasm.opcode(.end)); // end $drop - } - - for (wasm.data_segments.keys(), wasm.data_segments.values(), 0..) |name, value, segment_index_usize| { - const segment_index: u32 = @intCast(segment_index_usize); - const segment = wasm.segmentPtr(value); - if (segment.needsPassiveInitialization(import_memory, name) and - !std.mem.eql(u8, name, ".bss")) - { - // The TLS region should not be dropped since its is needed - // during the initialization of each thread (__wasm_init_tls). - if (shared_memory and std.mem.eql(u8, name, ".tdata")) { - continue; - } - - try writer.writeByte(std.wasm.opcode(.misc_prefix)); - try leb.writeUleb128(writer, std.wasm.miscOpcode(.data_drop)); - try leb.writeUleb128(writer, segment_index); - } - } - - // End of the function body - try writer.writeByte(std.wasm.opcode(.end)); - - try wasm.createSyntheticFunction( - wasm.preloaded_strings.__wasm_init_memory, - std.wasm.Type{ .params = &.{}, .returns = &.{} }, - &function_body, - ); -} - -/// Constructs a synthetic function that performs runtime relocations for -/// TLS symbols. This function is called by `__wasm_init_tls`. -fn setupTLSRelocationsFunction(wasm: *Wasm) !void { - const comp = wasm.base.comp; - const gpa = comp.gpa; - const shared_memory = comp.config.shared_memory; - - // When we have TLS GOT entries and shared memory is enabled, - // we must perform runtime relocations or else we don't create the function. - if (!shared_memory or !wasm.requiresTLSReloc()) { - return; - } - - const loc = try wasm.createSyntheticSymbol(wasm.preloaded_strings.__wasm_apply_global_tls_relocs, .function); - wasm.symbolLocSymbol(loc).mark(); - var function_body = std.ArrayList(u8).init(gpa); - defer function_body.deinit(); - const writer = function_body.writer(); - - // locals (we have none) - try writer.writeByte(0); - for (wasm.got_symbols.items, 0..) |got_loc, got_index| { - const sym: *Symbol = wasm.symbolLocSymbol(got_loc); - if (!sym.isTLS()) continue; // only relocate TLS symbols - if (sym.tag == .data and sym.isDefined()) { - // get __tls_base - try writer.writeByte(std.wasm.opcode(.global_get)); - try leb.writeUleb128(writer, wasm.symbolLocSymbol(wasm.globals.get(wasm.preloaded_strings.__tls_base).?).index); - - // add the virtual address of the symbol - try writer.writeByte(std.wasm.opcode(.i32_const)); - try leb.writeUleb128(writer, sym.virtual_address); - } else if (sym.tag == .function) { - @panic("TODO: relocate GOT entry of function"); - } else continue; - - try writer.writeByte(std.wasm.opcode(.i32_add)); - try writer.writeByte(std.wasm.opcode(.global_set)); - try leb.writeUleb128(writer, wasm.imported_globals_count + @as(u32, @intCast(wasm.wasm_globals.items.len + got_index))); - } - try writer.writeByte(std.wasm.opcode(.end)); - - try wasm.createSyntheticFunction( - wasm.preloaded_strings.__wasm_apply_global_tls_relocs, - std.wasm.Type{ .params = &.{}, .returns = &.{} }, - &function_body, - ); -} - -fn validateFeatures( - wasm: *const Wasm, - to_emit: *[@typeInfo(Feature.Tag).@"enum".fields.len]bool, - emit_features_count: *u32, -) !void { - const comp = wasm.base.comp; - const diags = &wasm.base.comp.link_diags; - const target = comp.root_mod.resolved_target.result; - const shared_memory = comp.config.shared_memory; - const cpu_features = target.cpu.features; - const infer = cpu_features.isEmpty(); // when the user did not define any features, we infer them from linked objects. - const known_features_count = @typeInfo(Feature.Tag).@"enum".fields.len; - - var allowed = [_]bool{false} ** known_features_count; - var used = [_]u17{0} ** known_features_count; - var disallowed = [_]u17{0} ** known_features_count; - var required = [_]u17{0} ** known_features_count; - - // when false, we fail linking. We only verify this after a loop to catch all invalid features. - var valid_feature_set = true; - // will be set to true when there's any TLS segment found in any of the object files - var has_tls = false; - - // When the user has given an explicit list of features to enable, - // we extract them and insert each into the 'allowed' list. - if (!infer) { - inline for (@typeInfo(std.Target.wasm.Feature).@"enum".fields) |feature_field| { - if (cpu_features.isEnabled(feature_field.value)) { - allowed[feature_field.value] = true; - emit_features_count.* += 1; - } - } - } - - // extract all the used, disallowed and required features from each - // linked object file so we can test them. - for (wasm.objects.items, 0..) |*object, file_index| { - for (object.features) |feature| { - const value = (@as(u16, @intCast(file_index)) << 1) | 1; - switch (feature.prefix) { - .used => { - used[@intFromEnum(feature.tag)] = value; - }, - .disallowed => { - disallowed[@intFromEnum(feature.tag)] = value; - }, - .required => { - required[@intFromEnum(feature.tag)] = value; - used[@intFromEnum(feature.tag)] = value; - }, - } - } - - for (object.segment_info) |segment| { - if (segment.isTLS()) { - has_tls = true; - } - } - } - - // when we infer the features, we allow each feature found in the 'used' set - // and insert it into the 'allowed' set. When features are not inferred, - // we validate that a used feature is allowed. - for (used, 0..) |used_set, used_index| { - const is_enabled = @as(u1, @truncate(used_set)) != 0; - if (infer) { - allowed[used_index] = is_enabled; - emit_features_count.* += @intFromBool(is_enabled); - } else if (is_enabled and !allowed[used_index]) { - diags.addParseError( - wasm.objects.items[used_set >> 1].path, - "feature '{}' not allowed, but used by linked object", - .{@as(Feature.Tag, @enumFromInt(used_index))}, - ); - valid_feature_set = false; - } - } - - if (!valid_feature_set) { - return error.FlushFailure; - } - - if (shared_memory) { - const disallowed_feature = disallowed[@intFromEnum(Feature.Tag.shared_mem)]; - if (@as(u1, @truncate(disallowed_feature)) != 0) { - diags.addParseError( - wasm.objects.items[disallowed_feature >> 1].path, - "shared-memory is disallowed because it wasn't compiled with 'atomics' and 'bulk-memory' features enabled", - .{}, - ); - valid_feature_set = false; - } - - for ([_]Feature.Tag{ .atomics, .bulk_memory }) |feature| { - if (!allowed[@intFromEnum(feature)]) { - var err = try diags.addErrorWithNotes(0); - try err.addMsg("feature '{}' is not used but is required for shared-memory", .{feature}); - } - } - } - - if (has_tls) { - for ([_]Feature.Tag{ .atomics, .bulk_memory }) |feature| { - if (!allowed[@intFromEnum(feature)]) { - var err = try diags.addErrorWithNotes(0); - try err.addMsg("feature '{}' is not used but is required for thread-local storage", .{feature}); - } - } - } - // For each linked object, validate the required and disallowed features - for (wasm.objects.items) |*object| { - var object_used_features = [_]bool{false} ** known_features_count; - for (object.features) |feature| { - if (feature.prefix == .disallowed) continue; // already defined in 'disallowed' set. - // from here a feature is always used - const disallowed_feature = disallowed[@intFromEnum(feature.tag)]; - if (@as(u1, @truncate(disallowed_feature)) != 0) { - var err = try diags.addErrorWithNotes(2); - try err.addMsg("feature '{}' is disallowed, but used by linked object", .{feature.tag}); - try err.addNote("disallowed by '{'}'", .{wasm.objects.items[disallowed_feature >> 1].path}); - try err.addNote("used in '{'}'", .{object.path}); - valid_feature_set = false; - } - - object_used_features[@intFromEnum(feature.tag)] = true; - } - - // validate the linked object file has each required feature - for (required, 0..) |required_feature, feature_index| { - const is_required = @as(u1, @truncate(required_feature)) != 0; - if (is_required and !object_used_features[feature_index]) { - var err = try diags.addErrorWithNotes(2); - try err.addMsg("feature '{}' is required but not used in linked object", .{@as(Feature.Tag, @enumFromInt(feature_index))}); - try err.addNote("required by '{'}'", .{wasm.objects.items[required_feature >> 1].path}); - try err.addNote("missing in '{'}'", .{object.path}); - valid_feature_set = false; - } - } - } - - if (!valid_feature_set) { - return error.FlushFailure; - } - - to_emit.* = allowed; -} - -/// Creates synthetic linker-symbols, but only if they are being referenced from -/// any object file. For instance, the `__heap_base` symbol will only be created, -/// if one or multiple undefined references exist. When none exist, the symbol will -/// not be created, ensuring we don't unnecessarily emit unreferenced symbols. -fn resolveLazySymbols(wasm: *Wasm) !void { - const comp = wasm.base.comp; - const gpa = comp.gpa; - const shared_memory = comp.config.shared_memory; - - if (wasm.getExistingString("__heap_base")) |name_offset| { - if (wasm.undefs.fetchSwapRemove(name_offset)) |kv| { - const loc = try wasm.createSyntheticSymbolOffset(name_offset, .data); - try wasm.discarded.putNoClobber(gpa, kv.value, loc); - _ = wasm.resolved_symbols.swapRemove(loc); // we don't want to emit this symbol, only use it for relocations. - } - } - - if (wasm.getExistingString("__heap_end")) |name_offset| { - if (wasm.undefs.fetchSwapRemove(name_offset)) |kv| { - const loc = try wasm.createSyntheticSymbolOffset(name_offset, .data); - try wasm.discarded.putNoClobber(gpa, kv.value, loc); - _ = wasm.resolved_symbols.swapRemove(loc); - } - } - - if (!shared_memory) { - if (wasm.getExistingString("__tls_base")) |name_offset| { - if (wasm.undefs.fetchSwapRemove(name_offset)) |kv| { - const loc = try wasm.createSyntheticSymbolOffset(name_offset, .global); - try wasm.discarded.putNoClobber(gpa, kv.value, loc); - _ = wasm.resolved_symbols.swapRemove(kv.value); - const symbol = wasm.symbolLocSymbol(loc); - symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); - symbol.index = @intCast(wasm.imported_globals_count + wasm.wasm_globals.items.len); - try wasm.wasm_globals.append(gpa, .{ - .global_type = .{ .valtype = .i32, .mutable = true }, - .init = .{ .i32_const = undefined }, - }); - } - } - } -} - -pub fn findGlobalSymbol(wasm: *const Wasm, name: []const u8) ?SymbolLoc { - const name_index = wasm.getExistingString(name) orelse return null; - return wasm.globals.get(name_index); -} - -fn checkUndefinedSymbols(wasm: *const Wasm) !void { - const comp = wasm.base.comp; - const diags = &wasm.base.comp.link_diags; - if (comp.config.output_mode == .Obj) return; - if (wasm.import_symbols) return; - - var found_undefined_symbols = false; - for (wasm.undefs.values()) |undef| { - const symbol = wasm.symbolLocSymbol(undef); - if (symbol.tag == .data) { - found_undefined_symbols = true; - const symbol_name = wasm.symbolLocName(undef); - switch (undef.file) { - .zig_object => { - // TODO: instead of saying the zig compilation unit, attach an actual source location - // to this diagnostic - diags.addError("unresolved symbol in Zig compilation unit: {s}", .{symbol_name}); - }, - .none => { - diags.addError("internal linker bug: unresolved synthetic symbol: {s}", .{symbol_name}); - }, - _ => { - const path = wasm.objects.items[@intFromEnum(undef.file)].path; - diags.addParseError(path, "unresolved symbol: {s}", .{symbol_name}); - }, - } - } - } - if (found_undefined_symbols) { - return error.LinkFailure; + const contents = file_contents[file_offset..]; + const object = try archive.parseObject(wasm, contents, obj.path, wasm.host_name, &ss, obj.must_link, gc_sections); + wasm.objects.appendAssumeCapacity(object); } } @@ -1507,48 +1207,49 @@ pub fn deinit(wasm: *Wasm) void { const gpa = wasm.base.comp.gpa; if (wasm.llvm_object) |llvm_object| llvm_object.deinit(); - for (wasm.func_types.items) |*func_type| { - func_type.deinit(gpa); - } - for (wasm.segment_info.values()) |segment_info| { - gpa.free(segment_info.name); - } - if (wasm.zig_object) |zig_obj| { - zig_obj.deinit(wasm); - } - for (wasm.objects.items) |*object| { - object.deinit(gpa); - } + wasm.navs.deinit(gpa); + wasm.nav_exports.deinit(gpa); + wasm.uav_exports.deinit(gpa); + wasm.imports.deinit(gpa); - for (wasm.lazy_archives.items) |*lazy_archive| lazy_archive.deinit(gpa); - wasm.lazy_archives.deinit(gpa); + wasm.flush_buffer.deinit(gpa); - if (wasm.globals.get(wasm.preloaded_strings.__wasm_init_tls)) |loc| { - const atom = wasm.symbol_atom.get(loc).?; - wasm.getAtomPtr(atom).deinit(gpa); - } + if (wasm.dwarf) |*dwarf| dwarf.deinit(); + + wasm.object_function_imports.deinit(gpa); + wasm.object_functions.deinit(gpa); + wasm.object_global_imports.deinit(gpa); + wasm.object_globals.deinit(gpa); + wasm.object_table_imports.deinit(gpa); + wasm.object_tables.deinit(gpa); + wasm.object_memory_imports.deinit(gpa); + wasm.object_memories.deinit(gpa); + + wasm.object_data_segments.deinit(gpa); + wasm.object_relocatable_codes.deinit(gpa); + wasm.object_custom_segments.deinit(gpa); + wasm.object_symbols.deinit(gpa); + wasm.object_named_segments.deinit(gpa); + wasm.object_init_funcs.deinit(gpa); + wasm.object_comdats.deinit(gpa); + wasm.object_relocations.deinit(gpa); + wasm.object_relocations_table.deinit(gpa); + wasm.object_comdat_symbols.deinit(gpa); + wasm.objects.deinit(gpa); + + wasm.atoms.deinit(gpa); wasm.synthetic_symbols.deinit(gpa); wasm.globals.deinit(gpa); - wasm.resolved_symbols.deinit(gpa); wasm.undefs.deinit(gpa); wasm.discarded.deinit(gpa); - wasm.symbol_atom.deinit(gpa); - wasm.atoms.deinit(gpa); - wasm.managed_atoms.deinit(gpa); wasm.segments.deinit(gpa); - wasm.data_segments.deinit(gpa); wasm.segment_info.deinit(gpa); - wasm.objects.deinit(gpa); - // free output sections - wasm.imports.deinit(gpa); + wasm.global_imports.deinit(gpa); wasm.func_types.deinit(gpa); wasm.functions.deinit(gpa); - wasm.wasm_globals.deinit(gpa); - wasm.function_table.deinit(gpa); - wasm.tables.deinit(gpa); - wasm.init_funcs.deinit(gpa); + wasm.output_globals.deinit(gpa); wasm.exports.deinit(gpa); wasm.string_bytes.deinit(gpa); @@ -1561,138 +1262,158 @@ pub fn updateFunc(wasm: *Wasm, pt: Zcu.PerThread, func_index: InternPool.Index, @panic("Attempted to compile for object format that was disabled by build configuration"); } if (wasm.llvm_object) |llvm_object| return llvm_object.updateFunc(pt, func_index, air, liveness); - try wasm.zig_object.?.updateFunc(wasm, pt, func_index, air, liveness); + + const zcu = pt.zcu; + const gpa = zcu.gpa; + const func = pt.zcu.funcInfo(func_index); + const nav_index = func.owner_nav; + + const code_start: u32 = @intCast(wasm.string_bytes.items.len); + const relocs_start: u32 = @intCast(wasm.relocations.items.len); + wasm.string_bytes_lock.lock(); + + const wasm_codegen = @import("../../arch/wasm/CodeGen.zig"); + dev.check(.wasm_backend); + const result = try wasm_codegen.generate( + &wasm.base, + pt, + zcu.navSrcLoc(nav_index), + func_index, + air, + liveness, + &wasm.string_bytes, + .none, + ); + + const code_len: u32 = @intCast(wasm.string_bytes.items.len - code_start); + const relocs_len: u32 = @intCast(wasm.relocations.items.len - relocs_start); + wasm.string_bytes_lock.unlock(); + + const code: Nav.Code = switch (result) { + .ok => .{ + .off = code_start, + .len = code_len, + }, + .fail => |em| { + try pt.zcu.failed_codegen.put(gpa, nav_index, em); + return; + }, + }; + + const gop = try wasm.navs.getOrPut(gpa, nav_index); + if (gop.found_existing) { + @panic("TODO reuse these resources"); + } else { + _ = wasm.imports.swapRemove(nav_index); + } + gop.value_ptr.* = .{ + .code = code, + .relocs = .{ + .off = relocs_start, + .len = relocs_len, + }, + }; } // Generate code for the "Nav", storing it in memory to be later written to // the file on flush(). -pub fn updateNav(wasm: *Wasm, pt: Zcu.PerThread, nav: InternPool.Nav.Index) !void { +pub fn updateNav(wasm: *Wasm, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !void { if (build_options.skip_non_native and builtin.object_format != .wasm) { @panic("Attempted to compile for object format that was disabled by build configuration"); } - if (wasm.llvm_object) |llvm_object| return llvm_object.updateNav(pt, nav); - try wasm.zig_object.?.updateNav(wasm, pt, nav); + if (wasm.llvm_object) |llvm_object| return llvm_object.updateNav(pt, nav_index); + const zcu = pt.zcu; + const ip = &zcu.intern_pool; + const nav = ip.getNav(nav_index); + const gpa = wasm.base.comp.gpa; + + const nav_val = zcu.navValue(nav_index); + const is_extern, const nav_init = switch (ip.indexToKey(nav_val.toIntern())) { + .variable => |variable| .{ false, Value.fromInterned(variable.init) }, + .func => unreachable, + .@"extern" => b: { + assert(!ip.isFunctionType(nav.typeOf(ip))); + break :b .{ true, nav_val }; + }, + else => .{ false, nav_val }, + }; + + if (!nav_init.typeOf(zcu).hasRuntimeBits(zcu)) { + _ = wasm.imports.swapRemove(nav_index); + _ = wasm.navs.swapRemove(nav_index); // TODO reclaim resources + return; + } + + if (is_extern) { + try wasm.imports.put(nav_index, {}); + _ = wasm.navs.swapRemove(nav_index); // TODO reclaim resources + return; + } + + const code_start: u32 = @intCast(wasm.string_bytes.items.len); + const relocs_start: u32 = @intCast(wasm.relocations.items.len); + wasm.string_bytes_lock.lock(); + + const res = try codegen.generateSymbol( + &wasm.base, + pt, + zcu.navSrcLoc(nav_index), + nav_init, + &wasm.string_bytes, + .none, + ); + + const code_len: u32 = @intCast(wasm.string_bytes.items.len - code_start); + const relocs_len: u32 = @intCast(wasm.relocations.items.len - relocs_start); + wasm.string_bytes_lock.unlock(); + + const code: Nav.Code = switch (res) { + .ok => .{ + .off = code_start, + .len = code_len, + }, + .fail => |em| { + try zcu.failed_codegen.put(gpa, nav_index, em); + return; + }, + }; + + const gop = try wasm.navs.getOrPut(gpa, nav_index); + if (gop.found_existing) { + @panic("TODO reuse these resources"); + } else { + _ = wasm.imports.swapRemove(nav_index); + } + gop.value_ptr.* = .{ + .code = code, + .relocs = .{ + .off = relocs_start, + .len = relocs_len, + }, + }; } pub fn updateLineNumber(wasm: *Wasm, pt: Zcu.PerThread, ti_id: InternPool.TrackedInst.Index) !void { - if (wasm.llvm_object) |_| return; - try wasm.zig_object.?.updateLineNumber(pt, ti_id); -} - -/// From a given symbol location, returns its `wasm.GlobalType`. -/// Asserts the Symbol represents a global. -fn getGlobalType(wasm: *const Wasm, loc: SymbolLoc) std.wasm.GlobalType { - const symbol = wasm.symbolLocSymbol(loc); - assert(symbol.tag == .global); - const is_undefined = symbol.isUndefined(); - switch (loc.file) { - .zig_object => { - const zo = wasm.zig_object.?; - return if (is_undefined) - zo.imports.get(loc.index).?.kind.global - else - zo.globals.items[symbol.index - zo.imported_globals_count].global_type; - }, - .none => { - return if (is_undefined) - wasm.imports.get(loc).?.kind.global - else - wasm.wasm_globals.items[symbol.index].global_type; - }, - _ => { - const obj = &wasm.objects.items[@intFromEnum(loc.file)]; - return if (is_undefined) - obj.findImport(obj.symtable[@intFromEnum(loc.index)]).kind.global - else - obj.globals[symbol.index - obj.imported_globals_count].global_type; - }, + if (wasm.dwarf) |*dw| { + try dw.updateLineNumber(pt.zcu, ti_id); } } -/// From a given symbol location, returns its `wasm.Type`. -/// Asserts the Symbol represents a function. -fn getFunctionSignature(wasm: *const Wasm, loc: SymbolLoc) std.wasm.Type { - const symbol = wasm.symbolLocSymbol(loc); - assert(symbol.tag == .function); - const is_undefined = symbol.isUndefined(); - switch (loc.file) { - .zig_object => { - const zo = wasm.zig_object.?; - if (is_undefined) { - const type_index = zo.imports.get(loc.index).?.kind.function; - return zo.func_types.items[type_index]; - } - const sym = zo.symbols.items[@intFromEnum(loc.index)]; - const type_index = zo.functions.items[sym.index].type_index; - return zo.func_types.items[type_index]; - }, - .none => { - if (is_undefined) { - const type_index = wasm.imports.get(loc).?.kind.function; - return wasm.func_types.items[type_index]; - } - return wasm.func_types.items[ - wasm.functions.get(.{ - .file = .none, - .index = symbol.index, - }).?.func.type_index - ]; - }, - _ => { - const obj = &wasm.objects.items[@intFromEnum(loc.file)]; - if (is_undefined) { - const type_index = obj.findImport(obj.symtable[@intFromEnum(loc.index)]).kind.function; - return obj.func_types[type_index]; - } - const sym = obj.symtable[@intFromEnum(loc.index)]; - const type_index = obj.functions[sym.index - obj.imported_functions_count].type_index; - return obj.func_types[type_index]; - }, - } -} - -/// 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(wasm: *Wasm, name: []const u8, lib_name: ?[]const u8) !Symbol.Index { - _ = lib_name; - const name_index = try wasm.internString(name); - return wasm.zig_object.?.getGlobalSymbol(wasm.base.comp.gpa, name_index); -} - -/// For a given `Nav`, find the given symbol index's atom, and create a relocation for the type. -/// Returns the given pointer address -pub fn getNavVAddr( - wasm: *Wasm, - pt: Zcu.PerThread, - nav: InternPool.Nav.Index, - reloc_info: link.File.RelocInfo, -) !u64 { - return wasm.zig_object.?.getNavVAddr(wasm, pt, nav, reloc_info); -} - -pub fn lowerUav( - wasm: *Wasm, - pt: Zcu.PerThread, - uav: InternPool.Index, - explicit_alignment: Alignment, - src_loc: Zcu.LazySrcLoc, -) !codegen.GenResult { - return wasm.zig_object.?.lowerUav(wasm, pt, uav, explicit_alignment, src_loc); -} - -pub fn getUavVAddr(wasm: *Wasm, uav: InternPool.Index, reloc_info: link.File.RelocInfo) !u64 { - return wasm.zig_object.?.getUavVAddr(wasm, uav, reloc_info); -} - pub fn deleteExport( wasm: *Wasm, exported: Zcu.Exported, name: InternPool.NullTerminatedString, ) void { - if (wasm.llvm_object) |_| return; - return wasm.zig_object.?.deleteExport(wasm, exported, name); + if (wasm.llvm_object != null) return; + + const zcu = wasm.base.comp.zcu.?; + const ip = &zcu.intern_pool; + const export_name = wasm.getExistingString(name.toSlice(ip)).?; + switch (exported) { + .nav => |nav_index| assert(wasm.nav_exports.swapRemove(.{ .nav_index = nav_index, .name = export_name })), + .uav => |uav_index| assert(wasm.uav_exports.swapRemove(.{ .uav_index = uav_index, .name = export_name })), + } + wasm.any_exports_updated = true; } pub fn updateExports( @@ -1705,882 +1426,19 @@ pub fn updateExports( @panic("Attempted to compile for object format that was disabled by build configuration"); } if (wasm.llvm_object) |llvm_object| return llvm_object.updateExports(pt, exported, export_indices); - return wasm.zig_object.?.updateExports(wasm, pt, exported, export_indices); -} -pub fn freeDecl(wasm: *Wasm, decl_index: InternPool.DeclIndex) void { - if (wasm.llvm_object) |llvm_object| return llvm_object.freeDecl(decl_index); - return wasm.zig_object.?.freeDecl(wasm, decl_index); -} - -/// Assigns indexes to all indirect functions. -/// Starts at offset 1, where the value `0` represents an unresolved function pointer -/// or null-pointer -fn mapFunctionTable(wasm: *Wasm) void { - var it = wasm.function_table.iterator(); - var index: u32 = 1; - while (it.next()) |entry| { - const symbol = wasm.symbolLocSymbol(entry.key_ptr.*); - if (symbol.isAlive()) { - entry.value_ptr.* = index; - index += 1; - } else { - wasm.function_table.removeByPtr(entry.key_ptr); + const zcu = pt.zcu; + const gpa = zcu.gpa; + const ip = &zcu.intern_pool; + for (export_indices) |export_idx| { + const exp = export_idx.ptr(zcu); + const name = try wasm.internString(exp.opts.name.toSlice(ip)); + switch (exported) { + .nav => |nav_index| wasm.nav_exports.put(gpa, .{ .nav_index = nav_index, .name = name }, export_idx), + .uav => |uav_index| wasm.uav_exports.put(gpa, .{ .uav_index = uav_index, .name = name }, export_idx), } } - - if (wasm.import_table or wasm.base.comp.config.output_mode == .Obj) { - const sym_loc = wasm.globals.get(wasm.preloaded_strings.__indirect_function_table).?; - const import = wasm.imports.getPtr(sym_loc).?; - import.kind.table.limits.min = index - 1; // we start at index 1. - } else if (index > 1) { - log.debug("Appending indirect function table", .{}); - const sym_loc = wasm.globals.get(wasm.preloaded_strings.__indirect_function_table).?; - const symbol = wasm.symbolLocSymbol(sym_loc); - const table = &wasm.tables.items[symbol.index - wasm.imported_tables_count]; - table.limits = .{ .min = index, .max = index, .flags = 0x1 }; - } -} - -/// From a given index, append the given `Atom` at the back of the linked list. -/// Simply inserts it into the map of atoms when it doesn't exist yet. -pub fn appendAtomAtIndex(wasm: *Wasm, index: Segment.Index, atom_index: Atom.Index) !void { - const gpa = wasm.base.comp.gpa; - const atom = wasm.getAtomPtr(atom_index); - if (wasm.atoms.getPtr(index)) |last_index_ptr| { - atom.prev = last_index_ptr.*; - last_index_ptr.* = atom_index; - } else { - try wasm.atoms.putNoClobber(gpa, index, atom_index); - } -} - -fn allocateAtoms(wasm: *Wasm) !void { - // first sort the data segments - try sortDataSegments(wasm); - - var it = wasm.atoms.iterator(); - while (it.next()) |entry| { - const segment = wasm.segmentPtr(entry.key_ptr.*); - var atom_index = entry.value_ptr.*; - if (entry.key_ptr.toOptional() == wasm.code_section_index) { - // Code section is allocated upon writing as they are required to be ordered - // to synchronise with the function section. - continue; - } - var offset: u32 = 0; - while (true) { - const atom = wasm.getAtomPtr(atom_index); - const symbol_loc = atom.symbolLoc(); - // Ensure we get the original symbol, so we verify the correct symbol on whether - // it is dead or not and ensure an atom is removed when dead. - // This is required as we may have parsed aliases into atoms. - const sym = switch (symbol_loc.file) { - .zig_object => wasm.zig_object.?.symbols.items[@intFromEnum(symbol_loc.index)], - .none => wasm.synthetic_symbols.items[@intFromEnum(symbol_loc.index)], - _ => wasm.objects.items[@intFromEnum(symbol_loc.file)].symtable[@intFromEnum(symbol_loc.index)], - }; - - // Dead symbols must be unlinked from the linked-list to prevent them - // from being emit into the binary. - if (sym.isDead()) { - if (entry.value_ptr.* == atom_index and atom.prev != .null) { - // When the atom is dead and is also the first atom retrieved from wasm.atoms(index) we update - // the entry to point it to the previous atom to ensure we do not start with a dead symbol that - // was removed and therefore do not emit any code at all. - entry.value_ptr.* = atom.prev; - } - if (atom.prev == .null) break; - atom_index = atom.prev; - atom.prev = .null; - continue; - } - offset = @intCast(atom.alignment.forward(offset)); - atom.offset = offset; - log.debug("Atom '{s}' allocated from 0x{x:0>8} to 0x{x:0>8} size={d}", .{ - wasm.symbolLocName(symbol_loc), - offset, - offset + atom.size, - atom.size, - }); - offset += atom.size; - if (atom.prev == .null) break; - atom_index = atom.prev; - } - segment.size = @intCast(segment.alignment.forward(offset)); - } -} - -/// For each data symbol, sets the virtual address. -fn allocateVirtualAddresses(wasm: *Wasm) void { - for (wasm.resolved_symbols.keys()) |loc| { - const symbol = wasm.symbolLocSymbol(loc); - if (symbol.tag != .data or symbol.isDead()) { - // Only data symbols have virtual addresses. - // Dead symbols do not get allocated, so we don't need to set their virtual address either. - continue; - } - const atom_index = wasm.symbol_atom.get(loc) orelse { - // synthetic symbol that does not contain an atom - continue; - }; - - const atom = wasm.getAtom(atom_index); - const merge_segment = wasm.base.comp.config.output_mode != .Obj; - const segment_info = switch (atom.file) { - .zig_object => wasm.zig_object.?.segment_info.items, - .none => wasm.segment_info.values(), - _ => wasm.objects.items[@intFromEnum(atom.file)].segment_info, - }; - const segment_name = segment_info[symbol.index].outputName(merge_segment); - const segment_index = wasm.data_segments.get(segment_name).?; - const segment = wasm.segmentPtr(segment_index); - - // TLS symbols have their virtual address set relative to their own TLS segment, - // rather than the entire Data section. - if (symbol.hasFlag(.WASM_SYM_TLS)) { - symbol.virtual_address = atom.offset; - } else { - symbol.virtual_address = atom.offset + segment.offset; - } - } -} - -fn sortDataSegments(wasm: *Wasm) !void { - const gpa = wasm.base.comp.gpa; - var new_mapping: std.StringArrayHashMapUnmanaged(Segment.Index) = .empty; - try new_mapping.ensureUnusedCapacity(gpa, wasm.data_segments.count()); - errdefer new_mapping.deinit(gpa); - - const keys = try gpa.dupe([]const u8, wasm.data_segments.keys()); - defer gpa.free(keys); - - const SortContext = struct { - fn sort(_: void, lhs: []const u8, rhs: []const u8) bool { - return order(lhs) < order(rhs); - } - - fn order(name: []const u8) u8 { - if (mem.startsWith(u8, name, ".rodata")) return 0; - if (mem.startsWith(u8, name, ".data")) return 1; - if (mem.startsWith(u8, name, ".text")) return 2; - return 3; - } - }; - - mem.sort([]const u8, keys, {}, SortContext.sort); - for (keys) |key| { - const segment_index = wasm.data_segments.get(key).?; - new_mapping.putAssumeCapacity(key, segment_index); - } - wasm.data_segments.deinit(gpa); - wasm.data_segments = new_mapping; -} - -/// Obtains all initfuncs from each object file, verifies its function signature, -/// and then appends it to our final `init_funcs` list. -/// After all functions have been inserted, the functions will be ordered based -/// on their priority. -/// NOTE: This function must be called before we merged any other section. -/// This is because all init funcs in the object files contain references to the -/// original functions and their types. We need to know the type to verify it doesn't -/// contain any parameters. -fn setupInitFunctions(wasm: *Wasm) !void { - const gpa = wasm.base.comp.gpa; - const diags = &wasm.base.comp.link_diags; - // There's no constructors for Zig so we can simply search through linked object files only. - for (wasm.objects.items, 0..) |*object, object_index| { - try wasm.init_funcs.ensureUnusedCapacity(gpa, object.init_funcs.len); - for (object.init_funcs) |init_func| { - const symbol = object.symtable[init_func.symbol_index]; - const ty: std.wasm.Type = if (symbol.isUndefined()) ty: { - const imp: Import = object.findImport(symbol); - break :ty object.func_types[imp.kind.function]; - } else ty: { - const func_index = symbol.index - object.imported_functions_count; - const func = object.functions[func_index]; - break :ty object.func_types[func.type_index]; - }; - if (ty.params.len != 0) { - var err = try diags.addErrorWithNotes(0); - try err.addMsg("constructor functions cannot take arguments: '{s}'", .{wasm.stringSlice(symbol.name)}); - } - log.debug("appended init func '{s}'\n", .{wasm.stringSlice(symbol.name)}); - wasm.init_funcs.appendAssumeCapacity(.{ - .index = @enumFromInt(init_func.symbol_index), - .file = @enumFromInt(object_index), - .priority = init_func.priority, - }); - try wasm.mark(.{ - .index = @enumFromInt(init_func.symbol_index), - .file = @enumFromInt(object_index), - }); - } - } - - // sort the initfunctions based on their priority - mem.sort(InitFuncLoc, wasm.init_funcs.items, {}, InitFuncLoc.lessThan); - - if (wasm.init_funcs.items.len > 0) { - const loc = wasm.globals.get(wasm.preloaded_strings.__wasm_call_ctors).?; - try wasm.mark(loc); - } -} - -/// Creates a function body for the `__wasm_call_ctors` symbol. -/// Loops over all constructors found in `init_funcs` and calls them -/// respectively based on their priority which was sorted by `setupInitFunctions`. -/// NOTE: This function must be called after we merged all sections to ensure the -/// references to the function stored in the symbol have been finalized so we end -/// up calling the resolved function. -fn initializeCallCtorsFunction(wasm: *Wasm) !void { - const gpa = wasm.base.comp.gpa; - // No code to emit, so also no ctors to call - if (wasm.code_section_index == .none) { - // Make sure to remove it from the resolved symbols so we do not emit - // it within any section. TODO: Remove this once we implement garbage collection. - const loc = wasm.globals.get(wasm.preloaded_strings.__wasm_call_ctors).?; - assert(wasm.resolved_symbols.swapRemove(loc)); - return; - } - - var function_body = std.ArrayList(u8).init(gpa); - defer function_body.deinit(); - const writer = function_body.writer(); - - // Create the function body - { - // Write locals count (we have none) - try leb.writeUleb128(writer, @as(u32, 0)); - - // call constructors - for (wasm.init_funcs.items) |init_func_loc| { - const symbol = init_func_loc.getSymbol(wasm); - const func = wasm.functions.values()[symbol.index - wasm.imported_functions_count].func; - const ty = wasm.func_types.items[func.type_index]; - - // Call function by its function index - try writer.writeByte(std.wasm.opcode(.call)); - try leb.writeUleb128(writer, symbol.index); - - // drop all returned values from the stack as __wasm_call_ctors has no return value - for (ty.returns) |_| { - try writer.writeByte(std.wasm.opcode(.drop)); - } - } - - // End function body - try writer.writeByte(std.wasm.opcode(.end)); - } - - try wasm.createSyntheticFunction( - wasm.preloaded_strings.__wasm_call_ctors, - std.wasm.Type{ .params = &.{}, .returns = &.{} }, - &function_body, - ); -} - -fn createSyntheticFunction( - wasm: *Wasm, - symbol_name: String, - func_ty: std.wasm.Type, - function_body: *std.ArrayList(u8), -) !void { - const gpa = wasm.base.comp.gpa; - const loc = wasm.globals.get(symbol_name).?; - const symbol = wasm.symbolLocSymbol(loc); - if (symbol.isDead()) { - return; - } - const ty_index = try wasm.putOrGetFuncType(func_ty); - // create function with above type - const func_index = wasm.imported_functions_count + @as(u32, @intCast(wasm.functions.count())); - try wasm.functions.putNoClobber( - gpa, - .{ .file = .none, .index = func_index }, - .{ .func = .{ .type_index = ty_index }, .sym_index = loc.index }, - ); - symbol.index = func_index; - - // create the atom that will be output into the final binary - const atom_index = try wasm.createAtom(loc.index, .none); - const atom = wasm.getAtomPtr(atom_index); - atom.size = @intCast(function_body.items.len); - atom.code = function_body.moveToUnmanaged(); - try wasm.appendAtomAtIndex(wasm.code_section_index.unwrap().?, atom_index); -} - -/// Unlike `createSyntheticFunction` this function is to be called by -/// the codegeneration backend. This will not allocate the created Atom yet. -/// Returns the index of the symbol. -pub fn createFunction( - wasm: *Wasm, - symbol_name: []const u8, - func_ty: std.wasm.Type, - function_body: *std.ArrayList(u8), - relocations: *std.ArrayList(Relocation), -) !Symbol.Index { - return wasm.zig_object.?.createFunction(wasm, symbol_name, func_ty, function_body, relocations); -} - -/// If required, sets the function index in the `start` section. -fn setupStartSection(wasm: *Wasm) !void { - if (wasm.globals.get(wasm.preloaded_strings.__wasm_init_memory)) |loc| { - wasm.entry = wasm.symbolLocSymbol(loc).index; - } -} - -fn initializeTLSFunction(wasm: *Wasm) !void { - const comp = wasm.base.comp; - const gpa = comp.gpa; - const shared_memory = comp.config.shared_memory; - - if (!shared_memory) return; - - // ensure function is marked as we must emit it - wasm.symbolLocSymbol(wasm.globals.get(wasm.preloaded_strings.__wasm_init_tls).?).mark(); - - var function_body = std.ArrayList(u8).init(gpa); - defer function_body.deinit(); - const writer = function_body.writer(); - - // locals - try writer.writeByte(0); - - // If there's a TLS segment, initialize it during runtime using the bulk-memory feature - if (wasm.data_segments.getIndex(".tdata")) |data_index| { - const segment_index = wasm.data_segments.entries.items(.value)[data_index]; - const segment = wasm.segmentPtr(segment_index); - - const param_local: u32 = 0; - - try writer.writeByte(std.wasm.opcode(.local_get)); - try leb.writeUleb128(writer, param_local); - - const tls_base_loc = wasm.globals.get(wasm.preloaded_strings.__tls_base).?; - try writer.writeByte(std.wasm.opcode(.global_set)); - try leb.writeUleb128(writer, wasm.symbolLocSymbol(tls_base_loc).index); - - // load stack values for the bulk-memory operation - { - try writer.writeByte(std.wasm.opcode(.local_get)); - try leb.writeUleb128(writer, param_local); - - try writer.writeByte(std.wasm.opcode(.i32_const)); - try leb.writeUleb128(writer, @as(u32, 0)); //segment offset - - try writer.writeByte(std.wasm.opcode(.i32_const)); - try leb.writeUleb128(writer, @as(u32, segment.size)); //segment offset - } - - // perform the bulk-memory operation to initialize the data segment - try writer.writeByte(std.wasm.opcode(.misc_prefix)); - try leb.writeUleb128(writer, std.wasm.miscOpcode(.memory_init)); - // segment immediate - try leb.writeUleb128(writer, @as(u32, @intCast(data_index))); - // memory index immediate (always 0) - try leb.writeUleb128(writer, @as(u32, 0)); - } - - // If we have to perform any TLS relocations, call the corresponding function - // which performs all runtime TLS relocations. This is a synthetic function, - // generated by the linker. - if (wasm.globals.get(wasm.preloaded_strings.__wasm_apply_global_tls_relocs)) |loc| { - try writer.writeByte(std.wasm.opcode(.call)); - try leb.writeUleb128(writer, wasm.symbolLocSymbol(loc).index); - wasm.symbolLocSymbol(loc).mark(); - } - - try writer.writeByte(std.wasm.opcode(.end)); - - try wasm.createSyntheticFunction( - wasm.preloaded_strings.__wasm_init_tls, - std.wasm.Type{ .params = &.{.i32}, .returns = &.{} }, - &function_body, - ); -} - -fn setupImports(wasm: *Wasm) !void { - const gpa = wasm.base.comp.gpa; - log.debug("Merging imports", .{}); - for (wasm.resolved_symbols.keys()) |symbol_loc| { - const object_id = symbol_loc.file.unwrap() orelse { - // Synthetic symbols will already exist in the `import` section - continue; - }; - - const symbol = wasm.symbolLocSymbol(symbol_loc); - if (symbol.isDead()) continue; - if (!symbol.requiresImport()) continue; - if (symbol.name == wasm.preloaded_strings.__indirect_function_table) continue; - - log.debug("Symbol '{s}' will be imported from the host", .{wasm.stringSlice(symbol.name)}); - const import = objectImport(wasm, object_id, symbol_loc.index); - - // We copy the import to a new import to ensure the names contain references - // to the internal string table, rather than of the object file. - const new_imp: Import = .{ - .module_name = import.module_name, - .name = import.name, - .kind = import.kind, - }; - // TODO: De-duplicate imports when they contain the same names and type - try wasm.imports.putNoClobber(gpa, symbol_loc, new_imp); - } - - // Assign all indexes of the imports to their representing symbols - var function_index: u32 = 0; - var global_index: u32 = 0; - var table_index: u32 = 0; - var it = wasm.imports.iterator(); - while (it.next()) |entry| { - const symbol = wasm.symbolLocSymbol(entry.key_ptr.*); - const import: Import = entry.value_ptr.*; - switch (import.kind) { - .function => { - symbol.index = function_index; - function_index += 1; - }, - .global => { - symbol.index = global_index; - global_index += 1; - }, - .table => { - symbol.index = table_index; - table_index += 1; - }, - else => unreachable, - } - } - wasm.imported_functions_count = function_index; - wasm.imported_globals_count = global_index; - wasm.imported_tables_count = table_index; - - log.debug("Merged ({d}) functions, ({d}) globals, and ({d}) tables into import section", .{ - function_index, - global_index, - table_index, - }); -} - -/// Takes the global, function and table section from each linked object file -/// and merges it into a single section for each. -fn mergeSections(wasm: *Wasm) !void { - const gpa = wasm.base.comp.gpa; - - var removed_duplicates = std.ArrayList(SymbolLoc).init(gpa); - defer removed_duplicates.deinit(); - - for (wasm.resolved_symbols.keys()) |sym_loc| { - const object_id = sym_loc.file.unwrap() orelse { - // Synthetic symbols already live in the corresponding sections. - continue; - }; - - const symbol = objectSymbol(wasm, object_id, sym_loc.index); - if (symbol.isDead() or symbol.isUndefined()) { - // Skip undefined symbols as they go in the `import` section - continue; - } - - switch (symbol.tag) { - .function => { - const gop = try wasm.functions.getOrPut( - gpa, - .{ .file = sym_loc.file, .index = symbol.index }, - ); - if (gop.found_existing) { - // We found an alias to the same function, discard this symbol in favor of - // the original symbol and point the discard function to it. This ensures - // we only emit a single function, instead of duplicates. - // we favor keeping the global over a local. - const original_loc: SymbolLoc = .{ .file = gop.key_ptr.file, .index = gop.value_ptr.sym_index }; - const original_sym = wasm.symbolLocSymbol(original_loc); - if (original_sym.isLocal() and symbol.isGlobal()) { - original_sym.unmark(); - try wasm.discarded.put(gpa, original_loc, sym_loc); - try removed_duplicates.append(original_loc); - } else { - symbol.unmark(); - try wasm.discarded.putNoClobber(gpa, sym_loc, original_loc); - try removed_duplicates.append(sym_loc); - continue; - } - } - gop.value_ptr.* = .{ - .func = objectFunction(wasm, object_id, sym_loc.index), - .sym_index = sym_loc.index, - }; - symbol.index = @as(u32, @intCast(gop.index)) + wasm.imported_functions_count; - }, - .global => { - const index = symbol.index - objectImportedFunctions(wasm, object_id); - const original_global = objectGlobals(wasm, object_id)[index]; - symbol.index = @as(u32, @intCast(wasm.wasm_globals.items.len)) + wasm.imported_globals_count; - try wasm.wasm_globals.append(gpa, original_global); - }, - .table => { - const index = symbol.index - objectImportedFunctions(wasm, object_id); - // assert it's a regular relocatable object file as `ZigObject` will never - // contain a table. - const original_table = wasm.objectById(object_id).?.tables[index]; - symbol.index = @as(u32, @intCast(wasm.tables.items.len)) + wasm.imported_tables_count; - try wasm.tables.append(gpa, original_table); - }, - .dead, .undefined => unreachable, - else => {}, - } - } - - // For any removed duplicates, remove them from the resolved symbols list - for (removed_duplicates.items) |sym_loc| { - assert(wasm.resolved_symbols.swapRemove(sym_loc)); - gc_log.debug("Removed duplicate for function '{s}'", .{wasm.symbolLocName(sym_loc)}); - } - - log.debug("Merged ({d}) functions", .{wasm.functions.count()}); - log.debug("Merged ({d}) globals", .{wasm.wasm_globals.items.len}); - log.debug("Merged ({d}) tables", .{wasm.tables.items.len}); -} - -/// Merges function types of all object files into the final -/// 'types' section, while assigning the type index to the representing -/// section (import, export, function). -fn mergeTypes(wasm: *Wasm) !void { - const gpa = wasm.base.comp.gpa; - // A map to track which functions have already had their - // type inserted. If we do this for the same function multiple times, - // it will be overwritten with the incorrect type. - var dirty = std.AutoHashMap(u32, void).init(gpa); - try dirty.ensureUnusedCapacity(@as(u32, @intCast(wasm.functions.count()))); - defer dirty.deinit(); - - for (wasm.resolved_symbols.keys()) |sym_loc| { - const object_id = sym_loc.file.unwrap() orelse { - // zig code-generated symbols are already present in final type section - continue; - }; - - const symbol = objectSymbol(wasm, object_id, sym_loc.index); - if (symbol.tag != .function or symbol.isDead()) { - // Only functions have types. Only retrieve the type of referenced functions. - continue; - } - - if (symbol.isUndefined()) { - log.debug("Adding type from extern function '{s}'", .{wasm.symbolLocName(sym_loc)}); - const import: *Import = wasm.imports.getPtr(sym_loc) orelse continue; - const original_type = objectFuncTypes(wasm, object_id)[import.kind.function]; - import.kind.function = try wasm.putOrGetFuncType(original_type); - } else if (!dirty.contains(symbol.index)) { - log.debug("Adding type from function '{s}'", .{wasm.symbolLocName(sym_loc)}); - const func = &wasm.functions.values()[symbol.index - wasm.imported_functions_count].func; - func.type_index = try wasm.putOrGetFuncType(objectFuncTypes(wasm, object_id)[func.type_index]); - dirty.putAssumeCapacityNoClobber(symbol.index, {}); - } - } - log.debug("Completed merging and deduplicating types. Total count: ({d})", .{wasm.func_types.items.len}); -} - -fn checkExportNames(wasm: *Wasm) !void { - const force_exp_names = wasm.export_symbol_names; - const diags = &wasm.base.comp.link_diags; - if (force_exp_names.len > 0) { - var failed_exports = false; - - for (force_exp_names) |exp_name| { - const exp_name_interned = try wasm.internString(exp_name); - const loc = wasm.globals.get(exp_name_interned) orelse { - var err = try diags.addErrorWithNotes(0); - try err.addMsg("could not export '{s}', symbol not found", .{exp_name}); - failed_exports = true; - continue; - }; - - const symbol = wasm.symbolLocSymbol(loc); - symbol.setFlag(.WASM_SYM_EXPORTED); - } - - if (failed_exports) { - return error.FlushFailure; - } - } -} - -fn setupExports(wasm: *Wasm) !void { - const comp = wasm.base.comp; - const gpa = comp.gpa; - if (comp.config.output_mode == .Obj) return; - log.debug("Building exports from symbols", .{}); - - for (wasm.resolved_symbols.keys()) |sym_loc| { - const symbol = wasm.symbolLocSymbol(sym_loc); - if (!symbol.isExported(comp.config.rdynamic)) continue; - - const exp: Export = if (symbol.tag == .data) exp: { - const global_index = @as(u32, @intCast(wasm.imported_globals_count + wasm.wasm_globals.items.len)); - try wasm.wasm_globals.append(gpa, .{ - .global_type = .{ .valtype = .i32, .mutable = false }, - .init = .{ .i32_const = @as(i32, @intCast(symbol.virtual_address)) }, - }); - break :exp .{ - .name = symbol.name, - .kind = .global, - .index = global_index, - }; - } else .{ - .name = symbol.name, - .kind = symbol.tag.externalType(), - .index = symbol.index, - }; - log.debug("Exporting symbol '{s}' as '{s}' at index: ({d})", .{ - wasm.stringSlice(symbol.name), - wasm.stringSlice(exp.name), - exp.index, - }); - try wasm.exports.append(gpa, exp); - } - - log.debug("Completed building exports. Total count: ({d})", .{wasm.exports.items.len}); -} - -fn setupStart(wasm: *Wasm) !void { - const comp = wasm.base.comp; - const diags = &wasm.base.comp.link_diags; - // do not export entry point if user set none or no default was set. - const entry_name = wasm.entry_name.unwrap() orelse return; - - const symbol_loc = wasm.globals.get(entry_name) orelse { - var err = try diags.addErrorWithNotes(1); - try err.addMsg("entry symbol '{s}' missing", .{wasm.stringSlice(entry_name)}); - try err.addNote("'-fno-entry' suppresses this error", .{}); - return error.LinkFailure; - }; - - const symbol = wasm.symbolLocSymbol(symbol_loc); - if (symbol.tag != .function) - return diags.fail("entry symbol '{s}' is not a function", .{wasm.stringSlice(entry_name)}); - - // Ensure the symbol is exported so host environment can access it - if (comp.config.output_mode != .Obj) { - symbol.setFlag(.WASM_SYM_EXPORTED); - } -} - -/// Sets up the memory section of the wasm module, as well as the stack. -fn setupMemory(wasm: *Wasm) !void { - const comp = wasm.base.comp; - const diags = &wasm.base.comp.link_diags; - const shared_memory = comp.config.shared_memory; - log.debug("Setting up memory layout", .{}); - const page_size = std.wasm.page_size; // 64kb - const stack_alignment: Alignment = .@"16"; // wasm's stack alignment as specified by tool-convention - const heap_alignment: Alignment = .@"16"; // wasm's heap alignment as specified by tool-convention - - // Always place the stack at the start by default - // unless the user specified the global-base flag - var place_stack_first = true; - var memory_ptr: u64 = if (wasm.global_base) |base| blk: { - place_stack_first = false; - break :blk base; - } else 0; - - const is_obj = comp.config.output_mode == .Obj; - - const stack_ptr = if (wasm.globals.get(wasm.preloaded_strings.__stack_pointer)) |loc| index: { - const sym = wasm.symbolLocSymbol(loc); - break :index sym.index - wasm.imported_globals_count; - } else null; - - if (place_stack_first and !is_obj) { - memory_ptr = stack_alignment.forward(memory_ptr); - memory_ptr += wasm.base.stack_size; - // We always put the stack pointer global at index 0 - if (stack_ptr) |index| { - wasm.wasm_globals.items[index].init.i32_const = @as(i32, @bitCast(@as(u32, @intCast(memory_ptr)))); - } - } - - var offset: u32 = @as(u32, @intCast(memory_ptr)); - var data_seg_it = wasm.data_segments.iterator(); - while (data_seg_it.next()) |entry| { - const segment = wasm.segmentPtr(entry.value_ptr.*); - memory_ptr = segment.alignment.forward(memory_ptr); - - // set TLS-related symbols - if (mem.eql(u8, entry.key_ptr.*, ".tdata")) { - if (wasm.globals.get(wasm.preloaded_strings.__tls_size)) |loc| { - const sym = wasm.symbolLocSymbol(loc); - wasm.wasm_globals.items[sym.index - wasm.imported_globals_count].init.i32_const = @intCast(segment.size); - } - if (wasm.globals.get(wasm.preloaded_strings.__tls_align)) |loc| { - const sym = wasm.symbolLocSymbol(loc); - wasm.wasm_globals.items[sym.index - wasm.imported_globals_count].init.i32_const = @intCast(segment.alignment.toByteUnits().?); - } - if (wasm.globals.get(wasm.preloaded_strings.__tls_base)) |loc| { - const sym = wasm.symbolLocSymbol(loc); - wasm.wasm_globals.items[sym.index - wasm.imported_globals_count].init.i32_const = if (shared_memory) - @as(i32, 0) - else - @as(i32, @intCast(memory_ptr)); - } - } - - memory_ptr += segment.size; - segment.offset = offset; - offset += segment.size; - } - - // create the memory init flag which is used by the init memory function - if (shared_memory and wasm.hasPassiveInitializationSegments()) { - // align to pointer size - memory_ptr = mem.alignForward(u64, memory_ptr, 4); - const loc = try wasm.createSyntheticSymbol(wasm.preloaded_strings.__wasm_init_memory_flag, .data); - const sym = wasm.symbolLocSymbol(loc); - sym.mark(); - sym.virtual_address = @as(u32, @intCast(memory_ptr)); - memory_ptr += 4; - } - - if (!place_stack_first and !is_obj) { - memory_ptr = stack_alignment.forward(memory_ptr); - memory_ptr += wasm.base.stack_size; - if (stack_ptr) |index| { - wasm.wasm_globals.items[index].init.i32_const = @as(i32, @bitCast(@as(u32, @intCast(memory_ptr)))); - } - } - - // One of the linked object files has a reference to the __heap_base symbol. - // We must set its virtual address so it can be used in relocations. - if (wasm.globals.get(wasm.preloaded_strings.__heap_base)) |loc| { - const symbol = wasm.symbolLocSymbol(loc); - symbol.virtual_address = @intCast(heap_alignment.forward(memory_ptr)); - } - - // Setup the max amount of pages - // For now we only support wasm32 by setting the maximum allowed memory size 2^32-1 - const max_memory_allowed: u64 = (1 << 32) - 1; - - if (wasm.initial_memory) |initial_memory| { - if (!std.mem.isAlignedGeneric(u64, initial_memory, page_size)) { - var err = try diags.addErrorWithNotes(0); - try err.addMsg("Initial memory must be {d}-byte aligned", .{page_size}); - } - if (memory_ptr > initial_memory) { - var err = try diags.addErrorWithNotes(0); - try err.addMsg("Initial memory too small, must be at least {d} bytes", .{memory_ptr}); - } - if (initial_memory > max_memory_allowed) { - var err = try diags.addErrorWithNotes(0); - try err.addMsg("Initial memory exceeds maximum memory {d}", .{max_memory_allowed}); - } - memory_ptr = initial_memory; - } - memory_ptr = mem.alignForward(u64, memory_ptr, std.wasm.page_size); - // In case we do not import memory, but define it ourselves, - // set the minimum amount of pages on the memory section. - wasm.memories.limits.min = @as(u32, @intCast(memory_ptr / page_size)); - log.debug("Total memory pages: {d}", .{wasm.memories.limits.min}); - - if (wasm.globals.get(wasm.preloaded_strings.__heap_end)) |loc| { - const symbol = wasm.symbolLocSymbol(loc); - symbol.virtual_address = @as(u32, @intCast(memory_ptr)); - } - - if (wasm.max_memory) |max_memory| { - if (!std.mem.isAlignedGeneric(u64, max_memory, page_size)) { - var err = try diags.addErrorWithNotes(0); - try err.addMsg("Maximum memory must be {d}-byte aligned", .{page_size}); - } - if (memory_ptr > max_memory) { - var err = try diags.addErrorWithNotes(0); - try err.addMsg("Maximum memory too small, must be at least {d} bytes", .{memory_ptr}); - } - if (max_memory > max_memory_allowed) { - var err = try diags.addErrorWithNotes(0); - try err.addMsg("Maximum memory exceeds maximum amount {d}", .{max_memory_allowed}); - } - wasm.memories.limits.max = @as(u32, @intCast(max_memory / page_size)); - wasm.memories.limits.setFlag(.WASM_LIMITS_FLAG_HAS_MAX); - if (shared_memory) { - wasm.memories.limits.setFlag(.WASM_LIMITS_FLAG_IS_SHARED); - } - log.debug("Maximum memory pages: {?d}", .{wasm.memories.limits.max}); - } -} - -/// From a given object's index and the index of the segment, returns the corresponding -/// index of the segment within the final data section. When the segment does not yet -/// exist, a new one will be initialized and appended. The new index will be returned in that case. -pub fn getMatchingSegment(wasm: *Wasm, object_id: ObjectId, symbol_index: Symbol.Index) !Segment.Index { - const comp = wasm.base.comp; - const gpa = comp.gpa; - const diags = &wasm.base.comp.link_diags; - const symbol = objectSymbols(wasm, object_id)[@intFromEnum(symbol_index)]; - const index: Segment.Index = @enumFromInt(wasm.segments.items.len); - const shared_memory = comp.config.shared_memory; - - switch (symbol.tag) { - .data => { - const segment_info = objectSegmentInfo(wasm, object_id)[symbol.index]; - const merge_segment = comp.config.output_mode != .Obj; - const result = try wasm.data_segments.getOrPut(gpa, segment_info.outputName(merge_segment)); - if (!result.found_existing) { - result.value_ptr.* = index; - var flags: u32 = 0; - if (shared_memory) { - flags |= @intFromEnum(Segment.Flag.WASM_DATA_SEGMENT_IS_PASSIVE); - } - try wasm.segments.append(gpa, .{ - .alignment = .@"1", - .size = 0, - .offset = 0, - .flags = flags, - }); - try wasm.segment_info.putNoClobber(gpa, index, .{ - .name = try gpa.dupe(u8, segment_info.name), - .alignment = segment_info.alignment, - .flags = segment_info.flags, - }); - return index; - } else return result.value_ptr.*; - }, - .function => return wasm.code_section_index.unwrap() orelse blk: { - wasm.code_section_index = index.toOptional(); - try wasm.appendDummySegment(); - break :blk index; - }, - .section => { - const section_name = wasm.objectSymbol(object_id, symbol_index).name; - - inline for (@typeInfo(CustomSections).@"struct".fields) |field| { - if (@field(wasm.custom_sections, field.name).name == section_name) { - const field_ptr = &@field(wasm.custom_sections, field.name).index; - return field_ptr.unwrap() orelse { - field_ptr.* = index.toOptional(); - try wasm.appendDummySegment(); - return index; - }; - } - } else { - return diags.failParse(objectPath(wasm, object_id), "unknown section: {s}", .{ - wasm.stringSlice(section_name), - }); - } - }, - else => unreachable, - } -} - -/// Appends a new segment with default field values -fn appendDummySegment(wasm: *Wasm) !void { - const gpa = wasm.base.comp.gpa; - try wasm.segments.append(gpa, .{ - .alignment = .@"1", - .size = 0, - .offset = 0, - .flags = 0, - }); + wasm.any_exports_updated = true; } pub fn loadInput(wasm: *Wasm, input: link.Input) !void { @@ -2619,784 +1477,253 @@ pub fn flush(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: st return wasm.flushModule(arena, tid, prog_node); } -pub fn flushModule(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { +pub fn prelink(wasm: *Wasm, prog_node: std.Progress.Node) link.File.FlushError!void { const tracy = trace(@src()); defer tracy.end(); + const sub_prog_node = prog_node.start("Wasm Prelink", 0); + defer sub_prog_node.end(); + const comp = wasm.base.comp; - const diags = &comp.link_diags; + const gpa = comp.gpa; + const rdynamic = comp.config.rdynamic; + + { + var missing_exports: std.AutoArrayHashMapUnmanaged(String, void) = .empty; + defer missing_exports.deinit(gpa); + for (wasm.export_symbol_names) |exp_name| { + const exp_name_interned = try wasm.internString(exp_name); + if (wasm.object_function_imports.getPtr(exp_name_interned)) |import| { + if (import.resolution != .unresolved) { + import.flags.exported = true; + continue; + } + } + if (wasm.object_global_imports.getPtr(exp_name_interned)) |import| { + if (import.resolution != .unresolved) { + import.flags.exported = true; + continue; + } + } + try wasm.missing_exports.put(exp_name_interned, {}); + } + wasm.missing_exports_init = try gpa.dupe(String, wasm.missing_exports.keys()); + } + + if (wasm.entry_name.unwrap()) |entry_name| { + if (wasm.object_function_imports.getPtr(entry_name)) |import| { + if (import.resolution != .unresolved) { + import.flags.exported = true; + wasm.entry_resolution = import.resolution; + } + } + } + + // These loops do both recursive marking of alive symbols well as checking for undefined symbols. + // At the end, output functions and globals will be populated. + for (wasm.object_function_imports.keys(), wasm.object_function_imports.values(), 0..) |name, *import, i| { + if (import.flags.isIncluded(rdynamic)) { + try markFunction(wasm, name, import, @enumFromInt(i)); + continue; + } + } + wasm.functions_len = @intCast(wasm.functions.items.len); + wasm.function_imports_init = try gpa.dupe(FunctionImportId, wasm.functions.keys()); + wasm.function_exports_len = @intCast(wasm.function_exports.items.len); + + for (wasm.object_global_imports.keys(), wasm.object_global_imports.values(), 0..) |name, *import, i| { + if (import.flags.isIncluded(rdynamic)) { + try markGlobal(wasm, name, import, @enumFromInt(i)); + continue; + } + } + wasm.globals_len = @intCast(wasm.globals.items.len); + wasm.global_imports_init = try gpa.dupe(GlobalImportId, wasm.globals.keys()); + wasm.global_exports_len = @intCast(wasm.global_exports.items.len); + + for (wasm.object_table_imports.keys(), wasm.object_table_imports.values(), 0..) |name, *import, i| { + if (import.flags.isIncluded(rdynamic)) { + try markTable(wasm, name, import, @enumFromInt(i)); + continue; + } + } + wasm.tables_len = @intCast(wasm.tables.items.len); +} + +/// Recursively mark alive everything referenced by the function. +fn markFunction( + wasm: *Wasm, + name: String, + import: *FunctionImport, + func_index: ObjectFunctionImportIndex, +) error{OutOfMemory}!void { + if (import.flags.alive) return; + import.flags.alive = true; + + const comp = wasm.base.comp; + const gpa = comp.gpa; + const rdynamic = comp.config.rdynamic; + const is_obj = comp.config.output_mode == .Obj; + + try wasm.functions.ensureUnusedCapacity(gpa, 1); + + if (import.resolution == .unresolved) { + if (name == wasm.preloaded_strings.__wasm_init_memory) { + import.resolution = .__wasm_init_memory; + wasm.functions.putAssumeCapacity(.__wasm_init_memory, {}); + } else if (name == wasm.preloaded_strings.__wasm_apply_global_tls_relocs) { + import.resolution = .__wasm_apply_global_tls_relocs; + wasm.functions.putAssumeCapacity(.__wasm_apply_global_tls_relocs, {}); + } else if (name == wasm.preloaded_strings.__wasm_call_ctors) { + import.resolution = .__wasm_call_ctors; + wasm.functions.putAssumeCapacity(.__wasm_call_ctors, {}); + } else if (name == wasm.preloaded_strings.__wasm_init_tls) { + import.resolution = .__wasm_init_tls; + wasm.functions.putAssumeCapacity(.__wasm_init_tls, {}); + } else { + try wasm.function_imports.put(gpa, .fromObject(func_index), {}); + } + } else { + const gop = wasm.functions.getOrPutAssumeCapacity(import.resolution); + + if (!is_obj and import.flags.isExported(rdynamic)) + try wasm.function_exports.append(gpa, @intCast(gop.index)); + + for (wasm.functionResolutionRelocSlice(import.resolution)) |reloc| + try wasm.markReloc(reloc); + } +} + +/// Recursively mark alive everything referenced by the global. +fn markGlobal( + wasm: *Wasm, + name: String, + import: *GlobalImport, + global_index: ObjectGlobalImportIndex, +) !void { + if (import.flags.alive) return; + import.flags.alive = true; + + const comp = wasm.base.comp; + const gpa = comp.gpa; + const rdynamic = comp.config.rdynamic; + const is_obj = comp.config.output_mode == .Obj; + + try wasm.globals.ensureUnusedCapacity(gpa, 1); + + if (import.resolution == .unresolved) { + if (name == wasm.preloaded_strings.__heap_base) { + import.resolution = .__heap_base; + wasm.globals.putAssumeCapacity(.__heap_base, {}); + } else if (name == wasm.preloaded_strings.__heap_end) { + import.resolution = .__heap_end; + wasm.globals.putAssumeCapacity(.__heap_end, {}); + } else if (name == wasm.preloaded_strings.__stack_pointer) { + import.resolution = .__stack_pointer; + wasm.globals.putAssumeCapacity(.__stack_pointer, {}); + } else if (name == wasm.preloaded_strings.__tls_align) { + import.resolution = .__tls_align; + wasm.globals.putAssumeCapacity(.__tls_align, {}); + } else if (name == wasm.preloaded_strings.__tls_base) { + import.resolution = .__tls_base; + wasm.globals.putAssumeCapacity(.__tls_base, {}); + } else if (name == wasm.preloaded_strings.__tls_size) { + import.resolution = .__tls_size; + wasm.globals.putAssumeCapacity(.__tls_size, {}); + } else { + try wasm.global_imports.put(gpa, .fromObject(global_index), {}); + } + } else { + const gop = wasm.globals.getOrPutAssumeCapacity(import.resolution); + + if (!is_obj and import.flags.isExported(rdynamic)) + try wasm.global_exports.append(gpa, @intCast(gop.index)); + + for (wasm.globalResolutionRelocSlice(import.resolution)) |reloc| + try wasm.markReloc(reloc); + } +} + +fn markTable( + wasm: *Wasm, + name: String, + import: *TableImport, + table_index: ObjectTableImportIndex, +) !void { + if (import.flags.alive) return; + import.flags.alive = true; + + const comp = wasm.base.comp; + const gpa = comp.gpa; + + try wasm.tables.ensureUnusedCapacity(gpa, 1); + + if (import.resolution == .unresolved) { + if (name == wasm.preloaded_strings.__indirect_function_table) { + import.resolution = .__indirect_function_table; + wasm.tables.putAssumeCapacity(.__indirect_function_table, {}); + } else { + try wasm.table_imports.put(gpa, .fromObject(table_index), {}); + } + } else { + wasm.tables.putAssumeCapacity(import.resolution, {}); + // Tables have no relocations. + } +} + +fn globalResolutionRelocSlice(wasm: *Wasm, resolution: GlobalImport.Resolution) ![]const Relocation { + assert(resolution != .none); + _ = wasm; + @panic("TODO"); +} + +fn functionResolutionRelocSlice(wasm: *Wasm, resolution: FunctionImport.Resolution) ![]const Relocation { + assert(resolution != .none); + _ = wasm; + @panic("TODO"); +} + +pub fn flushModule( + wasm: *Wasm, + arena: Allocator, + tid: Zcu.PerThread.Id, + prog_node: std.Progress.Node, +) link.File.FlushError!void { + // The goal is to never use this because it's only needed if we need to + // write to InternPool, but flushModule is too late to be writing to the + // InternPool. + _ = tid; + const comp = wasm.base.comp; + const use_lld = build_options.have_llvm and comp.config.use_lld; + if (wasm.llvm_object) |llvm_object| { try wasm.base.emitLlvmObject(arena, llvm_object, prog_node); - const use_lld = build_options.have_llvm and comp.config.use_lld; if (use_lld) return; } if (comp.verbose_link) Compilation.dump_argv(wasm.dump_argv_list.items); + if (wasm.base.zcu_object_sub_path) |path| { + const module_obj_path: Path = .{ + .root_dir = wasm.base.emit.root_dir, + .sub_path = if (fs.path.dirname(wasm.base.emit.sub_path)) |dirname| + try fs.path.join(arena, &.{ dirname, path }) + else + path, + }; + openParseObjectReportingFailure(wasm, module_obj_path); + try prelink(wasm, prog_node); + } + + const tracy = trace(@src()); + defer tracy.end(); + const sub_prog_node = prog_node.start("Wasm Flush", 0); defer sub_prog_node.end(); - const module_obj_path: ?Path = if (wasm.base.zcu_object_sub_path) |path| .{ - .root_dir = wasm.base.emit.root_dir, - .sub_path = if (fs.path.dirname(wasm.base.emit.sub_path)) |dirname| - try fs.path.join(arena, &.{ dirname, path }) - else - path, - } else null; - - if (wasm.zig_object) |zig_object| try zig_object.flushModule(wasm, tid); - - if (module_obj_path) |path| openParseObjectReportingFailure(wasm, path); - - if (wasm.zig_object != null) { - try wasm.resolveSymbolsInObject(.zig_object); - } - if (diags.hasErrors()) return error.FlushFailure; - for (0..wasm.objects.items.len) |object_index| { - try wasm.resolveSymbolsInObject(@enumFromInt(object_index)); - } - if (diags.hasErrors()) return error.FlushFailure; - - var emit_features_count: u32 = 0; - var enabled_features: [@typeInfo(Feature.Tag).@"enum".fields.len]bool = undefined; - try wasm.validateFeatures(&enabled_features, &emit_features_count); - try wasm.resolveSymbolsInArchives(); - if (diags.hasErrors()) return error.FlushFailure; - try wasm.resolveLazySymbols(); - try wasm.checkUndefinedSymbols(); - try wasm.checkExportNames(); - - try wasm.setupInitFunctions(); - if (diags.hasErrors()) return error.FlushFailure; - try wasm.setupStart(); - - try wasm.markReferences(); - try wasm.setupImports(); - try wasm.mergeSections(); - try wasm.mergeTypes(); - try wasm.allocateAtoms(); - try wasm.setupMemory(); - if (diags.hasErrors()) return error.FlushFailure; - wasm.allocateVirtualAddresses(); - wasm.mapFunctionTable(); - try wasm.initializeCallCtorsFunction(); - try wasm.setupInitMemoryFunction(); - try wasm.setupTLSRelocationsFunction(); - try wasm.initializeTLSFunction(); - try wasm.setupStartSection(); - try wasm.setupExports(); - try wasm.writeToFile(enabled_features, emit_features_count, arena); - if (diags.hasErrors()) return error.FlushFailure; -} - -/// Writes the WebAssembly in-memory module to the file -fn writeToFile( - wasm: *Wasm, - enabled_features: [@typeInfo(Feature.Tag).@"enum".fields.len]bool, - feature_count: u32, - arena: Allocator, -) !void { - const comp = wasm.base.comp; - const diags = &comp.link_diags; - const gpa = comp.gpa; - const use_llvm = comp.config.use_llvm; - const use_lld = build_options.have_llvm and comp.config.use_lld; - const shared_memory = comp.config.shared_memory; - const import_memory = comp.config.import_memory; - const export_memory = comp.config.export_memory; - - // Size of each section header - const header_size = 5 + 1; - // The amount of sections that will be written - var section_count: u32 = 0; - // Index of the code section. Used to tell relocation table where the section lives. - var code_section_index: ?u32 = null; - // Index of the data section. Used to tell relocation table where the section lives. - var data_section_index: ?u32 = null; - const is_obj = comp.config.output_mode == .Obj or (!use_llvm and use_lld); - - var binary_bytes = std.ArrayList(u8).init(gpa); - defer binary_bytes.deinit(); - const binary_writer = binary_bytes.writer(); - - // We write the magic bytes at the end so they will only be written - // if everything succeeded as expected. So populate with 0's for now. - try binary_writer.writeAll(&[_]u8{0} ** 8); - // (Re)set file pointer to 0 - try wasm.base.file.?.setEndPos(0); - try wasm.base.file.?.seekTo(0); - - // Type section - if (wasm.func_types.items.len != 0) { - const header_offset = try reserveVecSectionHeader(&binary_bytes); - log.debug("Writing type section. Count: ({d})", .{wasm.func_types.items.len}); - for (wasm.func_types.items) |func_type| { - try leb.writeUleb128(binary_writer, std.wasm.function_type); - try leb.writeUleb128(binary_writer, @as(u32, @intCast(func_type.params.len))); - for (func_type.params) |param_ty| { - try leb.writeUleb128(binary_writer, std.wasm.valtype(param_ty)); - } - try leb.writeUleb128(binary_writer, @as(u32, @intCast(func_type.returns.len))); - for (func_type.returns) |ret_ty| { - try leb.writeUleb128(binary_writer, std.wasm.valtype(ret_ty)); - } - } - - try writeVecSectionHeader( - binary_bytes.items, - header_offset, - .type, - @intCast(binary_bytes.items.len - header_offset - header_size), - @intCast(wasm.func_types.items.len), - ); - section_count += 1; - } - - // Import section - if (wasm.imports.count() != 0 or import_memory) { - const header_offset = try reserveVecSectionHeader(&binary_bytes); - - var it = wasm.imports.iterator(); - while (it.next()) |entry| { - assert(wasm.symbolLocSymbol(entry.key_ptr.*).isUndefined()); - const import = entry.value_ptr.*; - try wasm.emitImport(binary_writer, import); - } - - if (import_memory) { - const mem_imp: Import = .{ - .module_name = wasm.host_name, - .name = if (is_obj) wasm.preloaded_strings.__linear_memory else wasm.preloaded_strings.memory, - .kind = .{ .memory = wasm.memories.limits }, - }; - try wasm.emitImport(binary_writer, mem_imp); - } - - try writeVecSectionHeader( - binary_bytes.items, - header_offset, - .import, - @intCast(binary_bytes.items.len - header_offset - header_size), - @intCast(wasm.imports.count() + @intFromBool(import_memory)), - ); - section_count += 1; - } - - // Function section - if (wasm.functions.count() != 0) { - const header_offset = try reserveVecSectionHeader(&binary_bytes); - for (wasm.functions.values()) |function| { - try leb.writeUleb128(binary_writer, function.func.type_index); - } - - try writeVecSectionHeader( - binary_bytes.items, - header_offset, - .function, - @intCast(binary_bytes.items.len - header_offset - header_size), - @intCast(wasm.functions.count()), - ); - section_count += 1; - } - - // Table section - if (wasm.tables.items.len > 0) { - const header_offset = try reserveVecSectionHeader(&binary_bytes); - - for (wasm.tables.items) |table| { - try leb.writeUleb128(binary_writer, std.wasm.reftype(table.reftype)); - try emitLimits(binary_writer, table.limits); - } - - try writeVecSectionHeader( - binary_bytes.items, - header_offset, - .table, - @intCast(binary_bytes.items.len - header_offset - header_size), - @intCast(wasm.tables.items.len), - ); - section_count += 1; - } - - // Memory section - if (!import_memory) { - const header_offset = try reserveVecSectionHeader(&binary_bytes); - - try emitLimits(binary_writer, wasm.memories.limits); - try writeVecSectionHeader( - binary_bytes.items, - header_offset, - .memory, - @intCast(binary_bytes.items.len - header_offset - header_size), - 1, // wasm currently only supports 1 linear memory segment - ); - section_count += 1; - } - - // Global section (used to emit stack pointer) - if (wasm.wasm_globals.items.len > 0) { - const header_offset = try reserveVecSectionHeader(&binary_bytes); - - for (wasm.wasm_globals.items) |global| { - try binary_writer.writeByte(std.wasm.valtype(global.global_type.valtype)); - try binary_writer.writeByte(@intFromBool(global.global_type.mutable)); - try emitInit(binary_writer, global.init); - } - - try writeVecSectionHeader( - binary_bytes.items, - header_offset, - .global, - @intCast(binary_bytes.items.len - header_offset - header_size), - @intCast(wasm.wasm_globals.items.len), - ); - section_count += 1; - } - - // Export section - if (wasm.exports.items.len != 0 or export_memory) { - const header_offset = try reserveVecSectionHeader(&binary_bytes); - - for (wasm.exports.items) |exp| { - const name = wasm.stringSlice(exp.name); - try leb.writeUleb128(binary_writer, @as(u32, @intCast(name.len))); - try binary_writer.writeAll(name); - try leb.writeUleb128(binary_writer, @intFromEnum(exp.kind)); - try leb.writeUleb128(binary_writer, exp.index); - } - - if (export_memory) { - try leb.writeUleb128(binary_writer, @as(u32, @intCast("memory".len))); - try binary_writer.writeAll("memory"); - try binary_writer.writeByte(std.wasm.externalKind(.memory)); - try leb.writeUleb128(binary_writer, @as(u32, 0)); - } - - try writeVecSectionHeader( - binary_bytes.items, - header_offset, - .@"export", - @intCast(binary_bytes.items.len - header_offset - header_size), - @intCast(wasm.exports.items.len + @intFromBool(export_memory)), - ); - section_count += 1; - } - - if (wasm.entry) |entry_index| { - const header_offset = try reserveVecSectionHeader(&binary_bytes); - try writeVecSectionHeader( - binary_bytes.items, - header_offset, - .start, - @intCast(binary_bytes.items.len - header_offset - header_size), - entry_index, - ); - } - - // element section (function table) - if (wasm.function_table.count() > 0) { - const header_offset = try reserveVecSectionHeader(&binary_bytes); - - const table_loc = wasm.globals.get(wasm.preloaded_strings.__indirect_function_table).?; - const table_sym = wasm.symbolLocSymbol(table_loc); - - const flags: u32 = if (table_sym.index == 0) 0x0 else 0x02; // passive with implicit 0-index table or set table index manually - try leb.writeUleb128(binary_writer, flags); - if (flags == 0x02) { - try leb.writeUleb128(binary_writer, table_sym.index); - } - try emitInit(binary_writer, .{ .i32_const = 1 }); // We start at index 1, so unresolved function pointers are invalid - if (flags == 0x02) { - try leb.writeUleb128(binary_writer, @as(u8, 0)); // represents funcref - } - try leb.writeUleb128(binary_writer, @as(u32, @intCast(wasm.function_table.count()))); - var symbol_it = wasm.function_table.keyIterator(); - while (symbol_it.next()) |symbol_loc_ptr| { - const sym = wasm.symbolLocSymbol(symbol_loc_ptr.*); - std.debug.assert(sym.isAlive()); - std.debug.assert(sym.index < wasm.functions.count() + wasm.imported_functions_count); - try leb.writeUleb128(binary_writer, sym.index); - } - - try writeVecSectionHeader( - binary_bytes.items, - header_offset, - .element, - @intCast(binary_bytes.items.len - header_offset - header_size), - 1, - ); - section_count += 1; - } - - // When the shared-memory option is enabled, we *must* emit the 'data count' section. - const data_segments_count = wasm.data_segments.count() - @intFromBool(wasm.data_segments.contains(".bss") and !import_memory); - if (data_segments_count != 0 and shared_memory) { - const header_offset = try reserveVecSectionHeader(&binary_bytes); - try writeVecSectionHeader( - binary_bytes.items, - header_offset, - .data_count, - @intCast(binary_bytes.items.len - header_offset - header_size), - @intCast(data_segments_count), - ); - } - - // Code section - if (wasm.code_section_index != .none) { - const header_offset = try reserveVecSectionHeader(&binary_bytes); - const start_offset = binary_bytes.items.len - 5; // minus 5 so start offset is 5 to include entry count - - var func_it = wasm.functions.iterator(); - while (func_it.next()) |entry| { - const sym_loc: SymbolLoc = .{ .index = entry.value_ptr.sym_index, .file = entry.key_ptr.file }; - const atom_index = wasm.symbol_atom.get(sym_loc).?; - const atom = wasm.getAtomPtr(atom_index); - - if (!is_obj) { - atom.resolveRelocs(wasm); - } - atom.offset = @intCast(binary_bytes.items.len - start_offset); - try leb.writeUleb128(binary_writer, atom.size); - try binary_writer.writeAll(atom.code.items); - } - - try writeVecSectionHeader( - binary_bytes.items, - header_offset, - .code, - @intCast(binary_bytes.items.len - header_offset - header_size), - @intCast(wasm.functions.count()), - ); - code_section_index = section_count; - section_count += 1; - } - - // Data section - if (data_segments_count != 0) { - const header_offset = try reserveVecSectionHeader(&binary_bytes); - - var it = wasm.data_segments.iterator(); - var segment_count: u32 = 0; - while (it.next()) |entry| { - // do not output 'bss' section unless we import memory and therefore - // want to guarantee the data is zero initialized - if (!import_memory and std.mem.eql(u8, entry.key_ptr.*, ".bss")) continue; - const segment_index = entry.value_ptr.*; - const segment = wasm.segmentPtr(segment_index); - if (segment.size == 0) continue; // do not emit empty segments - segment_count += 1; - var atom_index = wasm.atoms.get(segment_index).?; - - try leb.writeUleb128(binary_writer, segment.flags); - if (segment.flags & @intFromEnum(Wasm.Segment.Flag.WASM_DATA_SEGMENT_HAS_MEMINDEX) != 0) { - try leb.writeUleb128(binary_writer, @as(u32, 0)); // memory is always index 0 as we only have 1 memory entry - } - // when a segment is passive, it's initialized during runtime. - if (!segment.isPassive()) { - try emitInit(binary_writer, .{ .i32_const = @as(i32, @bitCast(segment.offset)) }); - } - // offset into data section - try leb.writeUleb128(binary_writer, segment.size); - - // fill in the offset table and the data segments - var current_offset: u32 = 0; - while (true) { - const atom = wasm.getAtomPtr(atom_index); - if (!is_obj) { - atom.resolveRelocs(wasm); - } - - // Pad with zeroes to ensure all segments are aligned - if (current_offset != atom.offset) { - const diff = atom.offset - current_offset; - try binary_writer.writeByteNTimes(0, diff); - current_offset += diff; - } - assert(current_offset == atom.offset); - assert(atom.code.items.len == atom.size); - try binary_writer.writeAll(atom.code.items); - - current_offset += atom.size; - if (atom.prev != .null) { - atom_index = atom.prev; - } else { - // also pad with zeroes when last atom to ensure - // segments are aligned. - if (current_offset != segment.size) { - try binary_writer.writeByteNTimes(0, segment.size - current_offset); - current_offset += segment.size - current_offset; - } - break; - } - } - assert(current_offset == segment.size); - } - - try writeVecSectionHeader( - binary_bytes.items, - header_offset, - .data, - @intCast(binary_bytes.items.len - header_offset - header_size), - @intCast(segment_count), - ); - data_section_index = section_count; - section_count += 1; - } - - if (is_obj) { - // relocations need to point to the index of a symbol in the final symbol table. To save memory, - // we never store all symbols in a single table, but store a location reference instead. - // This means that for a relocatable object file, we need to generate one and provide it to the relocation sections. - var symbol_table = std.AutoArrayHashMap(SymbolLoc, u32).init(arena); - try wasm.emitLinkSection(&binary_bytes, &symbol_table); - if (code_section_index) |code_index| { - try wasm.emitCodeRelocations(&binary_bytes, code_index, symbol_table); - } - if (data_section_index) |data_index| { - try wasm.emitDataRelocations(&binary_bytes, data_index, symbol_table); - } - } else if (comp.config.debug_format != .strip) { - try wasm.emitNameSection(&binary_bytes, arena); - } - - if (comp.config.debug_format != .strip) { - // The build id must be computed on the main sections only, - // so we have to do it now, before the debug sections. - switch (wasm.base.build_id) { - .none => {}, - .fast => { - var id: [16]u8 = undefined; - std.crypto.hash.sha3.TurboShake128(null).hash(binary_bytes.items, &id, .{}); - var uuid: [36]u8 = undefined; - _ = try std.fmt.bufPrint(&uuid, "{s}-{s}-{s}-{s}-{s}", .{ - std.fmt.fmtSliceHexLower(id[0..4]), - std.fmt.fmtSliceHexLower(id[4..6]), - std.fmt.fmtSliceHexLower(id[6..8]), - std.fmt.fmtSliceHexLower(id[8..10]), - std.fmt.fmtSliceHexLower(id[10..]), - }); - try emitBuildIdSection(&binary_bytes, &uuid); - }, - .hexstring => |hs| { - var buffer: [32 * 2]u8 = undefined; - const str = std.fmt.bufPrint(&buffer, "{s}", .{ - std.fmt.fmtSliceHexLower(hs.toSlice()), - }) catch unreachable; - try emitBuildIdSection(&binary_bytes, str); - }, - else => |mode| { - var err = try diags.addErrorWithNotes(0); - try err.addMsg("build-id '{s}' is not supported for WebAssembly", .{@tagName(mode)}); - }, - } - - var debug_bytes = std.ArrayList(u8).init(gpa); - defer debug_bytes.deinit(); - - inline for (@typeInfo(CustomSections).@"struct".fields) |field| { - if (@field(wasm.custom_sections, field.name).index.unwrap()) |index| { - var atom = wasm.getAtomPtr(wasm.atoms.get(index).?); - while (true) { - atom.resolveRelocs(wasm); - try debug_bytes.appendSlice(atom.code.items); - if (atom.prev == .null) break; - atom = wasm.getAtomPtr(atom.prev); - } - try emitDebugSection(&binary_bytes, debug_bytes.items, field.name); - debug_bytes.clearRetainingCapacity(); - } - } - - try emitProducerSection(&binary_bytes); - if (feature_count > 0) { - try emitFeaturesSection(&binary_bytes, &enabled_features, feature_count); - } - } - - // Only when writing all sections executed properly we write the magic - // bytes. This allows us to easily detect what went wrong while generating - // the final binary. - { - const src = std.wasm.magic ++ std.wasm.version; - binary_bytes.items[0..src.len].* = src; - } - - // finally, write the entire binary into the file. - var iovec = [_]std.posix.iovec_const{.{ - .base = binary_bytes.items.ptr, - .len = binary_bytes.items.len, - }}; - try wasm.base.file.?.writevAll(&iovec); -} - -fn emitDebugSection(binary_bytes: *std.ArrayList(u8), data: []const u8, name: []const u8) !void { - if (data.len == 0) return; - const header_offset = try reserveCustomSectionHeader(binary_bytes); - const writer = binary_bytes.writer(); - try leb.writeUleb128(writer, @as(u32, @intCast(name.len))); - try writer.writeAll(name); - - const start = binary_bytes.items.len - header_offset; - log.debug("Emit debug section: '{s}' start=0x{x:0>8} end=0x{x:0>8}", .{ name, start, start + data.len }); - try writer.writeAll(data); - - try writeCustomSectionHeader( - binary_bytes.items, - header_offset, - @as(u32, @intCast(binary_bytes.items.len - header_offset - 6)), - ); -} - -fn emitProducerSection(binary_bytes: *std.ArrayList(u8)) !void { - const header_offset = try reserveCustomSectionHeader(binary_bytes); - - const writer = binary_bytes.writer(); - const producers = "producers"; - try leb.writeUleb128(writer, @as(u32, @intCast(producers.len))); - try writer.writeAll(producers); - - try leb.writeUleb128(writer, @as(u32, 2)); // 2 fields: Language + processed-by - - // used for the Zig version - var version_buf: [100]u8 = undefined; - const version = try std.fmt.bufPrint(&version_buf, "{}", .{build_options.semver}); - - // language field - { - const language = "language"; - try leb.writeUleb128(writer, @as(u32, @intCast(language.len))); - try writer.writeAll(language); - - // field_value_count (TODO: Parse object files for producer sections to detect their language) - try leb.writeUleb128(writer, @as(u32, 1)); - - // versioned name - { - try leb.writeUleb128(writer, @as(u32, 3)); // len of "Zig" - try writer.writeAll("Zig"); - - try leb.writeUleb128(writer, @as(u32, @intCast(version.len))); - try writer.writeAll(version); - } - } - - // processed-by field - { - const processed_by = "processed-by"; - try leb.writeUleb128(writer, @as(u32, @intCast(processed_by.len))); - try writer.writeAll(processed_by); - - // field_value_count (TODO: Parse object files for producer sections to detect other used tools) - try leb.writeUleb128(writer, @as(u32, 1)); - - // versioned name - { - try leb.writeUleb128(writer, @as(u32, 3)); // len of "Zig" - try writer.writeAll("Zig"); - - try leb.writeUleb128(writer, @as(u32, @intCast(version.len))); - try writer.writeAll(version); - } - } - - try writeCustomSectionHeader( - binary_bytes.items, - header_offset, - @as(u32, @intCast(binary_bytes.items.len - header_offset - 6)), - ); -} - -fn emitBuildIdSection(binary_bytes: *std.ArrayList(u8), build_id: []const u8) !void { - const header_offset = try reserveCustomSectionHeader(binary_bytes); - - const writer = binary_bytes.writer(); - const hdr_build_id = "build_id"; - try leb.writeUleb128(writer, @as(u32, @intCast(hdr_build_id.len))); - try writer.writeAll(hdr_build_id); - - try leb.writeUleb128(writer, @as(u32, 1)); - try leb.writeUleb128(writer, @as(u32, @intCast(build_id.len))); - try writer.writeAll(build_id); - - try writeCustomSectionHeader( - binary_bytes.items, - header_offset, - @as(u32, @intCast(binary_bytes.items.len - header_offset - 6)), - ); -} - -fn emitFeaturesSection(binary_bytes: *std.ArrayList(u8), enabled_features: []const bool, features_count: u32) !void { - const header_offset = try reserveCustomSectionHeader(binary_bytes); - - const writer = binary_bytes.writer(); - const target_features = "target_features"; - try leb.writeUleb128(writer, @as(u32, @intCast(target_features.len))); - try writer.writeAll(target_features); - - try leb.writeUleb128(writer, features_count); - for (enabled_features, 0..) |enabled, feature_index| { - if (enabled) { - const feature: Feature = .{ .prefix = .used, .tag = @as(Feature.Tag, @enumFromInt(feature_index)) }; - try leb.writeUleb128(writer, @intFromEnum(feature.prefix)); - var buf: [100]u8 = undefined; - const string = try std.fmt.bufPrint(&buf, "{}", .{feature.tag}); - try leb.writeUleb128(writer, @as(u32, @intCast(string.len))); - try writer.writeAll(string); - } - } - - try writeCustomSectionHeader( - binary_bytes.items, - header_offset, - @as(u32, @intCast(binary_bytes.items.len - header_offset - 6)), - ); -} - -fn emitNameSection(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), arena: std.mem.Allocator) !void { - const comp = wasm.base.comp; - const import_memory = comp.config.import_memory; - const Name = struct { - index: u32, - name: []const u8, - - fn lessThan(context: void, lhs: @This(), rhs: @This()) bool { - _ = context; - return lhs.index < rhs.index; - } - }; - - // we must de-duplicate symbols that point to the same function - var funcs = std.AutoArrayHashMap(u32, Name).init(arena); - try funcs.ensureUnusedCapacity(wasm.functions.count() + wasm.imported_functions_count); - var globals = try std.ArrayList(Name).initCapacity(arena, wasm.wasm_globals.items.len + wasm.imported_globals_count); - var segments = try std.ArrayList(Name).initCapacity(arena, wasm.data_segments.count()); - - for (wasm.resolved_symbols.keys()) |sym_loc| { - const symbol = wasm.symbolLocSymbol(sym_loc).*; - if (symbol.isDead()) { - continue; - } - const name = wasm.symbolLocName(sym_loc); - switch (symbol.tag) { - .function => { - const gop = funcs.getOrPutAssumeCapacity(symbol.index); - if (!gop.found_existing) { - gop.value_ptr.* = .{ .index = symbol.index, .name = name }; - } - }, - .global => globals.appendAssumeCapacity(.{ .index = symbol.index, .name = name }), - else => {}, - } - } - // data segments are already 'ordered' - var data_segment_index: u32 = 0; - for (wasm.data_segments.keys()) |key| { - // bss section is not emitted when this condition holds true, so we also - // do not output a name for it. - if (!import_memory and std.mem.eql(u8, key, ".bss")) continue; - segments.appendAssumeCapacity(.{ .index = data_segment_index, .name = key }); - data_segment_index += 1; - } - - mem.sort(Name, funcs.values(), {}, Name.lessThan); - mem.sort(Name, globals.items, {}, Name.lessThan); - - const header_offset = try reserveCustomSectionHeader(binary_bytes); - const writer = binary_bytes.writer(); - try leb.writeUleb128(writer, @as(u32, @intCast("name".len))); - try writer.writeAll("name"); - - try wasm.emitNameSubsection(.function, funcs.values(), writer); - try wasm.emitNameSubsection(.global, globals.items, writer); - try wasm.emitNameSubsection(.data_segment, segments.items, writer); - - try writeCustomSectionHeader( - binary_bytes.items, - header_offset, - @as(u32, @intCast(binary_bytes.items.len - header_offset - 6)), - ); -} - -fn emitNameSubsection(wasm: *Wasm, section_id: std.wasm.NameSubsection, names: anytype, writer: anytype) !void { - const gpa = wasm.base.comp.gpa; - - // We must emit subsection size, so first write to a temporary list - var section_list = std.ArrayList(u8).init(gpa); - defer section_list.deinit(); - const sub_writer = section_list.writer(); - - try leb.writeUleb128(sub_writer, @as(u32, @intCast(names.len))); - for (names) |name| { - log.debug("Emit symbol '{s}' type({s})", .{ name.name, @tagName(section_id) }); - try leb.writeUleb128(sub_writer, name.index); - try leb.writeUleb128(sub_writer, @as(u32, @intCast(name.name.len))); - try sub_writer.writeAll(name.name); - } - - // From now, write to the actual writer - try leb.writeUleb128(writer, @intFromEnum(section_id)); - try leb.writeUleb128(writer, @as(u32, @intCast(section_list.items.len))); - try writer.writeAll(section_list.items); -} - -fn emitLimits(writer: anytype, limits: std.wasm.Limits) !void { - try writer.writeByte(limits.flags); - try leb.writeUleb128(writer, limits.min); - if (limits.hasFlag(.WASM_LIMITS_FLAG_HAS_MAX)) { - try leb.writeUleb128(writer, limits.max); - } -} - -fn emitInit(writer: anytype, init_expr: std.wasm.InitExpression) !void { - switch (init_expr) { - .i32_const => |val| { - try writer.writeByte(std.wasm.opcode(.i32_const)); - try leb.writeIleb128(writer, val); - }, - .i64_const => |val| { - try writer.writeByte(std.wasm.opcode(.i64_const)); - try leb.writeIleb128(writer, val); - }, - .f32_const => |val| { - try writer.writeByte(std.wasm.opcode(.f32_const)); - try writer.writeInt(u32, @bitCast(val), .little); - }, - .f64_const => |val| { - try writer.writeByte(std.wasm.opcode(.f64_const)); - try writer.writeInt(u64, @bitCast(val), .little); - }, - .global_get => |val| { - try writer.writeByte(std.wasm.opcode(.global_get)); - try leb.writeUleb128(writer, val); - }, - } - try writer.writeByte(std.wasm.opcode(.end)); -} - -fn emitImport(wasm: *Wasm, writer: anytype, import: Import) !void { - const module_name = wasm.stringSlice(import.module_name); - try leb.writeUleb128(writer, @as(u32, @intCast(module_name.len))); - try writer.writeAll(module_name); - - const name = wasm.stringSlice(import.name); - try leb.writeUleb128(writer, @as(u32, @intCast(name.len))); - try writer.writeAll(name); - - try writer.writeByte(@intFromEnum(import.kind)); - switch (import.kind) { - .function => |type_index| try leb.writeUleb128(writer, type_index), - .global => |global_type| { - try leb.writeUleb128(writer, std.wasm.valtype(global_type.valtype)); - try writer.writeByte(@intFromBool(global_type.mutable)); - }, - .table => |table| { - try leb.writeUleb128(writer, std.wasm.reftype(table.reftype)); - try emitLimits(writer, table.limits); - }, - .memory => |limits| { - try emitLimits(writer, limits); - }, - } + wasm.flush_buffer.clear(); + defer wasm.flush_buffer.subsequent = true; + return wasm.flush_buffer.finish(wasm, arena); } fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) !void { @@ -3811,281 +2138,6 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: } } -fn reserveVecSectionHeader(bytes: *std.ArrayList(u8)) !u32 { - // section id + fixed leb contents size + fixed leb vector length - const header_size = 1 + 5 + 5; - const offset = @as(u32, @intCast(bytes.items.len)); - try bytes.appendSlice(&[_]u8{0} ** header_size); - return offset; -} - -fn reserveCustomSectionHeader(bytes: *std.ArrayList(u8)) !u32 { - // unlike regular section, we don't emit the count - const header_size = 1 + 5; - const offset = @as(u32, @intCast(bytes.items.len)); - try bytes.appendSlice(&[_]u8{0} ** header_size); - return offset; -} - -fn writeVecSectionHeader(buffer: []u8, offset: u32, section: std.wasm.Section, size: u32, items: u32) !void { - var buf: [1 + 5 + 5]u8 = undefined; - buf[0] = @intFromEnum(section); - leb.writeUnsignedFixed(5, buf[1..6], size); - leb.writeUnsignedFixed(5, buf[6..], items); - buffer[offset..][0..buf.len].* = buf; -} - -fn writeCustomSectionHeader(buffer: []u8, offset: u32, size: u32) !void { - var buf: [1 + 5]u8 = undefined; - buf[0] = 0; // 0 = 'custom' section - leb.writeUnsignedFixed(5, buf[1..6], size); - buffer[offset..][0..buf.len].* = buf; -} - -fn emitLinkSection(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), symbol_table: *std.AutoArrayHashMap(SymbolLoc, u32)) !void { - const offset = try reserveCustomSectionHeader(binary_bytes); - const writer = binary_bytes.writer(); - // emit "linking" custom section name - const section_name = "linking"; - try leb.writeUleb128(writer, section_name.len); - try writer.writeAll(section_name); - - // meta data version, which is currently '2' - try leb.writeUleb128(writer, @as(u32, 2)); - - // For each subsection type (found in Subsection) we can emit a section. - // Currently, we only support emitting segment info and the symbol table. - try wasm.emitSymbolTable(binary_bytes, symbol_table); - try wasm.emitSegmentInfo(binary_bytes); - - const size: u32 = @intCast(binary_bytes.items.len - offset - 6); - try writeCustomSectionHeader(binary_bytes.items, offset, size); -} - -fn emitSymbolTable(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), symbol_table: *std.AutoArrayHashMap(SymbolLoc, u32)) !void { - const writer = binary_bytes.writer(); - - try leb.writeUleb128(writer, @intFromEnum(SubsectionType.WASM_SYMBOL_TABLE)); - const table_offset = binary_bytes.items.len; - - var symbol_count: u32 = 0; - for (wasm.resolved_symbols.keys()) |sym_loc| { - const symbol = wasm.symbolLocSymbol(sym_loc).*; - if (symbol.tag == .dead) continue; // Do not emit dead symbols - try symbol_table.putNoClobber(sym_loc, symbol_count); - symbol_count += 1; - log.debug("Emit symbol: {}", .{symbol}); - try leb.writeUleb128(writer, @intFromEnum(symbol.tag)); - try leb.writeUleb128(writer, symbol.flags); - - const sym_name = wasm.symbolLocName(sym_loc); - switch (symbol.tag) { - .data => { - try leb.writeUleb128(writer, @as(u32, @intCast(sym_name.len))); - try writer.writeAll(sym_name); - - if (symbol.isDefined()) { - try leb.writeUleb128(writer, symbol.index); - const atom_index = wasm.symbol_atom.get(sym_loc).?; - const atom = wasm.getAtom(atom_index); - try leb.writeUleb128(writer, @as(u32, atom.offset)); - try leb.writeUleb128(writer, @as(u32, atom.size)); - } - }, - .section => { - try leb.writeUleb128(writer, symbol.index); - }, - else => { - try leb.writeUleb128(writer, symbol.index); - if (symbol.isDefined()) { - try leb.writeUleb128(writer, @as(u32, @intCast(sym_name.len))); - try writer.writeAll(sym_name); - } - }, - } - } - - var buf: [10]u8 = undefined; - leb.writeUnsignedFixed(5, buf[0..5], @intCast(binary_bytes.items.len - table_offset + 5)); - leb.writeUnsignedFixed(5, buf[5..], symbol_count); - try binary_bytes.insertSlice(table_offset, &buf); -} - -fn emitSegmentInfo(wasm: *Wasm, binary_bytes: *std.ArrayList(u8)) !void { - const writer = binary_bytes.writer(); - try leb.writeUleb128(writer, @intFromEnum(SubsectionType.WASM_SEGMENT_INFO)); - const segment_offset = binary_bytes.items.len; - - try leb.writeUleb128(writer, @as(u32, @intCast(wasm.segment_info.count()))); - for (wasm.segment_info.values()) |segment_info| { - log.debug("Emit segment: {s} align({d}) flags({b})", .{ - segment_info.name, - segment_info.alignment, - segment_info.flags, - }); - try leb.writeUleb128(writer, @as(u32, @intCast(segment_info.name.len))); - try writer.writeAll(segment_info.name); - try leb.writeUleb128(writer, segment_info.alignment.toLog2Units()); - try leb.writeUleb128(writer, segment_info.flags); - } - - var buf: [5]u8 = undefined; - leb.writeUnsignedFixed(5, &buf, @as(u32, @intCast(binary_bytes.items.len - segment_offset))); - try binary_bytes.insertSlice(segment_offset, &buf); -} - -pub fn getUleb128Size(uint_value: anytype) u32 { - const T = @TypeOf(uint_value); - const U = if (@typeInfo(T).int.bits < 8) u8 else T; - var value = @as(U, @intCast(uint_value)); - - var size: u32 = 0; - while (value != 0) : (size += 1) { - value >>= 7; - } - return size; -} - -/// For each relocatable section, emits a custom "relocation." section -fn emitCodeRelocations( - wasm: *Wasm, - binary_bytes: *std.ArrayList(u8), - section_index: u32, - symbol_table: std.AutoArrayHashMap(SymbolLoc, u32), -) !void { - const code_index = wasm.code_section_index.unwrap() orelse return; - const writer = binary_bytes.writer(); - const header_offset = try reserveCustomSectionHeader(binary_bytes); - - // write custom section information - const name = "reloc.CODE"; - try leb.writeUleb128(writer, @as(u32, @intCast(name.len))); - try writer.writeAll(name); - try leb.writeUleb128(writer, section_index); - const reloc_start = binary_bytes.items.len; - - var count: u32 = 0; - var atom: *Atom = wasm.getAtomPtr(wasm.atoms.get(code_index).?); - // for each atom, we calculate the uleb size and append that - var size_offset: u32 = 5; // account for code section size leb128 - while (true) { - size_offset += getUleb128Size(atom.size); - for (atom.relocs.items) |relocation| { - count += 1; - const sym_loc: SymbolLoc = .{ .file = atom.file, .index = @enumFromInt(relocation.index) }; - const symbol_index = symbol_table.get(sym_loc).?; - try leb.writeUleb128(writer, @intFromEnum(relocation.relocation_type)); - const offset = atom.offset + relocation.offset + size_offset; - try leb.writeUleb128(writer, offset); - try leb.writeUleb128(writer, symbol_index); - if (relocation.relocation_type.addendIsPresent()) { - try leb.writeIleb128(writer, relocation.addend); - } - log.debug("Emit relocation: {}", .{relocation}); - } - if (atom.prev == .null) break; - atom = wasm.getAtomPtr(atom.prev); - } - if (count == 0) return; - var buf: [5]u8 = undefined; - leb.writeUnsignedFixed(5, &buf, count); - try binary_bytes.insertSlice(reloc_start, &buf); - const size: u32 = @intCast(binary_bytes.items.len - header_offset - 6); - try writeCustomSectionHeader(binary_bytes.items, header_offset, size); -} - -fn emitDataRelocations( - wasm: *Wasm, - binary_bytes: *std.ArrayList(u8), - section_index: u32, - symbol_table: std.AutoArrayHashMap(SymbolLoc, u32), -) !void { - if (wasm.data_segments.count() == 0) return; - const writer = binary_bytes.writer(); - const header_offset = try reserveCustomSectionHeader(binary_bytes); - - // write custom section information - const name = "reloc.DATA"; - try leb.writeUleb128(writer, @as(u32, @intCast(name.len))); - try writer.writeAll(name); - try leb.writeUleb128(writer, section_index); - const reloc_start = binary_bytes.items.len; - - var count: u32 = 0; - // for each atom, we calculate the uleb size and append that - var size_offset: u32 = 5; // account for code section size leb128 - for (wasm.data_segments.values()) |segment_index| { - var atom: *Atom = wasm.getAtomPtr(wasm.atoms.get(segment_index).?); - while (true) { - size_offset += getUleb128Size(atom.size); - for (atom.relocs.items) |relocation| { - count += 1; - const sym_loc: SymbolLoc = .{ .file = atom.file, .index = @enumFromInt(relocation.index) }; - const symbol_index = symbol_table.get(sym_loc).?; - try leb.writeUleb128(writer, @intFromEnum(relocation.relocation_type)); - const offset = atom.offset + relocation.offset + size_offset; - try leb.writeUleb128(writer, offset); - try leb.writeUleb128(writer, symbol_index); - if (relocation.relocation_type.addendIsPresent()) { - try leb.writeIleb128(writer, relocation.addend); - } - log.debug("Emit relocation: {}", .{relocation}); - } - if (atom.prev == .null) break; - atom = wasm.getAtomPtr(atom.prev); - } - } - if (count == 0) return; - - var buf: [5]u8 = undefined; - leb.writeUnsignedFixed(5, &buf, count); - try binary_bytes.insertSlice(reloc_start, &buf); - const size = @as(u32, @intCast(binary_bytes.items.len - header_offset - 6)); - try writeCustomSectionHeader(binary_bytes.items, header_offset, size); -} - -fn hasPassiveInitializationSegments(wasm: *const Wasm) bool { - const comp = wasm.base.comp; - const import_memory = comp.config.import_memory; - - var it = wasm.data_segments.iterator(); - while (it.next()) |entry| { - const segment = wasm.segmentPtr(entry.value_ptr.*); - if (segment.needsPassiveInitialization(import_memory, entry.key_ptr.*)) { - return true; - } - } - return false; -} - -/// 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(wasm: *Wasm, func_type: std.wasm.Type) !u32 { - if (wasm.getTypeIndex(func_type)) |index| { - return index; - } - - // functype does not exist. - const gpa = wasm.base.comp.gpa; - const index: u32 = @intCast(wasm.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 wasm.func_types.append(gpa, .{ - .params = params, - .returns = returns, - }); - return index; -} - -/// For the given `nav`, stores the corresponding type representing the function signature. -/// Asserts declaration has an associated `Atom`. -/// Returns the index into the list of types. -pub fn storeNavType(wasm: *Wasm, nav: InternPool.Nav.Index, func_type: std.wasm.Type) !u32 { - return wasm.zig_object.?.storeDeclType(wasm.base.comp.gpa, nav, func_type); -} - /// Returns the symbol index of the error name table. /// /// When the symbol does not yet exist, it will create a new one instead. @@ -4094,69 +2146,6 @@ pub fn getErrorTableSymbol(wasm: *Wasm, pt: Zcu.PerThread) !u32 { return @intFromEnum(sym_index); } -/// 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 getOrCreateAtomForNav(wasm: *Wasm, pt: Zcu.PerThread, nav: InternPool.Nav.Index) !Atom.Index { - return wasm.zig_object.?.getOrCreateAtomForNav(wasm, pt, nav); -} - -/// Verifies all resolved symbols and checks whether itself needs to be marked alive, -/// as well as any of its references. -fn markReferences(wasm: *Wasm) !void { - const tracy = trace(@src()); - defer tracy.end(); - - const do_garbage_collect = wasm.base.gc_sections; - const comp = wasm.base.comp; - - for (wasm.resolved_symbols.keys()) |sym_loc| { - const sym = wasm.symbolLocSymbol(sym_loc); - if (sym.isExported(comp.config.rdynamic) or sym.isNoStrip() or !do_garbage_collect) { - try wasm.mark(sym_loc); - continue; - } - - // Debug sections may require to be parsed and marked when it contains - // relocations to alive symbols. - if (sym.tag == .section and comp.config.debug_format != .strip) { - const object_id = sym_loc.file.unwrap() orelse continue; // Incremental debug info is done independently - _ = try wasm.parseSymbolIntoAtom(object_id, sym_loc.index); - sym.mark(); - } - } -} - -/// Marks a symbol as 'alive' recursively so itself and any references it contains to -/// other symbols will not be omit from the binary. -fn mark(wasm: *Wasm, loc: SymbolLoc) !void { - const symbol = wasm.symbolLocSymbol(loc); - if (symbol.isAlive()) { - // Symbol is already marked alive, including its references. - // This means we can skip it so we don't end up marking the same symbols - // multiple times. - return; - } - symbol.mark(); - gc_log.debug("Marked symbol '{s}'", .{wasm.symbolLocName(loc)}); - if (symbol.isUndefined()) { - // undefined symbols do not have an associated `Atom` and therefore also - // do not contain relocations. - return; - } - - const atom_index = if (loc.file.unwrap()) |object_id| - try wasm.parseSymbolIntoAtom(object_id, loc.index) - else - wasm.symbol_atom.get(loc) orelse return; - - const atom = wasm.getAtom(atom_index); - for (atom.relocs.items) |reloc| { - const target_loc: SymbolLoc = .{ .index = @enumFromInt(reloc.index), .file = loc.file }; - try wasm.mark(wasm.symbolLocFinalLoc(target_loc)); - } -} - fn defaultEntrySymbolName( preloaded_strings: *const PreloadedStrings, wasi_exec_model: std.builtin.WasiExecModel, @@ -4167,573 +2156,8 @@ fn defaultEntrySymbolName( }; } -pub const Atom = struct { - /// Represents the index of the file this atom was generated from. - /// This is `none` when the atom was generated by a synthetic linker symbol. - file: OptionalObjectId, - /// symbol index of the symbol representing this atom - sym_index: Symbol.Index, - /// Size of the atom, used to calculate section sizes in the final binary - size: u32 = 0, - /// List of relocations belonging to this atom - relocs: std.ArrayListUnmanaged(Relocation) = .empty, - /// Contains the binary data of an atom, which can be non-relocated - code: std.ArrayListUnmanaged(u8) = .empty, - /// For code this is 1, for data this is set to the highest value of all segments - alignment: Wasm.Alignment = .@"1", - /// Offset into the section where the atom lives, this already accounts - /// for alignment. - offset: u32 = 0, - /// The original offset within the object file. This value is subtracted from - /// relocation offsets to determine where in the `data` to rewrite the value - original_offset: u32 = 0, - /// Previous atom in relation to this atom. - /// is null when this atom is the first in its order - prev: Atom.Index = .null, - /// Contains atoms local to a decl, all managed by this `Atom`. - /// When the parent atom is being freed, it will also do so for all local atoms. - locals: std.ArrayListUnmanaged(Atom.Index) = .empty, - - /// Represents the index of an Atom where `null` is considered - /// an invalid atom. - pub const Index = enum(u32) { - null = std.math.maxInt(u32), - _, - }; - - /// Frees all resources owned by this `Atom`. - pub fn deinit(atom: *Atom, gpa: std.mem.Allocator) void { - atom.relocs.deinit(gpa); - atom.code.deinit(gpa); - atom.locals.deinit(gpa); - atom.* = undefined; - } - - /// Sets the length of relocations and code to '0', - /// effectively resetting them and allowing them to be re-populated. - pub fn clear(atom: *Atom) void { - atom.relocs.clearRetainingCapacity(); - atom.code.clearRetainingCapacity(); - } - - pub fn format(atom: Atom, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { - _ = fmt; - _ = options; - try writer.print("Atom{{ .sym_index = {d}, .alignment = {d}, .size = {d}, .offset = 0x{x:0>8} }}", .{ - @intFromEnum(atom.sym_index), - atom.alignment, - atom.size, - atom.offset, - }); - } - - /// Returns the location of the symbol that represents this `Atom` - pub fn symbolLoc(atom: Atom) Wasm.SymbolLoc { - return .{ - .file = atom.file, - .index = atom.sym_index, - }; - } - - /// Resolves the relocations within the atom, writing the new value - /// at the calculated offset. - pub fn resolveRelocs(atom: *Atom, wasm: *const Wasm) void { - if (atom.relocs.items.len == 0) return; - const symbol_name = wasm.symbolLocName(atom.symbolLoc()); - log.debug("Resolving relocs in atom '{s}' count({d})", .{ - symbol_name, - atom.relocs.items.len, - }); - - for (atom.relocs.items) |reloc| { - const value = atom.relocationValue(reloc, wasm); - log.debug("Relocating '{s}' referenced in '{s}' offset=0x{x:0>8} value={d}", .{ - wasm.symbolLocName(.{ - .file = atom.file, - .index = @enumFromInt(reloc.index), - }), - symbol_name, - reloc.offset, - value, - }); - - switch (reloc.relocation_type) { - .R_WASM_TABLE_INDEX_I32, - .R_WASM_FUNCTION_OFFSET_I32, - .R_WASM_GLOBAL_INDEX_I32, - .R_WASM_MEMORY_ADDR_I32, - .R_WASM_SECTION_OFFSET_I32, - => std.mem.writeInt(u32, atom.code.items[reloc.offset - atom.original_offset ..][0..4], @as(u32, @truncate(value)), .little), - .R_WASM_TABLE_INDEX_I64, - .R_WASM_MEMORY_ADDR_I64, - => std.mem.writeInt(u64, atom.code.items[reloc.offset - atom.original_offset ..][0..8], value, .little), - .R_WASM_GLOBAL_INDEX_LEB, - .R_WASM_EVENT_INDEX_LEB, - .R_WASM_FUNCTION_INDEX_LEB, - .R_WASM_MEMORY_ADDR_LEB, - .R_WASM_MEMORY_ADDR_SLEB, - .R_WASM_TABLE_INDEX_SLEB, - .R_WASM_TABLE_NUMBER_LEB, - .R_WASM_TYPE_INDEX_LEB, - .R_WASM_MEMORY_ADDR_TLS_SLEB, - => leb.writeUnsignedFixed(5, atom.code.items[reloc.offset - atom.original_offset ..][0..5], @as(u32, @truncate(value))), - .R_WASM_MEMORY_ADDR_LEB64, - .R_WASM_MEMORY_ADDR_SLEB64, - .R_WASM_TABLE_INDEX_SLEB64, - .R_WASM_MEMORY_ADDR_TLS_SLEB64, - => leb.writeUnsignedFixed(10, atom.code.items[reloc.offset - atom.original_offset ..][0..10], value), - } - } - } - - /// From a given `relocation` will return the new value to be written. - /// All values will be represented as a `u64` as all values can fit within it. - /// The final value must be casted to the correct size. - fn relocationValue(atom: Atom, relocation: Relocation, wasm: *const Wasm) u64 { - const target_loc = wasm.symbolLocFinalLoc(.{ - .file = atom.file, - .index = @enumFromInt(relocation.index), - }); - const symbol = wasm.symbolLocSymbol(target_loc); - if (relocation.relocation_type != .R_WASM_TYPE_INDEX_LEB and - symbol.tag != .section and - symbol.isDead()) - { - const val = atom.tombstone(wasm) orelse relocation.addend; - return @bitCast(val); - } - switch (relocation.relocation_type) { - .R_WASM_FUNCTION_INDEX_LEB => return symbol.index, - .R_WASM_TABLE_NUMBER_LEB => return symbol.index, - .R_WASM_TABLE_INDEX_I32, - .R_WASM_TABLE_INDEX_I64, - .R_WASM_TABLE_INDEX_SLEB, - .R_WASM_TABLE_INDEX_SLEB64, - => return wasm.function_table.get(.{ .file = atom.file, .index = @enumFromInt(relocation.index) }) orelse 0, - .R_WASM_TYPE_INDEX_LEB => { - const object_id = atom.file.unwrap() orelse return relocation.index; - const original_type = objectFuncTypes(wasm, object_id)[relocation.index]; - return wasm.getTypeIndex(original_type).?; - }, - .R_WASM_GLOBAL_INDEX_I32, - .R_WASM_GLOBAL_INDEX_LEB, - => return symbol.index, - .R_WASM_MEMORY_ADDR_I32, - .R_WASM_MEMORY_ADDR_I64, - .R_WASM_MEMORY_ADDR_LEB, - .R_WASM_MEMORY_ADDR_LEB64, - .R_WASM_MEMORY_ADDR_SLEB, - .R_WASM_MEMORY_ADDR_SLEB64, - => { - std.debug.assert(symbol.tag == .data); - if (symbol.isUndefined()) { - return 0; - } - const va: i33 = @intCast(symbol.virtual_address); - return @intCast(va + relocation.addend); - }, - .R_WASM_EVENT_INDEX_LEB => return symbol.index, - .R_WASM_SECTION_OFFSET_I32 => { - const target_atom_index = wasm.symbol_atom.get(target_loc).?; - const target_atom = wasm.getAtom(target_atom_index); - const rel_value: i33 = @intCast(target_atom.offset); - return @intCast(rel_value + relocation.addend); - }, - .R_WASM_FUNCTION_OFFSET_I32 => { - if (symbol.isUndefined()) { - const val = atom.tombstone(wasm) orelse relocation.addend; - return @bitCast(val); - } - const target_atom_index = wasm.symbol_atom.get(target_loc).?; - const target_atom = wasm.getAtom(target_atom_index); - const rel_value: i33 = @intCast(target_atom.offset); - return @intCast(rel_value + relocation.addend); - }, - .R_WASM_MEMORY_ADDR_TLS_SLEB, - .R_WASM_MEMORY_ADDR_TLS_SLEB64, - => { - const va: i33 = @intCast(symbol.virtual_address); - return @intCast(va + relocation.addend); - }, - } - } - - // For a given `Atom` returns whether it has a tombstone value or not. - /// This defines whether we want a specific value when a section is dead. - fn tombstone(atom: Atom, wasm: *const Wasm) ?i64 { - const atom_name = wasm.symbolLocSymbol(atom.symbolLoc()).name; - if (atom_name == wasm.custom_sections.@".debug_ranges".name or - atom_name == wasm.custom_sections.@".debug_loc".name) - { - return -2; - } else if (std.mem.startsWith(u8, wasm.stringSlice(atom_name), ".debug_")) { - return -1; - } else { - return null; - } - } -}; - -pub const Relocation = struct { - /// Represents the type of the `Relocation` - relocation_type: RelocationType, - /// Offset of the value to rewrite relative to the relevant section's contents. - /// When `offset` is zero, its position is immediately after the id and size of the section. - offset: u32, - /// The index of the symbol used. - /// When the type is `R_WASM_TYPE_INDEX_LEB`, it represents the index of the type. - index: u32, - /// Addend to add to the address. - /// This field is only non-zero for `R_WASM_MEMORY_ADDR_*`, `R_WASM_FUNCTION_OFFSET_I32` and `R_WASM_SECTION_OFFSET_I32`. - addend: i32 = 0, - - /// All possible relocation types currently existing. - /// This enum is exhaustive as the spec is WIP and new types - /// can be added which means that a generated binary will be invalid, - /// so instead we will show an error in such cases. - pub const RelocationType = enum(u8) { - R_WASM_FUNCTION_INDEX_LEB = 0, - R_WASM_TABLE_INDEX_SLEB = 1, - R_WASM_TABLE_INDEX_I32 = 2, - R_WASM_MEMORY_ADDR_LEB = 3, - R_WASM_MEMORY_ADDR_SLEB = 4, - R_WASM_MEMORY_ADDR_I32 = 5, - R_WASM_TYPE_INDEX_LEB = 6, - R_WASM_GLOBAL_INDEX_LEB = 7, - R_WASM_FUNCTION_OFFSET_I32 = 8, - R_WASM_SECTION_OFFSET_I32 = 9, - R_WASM_EVENT_INDEX_LEB = 10, - R_WASM_GLOBAL_INDEX_I32 = 13, - R_WASM_MEMORY_ADDR_LEB64 = 14, - R_WASM_MEMORY_ADDR_SLEB64 = 15, - R_WASM_MEMORY_ADDR_I64 = 16, - R_WASM_TABLE_INDEX_SLEB64 = 18, - R_WASM_TABLE_INDEX_I64 = 19, - R_WASM_TABLE_NUMBER_LEB = 20, - R_WASM_MEMORY_ADDR_TLS_SLEB = 21, - R_WASM_MEMORY_ADDR_TLS_SLEB64 = 25, - - /// Returns true for relocation types where the `addend` field is present. - pub fn addendIsPresent(self: RelocationType) bool { - return switch (self) { - .R_WASM_MEMORY_ADDR_LEB, - .R_WASM_MEMORY_ADDR_SLEB, - .R_WASM_MEMORY_ADDR_I32, - .R_WASM_MEMORY_ADDR_LEB64, - .R_WASM_MEMORY_ADDR_SLEB64, - .R_WASM_MEMORY_ADDR_I64, - .R_WASM_MEMORY_ADDR_TLS_SLEB, - .R_WASM_MEMORY_ADDR_TLS_SLEB64, - .R_WASM_FUNCTION_OFFSET_I32, - .R_WASM_SECTION_OFFSET_I32, - => true, - else => false, - }; - } - }; - - /// Verifies the relocation type of a given `Relocation` and returns - /// true when the relocation references a function call or address to a function. - pub fn isFunction(self: Relocation) bool { - return switch (self.relocation_type) { - .R_WASM_FUNCTION_INDEX_LEB, - .R_WASM_TABLE_INDEX_SLEB, - => true, - else => false, - }; - } - - pub fn format(self: Relocation, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { - _ = fmt; - _ = options; - try writer.print("{s} offset=0x{x:0>6} symbol={d}", .{ - @tagName(self.relocation_type), - self.offset, - self.index, - }); - } -}; - -/// Unlike the `Import` object defined by the wasm spec, and existing -/// in the std.wasm namespace, this construct saves the 'module name' and 'name' -/// of the import using offsets into a string table, rather than the slices itself. -/// This saves us (potentially) 24 bytes per import on 64bit machines. -pub const Import = struct { - module_name: String, - name: String, - kind: std.wasm.Import.Kind, -}; - -/// Unlike the `Export` object defined by the wasm spec, and existing -/// in the std.wasm namespace, this construct saves the 'name' -/// of the export using offsets into a string table, rather than the slice itself. -/// This saves us (potentially) 12 bytes per export on 64bit machines. -pub const Export = struct { - name: String, - index: u32, - kind: std.wasm.ExternalKind, -}; - -pub const SubsectionType = enum(u8) { - WASM_SEGMENT_INFO = 5, - WASM_INIT_FUNCS = 6, - WASM_COMDAT_INFO = 7, - WASM_SYMBOL_TABLE = 8, -}; - -pub const Alignment = @import("../InternPool.zig").Alignment; - -pub const NamedSegment = struct { - /// Segment's name, encoded as UTF-8 bytes. - name: []const u8, - /// The required alignment of the segment, encoded as a power of 2 - alignment: Alignment, - /// Bitfield containing flags for a segment - flags: u32, - - pub fn isTLS(segment: NamedSegment) bool { - return segment.flags & @intFromEnum(Flags.WASM_SEG_FLAG_TLS) != 0; - } - - /// Returns the name as how it will be output into the final object - /// file or binary. When `merge_segments` is true, this will return the - /// short name. i.e. ".rodata". When false, it returns the entire name instead. - pub fn outputName(segment: NamedSegment, merge_segments: bool) []const u8 { - if (segment.isTLS()) { - return ".tdata"; - } else if (!merge_segments) { - return segment.name; - } else if (std.mem.startsWith(u8, segment.name, ".rodata.")) { - return ".rodata"; - } else if (std.mem.startsWith(u8, segment.name, ".text.")) { - return ".text"; - } else if (std.mem.startsWith(u8, segment.name, ".data.")) { - return ".data"; - } else if (std.mem.startsWith(u8, segment.name, ".bss.")) { - return ".bss"; - } - return segment.name; - } - - pub const Flags = enum(u32) { - WASM_SEG_FLAG_STRINGS = 0x1, - WASM_SEG_FLAG_TLS = 0x2, - }; -}; - -pub const InitFunc = struct { - /// Priority of the init function - priority: u32, - /// The symbol index of init function (not the function index). - symbol_index: u32, -}; - -pub const Comdat = struct { - name: []const u8, - /// Must be zero, no flags are currently defined by the tool-convention. - flags: u32, - symbols: []const ComdatSym, -}; - -pub const ComdatSym = struct { - kind: @This().Type, - /// Index of the data segment/function/global/event/table within a WASM module. - /// The object must not be an import. - index: u32, - - pub const Type = enum(u8) { - WASM_COMDAT_DATA = 0, - WASM_COMDAT_FUNCTION = 1, - WASM_COMDAT_GLOBAL = 2, - WASM_COMDAT_EVENT = 3, - WASM_COMDAT_TABLE = 4, - WASM_COMDAT_SECTION = 5, - }; -}; - -pub const Feature = struct { - /// Provides information about the usage of the feature. - /// - '0x2b' (+): Object uses this feature, and the link fails if feature is not in the allowed set. - /// - '0x2d' (-): Object does not use this feature, and the link fails if this feature is in the allowed set. - /// - '0x3d' (=): Object uses this feature, and the link fails if this feature is not in the allowed set, - /// or if any object does not use this feature. - prefix: Prefix, - /// Type of the feature, must be unique in the sequence of features. - tag: Tag, - - /// Unlike `std.Target.wasm.Feature` this also contains linker-features such as shared-mem - pub const Tag = enum { - atomics, - bulk_memory, - exception_handling, - extended_const, - half_precision, - multimemory, - multivalue, - mutable_globals, - nontrapping_fptoint, - reference_types, - relaxed_simd, - sign_ext, - simd128, - tail_call, - shared_mem, - - /// From a given cpu feature, returns its linker feature - pub fn fromCpuFeature(feature: std.Target.wasm.Feature) Tag { - return @as(Tag, @enumFromInt(@intFromEnum(feature))); - } - - pub fn format(tag: Tag, comptime fmt: []const u8, opt: std.fmt.FormatOptions, writer: anytype) !void { - _ = fmt; - _ = opt; - try writer.writeAll(switch (tag) { - .atomics => "atomics", - .bulk_memory => "bulk-memory", - .exception_handling => "exception-handling", - .extended_const => "extended-const", - .half_precision => "half-precision", - .multimemory => "multimemory", - .multivalue => "multivalue", - .mutable_globals => "mutable-globals", - .nontrapping_fptoint => "nontrapping-fptoint", - .reference_types => "reference-types", - .relaxed_simd => "relaxed-simd", - .sign_ext => "sign-ext", - .simd128 => "simd128", - .tail_call => "tail-call", - .shared_mem => "shared-mem", - }); - } - }; - - pub const Prefix = enum(u8) { - used = '+', - disallowed = '-', - required = '=', - }; - - pub fn format(feature: Feature, comptime fmt: []const u8, opt: std.fmt.FormatOptions, writer: anytype) !void { - _ = opt; - _ = fmt; - try writer.print("{c} {}", .{ feature.prefix, feature.tag }); - } -}; - -pub const known_features = std.StaticStringMap(Feature.Tag).initComptime(.{ - .{ "atomics", .atomics }, - .{ "bulk-memory", .bulk_memory }, - .{ "exception-handling", .exception_handling }, - .{ "extended-const", .extended_const }, - .{ "half-precision", .half_precision }, - .{ "multimemory", .multimemory }, - .{ "multivalue", .multivalue }, - .{ "mutable-globals", .mutable_globals }, - .{ "nontrapping-fptoint", .nontrapping_fptoint }, - .{ "reference-types", .reference_types }, - .{ "relaxed-simd", .relaxed_simd }, - .{ "sign-ext", .sign_ext }, - .{ "simd128", .simd128 }, - .{ "tail-call", .tail_call }, - .{ "shared-mem", .shared_mem }, -}); - -/// Parses an object file into atoms, for code and data sections -fn parseSymbolIntoAtom(wasm: *Wasm, object_id: ObjectId, symbol_index: Symbol.Index) !Atom.Index { - const object = wasm.objectById(object_id) orelse - return wasm.zig_object.?.parseSymbolIntoAtom(wasm, symbol_index); - const comp = wasm.base.comp; - const gpa = comp.gpa; - const symbol = &object.symtable[@intFromEnum(symbol_index)]; - const relocatable_data: Object.RelocatableData = switch (symbol.tag) { - .function => object.relocatable_data.get(.code).?[symbol.index - object.imported_functions_count], - .data => object.relocatable_data.get(.data).?[symbol.index], - .section => blk: { - const data = object.relocatable_data.get(.custom).?; - for (data) |dat| { - if (dat.section_index == symbol.index) { - break :blk dat; - } - } - unreachable; - }, - else => unreachable, - }; - const final_index = try wasm.getMatchingSegment(object_id, symbol_index); - const atom_index = try wasm.createAtom(symbol_index, object_id.toOptional()); - try wasm.appendAtomAtIndex(final_index, atom_index); - - const atom = wasm.getAtomPtr(atom_index); - atom.size = relocatable_data.size; - atom.alignment = relocatable_data.getAlignment(object); - atom.code = std.ArrayListUnmanaged(u8).fromOwnedSlice(relocatable_data.data[0..relocatable_data.size]); - atom.original_offset = relocatable_data.offset; - - const segment = wasm.segmentPtr(final_index); - if (relocatable_data.type == .data) { //code section and custom sections are 1-byte aligned - segment.alignment = segment.alignment.max(atom.alignment); - } - - if (object.relocations.get(relocatable_data.section_index)) |relocations| { - const start = searchRelocStart(relocations, relocatable_data.offset); - const len = searchRelocEnd(relocations[start..], relocatable_data.offset + atom.size); - atom.relocs = std.ArrayListUnmanaged(Wasm.Relocation).fromOwnedSlice(relocations[start..][0..len]); - for (atom.relocs.items) |reloc| { - 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 = object_id.toOptional(), - .index = @enumFromInt(reloc.index), - }, 0); - }, - .R_WASM_GLOBAL_INDEX_I32, - .R_WASM_GLOBAL_INDEX_LEB, - => { - const sym = object.symtable[reloc.index]; - if (sym.tag != .global) { - try wasm.got_symbols.append(gpa, .{ - .file = object_id.toOptional(), - .index = @enumFromInt(reloc.index), - }); - } - }, - else => {}, - } - } - } - - return atom_index; -} - -fn searchRelocStart(relocs: []const Wasm.Relocation, address: u32) usize { - var min: usize = 0; - var max: usize = relocs.len; - while (min < max) { - const index = (min + max) / 2; - const curr = relocs[index]; - if (curr.offset < address) { - min = index + 1; - } else { - max = index; - } - } - return min; -} - -fn searchRelocEnd(relocs: []const Wasm.Relocation, address: u32) usize { - for (relocs, 0..relocs.len) |reloc, index| { - if (reloc.offset > address) { - return index; - } - } - return relocs.len; -} - pub fn internString(wasm: *Wasm, bytes: []const u8) error{OutOfMemory}!String { + assert(mem.indexOfScalar(u8, bytes, 0) == null); const gpa = wasm.base.comp.gpa; const gop = try wasm.string_table.getOrPutContextAdapted( gpa, @@ -4755,25 +2179,34 @@ pub fn internString(wasm: *Wasm, bytes: []const u8) error{OutOfMemory}!String { } pub fn getExistingString(wasm: *const Wasm, bytes: []const u8) ?String { + assert(mem.indexOfScalar(u8, bytes, 0) == null); return wasm.string_table.getKeyAdapted(bytes, @as(String.TableIndexAdapter, .{ .bytes = wasm.string_bytes.items, })); } -pub fn stringSlice(wasm: *const Wasm, index: String) [:0]const u8 { - const slice = wasm.string_bytes.items[@intFromEnum(index)..]; - return slice[0..mem.indexOfScalar(u8, slice, 0).? :0]; +pub fn internValtypeList(wasm: *Wasm, valtype_list: []const std.wasm.Valtype) error{OutOfMemory}!ValtypeList { + return .fromString(try internString(wasm, @ptrCast(valtype_list))); } -pub fn optionalStringSlice(wasm: *const Wasm, index: OptionalString) ?[:0]const u8 { - return stringSlice(wasm, index.unwrap() orelse return null); +pub fn addFuncType(wasm: *Wasm, ft: FunctionType) error{OutOfMemory}!FunctionType.Index { + const gpa = wasm.base.comp.gpa; + const gop = try wasm.func_types.getOrPut(gpa, ft); + return @enumFromInt(gop.index); } -pub fn castToString(wasm: *const Wasm, index: u32) String { - assert(index == 0 or wasm.string_bytes.items[index - 1] == 0); - return @enumFromInt(index); +pub fn addExpr(wasm: *Wasm, bytes: []const u8) error{OutOfMemory}!Expr { + const gpa = wasm.base.comp.gpa; + // We can't use string table deduplication here since these expressions can + // have null bytes in them however it may be interesting to explore since + // it is likely for globals to share initialization values. Then again + // there may not be very many globals in total. + try wasm.string_bytes.appendSlice(gpa, bytes); + return @enumFromInt(wasm.string_bytes.items.len - bytes.len); } -fn segmentPtr(wasm: *const Wasm, index: Segment.Index) *Segment { - return &wasm.segments.items[@intFromEnum(index)]; +pub fn addRelocatableDataPayload(wasm: *Wasm, bytes: []const u8) error{OutOfMemory}!DataSegment.Payload { + const gpa = wasm.base.comp.gpa; + try wasm.string_bytes.appendSlice(gpa, bytes); + return @enumFromInt(wasm.string_bytes.items.len - bytes.len); } diff --git a/src/link/Wasm/Archive.zig b/src/link/Wasm/Archive.zig index c2078fa525..97c654211f 100644 --- a/src/link/Wasm/Archive.zig +++ b/src/link/Wasm/Archive.zig @@ -142,7 +142,16 @@ pub fn parse(gpa: Allocator, file_contents: []const u8) !Archive { /// From a given file offset, starts reading for a file header. /// When found, parses the object file into an `Object` and returns it. -pub fn parseObject(archive: Archive, wasm: *Wasm, file_contents: []const u8, path: Path) !Object { +pub fn parseObject( + archive: Archive, + wasm: *Wasm, + file_contents: []const u8, + path: Path, + host_name: Wasm.String, + scratch_space: *Object.ScratchSpace, + must_link: bool, + gc_sections: bool, +) !Object { const header = mem.bytesAsValue(Header, file_contents[0..@sizeOf(Header)]); if (!mem.eql(u8, &header.fmag, ARFMAG)) return error.BadHeaderDelimiter; @@ -157,8 +166,9 @@ pub fn parseObject(archive: Archive, wasm: *Wasm, file_contents: []const u8, pat }; const object_file_size = try header.parsedSize(); + const contents = file_contents[@sizeOf(Header)..][0..object_file_size]; - return Object.create(wasm, file_contents[@sizeOf(Header)..][0..object_file_size], path, object_name); + return Object.parse(wasm, contents, path, object_name, host_name, scratch_space, must_link, gc_sections); } const Archive = @This(); diff --git a/src/link/Wasm/Flush.zig b/src/link/Wasm/Flush.zig new file mode 100644 index 0000000000..eec21df757 --- /dev/null +++ b/src/link/Wasm/Flush.zig @@ -0,0 +1,1448 @@ +//! Temporary, dynamically allocated structures used only during flush. +//! Could be constructed fresh each time, or kept around between updates to reduce heap allocations. + +const Flush = @This(); +const Wasm = @import("../Wasm.zig"); +const Object = @import("Object.zig"); +const Zcu = @import("../../Zcu.zig"); +const Alignment = Wasm.Alignment; +const String = Wasm.String; +const Relocation = Wasm.Relocation; +const InternPool = @import("../../InternPool.zig"); + +const build_options = @import("build_options"); + +const std = @import("std"); +const Allocator = std.mem.Allocator; +const mem = std.mem; +const leb = std.leb; +const log = std.log.scoped(.link); +const assert = std.debug.assert; + +/// Ordered list of data segments that will appear in the final binary. +/// When sorted, to-be-merged segments will be made adjacent. +/// Values are offset relative to segment start. +data_segments: std.AutoArrayHashMapUnmanaged(Wasm.DataSegment.Index, u32) = .empty, +/// Each time a `data_segment` offset equals zero it indicates a new group, and +/// the next element in this array will contain the total merged segment size. +data_segment_groups: std.ArrayListUnmanaged(u32) = .empty, + +binary_bytes: std.ArrayListUnmanaged(u8) = .empty, +missing_exports: std.AutoArrayHashMapUnmanaged(String, void) = .empty, + +indirect_function_table: std.AutoArrayHashMapUnmanaged(Wasm.OutputFunctionIndex, u32) = .empty, + +/// 0. Index into `data_segments`. +const DataSegmentIndex = enum(u32) { + _, +}; + +pub fn clear(f: *Flush) void { + f.binary_bytes.clearRetainingCapacity(); + f.function_imports.clearRetainingCapacity(); + f.global_imports.clearRetainingCapacity(); + f.functions.clearRetainingCapacity(); + f.globals.clearRetainingCapacity(); + f.data_segments.clearRetainingCapacity(); + f.data_segment_groups.clearRetainingCapacity(); + f.indirect_function_table.clearRetainingCapacity(); + f.function_exports.clearRetainingCapacity(); + f.global_exports.clearRetainingCapacity(); +} + +pub fn deinit(f: *Flush, gpa: Allocator) void { + f.binary_bytes.deinit(gpa); + f.function_imports.deinit(gpa); + f.global_imports.deinit(gpa); + f.functions.deinit(gpa); + f.globals.deinit(gpa); + f.data_segments.deinit(gpa); + f.data_segment_groups.deinit(gpa); + f.indirect_function_table.deinit(gpa); + f.function_exports.deinit(gpa); + f.global_exports.deinit(gpa); + f.* = undefined; +} + +pub fn finish(f: *Flush, wasm: *Wasm, arena: Allocator) anyerror!void { + const comp = wasm.base.comp; + const shared_memory = comp.config.shared_memory; + const diags = &comp.link_diags; + const gpa = comp.gpa; + const import_memory = comp.config.import_memory; + const export_memory = comp.config.export_memory; + const target = comp.root_mod.resolved_target.result; + const is_obj = comp.config.output_mode == .Obj; + const allow_undefined = is_obj or wasm.import_symbols; + const zcu = wasm.base.comp.zcu.?; + const ip: *const InternPool = &zcu.intern_pool; // No mutations allowed! + + if (wasm.any_exports_updated) { + wasm.any_exports_updated = false; + wasm.function_exports.shrinkRetainingCapacity(wasm.function_exports_len); + wasm.global_exports.shrinkRetainingCapacity(wasm.global_exports_len); + + const entry_name = if (wasm.entry_resolution.isNavOrUnresolved(wasm)) wasm.entry_name else .none; + + try f.missing_exports.reinit(gpa, wasm.missing_exports_init, &.{}); + for (wasm.nav_exports.keys()) |*nav_export| { + if (ip.isFunctionType(ip.getNav(nav_export.nav_index).typeOf(ip))) { + try wasm.function_exports.append(gpa, .fromNav(nav_export.nav_index, wasm)); + if (nav_export.name.toOptional() == entry_name) { + wasm.entry_resolution = .pack(wasm, .{ .nav = nav_export.nav_index }); + } else { + f.missing_exports.swapRemove(nav_export.name); + } + } else { + try wasm.global_exports.append(gpa, .fromNav(nav_export.nav_index)); + f.missing_exports.swapRemove(nav_export.name); + } + } + + for (f.missing_exports.keys()) |exp_name| { + if (exp_name != .none) continue; + diags.addError("manually specified export name '{s}' undefined", .{exp_name.slice(wasm)}); + } + + if (entry_name.unwrap()) |name| { + var err = try diags.addErrorWithNotes(1); + try err.addMsg("entry symbol '{s}' missing", .{name.slice(wasm)}); + try err.addNote("'-fno-entry' suppresses this error", .{}); + } + } + + if (!allow_undefined) { + for (wasm.function_imports.keys()) |function_import_id| { + const name, const src_loc = function_import_id.nameAndLoc(wasm); + diags.addSrcError(src_loc, "undefined function: {s}", .{name.slice(wasm)}); + } + for (wasm.global_imports.keys()) |global_import_id| { + const name, const src_loc = global_import_id.nameAndLoc(wasm); + diags.addSrcError(src_loc, "undefined global: {s}", .{name.slice(wasm)}); + } + for (wasm.table_imports.keys()) |table_import_id| { + const name, const src_loc = table_import_id.nameAndLoc(wasm); + diags.addSrcError(src_loc, "undefined table: {s}", .{name.slice(wasm)}); + } + } + + if (diags.hasErrors()) return error.LinkFailure; + + // TODO only include init functions for objects with must_link=true or + // which have any alive functions inside them. + if (wasm.object_init_funcs.items.len > 0) { + // Zig has no constructors so these are only for object file inputs. + mem.sortUnstable(Wasm.InitFunc, wasm.object_init_funcs.items, {}, Wasm.InitFunc.lessThan); + try f.functions.put(gpa, .__wasm_call_ctors, {}); + } + + var any_passive_inits = false; + + // Merge and order the data segments. Depends on garbage collection so that + // unused segments can be omitted. + try f.ensureUnusedCapacity(gpa, wasm.object_data_segments.items.len); + for (wasm.object_data_segments.items, 0..) |*ds, i| { + if (!ds.flags.alive) continue; + any_passive_inits = any_passive_inits or ds.flags.is_passive or (import_memory and !isBss(wasm, ds.name)); + f.data_segments.putAssumeCapacityNoClobber(@intCast(i), .{ + .offset = undefined, + }); + } + + try f.functions.ensureUnusedCapacity(gpa, 3); + + // Passive segments are used to avoid memory being reinitialized on each + // thread's instantiation. These passive segments are initialized and + // dropped in __wasm_init_memory, which is registered as the start function + // We also initialize bss segments (using memory.fill) as part of this + // function. + if (any_passive_inits) { + f.functions.putAssumeCapacity(.__wasm_init_memory, {}); + } + + // When we have TLS GOT entries and shared memory is enabled, + // we must perform runtime relocations or else we don't create the function. + if (shared_memory) { + if (f.need_tls_relocs) f.functions.putAssumeCapacity(.__wasm_apply_global_tls_relocs, {}); + f.functions.putAssumeCapacity(gpa, .__wasm_init_tls, {}); + } + + // Sort order: + // 0. Whether the segment is TLS + // 1. Segment name prefix + // 2. Segment alignment + // 3. Segment name suffix + // 4. Segment index (to break ties, keeping it deterministic) + // TLS segments are intended to be merged with each other, and segments + // with a common prefix name are intended to be merged with each other. + // Sorting ensures the segments intended to be merged will be adjacent. + const Sort = struct { + wasm: *const Wasm, + segments: []const Wasm.DataSegment.Index, + pub fn lessThan(ctx: @This(), lhs: usize, rhs: usize) bool { + const lhs_segment_index = ctx.segments[lhs]; + const rhs_segment_index = ctx.segments[rhs]; + const lhs_segment = lhs_segment_index.ptr(wasm); + const rhs_segment = rhs_segment_index.ptr(wasm); + const lhs_tls = @intFromBool(lhs_segment.flags.tls); + const rhs_tls = @intFromBool(rhs_segment.flags.tls); + if (lhs_tls < rhs_tls) return true; + if (lhs_tls > rhs_tls) return false; + const lhs_prefix, const lhs_suffix = splitSegmentName(lhs_segment.name.unwrap().slice(ctx.wasm)); + const rhs_prefix, const rhs_suffix = splitSegmentName(rhs_segment.name.unwrap().slice(ctx.wasm)); + switch (mem.order(u8, lhs_prefix, rhs_prefix)) { + .lt => return true, + .gt => return false, + .eq => {}, + } + switch (lhs_segment.flags.alignment.order(rhs_segment.flags.alignment)) { + .lt => return false, + .gt => return true, + .eq => {}, + } + return switch (mem.order(u8, lhs_suffix, rhs_suffix)) { + .lt => true, + .gt => false, + .eq => @intFromEnum(lhs_segment_index) < @intFromEnum(rhs_segment_index), + }; + } + }; + f.data_segments.sortUnstable(@as(Sort, .{ + .wasm = wasm, + .segments = f.data_segments.keys(), + })); + + const page_size = std.wasm.page_size; // 64kb + const stack_alignment: Alignment = .@"16"; // wasm's stack alignment as specified by tool-convention + const heap_alignment: Alignment = .@"16"; // wasm's heap alignment as specified by tool-convention + const pointer_alignment: Alignment = .@"4"; + // Always place the stack at the start by default unless the user specified the global-base flag. + const place_stack_first, var memory_ptr: u32 = if (wasm.global_base) |base| .{ false, base } else .{ true, 0 }; + + const VirtualAddrs = struct { + stack_pointer: u32, + heap_base: u32, + heap_end: u32, + tls_base: ?u32, + tls_align: ?u32, + tls_size: ?u32, + init_memory_flag: ?u32, + }; + var virtual_addrs: VirtualAddrs = .{ + .stack_pointer = undefined, + .heap_base = undefined, + .heap_end = undefined, + .tls_base = null, + .tls_align = null, + .tls_size = null, + .init_memory_flag = null, + }; + + if (place_stack_first and !is_obj) { + memory_ptr = stack_alignment.forward(memory_ptr); + memory_ptr += wasm.base.stack_size; + virtual_addrs.stack_pointer = memory_ptr; + } + + const segment_indexes = f.data_segments.keys(); + const segment_offsets = f.data_segments.values(); + assert(f.data_segment_groups.items.len == 0); + { + var seen_tls: enum { before, during, after } = .before; + var offset: u32 = 0; + for (segment_indexes, segment_offsets, 0..) |segment_index, *segment_offset, i| { + const segment = segment_index.ptr(f); + memory_ptr = segment.alignment.forward(memory_ptr); + + const want_new_segment = b: { + if (is_obj) break :b false; + switch (seen_tls) { + .before => if (segment.flags.tls) { + virtual_addrs.tls_base = if (shared_memory) 0 else memory_ptr; + virtual_addrs.tls_align = segment.flags.alignment; + seen_tls = .during; + break :b true; + }, + .during => if (!segment.flags.tls) { + virtual_addrs.tls_size = memory_ptr - virtual_addrs.tls_base; + virtual_addrs.tls_align = virtual_addrs.tls_align.maxStrict(segment.flags.alignment); + seen_tls = .after; + break :b true; + }, + .after => {}, + } + break :b i >= 1 and !wasm.wantSegmentMerge(segment_indexes[i - 1], segment_index); + }; + if (want_new_segment) { + if (offset > 0) try f.data_segment_groups.append(gpa, offset); + offset = 0; + } + + segment_offset.* = offset; + offset += segment.size; + memory_ptr += segment.size; + } + if (offset > 0) try f.data_segment_groups.append(gpa, offset); + } + + if (shared_memory and any_passive_inits) { + memory_ptr = pointer_alignment.forward(memory_ptr); + virtual_addrs.init_memory_flag = memory_ptr; + memory_ptr += 4; + } + + if (!place_stack_first and !is_obj) { + memory_ptr = stack_alignment.forward(memory_ptr); + memory_ptr += wasm.base.stack_size; + virtual_addrs.stack_pointer = memory_ptr; + } + + memory_ptr = heap_alignment.forward(memory_ptr); + virtual_addrs.heap_base = memory_ptr; + + if (wasm.initial_memory) |initial_memory| { + if (!mem.isAlignedGeneric(u64, initial_memory, page_size)) { + diags.addError("initial memory value {d} is not {d}-byte aligned", .{ initial_memory, page_size }); + } + if (memory_ptr > initial_memory) { + diags.addError("initial memory value {d} insufficient; minimum {d}", .{ initial_memory, memory_ptr }); + } + if (initial_memory > std.math.maxInt(u32)) { + diags.addError("initial memory value {d} exceeds 32-bit address space", .{initial_memory}); + } + if (diags.hasErrors()) return error.LinkFailure; + memory_ptr = initial_memory; + } else { + memory_ptr = mem.alignForward(u64, memory_ptr, std.wasm.page_size); + } + virtual_addrs.heap_end = memory_ptr; + + // In case we do not import memory, but define it ourselves, set the + // minimum amount of pages on the memory section. + wasm.memories.limits.min = @intCast(memory_ptr / page_size); + log.debug("total memory pages: {d}", .{wasm.memories.limits.min}); + + if (wasm.max_memory) |max_memory| { + if (!mem.isAlignedGeneric(u64, max_memory, page_size)) { + diags.addError("maximum memory value {d} is not {d}-byte aligned", .{ max_memory, page_size }); + } + if (memory_ptr > max_memory) { + diags.addError("maximum memory value {d} insufficient; minimum {d}", .{ max_memory, memory_ptr }); + } + if (max_memory > std.math.maxInt(u32)) { + diags.addError("maximum memory exceeds 32-bit address space", .{max_memory}); + } + if (diags.hasErrors()) return error.LinkFailure; + wasm.memories.limits.max = @intCast(max_memory / page_size); + wasm.memories.limits.flags.has_max = true; + if (shared_memory) wasm.memories.limits.flags.is_shared = true; + log.debug("maximum memory pages: {?d}", .{wasm.memories.limits.max}); + } + + // Size of each section header + const header_size = 5 + 1; + var section_index: u32 = 0; + // Index of the code section. Used to tell relocation table where the section lives. + var code_section_index: ?u32 = null; + // Index of the data section. Used to tell relocation table where the section lives. + var data_section_index: ?u32 = null; + + const binary_bytes = &f.binary_bytes; + assert(binary_bytes.items.len == 0); + + try binary_bytes.appendSlice(gpa, std.wasm.magic ++ std.wasm.version); + assert(binary_bytes.items.len == 8); + + const binary_writer = binary_bytes.writer(gpa); + + // Type section + if (wasm.func_types.items.len != 0) { + const header_offset = try reserveVecSectionHeader(gpa, binary_bytes); + log.debug("Writing type section. Count: ({d})", .{wasm.func_types.items.len}); + for (wasm.func_types.items) |func_type| { + try leb.writeUleb128(binary_writer, std.wasm.function_type); + try leb.writeUleb128(binary_writer, @as(u32, @intCast(func_type.params.len))); + for (func_type.params) |param_ty| { + try leb.writeUleb128(binary_writer, std.wasm.valtype(param_ty)); + } + try leb.writeUleb128(binary_writer, @as(u32, @intCast(func_type.returns.len))); + for (func_type.returns) |ret_ty| { + try leb.writeUleb128(binary_writer, std.wasm.valtype(ret_ty)); + } + } + + try writeVecSectionHeader( + binary_bytes.items, + header_offset, + .type, + @intCast(binary_bytes.items.len - header_offset - header_size), + @intCast(wasm.func_types.items.len), + ); + section_index += 1; + } + + // Import section + const total_imports_len = wasm.function_imports.items.len + wasm.global_imports.items.len + + wasm.table_imports.items.len + wasm.memory_imports.items.len + @intFromBool(import_memory); + + if (total_imports_len > 0) { + const header_offset = try reserveVecSectionHeader(gpa, binary_bytes); + + for (wasm.function_imports.items) |*function_import| { + const module_name = function_import.module_name.slice(wasm); + try leb.writeUleb128(binary_writer, @as(u32, @intCast(module_name.len))); + try binary_writer.writeAll(module_name); + + const name = function_import.name.slice(wasm); + try leb.writeUleb128(binary_writer, @as(u32, @intCast(name.len))); + try binary_writer.writeAll(name); + + try binary_writer.writeByte(@intFromEnum(std.wasm.ExternalKind.function)); + try leb.writeUleb128(binary_writer, function_import.index); + } + + for (wasm.table_imports.items) |*table_import| { + const module_name = table_import.module_name.slice(wasm); + try leb.writeUleb128(binary_writer, @as(u32, @intCast(module_name.len))); + try binary_writer.writeAll(module_name); + + const name = table_import.name.slice(wasm); + try leb.writeUleb128(binary_writer, @as(u32, @intCast(name.len))); + try binary_writer.writeAll(name); + + try binary_writer.writeByte(@intFromEnum(std.wasm.ExternalKind.table)); + try leb.writeUleb128(binary_writer, std.wasm.reftype(table_import.reftype)); + try emitLimits(binary_writer, table_import.limits); + } + + for (wasm.memory_imports.items) |*memory_import| { + try emitMemoryImport(wasm, binary_writer, memory_import); + } else if (import_memory) { + try emitMemoryImport(wasm, binary_writer, &.{ + .module_name = wasm.host_name, + .name = if (is_obj) wasm.preloaded_strings.__linear_memory else wasm.preloaded_strings.memory, + .limits_min = wasm.memories.limits.min, + .limits_max = wasm.memories.limits.max, + .limits_has_max = wasm.memories.limits.flags.has_max, + .limits_is_shared = wasm.memories.limits.flags.is_shared, + }); + } + + for (wasm.global_imports.items) |*global_import| { + const module_name = global_import.module_name.slice(wasm); + try leb.writeUleb128(binary_writer, @as(u32, @intCast(module_name.len))); + try binary_writer.writeAll(module_name); + + const name = global_import.name.slice(wasm); + try leb.writeUleb128(binary_writer, @as(u32, @intCast(name.len))); + try binary_writer.writeAll(name); + + try binary_writer.writeByte(@intFromEnum(std.wasm.ExternalKind.global)); + try leb.writeUleb128(binary_writer, @intFromEnum(global_import.valtype)); + try binary_writer.writeByte(@intFromBool(global_import.mutable)); + } + + try writeVecSectionHeader( + binary_bytes.items, + header_offset, + .import, + @intCast(binary_bytes.items.len - header_offset - header_size), + @intCast(total_imports_len), + ); + section_index += 1; + } + + // Function section + if (wasm.functions.count() != 0) { + const header_offset = try reserveVecSectionHeader(gpa, binary_bytes); + for (wasm.functions.values()) |function| { + try leb.writeUleb128(binary_writer, function.func.type_index); + } + + try writeVecSectionHeader( + binary_bytes.items, + header_offset, + .function, + @intCast(binary_bytes.items.len - header_offset - header_size), + @intCast(wasm.functions.count()), + ); + section_index += 1; + } + + // Table section + if (wasm.tables.items.len > 0) { + const header_offset = try reserveVecSectionHeader(gpa, binary_bytes); + + for (wasm.tables.items) |table| { + try leb.writeUleb128(binary_writer, std.wasm.reftype(table.reftype)); + try emitLimits(binary_writer, table.limits); + } + + try writeVecSectionHeader( + binary_bytes.items, + header_offset, + .table, + @intCast(binary_bytes.items.len - header_offset - header_size), + @intCast(wasm.tables.items.len), + ); + section_index += 1; + } + + // Memory section + if (!import_memory) { + const header_offset = try reserveVecSectionHeader(gpa, binary_bytes); + + try emitLimits(binary_writer, wasm.memories.limits); + try writeVecSectionHeader( + binary_bytes.items, + header_offset, + .memory, + @intCast(binary_bytes.items.len - header_offset - header_size), + 1, // wasm currently only supports 1 linear memory segment + ); + section_index += 1; + } + + // Global section (used to emit stack pointer) + if (wasm.output_globals.items.len > 0) { + const header_offset = try reserveVecSectionHeader(gpa, binary_bytes); + + for (wasm.output_globals.items) |global| { + try binary_writer.writeByte(std.wasm.valtype(global.global_type.valtype)); + try binary_writer.writeByte(@intFromBool(global.global_type.mutable)); + try emitInit(binary_writer, global.init); + } + + try writeVecSectionHeader( + binary_bytes.items, + header_offset, + .global, + @intCast(binary_bytes.items.len - header_offset - header_size), + @intCast(wasm.output_globals.items.len), + ); + section_index += 1; + } + + // Export section + if (wasm.exports.items.len != 0 or export_memory) { + const header_offset = try reserveVecSectionHeader(gpa, binary_bytes); + + for (wasm.exports.items) |exp| { + const name = exp.name.slice(wasm); + try leb.writeUleb128(binary_writer, @as(u32, @intCast(name.len))); + try binary_writer.writeAll(name); + try leb.writeUleb128(binary_writer, @intFromEnum(exp.kind)); + try leb.writeUleb128(binary_writer, exp.index); + } + + if (export_memory) { + try leb.writeUleb128(binary_writer, @as(u32, @intCast("memory".len))); + try binary_writer.writeAll("memory"); + try binary_writer.writeByte(std.wasm.externalKind(.memory)); + try leb.writeUleb128(binary_writer, @as(u32, 0)); + } + + try writeVecSectionHeader( + binary_bytes.items, + header_offset, + .@"export", + @intCast(binary_bytes.items.len - header_offset - header_size), + @intCast(wasm.exports.items.len + @intFromBool(export_memory)), + ); + section_index += 1; + } + + if (wasm.entry) |entry_index| { + const header_offset = try reserveVecSectionHeader(gpa, binary_bytes); + try writeVecSectionHeader( + binary_bytes.items, + header_offset, + .start, + @intCast(binary_bytes.items.len - header_offset - header_size), + entry_index, + ); + } + + // element section (function table) + if (wasm.function_table.count() > 0) { + const header_offset = try reserveVecSectionHeader(gpa, binary_bytes); + + const table_loc = wasm.globals.get(wasm.preloaded_strings.__indirect_function_table).?; + const table_sym = wasm.finalSymbolByLoc(table_loc); + + const flags: u32 = if (table_sym.index == 0) 0x0 else 0x02; // passive with implicit 0-index table or set table index manually + try leb.writeUleb128(binary_writer, flags); + if (flags == 0x02) { + try leb.writeUleb128(binary_writer, table_sym.index); + } + try emitInit(binary_writer, .{ .i32_const = 1 }); // We start at index 1, so unresolved function pointers are invalid + if (flags == 0x02) { + try leb.writeUleb128(binary_writer, @as(u8, 0)); // represents funcref + } + try leb.writeUleb128(binary_writer, @as(u32, @intCast(wasm.function_table.count()))); + var symbol_it = wasm.function_table.keyIterator(); + while (symbol_it.next()) |symbol_loc_ptr| { + const sym = wasm.finalSymbolByLoc(symbol_loc_ptr.*); + assert(sym.flags.alive); + assert(sym.index < wasm.functions.count() + wasm.imported_functions_count); + try leb.writeUleb128(binary_writer, sym.index); + } + + try writeVecSectionHeader( + binary_bytes.items, + header_offset, + .element, + @intCast(binary_bytes.items.len - header_offset - header_size), + 1, + ); + section_index += 1; + } + + // When the shared-memory option is enabled, we *must* emit the 'data count' section. + if (f.data_segment_groups.items.len > 0 and shared_memory) { + const header_offset = try reserveVecSectionHeader(gpa, binary_bytes); + try writeVecSectionHeader( + binary_bytes.items, + header_offset, + .data_count, + @intCast(binary_bytes.items.len - header_offset - header_size), + @intCast(f.data_segment_groups.items.len), + ); + } + + // Code section. + if (f.functions.count() != 0) { + const header_offset = try reserveVecSectionHeader(gpa, binary_bytes); + const start_offset = binary_bytes.items.len - 5; // minus 5 so start offset is 5 to include entry count + + for (f.functions.keys()) |resolution| switch (resolution.unpack()) { + .unresolved => unreachable, + .__wasm_apply_global_tls_relocs => @panic("TODO lower __wasm_apply_global_tls_relocs"), + .__wasm_call_ctors => @panic("TODO lower __wasm_call_ctors"), + .__wasm_init_memory => @panic("TODO lower __wasm_init_memory "), + .__wasm_init_tls => @panic("TODO lower __wasm_init_tls "), + .__zig_error_names => @panic("TODO lower __zig_error_names "), + .object_function => |i| { + _ = i; + _ = start_offset; + @panic("TODO lower object function code and apply relocations"); + //try leb.writeUleb128(binary_writer, atom.code.len); + //try binary_bytes.appendSlice(gpa, atom.code.slice(wasm)); + }, + .nav => |i| { + _ = i; + _ = start_offset; + @panic("TODO lower nav code and apply relocations"); + //try leb.writeUleb128(binary_writer, atom.code.len); + //try binary_bytes.appendSlice(gpa, atom.code.slice(wasm)); + }, + }; + + try writeVecSectionHeader( + binary_bytes.items, + header_offset, + .code, + @intCast(binary_bytes.items.len - header_offset - header_size), + @intCast(wasm.functions.count()), + ); + code_section_index = section_index; + section_index += 1; + } + + // Data section. + if (f.data_segment_groups.items.len != 0) { + const header_offset = try reserveVecSectionHeader(gpa, binary_bytes); + + var group_index: u32 = 0; + var offset: u32 = undefined; + for (segment_indexes, segment_offsets) |segment_index, segment_offset| { + const segment = segment_index.ptr(wasm); + if (segment.size == 0) continue; + if (!import_memory and isBss(wasm, segment.name)) { + // It counted for virtual memory but it does not go into the binary. + continue; + } + if (segment_offset == 0) { + const group_size = f.data_segment_groups.items[group_index]; + group_index += 1; + offset = 0; + + const flags: Object.DataSegmentFlags = if (segment.flags.is_passive) .passive else .active; + try leb.writeUleb128(binary_writer, @intFromEnum(flags)); + // when a segment is passive, it's initialized during runtime. + if (flags != .passive) { + try emitInit(binary_writer, .{ .i32_const = @as(i32, @bitCast(segment_offset)) }); + } + try leb.writeUleb128(binary_writer, group_size); + } + + try binary_bytes.appendNTimes(gpa, 0, segment_offset - offset); + offset = segment_offset; + try binary_bytes.appendSlice(gpa, segment.payload.slice(wasm)); + offset += segment.payload.len; + if (true) @panic("TODO apply data segment relocations"); + } + assert(group_index == f.data_segment_groups.items.len); + + try writeVecSectionHeader( + binary_bytes.items, + header_offset, + .data, + @intCast(binary_bytes.items.len - header_offset - header_size), + group_index, + ); + data_section_index = section_index; + section_index += 1; + } + + if (is_obj) { + @panic("TODO emit link section for object file and apply relocations"); + //var symbol_table = std.AutoArrayHashMap(SymbolLoc, u32).init(arena); + //try wasm.emitLinkSection(binary_bytes, &symbol_table); + //if (code_section_index) |code_index| { + // try wasm.emitCodeRelocations(binary_bytes, code_index, symbol_table); + //} + //if (data_section_index) |data_index| { + // if (wasm.data_segments.count() > 0) + // try wasm.emitDataRelocations(binary_bytes, data_index, symbol_table); + //} + } else if (comp.config.debug_format != .strip) { + try wasm.emitNameSection(binary_bytes, arena); + } + + if (comp.config.debug_format != .strip) { + // The build id must be computed on the main sections only, + // so we have to do it now, before the debug sections. + switch (wasm.base.build_id) { + .none => {}, + .fast => { + var id: [16]u8 = undefined; + std.crypto.hash.sha3.TurboShake128(null).hash(binary_bytes.items, &id, .{}); + var uuid: [36]u8 = undefined; + _ = try std.fmt.bufPrint(&uuid, "{s}-{s}-{s}-{s}-{s}", .{ + std.fmt.fmtSliceHexLower(id[0..4]), + std.fmt.fmtSliceHexLower(id[4..6]), + std.fmt.fmtSliceHexLower(id[6..8]), + std.fmt.fmtSliceHexLower(id[8..10]), + std.fmt.fmtSliceHexLower(id[10..]), + }); + try emitBuildIdSection(binary_bytes, &uuid); + }, + .hexstring => |hs| { + var buffer: [32 * 2]u8 = undefined; + const str = std.fmt.bufPrint(&buffer, "{s}", .{ + std.fmt.fmtSliceHexLower(hs.toSlice()), + }) catch unreachable; + try emitBuildIdSection(binary_bytes, str); + }, + else => |mode| { + var err = try diags.addErrorWithNotes(0); + try err.addMsg("build-id '{s}' is not supported for WebAssembly", .{@tagName(mode)}); + }, + } + + var debug_bytes = std.ArrayList(u8).init(gpa); + defer debug_bytes.deinit(); + + try emitProducerSection(binary_bytes); + if (!target.cpu.features.isEmpty()) + try emitFeaturesSection(binary_bytes, target.cpu.features); + } + + // Finally, write the entire binary into the file. + const file = wasm.base.file.?; + try file.pwriteAll(binary_bytes.items, 0); + try file.setEndPos(binary_bytes.items.len); +} + +fn emitNameSection(wasm: *Wasm, binary_bytes: *std.ArrayListUnmanaged(u8), arena: Allocator) !void { + const comp = wasm.base.comp; + const gpa = comp.gpa; + const import_memory = comp.config.import_memory; + + // Deduplicate symbols that point to the same function. + var funcs: std.AutoArrayHashMapUnmanaged(u32, String) = .empty; + try funcs.ensureUnusedCapacityPrecise(arena, wasm.functions.count() + wasm.function_imports.items.len); + + const NamedIndex = struct { + index: u32, + name: String, + }; + + var globals: std.MultiArrayList(NamedIndex) = .empty; + try globals.ensureTotalCapacityPrecise(arena, wasm.output_globals.items.len + wasm.global_imports.items.len); + + var segments: std.MultiArrayList(NamedIndex) = .empty; + try segments.ensureTotalCapacityPrecise(arena, wasm.data_segments.count()); + + for (wasm.resolved_symbols.keys()) |sym_loc| { + const symbol = wasm.finalSymbolByLoc(sym_loc).*; + if (!symbol.flags.alive) continue; + const name = wasm.finalSymbolByLoc(sym_loc).name; + switch (symbol.tag) { + .function => { + const index = if (symbol.flags.undefined) + @intFromEnum(symbol.pointee.function_import) + else + wasm.function_imports.items.len + @intFromEnum(symbol.pointee.function); + const gop = funcs.getOrPutAssumeCapacity(index); + if (gop.found_existing) { + assert(gop.value_ptr.* == name); + } else { + gop.value_ptr.* = name; + } + }, + .global => { + globals.appendAssumeCapacity(.{ + .index = if (symbol.flags.undefined) + @intFromEnum(symbol.pointee.global_import) + else + @intFromEnum(symbol.pointee.global), + .name = name, + }); + }, + else => {}, + } + } + + for (wasm.data_segments.keys(), 0..) |key, index| { + // bss section is not emitted when this condition holds true, so we also + // do not output a name for it. + if (!import_memory and mem.eql(u8, key, ".bss")) continue; + segments.appendAssumeCapacity(.{ .index = @intCast(index), .name = key }); + } + + const Sort = struct { + indexes: []const u32, + pub fn lessThan(ctx: @This(), lhs: usize, rhs: usize) bool { + return ctx.indexes[lhs] < ctx.indexes[rhs]; + } + }; + funcs.entries.sortUnstable(@as(Sort, .{ .indexes = funcs.keys() })); + globals.sortUnstable(@as(Sort, .{ .indexes = globals.items(.index) })); + // Data segments are already ordered. + + const header_offset = try reserveCustomSectionHeader(gpa, binary_bytes); + const writer = binary_bytes.writer(); + try leb.writeUleb128(writer, @as(u32, @intCast("name".len))); + try writer.writeAll("name"); + + try emitNameSubsection(wasm, binary_bytes, .function, funcs.keys(), funcs.values()); + try emitNameSubsection(wasm, binary_bytes, .global, globals.items(.index), globals.items(.name)); + try emitNameSubsection(wasm, binary_bytes, .data_segment, segments.items(.index), segments.items(.name)); + + try writeCustomSectionHeader( + binary_bytes.items, + header_offset, + @as(u32, @intCast(binary_bytes.items.len - header_offset - 6)), + ); +} + +fn writeCustomSectionHeader(buffer: []u8, offset: u32, size: u32) !void { + var buf: [1 + 5]u8 = undefined; + buf[0] = 0; // 0 = 'custom' section + leb.writeUnsignedFixed(5, buf[1..6], size); + buffer[offset..][0..buf.len].* = buf; +} + +fn reserveCustomSectionHeader(gpa: Allocator, bytes: *std.ArrayListUnmanaged(u8)) error{OutOfMemory}!u32 { + // unlike regular section, we don't emit the count + const header_size = 1 + 5; + try bytes.appendNTimes(gpa, 0, header_size); + return @intCast(bytes.items.len - header_size); +} + +fn emitNameSubsection( + wasm: *const Wasm, + binary_bytes: *std.ArrayListUnmanaged(u8), + section_id: std.wasm.NameSubsection, + indexes: []const u32, + names: []const String, +) !void { + assert(indexes.len == names.len); + const gpa = wasm.base.comp.gpa; + // We must emit subsection size, so first write to a temporary list + var section_list: std.ArrayListUnmanaged(u8) = .empty; + defer section_list.deinit(gpa); + const sub_writer = section_list.writer(gpa); + + try leb.writeUleb128(sub_writer, @as(u32, @intCast(names.len))); + for (indexes, names) |index, name_index| { + const name = name_index.slice(wasm); + log.debug("emit symbol '{s}' type({s})", .{ name, @tagName(section_id) }); + try leb.writeUleb128(sub_writer, index); + try leb.writeUleb128(sub_writer, @as(u32, @intCast(name.len))); + try sub_writer.writeAll(name); + } + + // From now, write to the actual writer + const writer = binary_bytes.writer(gpa); + try leb.writeUleb128(writer, @intFromEnum(section_id)); + try leb.writeUleb128(writer, @as(u32, @intCast(section_list.items.len))); + try binary_bytes.appendSlice(gpa, section_list.items); +} + +fn emitFeaturesSection( + gpa: Allocator, + binary_bytes: *std.ArrayListUnmanaged(u8), + features: []const Wasm.Feature, +) !void { + const header_offset = try reserveCustomSectionHeader(gpa, binary_bytes); + + const writer = binary_bytes.writer(); + const target_features = "target_features"; + try leb.writeUleb128(writer, @as(u32, @intCast(target_features.len))); + try writer.writeAll(target_features); + + try leb.writeUleb128(writer, @as(u32, @intCast(features.len))); + for (features) |feature| { + assert(feature.prefix != .invalid); + try leb.writeUleb128(writer, @tagName(feature.prefix)[0]); + const name = @tagName(feature.tag); + try leb.writeUleb128(writer, @as(u32, name.len)); + try writer.writeAll(name); + } + + try writeCustomSectionHeader( + binary_bytes.items, + header_offset, + @as(u32, @intCast(binary_bytes.items.len - header_offset - 6)), + ); +} + +fn emitBuildIdSection(gpa: Allocator, binary_bytes: *std.ArrayListUnmanaged(u8), build_id: []const u8) !void { + const header_offset = try reserveCustomSectionHeader(gpa, binary_bytes); + + const writer = binary_bytes.writer(); + const hdr_build_id = "build_id"; + try leb.writeUleb128(writer, @as(u32, @intCast(hdr_build_id.len))); + try writer.writeAll(hdr_build_id); + + try leb.writeUleb128(writer, @as(u32, 1)); + try leb.writeUleb128(writer, @as(u32, @intCast(build_id.len))); + try writer.writeAll(build_id); + + try writeCustomSectionHeader( + binary_bytes.items, + header_offset, + @as(u32, @intCast(binary_bytes.items.len - header_offset - 6)), + ); +} + +fn emitProducerSection(gpa: Allocator, binary_bytes: *std.ArrayListUnmanaged(u8)) !void { + const header_offset = try reserveCustomSectionHeader(gpa, binary_bytes); + + const writer = binary_bytes.writer(); + const producers = "producers"; + try leb.writeUleb128(writer, @as(u32, @intCast(producers.len))); + try writer.writeAll(producers); + + try leb.writeUleb128(writer, @as(u32, 2)); // 2 fields: Language + processed-by + + // language field + { + const language = "language"; + try leb.writeUleb128(writer, @as(u32, @intCast(language.len))); + try writer.writeAll(language); + + // field_value_count (TODO: Parse object files for producer sections to detect their language) + try leb.writeUleb128(writer, @as(u32, 1)); + + // versioned name + { + try leb.writeUleb128(writer, @as(u32, 3)); // len of "Zig" + try writer.writeAll("Zig"); + + try leb.writeUleb128(writer, @as(u32, @intCast(build_options.version.len))); + try writer.writeAll(build_options.version); + } + } + + // processed-by field + { + const processed_by = "processed-by"; + try leb.writeUleb128(writer, @as(u32, @intCast(processed_by.len))); + try writer.writeAll(processed_by); + + // field_value_count (TODO: Parse object files for producer sections to detect other used tools) + try leb.writeUleb128(writer, @as(u32, 1)); + + // versioned name + { + try leb.writeUleb128(writer, @as(u32, 3)); // len of "Zig" + try writer.writeAll("Zig"); + + try leb.writeUleb128(writer, @as(u32, @intCast(build_options.version.len))); + try writer.writeAll(build_options.version); + } + } + + try writeCustomSectionHeader( + binary_bytes.items, + header_offset, + @as(u32, @intCast(binary_bytes.items.len - header_offset - 6)), + ); +} + +///// For each relocatable section, emits a custom "relocation." section +//fn emitCodeRelocations( +// wasm: *Wasm, +// binary_bytes: *std.ArrayListUnmanaged(u8), +// section_index: u32, +// symbol_table: std.AutoArrayHashMapUnmanaged(SymbolLoc, u32), +//) !void { +// const comp = wasm.base.comp; +// const gpa = comp.gpa; +// const code_index = wasm.code_section_index.unwrap() orelse return; +// const writer = binary_bytes.writer(); +// const header_offset = try reserveCustomSectionHeader(gpa, binary_bytes); +// +// // write custom section information +// const name = "reloc.CODE"; +// try leb.writeUleb128(writer, @as(u32, @intCast(name.len))); +// try writer.writeAll(name); +// try leb.writeUleb128(writer, section_index); +// const reloc_start = binary_bytes.items.len; +// +// var count: u32 = 0; +// var atom: *Atom = wasm.atoms.get(code_index).?.ptr(wasm); +// // for each atom, we calculate the uleb size and append that +// var size_offset: u32 = 5; // account for code section size leb128 +// while (true) { +// size_offset += getUleb128Size(atom.code.len); +// for (atom.relocSlice(wasm)) |relocation| { +// count += 1; +// const sym_loc: SymbolLoc = .{ .file = atom.file, .index = @enumFromInt(relocation.index) }; +// const symbol_index = symbol_table.get(sym_loc).?; +// try leb.writeUleb128(writer, @intFromEnum(relocation.tag)); +// const offset = atom.offset + relocation.offset + size_offset; +// try leb.writeUleb128(writer, offset); +// try leb.writeUleb128(writer, symbol_index); +// if (relocation.tag.addendIsPresent()) { +// try leb.writeIleb128(writer, relocation.addend); +// } +// log.debug("Emit relocation: {}", .{relocation}); +// } +// if (atom.prev == .none) break; +// atom = atom.prev.ptr(wasm); +// } +// if (count == 0) return; +// var buf: [5]u8 = undefined; +// leb.writeUnsignedFixed(5, &buf, count); +// try binary_bytes.insertSlice(reloc_start, &buf); +// const size: u32 = @intCast(binary_bytes.items.len - header_offset - 6); +// try writeCustomSectionHeader(binary_bytes.items, header_offset, size); +//} + +//fn emitDataRelocations( +// wasm: *Wasm, +// binary_bytes: *std.ArrayList(u8), +// section_index: u32, +// symbol_table: std.AutoArrayHashMap(SymbolLoc, u32), +//) !void { +// const comp = wasm.base.comp; +// const gpa = comp.gpa; +// const writer = binary_bytes.writer(); +// const header_offset = try reserveCustomSectionHeader(gpa, binary_bytes); +// +// // write custom section information +// const name = "reloc.DATA"; +// try leb.writeUleb128(writer, @as(u32, @intCast(name.len))); +// try writer.writeAll(name); +// try leb.writeUleb128(writer, section_index); +// const reloc_start = binary_bytes.items.len; +// +// var count: u32 = 0; +// // for each atom, we calculate the uleb size and append that +// var size_offset: u32 = 5; // account for code section size leb128 +// for (wasm.data_segments.values()) |segment_index| { +// var atom: *Atom = wasm.atoms.get(segment_index).?.ptr(wasm); +// while (true) { +// size_offset += getUleb128Size(atom.code.len); +// for (atom.relocSlice(wasm)) |relocation| { +// count += 1; +// const sym_loc: SymbolLoc = .{ .file = atom.file, .index = @enumFromInt(relocation.index) }; +// const symbol_index = symbol_table.get(sym_loc).?; +// try leb.writeUleb128(writer, @intFromEnum(relocation.tag)); +// const offset = atom.offset + relocation.offset + size_offset; +// try leb.writeUleb128(writer, offset); +// try leb.writeUleb128(writer, symbol_index); +// if (relocation.tag.addendIsPresent()) { +// try leb.writeIleb128(writer, relocation.addend); +// } +// log.debug("Emit relocation: {}", .{relocation}); +// } +// if (atom.prev == .none) break; +// atom = atom.prev.ptr(wasm); +// } +// } +// if (count == 0) return; +// +// var buf: [5]u8 = undefined; +// leb.writeUnsignedFixed(5, &buf, count); +// try binary_bytes.insertSlice(reloc_start, &buf); +// const size = @as(u32, @intCast(binary_bytes.items.len - header_offset - 6)); +// try writeCustomSectionHeader(binary_bytes.items, header_offset, size); +//} + +fn isBss(wasm: *Wasm, name: String) bool { + const s = name.slice(wasm); + return mem.eql(u8, s, ".bss") or mem.startsWith(u8, s, ".bss."); +} + +fn splitSegmentName(name: []const u8) struct { []const u8, []const u8 } { + const start = @intFromBool(name.len >= 1 and name[0] == '.'); + const pivot = mem.indexOfScalarPos(u8, name, start, '.') orelse 0; + return .{ name[0..pivot], name[pivot..] }; +} + +fn wantSegmentMerge(wasm: *const Wasm, a_index: Wasm.DataSegment.Index, b_index: Wasm.DataSegment.Index) bool { + const a = a_index.ptr(wasm); + const b = b_index.ptr(wasm); + if (a.flags.tls and b.flags.tls) return true; + if (a.flags.tls != b.flags.tls) return false; + if (a.flags.is_passive != b.flags.is_passive) return false; + if (a.name == b.name) return true; + const a_prefix, _ = splitSegmentName(a.name.slice(wasm)); + const b_prefix, _ = splitSegmentName(b.name.slice(wasm)); + return a_prefix.len > 0 and mem.eql(u8, a_prefix, b_prefix); +} + +fn reserveVecSectionHeader(gpa: Allocator, bytes: *std.ArrayListUnmanaged(u8)) error{OutOfMemory}!u32 { + // section id + fixed leb contents size + fixed leb vector length + const header_size = 1 + 5 + 5; + try bytes.appendNTimes(gpa, 0, header_size); + return @intCast(bytes.items.len - header_size); +} + +fn writeVecSectionHeader(buffer: []u8, offset: u32, section: std.wasm.Section, size: u32, items: u32) !void { + var buf: [1 + 5 + 5]u8 = undefined; + buf[0] = @intFromEnum(section); + leb.writeUnsignedFixed(5, buf[1..6], size); + leb.writeUnsignedFixed(5, buf[6..], items); + buffer[offset..][0..buf.len].* = buf; +} + +fn emitLimits(writer: anytype, limits: std.wasm.Limits) !void { + try writer.writeByte(limits.flags); + try leb.writeUleb128(writer, limits.min); + if (limits.flags.has_max) try leb.writeUleb128(writer, limits.max); +} + +fn emitMemoryImport(wasm: *Wasm, writer: anytype, memory_import: *const Wasm.MemoryImport) error{OutOfMemory}!void { + const module_name = memory_import.module_name.slice(wasm); + try leb.writeUleb128(writer, @as(u32, @intCast(module_name.len))); + try writer.writeAll(module_name); + + const name = memory_import.name.slice(wasm); + try leb.writeUleb128(writer, @as(u32, @intCast(name.len))); + try writer.writeAll(name); + + try writer.writeByte(@intFromEnum(std.wasm.ExternalKind.memory)); + try emitLimits(writer, memory_import.limits()); +} + +pub fn emitInit(writer: anytype, init_expr: std.wasm.InitExpression) !void { + switch (init_expr) { + .i32_const => |val| { + try writer.writeByte(@intFromEnum(std.wasm.Opcode.i32_const)); + try leb.writeIleb128(writer, val); + }, + .i64_const => |val| { + try writer.writeByte(@intFromEnum(std.wasm.Opcode.i64_const)); + try leb.writeIleb128(writer, val); + }, + .f32_const => |val| { + try writer.writeByte(@intFromEnum(std.wasm.Opcode.f32_const)); + try writer.writeInt(u32, @bitCast(val), .little); + }, + .f64_const => |val| { + try writer.writeByte(@intFromEnum(std.wasm.Opcode.f64_const)); + try writer.writeInt(u64, @bitCast(val), .little); + }, + .global_get => |val| { + try writer.writeByte(@intFromEnum(std.wasm.Opcode.global_get)); + try leb.writeUleb128(writer, val); + }, + } + try writer.writeByte(@intFromEnum(std.wasm.Opcode.end)); +} + +//fn emitLinkSection( +// wasm: *Wasm, +// binary_bytes: *std.ArrayListUnmanaged(u8), +// symbol_table: *std.AutoArrayHashMapUnmanaged(SymbolLoc, u32), +//) !void { +// const gpa = wasm.base.comp.gpa; +// const offset = try reserveCustomSectionHeader(gpa, binary_bytes); +// const writer = binary_bytes.writer(); +// // emit "linking" custom section name +// const section_name = "linking"; +// try leb.writeUleb128(writer, section_name.len); +// try writer.writeAll(section_name); +// +// // meta data version, which is currently '2' +// try leb.writeUleb128(writer, @as(u32, 2)); +// +// // For each subsection type (found in Subsection) we can emit a section. +// // Currently, we only support emitting segment info and the symbol table. +// try wasm.emitSymbolTable(binary_bytes, symbol_table); +// try wasm.emitSegmentInfo(binary_bytes); +// +// const size: u32 = @intCast(binary_bytes.items.len - offset - 6); +// try writeCustomSectionHeader(binary_bytes.items, offset, size); +//} + +fn emitSegmentInfo(wasm: *Wasm, binary_bytes: *std.ArrayList(u8)) !void { + const writer = binary_bytes.writer(); + try leb.writeUleb128(writer, @intFromEnum(Wasm.SubsectionType.segment_info)); + const segment_offset = binary_bytes.items.len; + + try leb.writeUleb128(writer, @as(u32, @intCast(wasm.segment_info.count()))); + for (wasm.segment_info.values()) |segment_info| { + log.debug("Emit segment: {s} align({d}) flags({b})", .{ + segment_info.name, + segment_info.alignment, + segment_info.flags, + }); + try leb.writeUleb128(writer, @as(u32, @intCast(segment_info.name.len))); + try writer.writeAll(segment_info.name); + try leb.writeUleb128(writer, segment_info.alignment.toLog2Units()); + try leb.writeUleb128(writer, segment_info.flags); + } + + var buf: [5]u8 = undefined; + leb.writeUnsignedFixed(5, &buf, @as(u32, @intCast(binary_bytes.items.len - segment_offset))); + try binary_bytes.insertSlice(segment_offset, &buf); +} + +//fn emitSymbolTable( +// wasm: *Wasm, +// binary_bytes: *std.ArrayListUnmanaged(u8), +// symbol_table: *std.AutoArrayHashMapUnmanaged(SymbolLoc, u32), +//) !void { +// const gpa = wasm.base.comp.gpa; +// const writer = binary_bytes.writer(gpa); +// +// try leb.writeUleb128(writer, @intFromEnum(SubsectionType.symbol_table)); +// const table_offset = binary_bytes.items.len; +// +// var symbol_count: u32 = 0; +// for (wasm.resolved_symbols.keys()) |sym_loc| { +// const symbol = wasm.finalSymbolByLoc(sym_loc).*; +// if (symbol.tag == .dead) continue; +// try symbol_table.putNoClobber(gpa, sym_loc, symbol_count); +// symbol_count += 1; +// log.debug("emit symbol: {}", .{symbol}); +// try leb.writeUleb128(writer, @intFromEnum(symbol.tag)); +// try leb.writeUleb128(writer, symbol.flags); +// +// const sym_name = wasm.symbolLocName(sym_loc); +// switch (symbol.tag) { +// .data => { +// try leb.writeUleb128(writer, @as(u32, @intCast(sym_name.len))); +// try writer.writeAll(sym_name); +// +// if (!symbol.flags.undefined) { +// try leb.writeUleb128(writer, @intFromEnum(symbol.pointee.data_out)); +// const atom_index = wasm.symbol_atom.get(sym_loc).?; +// const atom = wasm.getAtom(atom_index); +// try leb.writeUleb128(writer, @as(u32, atom.offset)); +// try leb.writeUleb128(writer, @as(u32, atom.code.len)); +// } +// }, +// .section => { +// try leb.writeUleb128(writer, @intFromEnum(symbol.pointee.section)); +// }, +// .function => { +// if (symbol.flags.undefined) { +// try leb.writeUleb128(writer, @intFromEnum(symbol.pointee.function_import)); +// } else { +// try leb.writeUleb128(writer, @intFromEnum(symbol.pointee.function)); +// try leb.writeUleb128(writer, @as(u32, @intCast(sym_name.len))); +// try writer.writeAll(sym_name); +// } +// }, +// .global => { +// if (symbol.flags.undefined) { +// try leb.writeUleb128(writer, @intFromEnum(symbol.pointee.global_import)); +// } else { +// try leb.writeUleb128(writer, @intFromEnum(symbol.pointee.global)); +// try leb.writeUleb128(writer, @as(u32, @intCast(sym_name.len))); +// try writer.writeAll(sym_name); +// } +// }, +// .table => { +// if (symbol.flags.undefined) { +// try leb.writeUleb128(writer, @intFromEnum(symbol.pointee.table_import)); +// } else { +// try leb.writeUleb128(writer, @intFromEnum(symbol.pointee.table)); +// try leb.writeUleb128(writer, @as(u32, @intCast(sym_name.len))); +// try writer.writeAll(sym_name); +// } +// }, +// .event => unreachable, +// .dead => unreachable, +// .uninitialized => unreachable, +// } +// } +// +// var buf: [10]u8 = undefined; +// leb.writeUnsignedFixed(5, buf[0..5], @intCast(binary_bytes.items.len - table_offset + 5)); +// leb.writeUnsignedFixed(5, buf[5..], symbol_count); +// try binary_bytes.insertSlice(table_offset, &buf); +//} + +///// Resolves the relocations within the atom, writing the new value +///// at the calculated offset. +//fn resolveAtomRelocs(wasm: *const Wasm, atom: *Atom) void { +// const symbol_name = wasm.symbolLocName(atom.symbolLoc()); +// log.debug("resolving {d} relocs in atom '{s}'", .{ atom.relocs.len, symbol_name }); +// +// for (atom.relocSlice(wasm)) |reloc| { +// const value = atomRelocationValue(wasm, atom, reloc); +// log.debug("relocating '{s}' referenced in '{s}' offset=0x{x:0>8} value={d}", .{ +// wasm.symbolLocName(.{ +// .file = atom.file, +// .index = @enumFromInt(reloc.index), +// }), +// symbol_name, +// reloc.offset, +// value, +// }); +// +// switch (reloc.tag) { +// .TABLE_INDEX_I32, +// .FUNCTION_OFFSET_I32, +// .GLOBAL_INDEX_I32, +// .MEMORY_ADDR_I32, +// .SECTION_OFFSET_I32, +// => mem.writeInt(u32, atom.code.slice(wasm)[reloc.offset - atom.original_offset ..][0..4], @as(u32, @truncate(value)), .little), +// +// .TABLE_INDEX_I64, +// .MEMORY_ADDR_I64, +// => mem.writeInt(u64, atom.code.slice(wasm)[reloc.offset - atom.original_offset ..][0..8], value, .little), +// +// .GLOBAL_INDEX_LEB, +// .EVENT_INDEX_LEB, +// .FUNCTION_INDEX_LEB, +// .MEMORY_ADDR_LEB, +// .MEMORY_ADDR_SLEB, +// .TABLE_INDEX_SLEB, +// .TABLE_NUMBER_LEB, +// .TYPE_INDEX_LEB, +// .MEMORY_ADDR_TLS_SLEB, +// => leb.writeUnsignedFixed(5, atom.code.slice(wasm)[reloc.offset - atom.original_offset ..][0..5], @as(u32, @truncate(value))), +// +// .MEMORY_ADDR_LEB64, +// .MEMORY_ADDR_SLEB64, +// .TABLE_INDEX_SLEB64, +// .MEMORY_ADDR_TLS_SLEB64, +// => leb.writeUnsignedFixed(10, atom.code.slice(wasm)[reloc.offset - atom.original_offset ..][0..10], value), +// } +// } +//} + +///// From a given `relocation` will return the new value to be written. +///// All values will be represented as a `u64` as all values can fit within it. +///// The final value must be casted to the correct size. +//fn atomRelocationValue(wasm: *const Wasm, atom: *const Atom, relocation: *const Relocation) u64 { +// if (relocation.tag == .TYPE_INDEX_LEB) { +// // Eagerly resolved when parsing the object file. +// if (true) @panic("TODO the eager resolve when parsing"); +// return relocation.index; +// } +// const target_loc = wasm.symbolLocFinalLoc(.{ +// .file = atom.file, +// .index = @enumFromInt(relocation.index), +// }); +// const symbol = wasm.finalSymbolByLoc(target_loc); +// if (symbol.tag != .section and !symbol.flags.alive) { +// const val = atom.tombstone(wasm) orelse relocation.addend; +// return @bitCast(val); +// } +// return switch (relocation.tag) { +// .FUNCTION_INDEX_LEB => if (symbol.flags.undefined) +// @intFromEnum(symbol.pointee.function_import) +// else +// @intFromEnum(symbol.pointee.function) + wasm.function_imports.items.len, +// .TABLE_NUMBER_LEB => if (symbol.flags.undefined) +// @intFromEnum(symbol.pointee.table_import) +// else +// @intFromEnum(symbol.pointee.table) + wasm.table_imports.items.len, +// .TABLE_INDEX_I32, +// .TABLE_INDEX_I64, +// .TABLE_INDEX_SLEB, +// .TABLE_INDEX_SLEB64, +// => wasm.function_table.get(.{ .file = atom.file, .index = @enumFromInt(relocation.index) }) orelse 0, +// +// .TYPE_INDEX_LEB => unreachable, // handled above +// .GLOBAL_INDEX_I32, .GLOBAL_INDEX_LEB => if (symbol.flags.undefined) +// @intFromEnum(symbol.pointee.global_import) +// else +// @intFromEnum(symbol.pointee.global) + wasm.global_imports.items.len, +// +// .MEMORY_ADDR_I32, +// .MEMORY_ADDR_I64, +// .MEMORY_ADDR_LEB, +// .MEMORY_ADDR_LEB64, +// .MEMORY_ADDR_SLEB, +// .MEMORY_ADDR_SLEB64, +// => { +// assert(symbol.tag == .data); +// if (symbol.flags.undefined) return 0; +// const va: i33 = symbol.virtual_address; +// return @intCast(va + relocation.addend); +// }, +// .EVENT_INDEX_LEB => @panic("TODO: expose this as an error, events are unsupported"), +// .SECTION_OFFSET_I32 => { +// const target_atom_index = wasm.symbol_atom.get(target_loc).?; +// const target_atom = wasm.getAtom(target_atom_index); +// const rel_value: i33 = target_atom.offset; +// return @intCast(rel_value + relocation.addend); +// }, +// .FUNCTION_OFFSET_I32 => { +// if (symbol.flags.undefined) { +// const val = atom.tombstone(wasm) orelse relocation.addend; +// return @bitCast(val); +// } +// const target_atom_index = wasm.symbol_atom.get(target_loc).?; +// const target_atom = wasm.getAtom(target_atom_index); +// const rel_value: i33 = target_atom.offset; +// return @intCast(rel_value + relocation.addend); +// }, +// .MEMORY_ADDR_TLS_SLEB, +// .MEMORY_ADDR_TLS_SLEB64, +// => { +// const va: i33 = symbol.virtual_address; +// return @intCast(va + relocation.addend); +// }, +// }; +//} + +///// For a given `Atom` returns whether it has a tombstone value or not. +///// This defines whether we want a specific value when a section is dead. +//fn tombstone(atom: Atom, wasm: *const Wasm) ?i64 { +// const atom_name = wasm.finalSymbolByLoc(atom.symbolLoc()).name; +// if (atom_name == wasm.custom_sections.@".debug_ranges".name or +// atom_name == wasm.custom_sections.@".debug_loc".name) +// { +// return -2; +// } else if (mem.startsWith(u8, atom_name.slice(wasm), ".debug_")) { +// return -1; +// } else { +// return null; +// } +//} + +fn getUleb128Size(uint_value: anytype) u32 { + const T = @TypeOf(uint_value); + const U = if (@typeInfo(T).int.bits < 8) u8 else T; + var value = @as(U, @intCast(uint_value)); + + var size: u32 = 0; + while (value != 0) : (size += 1) { + value >>= 7; + } + return size; +} diff --git a/src/link/Wasm/Object.zig b/src/link/Wasm/Object.zig index fc11f91876..9234faf028 100644 --- a/src/link/Wasm/Object.zig +++ b/src/link/Wasm/Object.zig @@ -1,23 +1,16 @@ -//! Object represents a wasm object file. When initializing a new -//! `Object`, it will parse the contents of a given file handler, and verify -//! the data on correctness. The result can then be used by the linker. const Object = @This(); const Wasm = @import("../Wasm.zig"); -const Atom = Wasm.Atom; const Alignment = Wasm.Alignment; -const Symbol = @import("Symbol.zig"); const std = @import("std"); const Allocator = std.mem.Allocator; -const leb = std.leb; -const meta = std.meta; const Path = std.Build.Cache.Path; - const log = std.log.scoped(.object); +const assert = std.debug.assert; /// Wasm spec version used for this `Object` -version: u32 = 0, +version: u32, /// For error reporting purposes only. /// Name (read path) of the object or archive file. path: Path, @@ -25,817 +18,969 @@ path: Path, /// If this represents an object in an archive, it's the basename of the /// object, and path refers to the archive. archive_member_name: ?[]const u8, -/// Parsed type section -func_types: []const std.wasm.Type = &.{}, -/// A list of all imports for this module -imports: []const Wasm.Import = &.{}, -/// Parsed function section -functions: []const std.wasm.Func = &.{}, -/// Parsed table section -tables: []const std.wasm.Table = &.{}, -/// Parsed memory section -memories: []const std.wasm.Memory = &.{}, -/// Parsed global section -globals: []const std.wasm.Global = &.{}, -/// Parsed export section -exports: []const Wasm.Export = &.{}, -/// Parsed element section -elements: []const std.wasm.Element = &.{}, /// Represents the function ID that must be called on startup. /// This is `null` by default as runtimes may determine the startup /// function themselves. This is essentially legacy. -start: ?u32 = null, -/// A slice of features that tell the linker what features are mandatory, -/// used (or therefore missing) and must generate an error when another -/// object uses features that are not supported by the other. -features: []const Wasm.Feature = &.{}, -/// A table that maps the relocations we must perform where the key represents -/// the section that the list of relocations applies to. -relocations: std.AutoArrayHashMapUnmanaged(u32, []Wasm.Relocation) = .empty, -/// Table of symbols belonging to this Object file -symtable: []Symbol = &.{}, -/// Extra metadata about the linking section, such as alignment of segments and their name -segment_info: []const Wasm.NamedSegment = &.{}, -/// A sequence of function initializers that must be called on startup -init_funcs: []const Wasm.InitFunc = &.{}, -/// Comdat information -comdat_info: []const Wasm.Comdat = &.{}, -/// Represents non-synthetic sections that can essentially be mem-cpy'd into place -/// after performing relocations. -relocatable_data: std.AutoHashMapUnmanaged(RelocatableData.Tag, []RelocatableData) = .empty, -/// Amount of functions in the `import` sections. -imported_functions_count: u32 = 0, -/// Amount of globals in the `import` section. -imported_globals_count: u32 = 0, -/// Amount of tables in the `import` section. -imported_tables_count: u32 = 0, +start_function: Wasm.OptionalObjectFunctionIndex, +/// A slice of features that tell the linker what features are mandatory, used +/// (or therefore missing) and must generate an error when another object uses +/// features that are not supported by the other. +features: Wasm.Feature.Set, +/// Points into Wasm functions +functions: RelativeSlice, +/// Points into Wasm object_globals_imports +globals_imports: RelativeSlice, +/// Points into Wasm object_tables_imports +tables_imports: RelativeSlice, +/// Points into Wasm object_custom_segments +custom_segments: RelativeSlice, +/// For calculating local section index from `Wasm.SectionIndex`. +local_section_index_base: u32, +/// Points into Wasm object_init_funcs +init_funcs: RelativeSlice, +/// Points into Wasm object_comdats +comdats: RelativeSlice, -/// Represents a single item within a section (depending on its `type`) -pub const RelocatableData = struct { - /// The type of the relocatable data - type: Tag, - /// Pointer to the data of the segment, where its length is written to `size` - data: [*]u8, - /// The size in bytes of the data representing the segment within the section - size: u32, - /// The index within the section itself, or in case of a debug section, - /// the offset within the `string_table`. - index: u32, - /// The offset within the section where the data starts - offset: u32, - /// Represents the index of the section it belongs to - section_index: u32, - /// Whether the relocatable section is represented by a symbol or not. - /// Can only be `true` for custom sections. - represented: bool = false, +pub const RelativeSlice = struct { + off: u32, + len: u32, +}; - const Tag = enum { data, code, custom }; +pub const SegmentInfo = struct { + name: Wasm.String, + flags: Flags, - /// Returns the alignment of the segment, by retrieving it from the segment - /// meta data of the given object file. - /// NOTE: Alignment is encoded as a power of 2, so we shift the symbol's - /// alignment to retrieve the natural alignment. - pub fn getAlignment(relocatable_data: RelocatableData, object: *const Object) Alignment { - if (relocatable_data.type != .data) return .@"1"; - return object.segment_info[relocatable_data.index].alignment; + const Flags = packed struct(u32) { + /// Signals that the segment contains only null terminated strings allowing + /// the linker to perform merging. + strings: bool, + /// The segment contains thread-local data. This means that a unique copy + /// of this segment will be created for each thread. + tls: bool, + /// If the object file is included in the final link, the segment should be + /// retained in the final output regardless of whether it is used by the + /// program. + retain: bool, + alignment: Alignment, + + _: u23 = 0, + }; +}; + +pub const FunctionImport = struct { + module_name: Wasm.String, + name: Wasm.String, + function_index: ScratchSpace.FuncTypeIndex, +}; + +pub const DataSegmentFlags = enum(u32) { active, passive, active_memidx }; + +pub const SubsectionType = enum(u8) { + segment_info = 5, + init_funcs = 6, + comdat_info = 7, + symbol_table = 8, +}; + +pub const Symbol = struct { + flags: Wasm.SymbolFlags, + name: Wasm.OptionalString, + pointee: Pointee, + + /// https://github.com/WebAssembly/tool-conventions/blob/df8d737539eb8a8f446ba5eab9dc670c40dfb81e/Linking.md#symbol-table-subsection + const Tag = enum(u8) { + function, + data, + global, + section, + event, + table, + }; + + const Pointee = union(enum) { + function: Wasm.ObjectFunctionIndex, + function_import: ScratchSpace.FuncImportIndex, + data: struct { + segment_index: Wasm.DataSegment.Index, + segment_offset: u32, + size: u32, + }, + data_import: void, + global: Wasm.ObjectGlobalIndex, + global_import: Wasm.ObjectGlobalImportIndex, + section: Wasm.ObjectSectionIndex, + table: Wasm.ObjectTableIndex, + table_import: Wasm.ObjectTableImportIndex, + }; +}; + +pub const ScratchSpace = struct { + func_types: std.ArrayListUnmanaged(Wasm.FunctionType.Index) = .empty, + func_type_indexes: std.ArrayListUnmanaged(FuncTypeIndex) = .empty, + func_imports: std.ArrayListUnmanaged(FunctionImport) = .empty, + symbol_table: std.ArrayListUnmanaged(Symbol) = .empty, + segment_info: std.ArrayListUnmanaged(SegmentInfo) = .empty, + + /// Index into `func_imports`. + const FuncImportIndex = enum(u32) { + _, + + fn ptr(index: FunctionImport, ss: *const ScratchSpace) *FunctionImport { + return &ss.func_imports.items[@intFromEnum(index)]; + } + }; + + /// Index into `func_types`. + const FuncTypeIndex = enum(u32) { + _, + + fn ptr(index: FuncTypeIndex, ss: *const ScratchSpace) *Wasm.FunctionType.Index { + return &ss.func_types.items[@intFromEnum(index)]; + } + }; + + pub fn deinit(ss: *ScratchSpace, gpa: Allocator) void { + ss.func_types.deinit(gpa); + ss.func_type_indexes.deinit(gpa); + ss.func_imports.deinit(gpa); + ss.symbol_table.deinit(gpa); + ss.segment_info.deinit(gpa); + ss.* = undefined; } - /// Returns the symbol kind that corresponds to the relocatable section - pub fn getSymbolKind(relocatable_data: RelocatableData) Symbol.Tag { - return switch (relocatable_data.type) { - .data => .data, - .code => .function, - .custom => .section, - }; - } - - /// Returns the index within a section, or in case of a custom section, - /// returns the section index within the object file. - pub fn getIndex(relocatable_data: RelocatableData) u32 { - if (relocatable_data.type == .custom) return relocatable_data.section_index; - return relocatable_data.index; + fn clear(ss: *ScratchSpace) void { + ss.func_types.clearRetainingCapacity(); + ss.func_type_indexes.clearRetainingCapacity(); + ss.func_imports.clearRetainingCapacity(); + ss.symbol_table.clearRetainingCapacity(); + ss.segment_info.clearRetainingCapacity(); } }; -/// Initializes a new `Object` from a wasm object file. -/// This also parses and verifies the object file. -/// When a max size is given, will only parse up to the given size, -/// else will read until the end of the file. -pub fn create( +fn parse( wasm: *Wasm, - file_contents: []const u8, + bytes: []const u8, path: Path, archive_member_name: ?[]const u8, -) !Object { + host_name: Wasm.String, + ss: *ScratchSpace, + must_link: bool, + gc_sections: bool, +) anyerror!Object { const gpa = wasm.base.comp.gpa; - var object: Object = .{ - .path = path, - .archive_member_name = archive_member_name, - }; - - var parser: Parser = .{ - .object = &object, - .wasm = wasm, - .reader = std.io.fixedBufferStream(file_contents), - }; - try parser.parseObject(gpa); - - return object; -} - -/// Frees all memory of `Object` at once. The given `Allocator` must be -/// the same allocator that was used when `init` was called. -pub fn deinit(object: *Object, gpa: Allocator) void { - for (object.func_types) |func_ty| { - gpa.free(func_ty.params); - gpa.free(func_ty.returns); - } - gpa.free(object.func_types); - gpa.free(object.functions); - gpa.free(object.imports); - gpa.free(object.tables); - gpa.free(object.memories); - gpa.free(object.globals); - gpa.free(object.exports); - for (object.elements) |el| { - gpa.free(el.func_indexes); - } - gpa.free(object.elements); - gpa.free(object.features); - for (object.relocations.values()) |val| { - gpa.free(val); - } - object.relocations.deinit(gpa); - gpa.free(object.symtable); - gpa.free(object.comdat_info); - gpa.free(object.init_funcs); - for (object.segment_info) |info| { - gpa.free(info.name); - } - gpa.free(object.segment_info); - { - var it = object.relocatable_data.valueIterator(); - while (it.next()) |relocatable_data| { - for (relocatable_data.*) |rel_data| { - gpa.free(rel_data.data[0..rel_data.size]); - } - gpa.free(relocatable_data.*); - } - } - object.relocatable_data.deinit(gpa); - object.* = undefined; -} - -/// Finds the import within the list of imports from a given kind and index of that kind. -/// Asserts the import exists -pub fn findImport(object: *const Object, sym: Symbol) Wasm.Import { - var i: u32 = 0; - return for (object.imports) |import| { - if (std.meta.activeTag(import.kind) == sym.tag.externalType()) { - if (i == sym.index) return import; - i += 1; - } - } else unreachable; // Only existing imports are allowed to be found -} - -/// Checks if the object file is an MVP version. -/// When that's the case, we check if there's an import table definition with its name -/// set to '__indirect_function_table". When that's also the case, -/// we initialize a new table symbol that corresponds to that import and return that symbol. -/// -/// When the object file is *NOT* MVP, we return `null`. -fn checkLegacyIndirectFunctionTable(object: *Object, wasm: *const Wasm) !?Symbol { const diags = &wasm.base.comp.link_diags; + var pos: usize = 0; + + if (!std.mem.eql(u8, bytes[0..std.wasm.magic.len], &std.wasm.magic)) return error.BadObjectMagic; + pos += std.wasm.magic.len; + + const version = std.mem.readInt(u32, bytes[pos..][0..4], .little); + pos += 4; + + const data_segment_start: u32 = @intCast(wasm.object_data_segments.items.len); + const custom_segment_start: u32 = @intCast(wasm.object_custom_segments.items.len); + const imports_start: u32 = @intCast(wasm.object_imports.items.len); + const functions_start: u32 = @intCast(wasm.object_functions.items.len); + const tables_start: u32 = @intCast(wasm.object_tables.items.len); + const memories_start: u32 = @intCast(wasm.object_memories.items.len); + const globals_start: u32 = @intCast(wasm.object_globals.items.len); + const init_funcs_start: u32 = @intCast(wasm.object_init_funcs.items.len); + const comdats_start: u32 = @intCast(wasm.object_comdats.items.len); + const global_imports_start: u32 = @intCast(wasm.object_global_imports.items.len); + const table_imports_start: u32 = @intCast(wasm.object_table_imports.items.len); + const local_section_index_base = wasm.object_total_sections; + const source_location: Wasm.SourceLocation = .fromObjectIndex(wasm.objects.items.len); + + ss.clear(); + + var start_function: Wasm.OptionalObjectFunctionIndex = .none; + var opt_features: ?Wasm.Feature.Set = null; + var saw_linking_section = false; + var has_tls = false; + var local_section_index: u32 = 0; var table_count: usize = 0; - for (object.symtable) |sym| { - if (sym.tag == .table) table_count += 1; - } + while (pos < bytes.len) : (local_section_index += 1) { + const section_index: Wasm.SectionIndex = @enumFromInt(local_section_index_base + local_section_index); - // For each import table, we also have a symbol so this is not a legacy object file - if (object.imported_tables_count == table_count) return null; + const section_tag: std.wasm.Section = @enumFromInt(bytes[pos]); + pos += 1; - if (table_count != 0) { - return diags.failParse(object.path, "expected a table entry symbol for each of the {d} table(s), but instead got {d} symbols.", .{ - object.imported_tables_count, - table_count, - }); - } + const len, pos = readLeb(u32, bytes, pos); + const section_end = pos + len; + switch (section_tag) { + .custom => { + const section_name, pos = readBytes(bytes, pos); + if (std.mem.eql(u8, section_name, "linking")) { + saw_linking_section = true; + const section_version, pos = readLeb(u32, bytes, pos); + log.debug("link meta data version: {d}", .{section_version}); + if (section_version != 2) return error.UnsupportedVersion; + while (pos < section_end) { + const sub_type, pos = readLeb(u8, bytes, pos); + log.debug("found subsection: {s}", .{@tagName(@as(SubsectionType, @enumFromInt(sub_type)))}); + const payload_len, pos = readLeb(u32, bytes, pos); + if (payload_len == 0) break; - // MVP object files cannot have any table definitions, only imports (for the indirect function table). - if (object.tables.len > 0) { - return diags.failParse(object.path, "unexpected table definition without representing table symbols.", .{}); - } + const count, pos = readLeb(u32, bytes, pos); - if (object.imported_tables_count != 1) { - return diags.failParse(object.path, "found more than one table import, but no representing table symbols", .{}); - } + switch (@as(SubsectionType, @enumFromInt(sub_type))) { + .segment_info => { + for (try ss.segment_info.addManyAsSlice(gpa, count)) |*segment| { + const name, pos = readBytes(bytes, pos); + const alignment, pos = readLeb(u32, bytes, pos); + const flags_u32, pos = readLeb(u32, bytes, pos); + const flags: SegmentInfo.Flags = @bitCast(flags_u32); + const tls = flags.tls or + // Supports legacy object files that specified + // being TLS by the name instead of the TLS flag. + std.mem.startsWith(u8, name, ".tdata") or + std.mem.startsWith(u8, name, ".tbss"); + has_tls = has_tls or tls; + segment.* = .{ + .name = try wasm.internString(name), + .flags = .{ + .strings = flags.strings, + .tls = tls, + .alignment = @enumFromInt(alignment), + .no_strip = flags.retain, + }, + }; + } + }, + .init_funcs => { + for (try wasm.object_init_funcs.addManyAsSlice(gpa, count)) |*func| { + const priority, pos = readLeb(u32, bytes, pos); + const symbol_index, pos = readLeb(u32, bytes, pos); + if (symbol_index > ss.symbol_table.items.len) + return diags.failParse(path, "init_funcs before symbol table", .{}); + const sym = &ss.symbol_table.items[symbol_index]; + if (sym.tag != .function) { + return diags.failParse(path, "init_func symbol '{s}' not a function", .{ + wasm.stringSlice(sym.name), + }); + } else if (sym.flags.undefined) { + return diags.failParse(path, "init_func symbol '{s}' is an import", .{ + wasm.stringSlice(sym.name), + }); + } + func.* = .{ + .priority = priority, + .function_index = sym.pointee.function, + }; + } + }, + .comdat_info => { + for (try wasm.object_comdats.addManyAsSlice(gpa, count)) |*comdat| { + const name, pos = readBytes(bytes, pos); + const flags, pos = readLeb(u32, bytes, pos); + if (flags != 0) return error.UnexpectedComdatFlags; + const symbol_count, pos = readLeb(u32, bytes, pos); + const start_off: u32 = @intCast(wasm.object_comdat_symbols.items.len); + for (try wasm.object_comdat_symbols.addManyAsSlice(gpa, symbol_count)) |*symbol| { + const kind, pos = readEnum(Wasm.Comdat.Symbol.Type, bytes, pos); + const index, pos = readLeb(u32, bytes, pos); + if (true) @panic("TODO rebase index depending on kind"); + symbol.* = .{ + .kind = kind, + .index = index, + }; + } + comdat.* = .{ + .name = try wasm.internString(name), + .flags = flags, + .symbols = .{ + .off = start_off, + .len = @intCast(wasm.object_comdat_symbols.items.len - start_off), + }, + }; + } + }, + .symbol_table => { + for (try ss.symbol_table.addManyAsSlice(gpa, count)) |*symbol| { + const tag, pos = readEnum(Symbol.Tag, bytes, pos); + const flags, pos = readLeb(u32, bytes, pos); + symbol.* = .{ + .flags = @bitCast(flags), + .name = .none, + .pointee = undefined, + }; + symbol.flags.initZigSpecific(must_link, gc_sections); - const table_import: Wasm.Import = for (object.imports) |imp| { - if (imp.kind == .table) { - break imp; + switch (tag) { + .data => { + const name, pos = readBytes(bytes, pos); + symbol.name = (try wasm.internString(name)).toOptional(); + if (symbol.flags.undefined) { + symbol.pointee = .data_import; + } else { + const segment_index, pos = readLeb(u32, bytes, pos); + const segment_offset, pos = readLeb(u32, bytes, pos); + const size, pos = readLeb(u32, bytes, pos); + + symbol.pointee = .{ .data = .{ + .index = @enumFromInt(data_segment_start + segment_index), + .segment_offset = segment_offset, + .size = size, + } }; + } + }, + .section => { + const local_section, pos = readLeb(u32, bytes, pos); + const section: Wasm.SectionIndex = @enumFromInt(local_section_index_base + local_section); + symbol.pointee = .{ .section = section }; + }, + + .function => { + const local_index, pos = readLeb(u32, bytes, pos); + if (symbol.flags.undefined) { + symbol.pointee = .{ .function_import = @enumFromInt(local_index) }; + if (flags.explicit_name) { + const name, pos = readBytes(bytes, pos); + symbol.name = (try wasm.internString(name)).toOptional(); + } + } else { + symbol.pointee = .{ .function = @enumFromInt(functions_start + local_index) }; + const name, pos = readBytes(bytes, pos); + symbol.name = (try wasm.internString(name)).toOptional(); + } + }, + .global => { + const local_index, pos = readLeb(u32, bytes, pos); + if (symbol.flags.undefined) { + symbol.pointee = .{ .global_import = @enumFromInt(global_imports_start + local_index) }; + if (flags.explicit_name) { + const name, pos = readBytes(bytes, pos); + symbol.name = (try wasm.internString(name)).toOptional(); + } + } else { + symbol.pointee = .{ .global = @enumFromInt(globals_start + local_index) }; + const name, pos = readBytes(bytes, pos); + symbol.name = (try wasm.internString(name)).toOptional(); + } + }, + .table => { + table_count += 1; + const local_index, pos = readLeb(u32, bytes, pos); + if (symbol.flags.undefined) { + symbol.pointee = .{ .table_import = @enumFromInt(table_imports_start + local_index) }; + if (flags.explicit_name) { + const name, pos = readBytes(bytes, pos); + symbol.name = (try wasm.internString(name)).toOptional(); + } + } else { + symbol.pointee = .{ .table = @enumFromInt(tables_start + local_index) }; + const name, pos = readBytes(bytes, pos); + symbol.name = (try wasm.internString(name)).toOptional(); + } + }, + else => { + log.debug("unrecognized symbol type tag: {x}", .{tag}); + return error.UnrecognizedSymbolType; + }, + } + log.debug("found symbol: {}", .{symbol}); + } + }, + } + } + } else if (std.mem.startsWith(u8, section_name, "reloc.")) { + // 'The "reloc." custom sections must come after the "linking" custom section' + if (!saw_linking_section) return error.RelocBeforeLinkingSection; + + // "Relocation sections start with an identifier specifying + // which section they apply to, and must be sequenced in + // the module after that section." + // "Relocation sections can only target code, data and custom sections." + const local_section, pos = readLeb(u32, bytes, pos); + const count, pos = readLeb(u32, bytes, pos); + const section: Wasm.SectionIndex = @enumFromInt(local_section_index_base + local_section); + + log.debug("found {d} relocations for section={d}", .{ count, section }); + + var prev_offset: u32 = 0; + try wasm.relocations.ensureUnusedCapacity(gpa, count); + for (0..count) |_| { + const tag: Wasm.Relocation.Tag = @enumFromInt(bytes[pos]); + pos += 1; + const offset, pos = readLeb(u32, bytes, pos); + const index, pos = readLeb(u32, bytes, pos); + + if (offset < prev_offset) + return diags.failParse(path, "relocation entries not sorted by offset", .{}); + prev_offset = offset; + + switch (tag) { + .MEMORY_ADDR_LEB, + .MEMORY_ADDR_SLEB, + .MEMORY_ADDR_I32, + .MEMORY_ADDR_REL_SLEB, + .MEMORY_ADDR_LEB64, + .MEMORY_ADDR_SLEB64, + .MEMORY_ADDR_I64, + .MEMORY_ADDR_REL_SLEB64, + .MEMORY_ADDR_TLS_SLEB, + .MEMORY_ADDR_LOCREL_I32, + .MEMORY_ADDR_TLS_SLEB64, + .FUNCTION_OFFSET_I32, + .SECTION_OFFSET_I32, + => { + const addend: i32, pos = readLeb(i32, bytes, pos); + wasm.relocations.appendAssumeCapacity(.{ + .tag = tag, + .offset = offset, + .pointee = .{ .section = ss.symbol_table.items[index].pointee.section }, + .addend = addend, + }); + }, + .TYPE_INDEX_LEB => { + wasm.relocations.appendAssumeCapacity(.{ + .tag = tag, + .offset = offset, + .pointee = .{ .type_index = ss.func_types.items[index] }, + .addend = undefined, + }); + }, + .FUNCTION_INDEX_LEB, + .GLOBAL_INDEX_LEB, + => { + wasm.relocations.appendAssumeCapacity(.{ + .tag = tag, + .offset = offset, + .pointee = .{ .symbol_name = ss.symbol_table.items[index].name.unwrap().? }, + .addend = undefined, + }); + }, + } + } + + try wasm.object_relocations_table.putNoClobber(gpa, section, .{ + .off = @intCast(wasm.relocations.items.len - count), + .len = count, + }); + } else if (std.mem.eql(u8, section_name, "target_features")) { + opt_features, pos = try parseFeatures(wasm, bytes, pos, path); + } else if (std.mem.startsWith(u8, section_name, ".debug")) { + const debug_content = bytes[pos..section_end]; + pos = section_end; + + const data_off: u32 = @enumFromInt(wasm.string_bytes.items.len); + try wasm.string_bytes.appendSlice(gpa, debug_content); + + try wasm.object_custom_segments.put(gpa, section_index, .{ + .data_off = data_off, + .flags = .{ + .data_len = @intCast(debug_content.len), + .represented = false, // set when scanning symbol table + }, + .section_name = try wasm.internString(section_name), + }); + } else { + pos = section_end; + } + }, + .type => { + const func_types_len, pos = readLeb(u32, bytes, pos); + for (ss.func_types.addManyAsSlice(gpa, func_types_len)) |*func_type| { + if (bytes[pos] != std.wasm.function_type) return error.ExpectedFuncType; + pos += 1; + + const params, pos = readBytes(bytes, pos); + const returns, pos = readBytes(bytes, pos); + func_type.* = try wasm.addFuncType(.{ + .params = .fromString(try wasm.internString(params)), + .returns = .fromString(try wasm.internString(returns)), + }); + } + }, + .import => { + const imports_len, pos = readLeb(u32, bytes, pos); + for (0..imports_len) |_| { + const module_name, pos = readBytes(bytes, pos); + const name, pos = readBytes(bytes, pos); + const kind, pos = readEnum(std.wasm.ExternalKind, bytes, pos); + const interned_module_name = try wasm.internString(module_name); + const interned_name = try wasm.internString(name); + switch (kind) { + .function => { + const function, pos = readLeb(u32, bytes, pos); + try ss.function_imports.append(gpa, .{ + .module_name = interned_module_name, + .name = interned_name, + .index = function, + }); + }, + .memory => { + const limits, pos = readLimits(bytes, pos); + try wasm.object_memory_imports.append(gpa, .{ + .module_name = interned_module_name, + .name = interned_name, + .limits_min = limits.min, + .limits_max = limits.max, + .limits_has_max = limits.flags.has_max, + .limits_is_shared = limits.flags.is_shared, + }); + }, + .global => { + const valtype, pos = readEnum(std.wasm.Valtype, bytes, pos); + const mutable = bytes[pos] == 0x01; + pos += 1; + try wasm.object_global_imports.append(gpa, .{ + .module_name = interned_module_name, + .name = interned_name, + .mutable = mutable, + .valtype = valtype, + }); + }, + .table => { + const reftype, pos = readEnum(std.wasm.RefType, bytes, pos); + const limits, pos = readLimits(bytes, pos); + try wasm.object_table_imports.append(gpa, .{ + .module_name = interned_module_name, + .name = interned_name, + .limits_min = limits.min, + .limits_max = limits.max, + .limits_has_max = limits.flags.has_max, + .limits_is_shared = limits.flags.is_shared, + .reftype = reftype, + }); + }, + } + } + }, + .function => { + const functions_len, pos = readLeb(u32, bytes, pos); + for (try ss.func_type_indexes.addManyAsSlice(gpa, functions_len)) |*func_type_index| { + func_type_index.*, pos = readLeb(u32, bytes, pos); + } + }, + .table => { + const tables_len, pos = readLeb(u32, bytes, pos); + for (try wasm.object_tables.addManyAsSlice(gpa, tables_len)) |*table| { + const reftype, pos = readEnum(std.wasm.RefType, bytes, pos); + const limits, pos = readLimits(bytes, pos); + table.* = .{ + .reftype = reftype, + .limits = limits, + }; + } + }, + .memory => { + const memories_len, pos = readLeb(u32, bytes, pos); + for (try wasm.object_memories.addManyAsSlice(gpa, memories_len)) |*memory| { + const limits, pos = readLimits(bytes, pos); + memory.* = .{ .limits = limits }; + } + }, + .global => { + const globals_len, pos = readLeb(u32, bytes, pos); + for (try wasm.object_globals.addManyAsSlice(gpa, globals_len)) |*global| { + const valtype, pos = readEnum(std.wasm.Valtype, bytes, pos); + const mutable = bytes[pos] == 0x01; + pos += 1; + const expr, pos = try readInit(wasm, bytes, pos); + global.* = .{ + .valtype = valtype, + .mutable = mutable, + .expr = expr, + }; + } + }, + .@"export" => { + const exports_len, pos = readLeb(u32, bytes, pos); + // TODO: instead, read into scratch space, and then later + // add this data as if it were extra symbol table entries, + // but allow merging with existing symbol table data if the name matches. + for (try wasm.object_exports.addManyAsSlice(gpa, exports_len)) |*exp| { + const name, pos = readBytes(bytes, pos); + const kind: std.wasm.ExternalKind = @enumFromInt(bytes[pos]); + pos += 1; + const index, pos = readLeb(u32, bytes, pos); + const rebased_index = index + switch (kind) { + .function => functions_start, + .table => tables_start, + .memory => memories_start, + .global => globals_start, + }; + exp.* = .{ + .name = try wasm.internString(name), + .kind = kind, + .index = rebased_index, + }; + } + }, + .start => { + const index, pos = readLeb(u32, bytes, pos); + start_function = @enumFromInt(functions_start + index); + }, + .element => { + log.warn("unimplemented: element section in {}", .{path}); + pos = section_end; + }, + .code => { + const start = pos; + const count, pos = readLeb(u32, bytes, pos); + for (try wasm.object_functions.addManyAsSlice(gpa, count)) |*elem| { + const code_len, pos = readLeb(u32, bytes, pos); + const offset: u32 = @intCast(pos - start); + const payload = try wasm.addRelocatableDataPayload(bytes[pos..][0..code_len]); + pos += code_len; + elem.* = .{ + .flags = .{}, // populated from symbol table + .name = .none, // populated from symbol table + .type_index = undefined, // populated from func_types + .code = payload, + .offset = offset, + .section_index = section_index, + .source_location = source_location, + }; + } + }, + .data => { + const start = pos; + const count, pos = readLeb(u32, bytes, pos); + for (try wasm.object_data_segments.addManyAsSlice(gpa, count)) |*elem| { + const flags, pos = readEnum(DataSegmentFlags, bytes, pos); + if (flags == .active_memidx) { + const memidx, pos = readLeb(u32, bytes, pos); + if (memidx != 0) return diags.failParse(path, "data section uses mem index {d}", .{memidx}); + } + //const expr, pos = if (flags != .passive) try readInit(wasm, bytes, pos) else .{ .none, pos }; + if (flags != .passive) pos = try skipInit(bytes, pos); + const data_len, pos = readLeb(u32, bytes, pos); + const segment_offset: u32 = @intCast(pos - start); + const payload = try wasm.addRelocatableDataPayload(bytes[pos..][0..data_len]); + pos += data_len; + elem.* = .{ + .payload = payload, + .segment_offset = segment_offset, + .section_index = section_index, + .name = .none, // Populated from symbol table + .flags = .{}, // Populated from symbol table and segment_info + }; + } + }, + else => pos = section_end, } - } else unreachable; + if (pos != section_end) return error.MalformedSection; + } + if (!saw_linking_section) return error.MissingLinkingSection; - if (table_import.name != wasm.preloaded_strings.__indirect_function_table) { - return diags.failParse(object.path, "non-indirect function table import '{s}' is missing a corresponding symbol", .{ - wasm.stringSlice(table_import.name), - }); + wasm.object_total_sections = local_section_index_base + local_section_index; + + if (has_tls) { + const cpu_features = wasm.base.comp.root_mod.resolved_target.result.cpu.features; + if (!std.Target.wasm.featureSetHas(cpu_features, .atomics)) + return diags.failParse(path, "object has TLS segment but target CPU feature atomics is disabled", .{}); + if (!std.Target.wasm.featureSetHas(cpu_features, .bulk_memory)) + return diags.failParse(path, "object has TLS segment but target CPU feature bulk_memory is disabled", .{}); } - var table_symbol: Symbol = .{ - .flags = 0, - .name = table_import.name, - .tag = .table, - .index = 0, - .virtual_address = undefined, - }; - table_symbol.setFlag(.WASM_SYM_UNDEFINED); - table_symbol.setFlag(.WASM_SYM_NO_STRIP); - return table_symbol; -} + const features = opt_features orelse return error.MissingFeatures; + if (true) @panic("iterate features, match against target features"); -const Parser = struct { - reader: std.io.FixedBufferStream([]const u8), - /// Object file we're building - object: *Object, - /// Mutable so that the string table can be modified. - wasm: *Wasm, + // Apply function type information. + for (ss.func_types.items, wasm.object_functions.items[functions_start..]) |func_type, *func| { + func.type_index = func_type; + } - fn parseObject(parser: *Parser, gpa: Allocator) anyerror!void { - const wasm = parser.wasm; - - { - var magic_bytes: [4]u8 = undefined; - try parser.reader.reader().readNoEof(&magic_bytes); - if (!std.mem.eql(u8, &magic_bytes, &std.wasm.magic)) return error.BadObjectMagic; - } - - const version = try parser.reader.reader().readInt(u32, .little); - parser.object.version = version; - - var saw_linking_section = false; - - var section_index: u32 = 0; - while (parser.reader.reader().readByte()) |byte| : (section_index += 1) { - const len = try readLeb(u32, parser.reader.reader()); - var limited_reader = std.io.limitedReader(parser.reader.reader(), len); - const reader = limited_reader.reader(); - switch (@as(std.wasm.Section, @enumFromInt(byte))) { - .custom => { - const name_len = try readLeb(u32, reader); - const name = try gpa.alloc(u8, name_len); - defer gpa.free(name); - try reader.readNoEof(name); - - if (std.mem.eql(u8, name, "linking")) { - saw_linking_section = true; - try parser.parseMetadata(gpa, @as(usize, @intCast(reader.context.bytes_left))); - } else if (std.mem.startsWith(u8, name, "reloc")) { - try parser.parseRelocations(gpa); - } else if (std.mem.eql(u8, name, "target_features")) { - try parser.parseFeatures(gpa); - } else if (std.mem.startsWith(u8, name, ".debug")) { - const gop = try parser.object.relocatable_data.getOrPut(gpa, .custom); - var relocatable_data: std.ArrayListUnmanaged(RelocatableData) = .empty; - defer relocatable_data.deinit(gpa); - if (!gop.found_existing) { - gop.value_ptr.* = &.{}; - } else { - relocatable_data = std.ArrayListUnmanaged(RelocatableData).fromOwnedSlice(gop.value_ptr.*); - } - const debug_size = @as(u32, @intCast(reader.context.bytes_left)); - const debug_content = try gpa.alloc(u8, debug_size); - errdefer gpa.free(debug_content); - try reader.readNoEof(debug_content); - - try relocatable_data.append(gpa, .{ - .type = .custom, - .data = debug_content.ptr, - .size = debug_size, - .index = @intFromEnum(try wasm.internString(name)), - .offset = 0, // debug sections only contain 1 entry, so no need to calculate offset - .section_index = section_index, - }); - gop.value_ptr.* = try relocatable_data.toOwnedSlice(gpa); - } else { - try reader.skipBytes(reader.context.bytes_left, .{}); - } - }, - .type => { - for (try readVec(&parser.object.func_types, reader, gpa)) |*type_val| { - if ((try reader.readByte()) != std.wasm.function_type) return error.ExpectedFuncType; - - for (try readVec(&type_val.params, reader, gpa)) |*param| { - param.* = try readEnum(std.wasm.Valtype, reader); - } - - for (try readVec(&type_val.returns, reader, gpa)) |*result| { - result.* = try readEnum(std.wasm.Valtype, reader); - } - } - try assertEnd(reader); - }, - .import => { - for (try readVec(&parser.object.imports, reader, gpa)) |*import| { - const module_len = try readLeb(u32, reader); - const module_name = try gpa.alloc(u8, module_len); - defer gpa.free(module_name); - try reader.readNoEof(module_name); - - const name_len = try readLeb(u32, reader); - const name = try gpa.alloc(u8, name_len); - defer gpa.free(name); - try reader.readNoEof(name); - - const kind = try readEnum(std.wasm.ExternalKind, reader); - const kind_value: std.wasm.Import.Kind = switch (kind) { - .function => val: { - parser.object.imported_functions_count += 1; - break :val .{ .function = try readLeb(u32, reader) }; - }, - .memory => .{ .memory = try readLimits(reader) }, - .global => val: { - parser.object.imported_globals_count += 1; - break :val .{ .global = .{ - .valtype = try readEnum(std.wasm.Valtype, reader), - .mutable = (try reader.readByte()) == 0x01, - } }; - }, - .table => val: { - parser.object.imported_tables_count += 1; - break :val .{ .table = .{ - .reftype = try readEnum(std.wasm.RefType, reader), - .limits = try readLimits(reader), - } }; - }, - }; - - import.* = .{ - .module_name = try wasm.internString(module_name), - .name = try wasm.internString(name), - .kind = kind_value, - }; - } - try assertEnd(reader); - }, - .function => { - for (try readVec(&parser.object.functions, reader, gpa)) |*func| { - func.* = .{ .type_index = try readLeb(u32, reader) }; - } - try assertEnd(reader); - }, - .table => { - for (try readVec(&parser.object.tables, reader, gpa)) |*table| { - table.* = .{ - .reftype = try readEnum(std.wasm.RefType, reader), - .limits = try readLimits(reader), - }; - } - try assertEnd(reader); - }, - .memory => { - for (try readVec(&parser.object.memories, reader, gpa)) |*memory| { - memory.* = .{ .limits = try readLimits(reader) }; - } - try assertEnd(reader); - }, - .global => { - for (try readVec(&parser.object.globals, reader, gpa)) |*global| { - global.* = .{ - .global_type = .{ - .valtype = try readEnum(std.wasm.Valtype, reader), - .mutable = (try reader.readByte()) == 0x01, - }, - .init = try readInit(reader), - }; - } - try assertEnd(reader); - }, - .@"export" => { - for (try readVec(&parser.object.exports, reader, gpa)) |*exp| { - const name_len = try readLeb(u32, reader); - const name = try gpa.alloc(u8, name_len); - defer gpa.free(name); - try reader.readNoEof(name); - exp.* = .{ - .name = try wasm.internString(name), - .kind = try readEnum(std.wasm.ExternalKind, reader), - .index = try readLeb(u32, reader), - }; - } - try assertEnd(reader); - }, - .start => { - parser.object.start = try readLeb(u32, reader); - try assertEnd(reader); - }, - .element => { - for (try readVec(&parser.object.elements, reader, gpa)) |*elem| { - elem.table_index = try readLeb(u32, reader); - elem.offset = try readInit(reader); - - for (try readVec(&elem.func_indexes, reader, gpa)) |*idx| { - idx.* = try readLeb(u32, reader); - } - } - try assertEnd(reader); - }, - .code => { - const start = reader.context.bytes_left; - var index: u32 = 0; - const count = try readLeb(u32, reader); - const imported_function_count = parser.object.imported_functions_count; - var relocatable_data = try std.ArrayList(RelocatableData).initCapacity(gpa, count); - defer relocatable_data.deinit(); - while (index < count) : (index += 1) { - const code_len = try readLeb(u32, reader); - const offset = @as(u32, @intCast(start - reader.context.bytes_left)); - const data = try gpa.alloc(u8, code_len); - errdefer gpa.free(data); - try reader.readNoEof(data); - relocatable_data.appendAssumeCapacity(.{ - .type = .code, - .data = data.ptr, - .size = code_len, - .index = imported_function_count + index, - .offset = offset, - .section_index = section_index, - }); - } - try parser.object.relocatable_data.put(gpa, .code, try relocatable_data.toOwnedSlice()); - }, - .data => { - const start = reader.context.bytes_left; - var index: u32 = 0; - const count = try readLeb(u32, reader); - var relocatable_data = try std.ArrayList(RelocatableData).initCapacity(gpa, count); - defer relocatable_data.deinit(); - while (index < count) : (index += 1) { - const flags = try readLeb(u32, reader); - const data_offset = try readInit(reader); - _ = flags; // TODO: Do we need to check flags to detect passive/active memory? - _ = data_offset; - const data_len = try readLeb(u32, reader); - const offset = @as(u32, @intCast(start - reader.context.bytes_left)); - const data = try gpa.alloc(u8, data_len); - errdefer gpa.free(data); - try reader.readNoEof(data); - relocatable_data.appendAssumeCapacity(.{ - .type = .data, - .data = data.ptr, - .size = data_len, - .index = index, - .offset = offset, - .section_index = section_index, - }); - } - try parser.object.relocatable_data.put(gpa, .data, try relocatable_data.toOwnedSlice()); - }, - else => try parser.reader.reader().skipBytes(len, .{}), + // Apply symbol table information. + for (ss.symbol_table.items) |symbol| switch (symbol.pointee) { + .function_import => |index| { + const ptr = index.ptr(ss); + const name = symbol.name.unwrap().?; + if (symbol.flags.binding == .local) { + diags.addParseError(path, "local symbol '{s}' references import", .{name.slice(wasm)}); + continue; } - } else |err| switch (err) { - error.EndOfStream => {}, // finished parsing the file - else => |e| return e, - } - if (!saw_linking_section) return error.MissingLinkingSection; - } + const gop = try wasm.object_function_imports.getOrPut(gpa, name); + const fn_ty_index = ptr.function_index.ptr(ss).*; + if (gop.found_existing) { + if (gop.value_ptr.type != fn_ty_index) { + var err = try diags.addErrorWithNotes(2); + try err.addMsg("symbol '{s}' mismatching function signatures", .{name.slice(wasm)}); + try err.addSrcNote(gop.value_ptr.source_location, "imported as {} here", .{gop.value_ptr.type.fmt(wasm)}); + try err.addSrcNote(source_location, "imported as {} here", .{fn_ty_index.fmt(wasm)}); + continue; + } + if (gop.value_ptr.module_name != ptr.module_name) { + var err = try diags.addErrorWithNotes(2); + try err.addMsg("symbol '{s}' mismatching module names", .{name.slice(wasm)}); + try err.addSrcNote(gop.value_ptr.source_location, "module '{s}' here", .{gop.value_ptr.module_name.slice(wasm)}); + try err.addSrcNote(source_location, "module '{s}' here", .{ptr.module_name.slice(wasm)}); + continue; + } + if (symbol.flags.binding == .strong) gop.value_ptr.flags.binding = .strong; + if (!symbol.flags.visibility_hidden) gop.value_ptr.flags.visibility_hidden = false; + if (symbol.flags.no_strip) gop.value_ptr.flags.no_strip = true; + } else { + gop.value_ptr.* = .{ + .flags = symbol.flags, + .module_name = ptr.module_name, + .source_location = source_location, + .resolution = .unresolved, + .type = fn_ty_index, + }; + } + }, + .function => |index| { + assert(!symbol.flags.undefined); + const ptr = index.ptr(); + ptr.name = symbol.name; + ptr.flags = symbol.flags; + if (symbol.flags.binding == .local) continue; // No participation in symbol resolution. + const name = symbol.name.unwrap().?; + const gop = try wasm.object_function_imports.getOrPut(gpa, name); + if (gop.found_existing) { + if (gop.value_ptr.type != ptr.type_index) { + var err = try diags.addErrorWithNotes(2); + try err.addMsg("function signature mismatch: {s}", .{name.slice(wasm)}); + try err.addSrcNote(gop.value_ptr.source_location, "exported as {} here", .{ptr.type_index.fmt(wasm)}); + const word = if (gop.value_ptr.resolution == .none) "imported" else "exported"; + try err.addSrcNote(source_location, "{s} as {} here", .{ word, gop.value_ptr.type.fmt(wasm) }); + continue; + } + if (gop.value_ptr.resolution == .none or gop.value_ptr.flags.binding == .weak) { + // Intentional: if they're both weak, take the last one. + gop.value_ptr.source_location = source_location; + gop.value_ptr.module_name = host_name; + gop.value_ptr.resolution = .fromObjectFunction(index); + continue; + } + var err = try diags.addErrorWithNotes(2); + try err.addMsg("symbol collision: {s}", .{name.slice(wasm)}); + try err.addSrcNote(gop.value_ptr.source_location, "exported as {} here", .{ptr.type_index.fmt(wasm)}); + try err.addSrcNote(source_location, "exported as {} here", .{gop.value_ptr.type.fmt(wasm)}); + continue; + } else { + gop.value_ptr.* = .{ + .flags = symbol.flags, + .module_name = host_name, + .source_location = source_location, + .resolution = .fromObjectFunction(index), + .type = ptr.type_index, + }; + } + }, - /// Based on the "features" custom section, parses it into a list of - /// features that tell the linker what features were enabled and may be mandatory - /// to be able to link. - /// Logs an info message when an undefined feature is detected. - fn parseFeatures(parser: *Parser, gpa: Allocator) !void { - const diags = &parser.wasm.base.comp.link_diags; - const reader = parser.reader.reader(); - for (try readVec(&parser.object.features, reader, gpa)) |*feature| { - const prefix = try readEnum(Wasm.Feature.Prefix, reader); - const name_len = try leb.readUleb128(u32, reader); - const name = try gpa.alloc(u8, name_len); - defer gpa.free(name); - try reader.readNoEof(name); + inline .global, .global_import, .table, .table_import => |i| { + const ptr = i.ptr(wasm); + ptr.name = symbol.name; + ptr.flags = symbol.flags; + if (symbol.flags.undefined and symbol.flags.binding == .local) { + const name = wasm.stringSlice(ptr.name.unwrap().?); + diags.addParseError(path, "local symbol '{s}' references import", .{name}); + } + }, + .section => |i| { + // Name is provided by the section directly; symbol table does not have it. + const ptr = i.ptr(wasm); + ptr.flags = symbol.flags; + if (symbol.flags.undefined and symbol.flags.binding == .local) { + const name = wasm.stringSlice(ptr.name); + diags.addParseError(path, "local symbol '{s}' references import", .{name}); + } + }, + .data_import => { + const name = symbol.name.unwrap().?; + log.warn("TODO data import '{s}'", .{name.slice(wasm)}); + }, + .data => |data| { + const ptr = data.ptr(wasm); + const is_passive = ptr.flags.is_passive; + ptr.name = symbol.name; + ptr.flags = symbol.flags; + ptr.flags.is_passive = is_passive; + ptr.offset = data.segment_offset; + ptr.size = data.size; + }, + }; - const tag = Wasm.known_features.get(name) orelse { - return diags.failParse(parser.object.path, "object file contains unknown feature: {s}", .{name}); - }; - feature.* = .{ - .prefix = prefix, - .tag = tag, - }; + // Apply segment_info. + for (wasm.object_data_segments.items[data_segment_start..], ss.segment_info.items) |*data, info| { + data.name = info.name.toOptional(); + data.flags.strings = info.flags.strings; + data.flags.tls = data.flags.tls or info.flags.tls; + data.flags.no_strip = info.flags.retain; + data.flags.alignment = info.flags.alignment; + if (data.flags.undefined and data.flags.binding == .local) { + const name = wasm.stringSlice(info.name); + diags.addParseError(path, "local symbol '{s}' references import", .{name}); } } - /// Parses a "reloc" custom section into a list of relocations. - /// The relocations are mapped into `Object` where the key is the section - /// they apply to. - fn parseRelocations(parser: *Parser, gpa: Allocator) !void { - const reader = parser.reader.reader(); - const section = try leb.readUleb128(u32, reader); - const count = try leb.readUleb128(u32, reader); - const relocations = try gpa.alloc(Wasm.Relocation, count); - errdefer gpa.free(relocations); - - log.debug("Found {d} relocations for section ({d})", .{ - count, - section, - }); - - for (relocations) |*relocation| { - const rel_type = try reader.readByte(); - const rel_type_enum = std.meta.intToEnum(Wasm.Relocation.RelocationType, rel_type) catch return error.MalformedSection; - relocation.* = .{ - .relocation_type = rel_type_enum, - .offset = try leb.readUleb128(u32, reader), - .index = try leb.readUleb128(u32, reader), - .addend = if (rel_type_enum.addendIsPresent()) try leb.readIleb128(i32, reader) else 0, - }; - log.debug("Found relocation: type({s}) offset({d}) index({d}) addend({?d})", .{ - @tagName(relocation.relocation_type), - relocation.offset, - relocation.index, - relocation.addend, + // Check for indirect function table in case of an MVP object file. + legacy_indirect_function_table: { + const table_imports = wasm.object_table_imports.items[table_imports_start..]; + // If there is a symbol for each import table, this is not a legacy object file. + if (table_imports.len == table_count) break :legacy_indirect_function_table; + if (table_count != 0) { + return diags.failParse(path, "expected a table entry symbol for each of the {d} table(s), but instead got {d} symbols.", .{ + table_imports.len, table_count, }); } - - try parser.object.relocations.putNoClobber(gpa, section, relocations); - } - - /// Parses the "linking" custom section. Versions that are not - /// supported will be an error. `payload_size` is required to be able - /// to calculate the subsections we need to parse, as that data is not - /// available within the section itparser. - fn parseMetadata(parser: *Parser, gpa: Allocator, payload_size: usize) !void { - var limited = std.io.limitedReader(parser.reader.reader(), payload_size); - const limited_reader = limited.reader(); - - const version = try leb.readUleb128(u32, limited_reader); - log.debug("Link meta data version: {d}", .{version}); - if (version != 2) return error.UnsupportedVersion; - - while (limited.bytes_left > 0) { - try parser.parseSubsection(gpa, limited_reader); + // MVP object files cannot have any table definitions, only + // imports (for the indirect function table). + const tables = wasm.object_tables.items[tables_start..]; + if (tables.len > 0) { + return diags.failParse(path, "table definition without representing table symbols", .{}); } - } - - /// Parses a `spec.Subsection`. - /// The `reader` param for this is to provide a `LimitedReader`, which allows - /// us to only read until a max length. - /// - /// `parser` is used to provide access to other sections that may be needed, - /// such as access to the `import` section to find the name of a symbol. - fn parseSubsection(parser: *Parser, gpa: Allocator, reader: anytype) !void { - const wasm = parser.wasm; - const sub_type = try leb.readUleb128(u8, reader); - log.debug("Found subsection: {s}", .{@tagName(@as(Wasm.SubsectionType, @enumFromInt(sub_type)))}); - const payload_len = try leb.readUleb128(u32, reader); - if (payload_len == 0) return; - - var limited = std.io.limitedReader(reader, payload_len); - const limited_reader = limited.reader(); - - // every subsection contains a 'count' field - const count = try leb.readUleb128(u32, limited_reader); - - switch (@as(Wasm.SubsectionType, @enumFromInt(sub_type))) { - .WASM_SEGMENT_INFO => { - const segments = try gpa.alloc(Wasm.NamedSegment, count); - errdefer gpa.free(segments); - for (segments) |*segment| { - const name_len = try leb.readUleb128(u32, reader); - const name = try gpa.alloc(u8, name_len); - errdefer gpa.free(name); - try reader.readNoEof(name); - segment.* = .{ - .name = name, - .alignment = @enumFromInt(try leb.readUleb128(u32, reader)), - .flags = try leb.readUleb128(u32, reader), - }; - log.debug("Found segment: {s} align({d}) flags({b})", .{ - segment.name, - segment.alignment, - segment.flags, - }); - - // support legacy object files that specified being TLS by the name instead of the TLS flag. - if (!segment.isTLS() and (std.mem.startsWith(u8, segment.name, ".tdata") or std.mem.startsWith(u8, segment.name, ".tbss"))) { - // set the flag so we can simply check for the flag in the rest of the linker. - segment.flags |= @intFromEnum(Wasm.NamedSegment.Flags.WASM_SEG_FLAG_TLS); - } - } - parser.object.segment_info = segments; - }, - .WASM_INIT_FUNCS => { - const funcs = try gpa.alloc(Wasm.InitFunc, count); - errdefer gpa.free(funcs); - for (funcs) |*func| { - func.* = .{ - .priority = try leb.readUleb128(u32, reader), - .symbol_index = try leb.readUleb128(u32, reader), - }; - log.debug("Found function - prio: {d}, index: {d}", .{ func.priority, func.symbol_index }); - } - parser.object.init_funcs = funcs; - }, - .WASM_COMDAT_INFO => { - const comdats = try gpa.alloc(Wasm.Comdat, count); - errdefer gpa.free(comdats); - for (comdats) |*comdat| { - const name_len = try leb.readUleb128(u32, reader); - const name = try gpa.alloc(u8, name_len); - errdefer gpa.free(name); - try reader.readNoEof(name); - - const flags = try leb.readUleb128(u32, reader); - if (flags != 0) { - return error.UnexpectedValue; - } - - const symbol_count = try leb.readUleb128(u32, reader); - const symbols = try gpa.alloc(Wasm.ComdatSym, symbol_count); - errdefer gpa.free(symbols); - for (symbols) |*symbol| { - symbol.* = .{ - .kind = @as(Wasm.ComdatSym.Type, @enumFromInt(try leb.readUleb128(u8, reader))), - .index = try leb.readUleb128(u32, reader), - }; - } - - comdat.* = .{ - .name = name, - .flags = flags, - .symbols = symbols, - }; - } - - parser.object.comdat_info = comdats; - }, - .WASM_SYMBOL_TABLE => { - var symbols = try std.ArrayList(Symbol).initCapacity(gpa, count); - - var i: usize = 0; - while (i < count) : (i += 1) { - const symbol = symbols.addOneAssumeCapacity(); - symbol.* = try parser.parseSymbol(gpa, reader); - log.debug("Found symbol: type({s}) name({s}) flags(0b{b:0>8})", .{ - @tagName(symbol.tag), - wasm.stringSlice(symbol.name), - symbol.flags, - }); - } - - // we found all symbols, check for indirect function table - // in case of an MVP object file - if (try parser.object.checkLegacyIndirectFunctionTable(parser.wasm)) |symbol| { - try symbols.append(symbol); - log.debug("Found legacy indirect function table. Created symbol", .{}); - } - - // Not all debug sections may be represented by a symbol, for those sections - // we manually create a symbol. - if (parser.object.relocatable_data.get(.custom)) |custom_sections| { - for (custom_sections) |*data| { - if (!data.represented) { - const name = wasm.castToString(data.index); - try symbols.append(.{ - .name = name, - .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL), - .tag = .section, - .virtual_address = 0, - .index = data.section_index, - }); - data.represented = true; - log.debug("Created synthetic custom section symbol for '{s}'", .{ - wasm.stringSlice(name), - }); - } - } - } - - parser.object.symtable = try symbols.toOwnedSlice(); - }, + if (table_imports.len != 1) { + return diags.failParse(path, "found more than one table import, but no representing table symbols", .{}); } - } - - /// Parses the symbol information based on its kind, - /// requires access to `Object` to find the name of a symbol when it's - /// an import and flag `WASM_SYM_EXPLICIT_NAME` is not set. - fn parseSymbol(parser: *Parser, gpa: Allocator, reader: anytype) !Symbol { - const wasm = parser.wasm; - const tag: Symbol.Tag = @enumFromInt(try leb.readUleb128(u8, reader)); - const flags = try leb.readUleb128(u32, reader); - var symbol: Symbol = .{ - .flags = flags, - .tag = tag, - .name = undefined, - .index = undefined, - .virtual_address = undefined, + const table_import_name = table_imports[0].name; + if (table_import_name != wasm.preloaded_strings.__indirect_function_table) { + return diags.failParse(path, "non-indirect function table import '{s}' is missing a corresponding symbol", .{ + wasm.stringSlice(table_import_name), + }); + } + table_imports[0].flags = .{ + .undefined = true, + .no_strip = true, }; - - switch (tag) { - .data => { - const name_len = try leb.readUleb128(u32, reader); - const name = try gpa.alloc(u8, name_len); - defer gpa.free(name); - try reader.readNoEof(name); - symbol.name = try wasm.internString(name); - - // Data symbols only have the following fields if the symbol is defined - if (symbol.isDefined()) { - symbol.index = try leb.readUleb128(u32, reader); - // @TODO: We should verify those values - _ = try leb.readUleb128(u32, reader); - _ = try leb.readUleb128(u32, reader); - } - }, - .section => { - symbol.index = try leb.readUleb128(u32, reader); - const section_data = parser.object.relocatable_data.get(.custom).?; - for (section_data) |*data| { - if (data.section_index == symbol.index) { - symbol.name = wasm.castToString(data.index); - data.represented = true; - break; - } - } - }, - else => { - symbol.index = try leb.readUleb128(u32, reader); - const is_undefined = symbol.isUndefined(); - const explicit_name = symbol.hasFlag(.WASM_SYM_EXPLICIT_NAME); - symbol.name = if (!is_undefined or (is_undefined and explicit_name)) name: { - const name_len = try leb.readUleb128(u32, reader); - const name = try gpa.alloc(u8, name_len); - defer gpa.free(name); - try reader.readNoEof(name); - break :name try wasm.internString(name); - } else parser.object.findImport(symbol).name; - }, - } - return symbol; } -}; -/// First reads the count from the reader and then allocate -/// a slice of ptr child's element type. -fn readVec(ptr: anytype, reader: anytype, gpa: Allocator) ![]ElementType(@TypeOf(ptr)) { - const len = try readLeb(u32, reader); - const slice = try gpa.alloc(ElementType(@TypeOf(ptr)), len); - ptr.* = slice; - return slice; -} + for (wasm.object_init_funcs.items[init_funcs_start..]) |init_func| { + const func = init_func.function_index.ptr(wasm); + const params = func.type_index.ptr(wasm).params.slice(wasm); + if (params.len != 0) diags.addError("constructor function '{s}' has non-empty parameter list", .{ + func.name.slice(wasm).?, + }); + } -fn ElementType(comptime ptr: type) type { - return meta.Elem(meta.Child(ptr)); -} - -/// Uses either `readIleb128` or `readUleb128` depending on the -/// signedness of the given type `T`. -/// Asserts `T` is an integer. -fn readLeb(comptime T: type, reader: anytype) !T { - return switch (@typeInfo(T).int.signedness) { - .signed => try leb.readIleb128(T, reader), - .unsigned => try leb.readUleb128(T, reader), + return .{ + .version = version, + .path = path, + .archive_member_name = archive_member_name, + .start_function = start_function, + .features = features, + .imports = .{ + .off = imports_start, + .len = @intCast(wasm.object_imports.items.len - imports_start), + }, + .functions = .{ + .off = functions_start, + .len = @intCast(wasm.functions.items.len - functions_start), + }, + .tables = .{ + .off = tables_start, + .len = @intCast(wasm.object_tables.items.len - tables_start), + }, + .memories = .{ + .off = memories_start, + .len = @intCast(wasm.object_memories.items.len - memories_start), + }, + .globals = .{ + .off = globals_start, + .len = @intCast(wasm.object_globals.items.len - globals_start), + }, + .init_funcs = .{ + .off = init_funcs_start, + .len = @intCast(wasm.object_init_funcs.items.len - init_funcs_start), + }, + .comdats = .{ + .off = comdats_start, + .len = @intCast(wasm.object_comdats.items.len - comdats_start), + }, + .custom_segments = .{ + .off = custom_segment_start, + .len = @intCast(wasm.object_custom_segments.items.len - custom_segment_start), + }, + .local_section_index_base = local_section_index_base, }; } -/// Reads an enum type from the given reader. -/// Asserts `T` is an enum -fn readEnum(comptime T: type, reader: anytype) !T { - switch (@typeInfo(T)) { - .@"enum" => |enum_type| return @as(T, @enumFromInt(try readLeb(enum_type.tag_type, reader))), - else => @compileError("T must be an enum. Instead was given type " ++ @typeName(T)), +/// Based on the "features" custom section, parses it into a list of +/// features that tell the linker what features were enabled and may be mandatory +/// to be able to link. +fn parseFeatures( + wasm: *Wasm, + bytes: []const u8, + start_pos: usize, + path: Path, +) error{ OutOfMemory, LinkFailure }!struct { Wasm.Feature.Set, usize } { + const gpa = wasm.base.comp.gpa; + const diags = &wasm.base.comp.link_diags; + const features_len, var pos = readLeb(u32, bytes, start_pos); + // This temporary allocation could be avoided by using the string_bytes buffer as a scratch space. + const feature_buffer = try gpa.alloc(Wasm.Feature, features_len); + defer gpa.free(feature_buffer); + for (feature_buffer) |*feature| { + const prefix: Wasm.Feature.Prefix = switch (bytes[pos]) { + '-' => .@"-", + '+' => .@"+", + '=' => .@"=", + else => return error.InvalidFeaturePrefix, + }; + pos += 1; + const name, pos = readBytes(bytes, pos); + const tag = std.meta.stringToEnum(Wasm.Feature.Tag, name) orelse { + return diags.failParse(path, "unrecognized wasm feature in object: {s}", .{name}); + }; + feature.* = .{ + .prefix = prefix, + .tag = tag, + }; } + std.mem.sortUnstable(Wasm.Feature, feature_buffer, {}, Wasm.Feature.lessThan); + + return .{ + .fromString(try wasm.internString(@bitCast(feature_buffer))), + pos, + }; } -fn readLimits(reader: anytype) !std.wasm.Limits { - const flags = try reader.readByte(); - const min = try readLeb(u32, reader); - var limits: std.wasm.Limits = .{ +fn readLeb(comptime T: type, bytes: []const u8, pos: usize) struct { T, usize } { + var fbr = std.io.fixedBufferStream(bytes[pos..]); + return .{ + switch (@typeInfo(T).int.signedness) { + .signed => std.leb.readIleb128(T, fbr.reader()) catch unreachable, + .unsigned => std.leb.readUleb128(T, fbr.reader()) catch unreachable, + }, + pos + fbr.pos, + }; +} + +fn readBytes(bytes: []const u8, start_pos: usize) struct { []const u8, usize } { + const len, const pos = readLeb(u32, bytes, start_pos); + return .{ + bytes[pos..][0..len], + pos + len, + }; +} + +fn readEnum(comptime T: type, bytes: []const u8, pos: usize) struct { T, usize } { + const Tag = @typeInfo(T).@"enum".tag_type; + const int, const new_pos = readLeb(Tag, bytes, pos); + return .{ @enumFromInt(int), new_pos }; +} + +fn readLimits(bytes: []const u8, start_pos: usize) struct { std.wasm.Limits, usize } { + const flags = bytes[start_pos]; + const min, const max_pos = readLeb(u32, bytes, start_pos + 1); + const max, const end_pos = if (flags.has_max) readLeb(u32, bytes, max_pos) else .{ undefined, max_pos }; + return .{ .{ .flags = flags, .min = min, - .max = undefined, + .max = max, + }, end_pos }; +} + +fn readInit(wasm: *Wasm, bytes: []const u8, pos: usize) !struct { Wasm.Expr, usize } { + const end_pos = skipInit(bytes, pos); // one after the end opcode + return .{ try wasm.addExpr(bytes[pos..end_pos]), end_pos }; +} + +fn skipInit(bytes: []const u8, pos: usize) !usize { + const opcode = bytes[pos]; + const end_pos = switch (@as(std.wasm.Opcode, @enumFromInt(opcode))) { + .i32_const => readLeb(i32, bytes, pos + 1)[1], + .i64_const => readLeb(i64, bytes, pos + 1)[1], + .f32_const => pos + 5, + .f64_const => pos + 9, + .global_get => readLeb(u32, bytes, pos + 1)[1], + else => return error.InvalidInitOpcode, }; - if (limits.hasFlag(.WASM_LIMITS_FLAG_HAS_MAX)) { - limits.max = try readLeb(u32, reader); - } - return limits; -} - -fn readInit(reader: anytype) !std.wasm.InitExpression { - const opcode = try reader.readByte(); - const init_expr: std.wasm.InitExpression = switch (@as(std.wasm.Opcode, @enumFromInt(opcode))) { - .i32_const => .{ .i32_const = try readLeb(i32, reader) }, - .global_get => .{ .global_get = try readLeb(u32, reader) }, - else => @panic("TODO: initexpression for other opcodes"), - }; - - if ((try readEnum(std.wasm.Opcode, reader)) != .end) return error.MissingEndForExpression; - return init_expr; -} - -fn assertEnd(reader: anytype) !void { - var buf: [1]u8 = undefined; - const len = try reader.read(&buf); - if (len != 0) return error.MalformedSection; - if (reader.context.bytes_left != 0) return error.MalformedSection; + if (readEnum(std.wasm.Opcode, bytes, end_pos) != .end) return error.InitExprMissingEnd; + return end_pos + 1; } diff --git a/src/link/Wasm/Symbol.zig b/src/link/Wasm/Symbol.zig deleted file mode 100644 index b60b73c46f..0000000000 --- a/src/link/Wasm/Symbol.zig +++ /dev/null @@ -1,210 +0,0 @@ -//! Represents a WebAssembly symbol. Containing all of its properties, -//! as well as providing helper methods to determine its functionality -//! and how it will/must be linked. -//! The name of the symbol can be found by providing the offset, found -//! on the `name` field, to a string table in the wasm binary or object file. - -/// Bitfield containings flags for a symbol -/// Can contain any of the flags defined in `Flag` -flags: u32, -/// Symbol name, when the symbol is undefined the name will be taken from the import. -/// Note: This is an index into the wasm string table. -name: wasm.String, -/// Index into the list of objects based on set `tag` -/// NOTE: This will be set to `undefined` when `tag` is `data` -/// and the symbol is undefined. -index: u32, -/// Represents the kind of the symbol, such as a function or global. -tag: Tag, -/// Contains the virtual address of the symbol, relative to the start of its section. -/// This differs from the offset of an `Atom` which is relative to the start of a segment. -virtual_address: u32, - -/// Represents a symbol index where `null` represents an invalid index. -pub const Index = enum(u32) { - null, - _, -}; - -pub const Tag = enum { - function, - data, - global, - section, - event, - table, - /// synthetic kind used by the wasm linker during incremental compilation - /// to notate a symbol has been freed, but still lives in the symbol list. - dead, - undefined, - - /// From a given symbol tag, returns the `ExternalType` - /// Asserts the given tag can be represented as an external type. - pub fn externalType(tag: Tag) std.wasm.ExternalKind { - return switch (tag) { - .function => .function, - .global => .global, - .data => unreachable, // Data symbols will generate a global - .section => unreachable, // Not an external type - .event => unreachable, // Not an external type - .dead => unreachable, // Dead symbols should not be referenced - .undefined => unreachable, - .table => .table, - }; - } -}; - -pub const Flag = enum(u32) { - /// Indicates a weak symbol. - /// When linking multiple modules defining the same symbol, all weak definitions are discarded - /// in favourite of the strong definition. When no strong definition exists, all weak but one definition is discarded. - /// If multiple definitions remain, we get an error: symbol collision. - WASM_SYM_BINDING_WEAK = 0x1, - /// Indicates a local, non-exported, non-module-linked symbol. - /// The names of local symbols are not required to be unique, unlike non-local symbols. - WASM_SYM_BINDING_LOCAL = 0x2, - /// Represents the binding of a symbol, indicating if it's local or not, and weak or not. - WASM_SYM_BINDING_MASK = 0x3, - /// Indicates a hidden symbol. Hidden symbols will not be exported to the link result, but may - /// link to other modules. - WASM_SYM_VISIBILITY_HIDDEN = 0x4, - /// Indicates an undefined symbol. For non-data symbols, this must match whether the symbol is - /// an import or is defined. For data symbols however, determines whether a segment is specified. - WASM_SYM_UNDEFINED = 0x10, - /// Indicates a symbol of which its intention is to be exported from the wasm module to the host environment. - /// This differs from the visibility flag as this flag affects the static linker. - WASM_SYM_EXPORTED = 0x20, - /// Indicates the symbol uses an explicit symbol name, rather than reusing the name from a wasm import. - /// Allows remapping imports from foreign WASM modules into local symbols with a different name. - WASM_SYM_EXPLICIT_NAME = 0x40, - /// Indicates the symbol is to be included in the linker output, regardless of whether it is used or has any references to it. - WASM_SYM_NO_STRIP = 0x80, - /// Indicates a symbol is TLS - WASM_SYM_TLS = 0x100, - /// Zig specific flag. Uses the most significant bit of the flag to annotate whether a symbol is - /// alive or not. Dead symbols are allowed to be garbage collected. - alive = 0x80000000, -}; - -/// Verifies if the given symbol should be imported from the -/// host environment or not -pub fn requiresImport(symbol: Symbol) bool { - if (symbol.tag == .data) return false; - if (!symbol.isUndefined()) return false; - if (symbol.isWeak()) return false; - // if (symbol.isDefined() and symbol.isWeak()) return true; //TODO: Only when building shared lib - - return true; -} - -/// Marks a symbol as 'alive', ensuring the garbage collector will not collect the trash. -pub fn mark(symbol: *Symbol) void { - symbol.flags |= @intFromEnum(Flag.alive); -} - -pub fn unmark(symbol: *Symbol) void { - symbol.flags &= ~@intFromEnum(Flag.alive); -} - -pub fn isAlive(symbol: Symbol) bool { - return symbol.flags & @intFromEnum(Flag.alive) != 0; -} - -pub fn isDead(symbol: Symbol) bool { - return symbol.flags & @intFromEnum(Flag.alive) == 0; -} - -pub fn isTLS(symbol: Symbol) bool { - return symbol.flags & @intFromEnum(Flag.WASM_SYM_TLS) != 0; -} - -pub fn hasFlag(symbol: Symbol, flag: Flag) bool { - return symbol.flags & @intFromEnum(flag) != 0; -} - -pub fn setFlag(symbol: *Symbol, flag: Flag) void { - symbol.flags |= @intFromEnum(flag); -} - -pub fn isUndefined(symbol: Symbol) bool { - return symbol.flags & @intFromEnum(Flag.WASM_SYM_UNDEFINED) != 0; -} - -pub fn setUndefined(symbol: *Symbol, is_undefined: bool) void { - if (is_undefined) { - symbol.setFlag(.WASM_SYM_UNDEFINED); - } else { - symbol.flags &= ~@intFromEnum(Flag.WASM_SYM_UNDEFINED); - } -} - -pub fn setGlobal(symbol: *Symbol, is_global: bool) void { - if (is_global) { - symbol.flags &= ~@intFromEnum(Flag.WASM_SYM_BINDING_LOCAL); - } else { - symbol.setFlag(.WASM_SYM_BINDING_LOCAL); - } -} - -pub fn isDefined(symbol: Symbol) bool { - return !symbol.isUndefined(); -} - -pub fn isVisible(symbol: Symbol) bool { - return symbol.flags & @intFromEnum(Flag.WASM_SYM_VISIBILITY_HIDDEN) == 0; -} - -pub fn isLocal(symbol: Symbol) bool { - return symbol.flags & @intFromEnum(Flag.WASM_SYM_BINDING_LOCAL) != 0; -} - -pub fn isGlobal(symbol: Symbol) bool { - return symbol.flags & @intFromEnum(Flag.WASM_SYM_BINDING_LOCAL) == 0; -} - -pub fn isHidden(symbol: Symbol) bool { - return symbol.flags & @intFromEnum(Flag.WASM_SYM_VISIBILITY_HIDDEN) != 0; -} - -pub fn isNoStrip(symbol: Symbol) bool { - return symbol.flags & @intFromEnum(Flag.WASM_SYM_NO_STRIP) != 0; -} - -pub fn isExported(symbol: Symbol, is_dynamic: bool) bool { - if (symbol.isUndefined() or symbol.isLocal()) return false; - if (is_dynamic and symbol.isVisible()) return true; - return symbol.hasFlag(.WASM_SYM_EXPORTED); -} - -pub fn isWeak(symbol: Symbol) bool { - return symbol.flags & @intFromEnum(Flag.WASM_SYM_BINDING_WEAK) != 0; -} - -/// Formats the symbol into human-readable text -pub fn format(symbol: Symbol, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { - _ = fmt; - _ = options; - - const kind_fmt: u8 = switch (symbol.tag) { - .function => 'F', - .data => 'D', - .global => 'G', - .section => 'S', - .event => 'E', - .table => 'T', - .dead => '-', - .undefined => unreachable, - }; - const visible: []const u8 = if (symbol.isVisible()) "yes" else "no"; - const binding: []const u8 = if (symbol.isLocal()) "local" else "global"; - const undef: []const u8 = if (symbol.isUndefined()) "undefined" else ""; - - try writer.print( - "{c} binding={s} visible={s} id={d} name_offset={d} {s}", - .{ kind_fmt, binding, visible, symbol.index, symbol.name, undef }, - ); -} - -const std = @import("std"); -const Symbol = @This(); -const wasm = @import("../Wasm.zig"); diff --git a/src/link/Wasm/ZigObject.zig b/src/link/Wasm/ZigObject.zig deleted file mode 100644 index f2bce777ed..0000000000 --- a/src/link/Wasm/ZigObject.zig +++ /dev/null @@ -1,1229 +0,0 @@ -//! 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, .none, 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); - if (nav.getExtern(ip)) |@"extern"| { - try zig_object.addOrUpdateImport( - wasm, - nav.name.toSlice(ip), - target_atom.sym_index, - @"extern".lib_name.toSlice(ip), - null, - ); - } - - 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 = ip.getNav(nav_index); - if (nav.getExtern(ip) != null) { - 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(nav.typeOf(ip))) { - 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 updateLineNumber(zig_object: *ZigObject, pt: Zcu.PerThread, ti_id: InternPool.TrackedInst.Index) !void { - if (zig_object.dwarf) |*dw| { - try dw.updateLineNumber(pt.zcu, ti_id); - } -} - -/// 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(); diff --git a/src/main.zig b/src/main.zig index 7bb51bbd8e..b17a753b2b 100644 --- a/src/main.zig +++ b/src/main.zig @@ -75,6 +75,10 @@ pub fn fatal(comptime format: []const u8, args: anytype) noreturn { process.exit(1); } +/// Shaming all the locations that inappropriately use an O(N) search algorithm. +/// Please delete this and fix the compilation errors! +pub const @"bad O(N)" = void; + const normal_usage = \\Usage: zig [command] [options] \\ diff --git a/src/register_manager.zig b/src/register_manager.zig index c24cda6cf5..48b12a59d2 100644 --- a/src/register_manager.zig +++ b/src/register_manager.zig @@ -14,19 +14,14 @@ const link = @import("link.zig"); const log = std.log.scoped(.register_manager); -pub const AllocateRegistersError = error{ - /// No registers are available anymore +pub const AllocationError = error{ OutOfRegisters, - /// Can happen when spilling an instruction in codegen runs out of - /// memory, so we propagate that error OutOfMemory, - /// Can happen when spilling an instruction in codegen triggers integer - /// overflow, so we propagate that error + /// Compiler was asked to operate on a number larger than supported. Overflow, - /// Can happen when spilling an instruction triggers a codegen - /// error, so we propagate that error + /// Indicates the error is already stored in `failed_codegen` on the Zcu. CodegenFail, -} || link.File.UpdateDebugInfoError; +}; pub fn RegisterManager( comptime Function: type, @@ -281,7 +276,7 @@ pub fn RegisterManager( comptime count: comptime_int, insts: [count]?Air.Inst.Index, register_class: RegisterBitSet, - ) AllocateRegistersError![count]Register { + ) AllocationError![count]Register { comptime assert(count > 0 and count <= tracked_registers.len); var locked_registers = self.locked_registers; @@ -338,7 +333,7 @@ pub fn RegisterManager( self: *Self, inst: ?Air.Inst.Index, register_class: RegisterBitSet, - ) AllocateRegistersError!Register { + ) AllocationError!Register { return (try self.allocRegs(1, .{inst}, register_class))[0]; } @@ -349,7 +344,7 @@ pub fn RegisterManager( self: *Self, tracked_index: TrackedIndex, inst: ?Air.Inst.Index, - ) AllocateRegistersError!void { + ) AllocationError!void { log.debug("getReg {} for inst {?}", .{ regAtTrackedIndex(tracked_index), inst }); if (!self.isRegIndexFree(tracked_index)) { // Move the instruction that was previously there to a @@ -362,7 +357,7 @@ pub fn RegisterManager( } self.getRegIndexAssumeFree(tracked_index, inst); } - pub fn getReg(self: *Self, reg: Register, inst: ?Air.Inst.Index) AllocateRegistersError!void { + pub fn getReg(self: *Self, reg: Register, inst: ?Air.Inst.Index) AllocationError!void { log.debug("getting reg: {}", .{reg}); return self.getRegIndex(indexOfRegIntoTracked(reg) orelse return, inst); } @@ -370,7 +365,7 @@ pub fn RegisterManager( self: *Self, comptime reg: Register, inst: ?Air.Inst.Index, - ) AllocateRegistersError!void { + ) AllocationError!void { return self.getRegIndex((comptime indexOfRegIntoTracked(reg)) orelse return, inst); }