authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-12-17 21:12:01-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-01-15 15:11:35-08:00
log458f658b427c26ede2776a04113ba00a3a491793
tree0dcb397bb124a786d35697a93265305f800da7f1
parent761387dc556e065e39d021bf96fb4d4c8873a145

wasm linker: implement missing logic

fix some compilation errors for reworked Emit now that it's actually referenced introduce DataSegment.Id for sorting data both from object files and from the Zcu. introduce optimization: data segment sorting includes a descending sort on reference count so that references to data can be smaller integers leading to better LEB encodings. this optimization is skipped for object files. implement uav address access function which is based on only 1 hash table lookup to find out the offset after sorting.

8 files changed, 622 insertions(+), 253 deletions(-)

src/InternPool.zig+7
......@@ -620,6 +620,13 @@ pub const Nav = struct {
620620 };
621621 }
622622
623 /// Asserts that `status == .resolved`.
624 pub fn isThreadLocal(nav: Nav, ip: *const InternPool) bool {
625 const val = nav.status.resolved.val;
626 if (!isVariable(ip, val)) return false;
627 return ip.indexToKey(val).variable.is_threadlocal;
628 }
629
623630 /// Get the ZIR instruction corresponding to this `Nav`, used to resolve source locations.
624631 /// This is a `declaration`.
625632 pub fn srcInst(nav: Nav, ip: *const InternPool) TrackedInst.Index {
src/arch/wasm/CodeGen.zig+27-10
......@@ -944,8 +944,11 @@ fn addExtraAssumeCapacity(cg: *CodeGen, extra: anytype) error{OutOfMemory}!u32 {
944944 cg.mir_extra.appendAssumeCapacity(switch (field.type) {
945945 u32 => @field(extra, field.name),
946946 i32 => @bitCast(@field(extra, field.name)),
947 InternPool.Index => @intFromEnum(@field(extra, field.name)),
948 InternPool.Nav.Index => @intFromEnum(@field(extra, field.name)),
947 InternPool.Index,
948 InternPool.Nav.Index,
949 Wasm.UavsObjIndex,
950 Wasm.UavsExeIndex,
951 => @intFromEnum(@field(extra, field.name)),
949952 else => |field_type| @compileError("Unsupported field type " ++ @typeName(field_type)),
950953 });
951954 }
......@@ -1034,14 +1037,26 @@ fn emitWValue(cg: *CodeGen, value: WValue) InnerError!void {
10341037 }
10351038 },
10361039 .uav_ref => |uav| {
1040 const wasm = cg.wasm;
1041 const is_obj = wasm.base.comp.config.output_mode == .Obj;
10371042 if (uav.offset == 0) {
1038 try cg.addInst(.{ .tag = .uav_ref, .data = .{ .ip_index = uav.ip_index } });
1043 try cg.addInst(.{
1044 .tag = .uav_ref,
1045 .data = if (is_obj) .{
1046 .uav_obj = try wasm.refUavObj(cg.pt, uav.ip_index),
1047 } else .{
1048 .uav_exe = try wasm.refUavExe(cg.pt, uav.ip_index),
1049 },
1050 });
10391051 } else {
10401052 try cg.addInst(.{
10411053 .tag = .uav_ref_off,
10421054 .data = .{
1043 .payload = try cg.addExtra(Mir.UavRefOff{
1044 .ip_index = uav.ip_index,
1055 .payload = if (is_obj) try cg.addExtra(Mir.UavRefOffObj{
1056 .uav_obj = try wasm.refUavObj(cg.pt, uav.ip_index),
1057 .offset = uav.offset,
1058 }) else try cg.addExtra(Mir.UavRefOffExe{
1059 .uav_exe = try wasm.refUavExe(cg.pt, uav.ip_index),
10451060 .offset = uav.offset,
10461061 }),
10471062 },
......@@ -1148,11 +1163,11 @@ pub const Function = extern struct {
11481163 }
11491164 };
11501165
1151 pub fn lower(f: *Function, wasm: *const Wasm, code: *std.ArrayList(u8)) Allocator.Error!void {
1166 pub fn lower(f: *Function, wasm: *Wasm, code: *std.ArrayListUnmanaged(u8)) Allocator.Error!void {
11521167 const gpa = wasm.base.comp.gpa;
11531168
11541169 // Write the locals in the prologue of the function body.
1155 const locals = wasm.all_zcu_locals[f.locals_off..][0..f.locals_len];
1170 const locals = wasm.all_zcu_locals.items[f.locals_off..][0..f.locals_len];
11561171 try code.ensureUnusedCapacity(gpa, 5 + locals.len * 6 + 38);
11571172
11581173 std.leb.writeUleb128(code.writer(gpa), @as(u32, @intCast(locals.len))) catch unreachable;
......@@ -1164,7 +1179,7 @@ pub const Function = extern struct {
11641179 // Stack management section of function prologue.
11651180 const stack_alignment = f.prologue.flags.stack_alignment;
11661181 if (stack_alignment.toByteUnits()) |align_bytes| {
1167 const sp_global = try wasm.stackPointerGlobalIndex();
1182 const sp_global: Wasm.GlobalIndex = .stack_pointer;
11681183 // load stack pointer
11691184 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.global_get));
11701185 std.leb.writeULEB128(code.writer(gpa), @intFromEnum(sp_global)) catch unreachable;
......@@ -1172,7 +1187,7 @@ pub const Function = extern struct {
11721187 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.local_tee));
11731188 leb.writeUleb128(code.writer(gpa), f.prologue.sp_local) catch unreachable;
11741189 // get the total stack size
1175 const aligned_stack: i32 = @intCast(f.stack_alignment.forward(f.prologue.stack_size));
1190 const aligned_stack: i32 = @intCast(stack_alignment.forward(f.prologue.stack_size));
11761191 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i32_const));
11771192 leb.writeIleb128(code.writer(gpa), aligned_stack) catch unreachable;
11781193 // subtract it from the current stack pointer
......@@ -1197,7 +1212,7 @@ pub const Function = extern struct {
11971212 .mir = .{
11981213 .instruction_tags = wasm.mir_instructions.items(.tag)[f.mir_off..][0..f.mir_len],
11991214 .instruction_datas = wasm.mir_instructions.items(.data)[f.mir_off..][0..f.mir_len],
1200 .extra = wasm.mir_extra[f.mir_extra_off..][0..f.mir_extra_len],
1215 .extra = wasm.mir_extra.items[f.mir_extra_off..][0..f.mir_extra_len],
12011216 },
12021217 .wasm = wasm,
12031218 .code = code,
......@@ -5846,6 +5861,8 @@ fn airErrorName(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
58465861 const name_ty = Type.slice_const_u8_sentinel_0;
58475862 const abi_size = name_ty.abiSize(pt.zcu);
58485863
5864 cg.wasm.error_name_table_ref_count += 1;
5865
58495866 // Lowers to a i32.const or i64.const with the error table memory address.
58505867 try cg.addTag(.error_name_table_ref);
58515868 try cg.emitWValue(operand);
src/arch/wasm/Emit.zig+43-52
......@@ -5,6 +5,7 @@ const assert = std.debug.assert;
55const Allocator = std.mem.Allocator;
66const leb = std.leb;
77
8const Wasm = link.File.Wasm;
89const Mir = @import("Mir.zig");
910const link = @import("../../link.zig");
1011const Zcu = @import("../../Zcu.zig");
......@@ -12,7 +13,7 @@ const InternPool = @import("../../InternPool.zig");
1213const codegen = @import("../../codegen.zig");
1314
1415mir: Mir,
15wasm: *link.File.Wasm,
16wasm: *Wasm,
1617/// The binary representation that will be emitted by this module.
1718code: *std.ArrayListUnmanaged(u8),
1819
......@@ -30,8 +31,8 @@ pub fn lowerToCode(emit: *Emit) Error!void {
3031 const target = &comp.root_mod.resolved_target.result;
3132 const is_wasm32 = target.cpu.arch == .wasm32;
3233
33 const tags = mir.instructions.items(.tag);
34 const datas = mir.instructions.items(.data);
34 const tags = mir.instruction_tags;
35 const datas = mir.instruction_datas;
3536 var inst: u32 = 0;
3637
3738 loop: switch (tags[inst]) {
......@@ -48,17 +49,25 @@ pub fn lowerToCode(emit: *Emit) Error!void {
4849 continue :loop tags[inst];
4950 },
5051 .uav_ref => {
51 try uavRefOff(wasm, code, .{ .ip_index = datas[inst].ip_index, .offset = 0 });
52 if (is_obj) {
53 try uavRefOffObj(wasm, code, .{ .uav_obj = datas[inst].uav_obj, .offset = 0 }, is_wasm32);
54 } else {
55 try uavRefOffExe(wasm, code, .{ .uav_exe = datas[inst].uav_exe, .offset = 0 }, is_wasm32);
56 }
5257 inst += 1;
5358 continue :loop tags[inst];
5459 },
5560 .uav_ref_off => {
56 try uavRefOff(wasm, code, mir.extraData(Mir.UavRefOff, datas[inst].payload).data);
61 if (is_obj) {
62 try uavRefOffObj(wasm, code, mir.extraData(Mir.UavRefOffObj, datas[inst].payload).data, is_wasm32);
63 } else {
64 try uavRefOffExe(wasm, code, mir.extraData(Mir.UavRefOffExe, datas[inst].payload).data, is_wasm32);
65 }
5766 inst += 1;
5867 continue :loop tags[inst];
5968 },
6069 .nav_ref => {
61 try navRefOff(wasm, code, .{ .ip_index = datas[inst].ip_index, .offset = 0 }, is_wasm32);
70 try navRefOff(wasm, code, .{ .nav_index = datas[inst].nav_index, .offset = 0 }, is_wasm32);
6271 inst += 1;
6372 continue :loop tags[inst];
6473 },
......@@ -124,7 +133,7 @@ pub fn lowerToCode(emit: *Emit) Error!void {
124133 },
125134
126135 .br_table => {
127 const extra_index = mir.instructions.items(.data)[inst].payload;
136 const extra_index = datas[inst].payload;
128137 const extra = mir.extraData(Mir.JumpTable, extra_index);
129138 const labels = mir.extra[extra.end..][0..extra.data.length];
130139 try code.ensureUnusedCapacity(gpa, 11 + 10 * labels.len);
......@@ -223,7 +232,7 @@ pub fn lowerToCode(emit: *Emit) Error!void {
223232 continue :loop tags[inst];
224233 },
225234
226 .global_set => {
235 .global_set_sp => {
227236 try code.ensureUnusedCapacity(gpa, 6);
228237 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.global_set));
229238 if (is_obj) {
......@@ -235,7 +244,7 @@ pub fn lowerToCode(emit: *Emit) Error!void {
235244 });
236245 code.appendNTimesAssumeCapacity(0, 5);
237246 } else {
238 const sp_global = try wasm.stackPointerGlobalIndex();
247 const sp_global: Wasm.GlobalIndex = .stack_pointer;
239248 std.leb.writeULEB128(code.fixedWriter(), @intFromEnum(sp_global)) catch unreachable;
240249 }
241250
......@@ -243,26 +252,6 @@ pub fn lowerToCode(emit: *Emit) Error!void {
243252 continue :loop tags[inst];
244253 },
245254
246 .function_index => {
247 try code.ensureUnusedCapacity(gpa, 6);
248 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i32_const));
249 if (is_obj) {
250 try wasm.out_relocs.append(gpa, .{
251 .offset = @intCast(code.items.len),
252 .pointee = .{ .symbol_index = try wasm.functionSymbolIndex(datas[inst].ip_index) },
253 .tag = .TABLE_INDEX_SLEB,
254 .addend = 0,
255 });
256 code.appendNTimesAssumeCapacity(0, 5);
257 } else {
258 const func_index = try wasm.functionIndex(datas[inst].ip_index);
259 std.leb.writeULEB128(code.fixedWriter(), @intFromEnum(func_index)) catch unreachable;
260 }
261
262 inst += 1;
263 continue :loop tags[inst];
264 },
265
266255 .f32_const => {
267256 try code.ensureUnusedCapacity(gpa, 5);
268257 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.f32_const));
......@@ -521,7 +510,7 @@ pub fn lowerToCode(emit: *Emit) Error!void {
521510 },
522511 .simd_prefix => {
523512 try code.ensureUnusedCapacity(gpa, 6 + 20);
524 const extra_index = mir.instructions.items(.data)[inst].payload;
513 const extra_index = datas[inst].payload;
525514 const opcode = mir.extra[extra_index];
526515 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.simd_prefix));
527516 leb.writeUleb128(code.fixedWriter(), opcode) catch unreachable;
......@@ -578,7 +567,7 @@ pub fn lowerToCode(emit: *Emit) Error!void {
578567 .atomics_prefix => {
579568 try code.ensureUnusedCapacity(gpa, 6 + 20);
580569
581 const extra_index = mir.instructions.items(.data)[inst].payload;
570 const extra_index = datas[inst].payload;
582571 const opcode = mir.extra[extra_index];
583572 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.atomics_prefix));
584573 leb.writeUleb128(code.fixedWriter(), opcode) catch unreachable;
......@@ -677,34 +666,36 @@ fn encodeMemArg(code: *std.ArrayListUnmanaged(u8), mem_arg: Mir.MemArg) void {
677666 leb.writeUleb128(code.fixedWriter(), mem_arg.offset) catch unreachable;
678667}
679668
680fn uavRefOff(wasm: *link.File.Wasm, code: *std.ArrayListUnmanaged(u8), data: Mir.UavRefOff, is_wasm32: bool) !void {
669fn uavRefOffObj(wasm: *Wasm, code: *std.ArrayListUnmanaged(u8), data: Mir.UavRefOffObj, is_wasm32: bool) !void {
681670 const comp = wasm.base.comp;
682671 const gpa = comp.gpa;
683 const is_obj = comp.config.output_mode == .Obj;
684672 const opcode: std.wasm.Opcode = if (is_wasm32) .i32_const else .i64_const;
685673
686674 try code.ensureUnusedCapacity(gpa, 11);
687675 code.appendAssumeCapacity(@intFromEnum(opcode));
688676
689 // If outputting an object file, this needs to be a relocation, since global
690 // constant data may be mixed with other object files in the final link.
691 if (is_obj) {
692 try wasm.out_relocs.append(gpa, .{
693 .offset = @intCast(code.items.len),
694 .pointee = .{ .symbol_index = try wasm.uavSymbolIndex(data.ip_index) },
695 .tag = if (is_wasm32) .MEMORY_ADDR_LEB else .MEMORY_ADDR_LEB64,
696 .addend = data.offset,
697 });
698 code.appendNTimesAssumeCapacity(0, if (is_wasm32) 5 else 10);
699 return;
700 }
677 try wasm.out_relocs.append(gpa, .{
678 .offset = @intCast(code.items.len),
679 .pointee = .{ .symbol_index = try wasm.uavSymbolIndex(data.uav_obj.key(wasm).*) },
680 .tag = if (is_wasm32) .MEMORY_ADDR_LEB else .MEMORY_ADDR_LEB64,
681 .addend = data.offset,
682 });
683 code.appendNTimesAssumeCapacity(0, if (is_wasm32) 5 else 10);
684}
685
686fn uavRefOffExe(wasm: *Wasm, code: *std.ArrayListUnmanaged(u8), data: Mir.UavRefOffExe, is_wasm32: bool) !void {
687 const comp = wasm.base.comp;
688 const gpa = comp.gpa;
689 const opcode: std.wasm.Opcode = if (is_wasm32) .i32_const else .i64_const;
690
691 try code.ensureUnusedCapacity(gpa, 11);
692 code.appendAssumeCapacity(@intFromEnum(opcode));
701693
702 // When linking into the final binary, no relocation mechanism is necessary.
703 const addr = try wasm.uavAddr(data.ip_index);
704 leb.writeUleb128(code.fixedWriter(), addr + data.offset) catch unreachable;
694 const addr = try wasm.uavAddr(data.uav_exe);
695 leb.writeUleb128(code.fixedWriter(), @as(u32, @intCast(@as(i64, addr) + data.offset))) catch unreachable;
705696}
706697
707fn navRefOff(wasm: *link.File.Wasm, code: *std.ArrayListUnmanaged(u8), data: Mir.NavRefOff, is_wasm32: bool) !void {
698fn navRefOff(wasm: *Wasm, code: *std.ArrayListUnmanaged(u8), data: Mir.NavRefOff, is_wasm32: bool) !void {
708699 const comp = wasm.base.comp;
709700 const zcu = comp.zcu.?;
710701 const ip = &zcu.intern_pool;
......@@ -715,7 +706,7 @@ fn navRefOff(wasm: *link.File.Wasm, code: *std.ArrayListUnmanaged(u8), data: Mir
715706 try code.ensureUnusedCapacity(gpa, 11);
716707
717708 if (ip.isFunctionType(nav_ty)) {
718 code.appendAssumeCapacity(std.wasm.Opcode.i32_const);
709 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i32_const));
719710 assert(data.offset == 0);
720711 if (is_obj) {
721712 try wasm.out_relocs.append(gpa, .{
......@@ -727,7 +718,7 @@ fn navRefOff(wasm: *link.File.Wasm, code: *std.ArrayListUnmanaged(u8), data: Mir
727718 code.appendNTimesAssumeCapacity(0, 5);
728719 } else {
729720 const addr = try wasm.navAddr(data.nav_index);
730 leb.writeUleb128(code.fixedWriter(), addr + data.offset) catch unreachable;
721 leb.writeUleb128(code.fixedWriter(), @as(u32, @intCast(@as(i64, addr) + data.offset))) catch unreachable;
731722 }
732723 } else {
733724 const opcode: std.wasm.Opcode = if (is_wasm32) .i32_const else .i64_const;
......@@ -742,7 +733,7 @@ fn navRefOff(wasm: *link.File.Wasm, code: *std.ArrayListUnmanaged(u8), data: Mir
742733 code.appendNTimesAssumeCapacity(0, if (is_wasm32) 5 else 10);
743734 } else {
744735 const addr = try wasm.navAddr(data.nav_index);
745 leb.writeUleb128(code.fixedWriter(), addr + data.offset) catch unreachable;
736 leb.writeUleb128(code.fixedWriter(), @as(u32, @intCast(@as(i64, addr) + data.offset))) catch unreachable;
746737 }
747738 }
748739}
src/arch/wasm/Mir.zig+14-2
......@@ -610,6 +610,8 @@ pub const Inst = struct {
610610 nav_index: InternPool.Nav.Index,
611611 func_ty: Wasm.FunctionType.Index,
612612 intrinsic: Intrinsic,
613 uav_obj: Wasm.UavsObjIndex,
614 uav_exe: Wasm.UavsExeIndex,
613615
614616 comptime {
615617 switch (builtin.mode) {
......@@ -633,6 +635,11 @@ pub fn extraData(self: *const Mir, comptime T: type, index: usize) struct { data
633635 inline for (fields) |field| {
634636 @field(result, field.name) = switch (field.type) {
635637 u32 => self.extra[i],
638 i32 => @bitCast(self.extra[i]),
639 Wasm.UavsObjIndex,
640 Wasm.UavsExeIndex,
641 InternPool.Nav.Index,
642 => @enumFromInt(self.extra[i]),
636643 else => |field_type| @compileError("Unsupported field type " ++ @typeName(field_type)),
637644 };
638645 i += 1;
......@@ -684,8 +691,13 @@ pub const MemArg = struct {
684691 alignment: u32,
685692};
686693
687pub const UavRefOff = struct {
688 ip_index: InternPool.Index,
694pub const UavRefOffObj = struct {
695 uav_obj: Wasm.UavsObjIndex,
696 offset: i32,
697};
698
699pub const UavRefOffExe = struct {
700 uav_exe: Wasm.UavsExeIndex,
689701 offset: i32,
690702};
691703
src/codegen.zig+1-3
......@@ -590,7 +590,7 @@ fn lowerPtr(
590590 const ptr = zcu.intern_pool.indexToKey(ptr_val).ptr;
591591 const offset: u64 = prev_offset + ptr.byte_offset;
592592 return switch (ptr.base_addr) {
593 .nav => |nav| try lowerNavRef(bin_file, pt, src_loc, nav, code, reloc_parent, offset),
593 .nav => |nav| try lowerNavRef(bin_file, pt, nav, code, reloc_parent, offset),
594594 .uav => |uav| try lowerUavRef(bin_file, pt, src_loc, uav, code, reloc_parent, offset),
595595 .int => try generateSymbol(bin_file, pt, src_loc, try pt.intValue(Type.usize, offset), code, reloc_parent),
596596 .eu_payload => |eu_ptr| try lowerPtr(
......@@ -708,13 +708,11 @@ fn lowerUavRef(
708708fn lowerNavRef(
709709 lf: *link.File,
710710 pt: Zcu.PerThread,
711 src_loc: Zcu.LazySrcLoc,
712711 nav_index: InternPool.Nav.Index,
713712 code: *std.ArrayListUnmanaged(u8),
714713 reloc_parent: link.File.RelocInfo.Parent,
715714 offset: u64,
716715) GenerateSymbolError!void {
717 _ = src_loc;
718716 const zcu = pt.zcu;
719717 const gpa = zcu.gpa;
720718 const ip = &zcu.intern_pool;
src/link/Wasm.zig+435-109
......@@ -119,21 +119,6 @@ object_relocations: std.MultiArrayList(ObjectRelocation) = .empty,
119119/// by the (synthetic) __wasm_call_ctors function.
120120object_init_funcs: std.ArrayListUnmanaged(InitFunc) = .empty,
121121
122/// Relocations to be emitted into an object file. Remains empty when not
123/// emitting an object file.
124out_relocs: std.MultiArrayList(OutReloc) = .empty,
125/// List of locations within `string_bytes` that must be patched with the virtual
126/// memory address of a Uav during `flush`.
127/// When emitting an object file, `out_relocs` is used instead.
128uav_fixups: std.ArrayListUnmanaged(UavFixup) = .empty,
129/// List of locations within `string_bytes` that must be patched with the virtual
130/// memory address of a Nav during `flush`.
131/// When emitting an object file, `out_relocs` is used instead.
132nav_fixups: std.ArrayListUnmanaged(NavFixup) = .empty,
133/// Symbols to be emitted into an object file. Remains empty when not emitting
134/// an object file.
135symbol_table: std.AutoArrayHashMapUnmanaged(String, void) = .empty,
136
137122/// Non-synthetic section that can essentially be mem-cpy'd into place after performing relocations.
138123object_data_segments: std.ArrayListUnmanaged(DataSegment) = .empty,
139124/// Non-synthetic section that can essentially be mem-cpy'd into place after performing relocations.
......@@ -149,6 +134,21 @@ object_total_sections: u32 = 0,
149134/// All comdat symbols from all objects concatenated.
150135object_comdat_symbols: std.MultiArrayList(Comdat.Symbol) = .empty,
151136
137/// Relocations to be emitted into an object file. Remains empty when not
138/// emitting an object file.
139out_relocs: std.MultiArrayList(OutReloc) = .empty,
140/// List of locations within `string_bytes` that must be patched with the virtual
141/// memory address of a Uav during `flush`.
142/// When emitting an object file, `out_relocs` is used instead.
143uav_fixups: std.ArrayListUnmanaged(UavFixup) = .empty,
144/// List of locations within `string_bytes` that must be patched with the virtual
145/// memory address of a Nav during `flush`.
146/// When emitting an object file, `out_relocs` is used instead.
147nav_fixups: std.ArrayListUnmanaged(NavFixup) = .empty,
148/// Symbols to be emitted into an object file. Remains empty when not emitting
149/// an object file.
150symbol_table: std.AutoArrayHashMapUnmanaged(String, void) = .empty,
151
152152/// When importing objects from the host environment, a name must be supplied.
153153/// LLVM uses "env" by default when none is given. This would be a good default for Zig
154154/// to support existing code.
......@@ -170,9 +170,15 @@ dump_argv_list: std.ArrayListUnmanaged([]const u8),
170170preloaded_strings: PreloadedStrings,
171171
172172/// This field is used when emitting an object; `navs_exe` used otherwise.
173navs_obj: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, NavObj) = .empty,
173navs_obj: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, ZcuDataObj) = .empty,
174174/// This field is unused when emitting an object; `navs_exe` used otherwise.
175navs_exe: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, NavExe) = .empty,
175navs_exe: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, ZcuDataExe) = .empty,
176/// Tracks all InternPool values referenced by codegen. Needed for outputting
177/// the data segment. This one does not track ref count because object files
178/// require using max LEB encoding for these references anyway.
179uavs_obj: std.AutoArrayHashMapUnmanaged(InternPool.Index, ZcuDataObj) = .empty,
180/// Tracks ref count to optimize LEB encodings for UAV references.
181uavs_exe: std.AutoArrayHashMapUnmanaged(InternPool.Index, ZcuDataExe) = .empty,
176182zcu_funcs: std.AutoArrayHashMapUnmanaged(InternPool.Index, ZcuFunc) = .empty,
177183nav_exports: std.AutoArrayHashMapUnmanaged(NavExport, Zcu.Export.Index) = .empty,
178184uav_exports: std.AutoArrayHashMapUnmanaged(UavExport, Zcu.Export.Index) = .empty,
......@@ -224,6 +230,13 @@ global_imports: std.AutoArrayHashMapUnmanaged(String, GlobalImportId) = .empty,
224230tables: std.AutoArrayHashMapUnmanaged(TableImport.Resolution, void) = .empty,
225231table_imports: std.AutoArrayHashMapUnmanaged(String, TableImport.Index) = .empty,
226232
233/// Ordered list of data segments that will appear in the final binary.
234/// When sorted, to-be-merged segments will be made adjacent.
235/// Values are offset relative to segment start.
236data_segments: std.AutoArrayHashMapUnmanaged(Wasm.DataSegment.Id, u32) = .empty,
237
238error_name_table_ref_count: u32 = 0,
239
227240any_exports_updated: bool = true,
228241/// Set to true if any `GLOBAL_INDEX` relocation is encountered with
229242/// `SymbolFlags.tls` set to true. This is for objects only; final
......@@ -307,6 +320,16 @@ pub const OutputFunctionIndex = enum(u32) {
307320pub const GlobalIndex = enum(u32) {
308321 _,
309322
323 /// This is only accurate when there is a Zcu.
324 pub const stack_pointer: GlobalIndex = @enumFromInt(0);
325
326 /// Same as `stack_pointer` but with a safety assertion.
327 pub fn stackPointer(wasm: *const Wasm) Global.Index {
328 const comp = wasm.base.comp;
329 assert(comp.zcu != null);
330 return .stack_pointer;
331 }
332
310333 pub fn ptr(index: GlobalIndex, f: *const Flush) *Wasm.GlobalImport.Resolution {
311334 return &f.globals.items[@intFromEnum(index)];
312335 }
......@@ -545,56 +568,83 @@ pub const Valtype3 = enum(u3) {
545568 }
546569};
547570
548pub const NavObj = extern struct {
549 code: DataSegment.Payload,
550 /// Empty if not emitting an object.
551 relocs: OutReloc.Slice,
571/// Index into `Wasm.navs_obj`.
572pub const NavsObjIndex = enum(u32) {
573 _,
552574
553 /// Index into `Wasm.navs_obj`.
554 /// Note that swapRemove is sometimes performed on `navs`.
555 pub const Index = enum(u32) {
556 _,
575 pub fn key(i: @This(), wasm: *const Wasm) *InternPool.Nav.Index {
576 return &wasm.navs_obj.keys()[@intFromEnum(i)];
577 }
557578
558 pub fn key(i: @This(), wasm: *const Wasm) *InternPool.Nav.Index {
559 return &wasm.navs_obj.keys()[@intFromEnum(i)];
560 }
579 pub fn value(i: @This(), wasm: *const Wasm) *ZcuDataObj {
580 return &wasm.navs_obj.values()[@intFromEnum(i)];
581 }
561582
562 pub fn value(i: @This(), wasm: *const Wasm) *NavObj {
563 return &wasm.navs_obj.values()[@intFromEnum(i)];
564 }
583 pub fn name(i: @This(), wasm: *const Wasm) [:0]const u8 {
584 const zcu = wasm.base.comp.zcu.?;
585 const ip = &zcu.intern_pool;
586 const nav = ip.getNav(i.key(wasm).*);
587 return nav.fqn.toSlice(ip);
588 }
589};
565590
566 pub fn name(i: @This(), wasm: *const Wasm) [:0]const u8 {
567 const zcu = wasm.base.comp.zcu.?;
568 const ip = &zcu.intern_pool;
569 const nav = ip.getNav(i.key(wasm).*);
570 return nav.fqn.toSlice(ip);
571 }
572 };
591/// Index into `Wasm.navs_exe`.
592pub const NavsExeIndex = enum(u32) {
593 _,
594
595 pub fn key(i: @This(), wasm: *const Wasm) *InternPool.Nav.Index {
596 return &wasm.navs_exe.keys()[@intFromEnum(i)];
597 }
598
599 pub fn value(i: @This(), wasm: *const Wasm) *ZcuDataExe {
600 return &wasm.navs_exe.values()[@intFromEnum(i)];
601 }
602
603 pub fn name(i: @This(), wasm: *const Wasm) [:0]const u8 {
604 const zcu = wasm.base.comp.zcu.?;
605 const ip = &zcu.intern_pool;
606 const nav = ip.getNav(i.key(wasm).*);
607 return nav.fqn.toSlice(ip);
608 }
573609};
574610
575pub const NavExe = extern struct {
576 code: DataSegment.Payload,
611/// Index into `Wasm.uavs_obj`.
612pub const UavsObjIndex = enum(u32) {
613 _,
577614
578 /// Index into `Wasm.navs_exe`.
579 /// Note that swapRemove is sometimes performed on `navs`.
580 pub const Index = enum(u32) {
581 _,
615 pub fn key(i: @This(), wasm: *const Wasm) *InternPool.Index {
616 return &wasm.uavs_obj.keys()[@intFromEnum(i)];
617 }
582618
583 pub fn key(i: @This(), wasm: *const Wasm) *InternPool.Nav.Index {
584 return &wasm.navs_exe.keys()[@intFromEnum(i)];
585 }
619 pub fn value(i: @This(), wasm: *const Wasm) *ZcuDataObj {
620 return &wasm.uavs_obj.values()[@intFromEnum(i)];
621 }
622};
586623
587 pub fn value(i: @This(), wasm: *const Wasm) *NavExe {
588 return &wasm.navs_exe.values()[@intFromEnum(i)];
589 }
624/// Index into `Wasm.uavs_exe`.
625pub const UavsExeIndex = enum(u32) {
626 _,
590627
591 pub fn name(i: @This(), wasm: *const Wasm) [:0]const u8 {
592 const zcu = wasm.base.comp.zcu.?;
593 const ip = &zcu.intern_pool;
594 const nav = ip.getNav(i.key(wasm).*);
595 return nav.fqn.toSlice(ip);
596 }
597 };
628 pub fn key(i: @This(), wasm: *const Wasm) *InternPool.Index {
629 return &wasm.uavs_exe.keys()[@intFromEnum(i)];
630 }
631
632 pub fn value(i: @This(), wasm: *const Wasm) *ZcuDataExe {
633 return &wasm.uavs_exe.values()[@intFromEnum(i)];
634 }
635};
636
637/// Used when emitting a relocatable object.
638pub const ZcuDataObj = extern struct {
639 code: DataSegment.Payload,
640 relocs: OutReloc.Slice,
641};
642
643/// Used when not emitting a relocatable object.
644pub const ZcuDataExe = extern struct {
645 code: DataSegment.Payload,
646 /// Tracks how many references there are for the purposes of sorting data segments.
647 count: u32,
598648};
599649
600650pub const ZcuFunc = extern struct {
......@@ -841,8 +891,8 @@ pub const GlobalImport = extern struct {
841891 __tls_size,
842892 __zig_error_name_table,
843893 object_global: ObjectGlobalIndex,
844 nav_exe: NavExe.Index,
845 nav_obj: NavObj.Index,
894 nav_exe: NavsExeIndex,
895 nav_obj: NavsObjIndex,
846896 };
847897
848898 pub fn unpack(r: Resolution, wasm: *const Wasm) Unpacked {
......@@ -1150,28 +1200,211 @@ pub const DataSegment = extern struct {
11501200 segment_offset: u32,
11511201 section_index: ObjectSectionIndex,
11521202
1203 pub const Category = enum {
1204 /// Thread-local variables.
1205 tls,
1206 /// Data that is not zero initialized and not threadlocal.
1207 data,
1208 /// Zero-initialized. Does not require corresponding bytes in the
1209 /// output file.
1210 zero,
1211 };
1212
11531213 pub const Payload = extern struct {
1154 /// Points into string_bytes. No corresponding string_table entry.
1155 off: u32,
1214 off: Off,
11561215 /// The size in bytes of the data representing the segment within the section.
11571216 len: u32,
11581217
1218 pub const Off = enum(u32) {
1219 /// The payload is all zeroes (bss section).
1220 none = std.math.maxInt(u32),
1221 /// Points into string_bytes. No corresponding string_table entry.
1222 _,
1223
1224 pub fn unwrap(off: Off) ?u32 {
1225 return if (off == .none) null else @intFromEnum(off);
1226 }
1227 };
1228
11591229 pub fn slice(p: DataSegment.Payload, wasm: *const Wasm) []const u8 {
1160 assert(p.off != p.len);
1161 return wasm.string_bytes.items[p.off..][0..p.len];
1230 return wasm.string_bytes.items[p.off.unwrap().?..][0..p.len];
11621231 }
11631232 };
11641233
1165 /// Index into `Wasm.object_data_segments`.
1166 pub const Index = enum(u32) {
1234 pub const Id = enum(u32) {
1235 __zig_error_name_table,
1236 /// First, an `ObjectDataSegmentIndex`.
1237 /// Next, index into `uavs_obj` or `uavs_exe` depending on whether emitting an object.
1238 /// Next, index into `navs_obj` or `navs_exe` depending on whether emitting an object.
11671239 _,
11681240
1169 pub fn ptr(i: Index, wasm: *const Wasm) *DataSegment {
1170 return &wasm.object_data_segments.items[@intFromEnum(i)];
1241 const first_object = @intFromEnum(Id.__zig_error_name_table) + 1;
1242
1243 pub const Unpacked = union(enum) {
1244 __zig_error_name_table,
1245 object: ObjectDataSegmentIndex,
1246 uav_exe: UavsExeIndex,
1247 uav_obj: UavsObjIndex,
1248 nav_exe: NavsExeIndex,
1249 nav_obj: NavsObjIndex,
1250 };
1251
1252 pub fn pack(wasm: *const Wasm, unpacked: Unpacked) Id {
1253 return switch (unpacked) {
1254 .__zig_error_name_table => .__zig_error_name_table,
1255 .object => |i| @enumFromInt(first_object + @intFromEnum(i)),
1256 inline .uav_exe, .uav_obj => |i| @enumFromInt(first_object + wasm.object_data_segments.items.len + @intFromEnum(i)),
1257 .nav_exe => |i| @enumFromInt(first_object + wasm.object_data_segments.items.len + wasm.uavs_exe.entries.len + @intFromEnum(i)),
1258 .nav_obj => |i| @enumFromInt(first_object + wasm.object_data_segments.items.len + wasm.uavs_obj.entries.len + @intFromEnum(i)),
1259 };
1260 }
1261
1262 pub fn unpack(id: Id, wasm: *const Wasm) Unpacked {
1263 return switch (id) {
1264 .__zig_error_name_table => .__zig_error_name_table,
1265 _ => {
1266 const object_index = @intFromEnum(id) - first_object;
1267
1268 const uav_index = if (object_index < wasm.object_data_segments.items.len)
1269 return .{ .object = @enumFromInt(object_index) }
1270 else
1271 object_index - wasm.object_data_segments.items.len;
1272
1273 const comp = wasm.base.comp;
1274 const is_obj = comp.config.output_mode == .Obj;
1275 if (is_obj) {
1276 const nav_index = if (uav_index < wasm.uavs_obj.entries.len)
1277 return .{ .uav_obj = @enumFromInt(uav_index) }
1278 else
1279 uav_index - wasm.uavs_obj.entries.len;
1280
1281 return .{ .nav_obj = @enumFromInt(nav_index) };
1282 } else {
1283 const nav_index = if (uav_index < wasm.uavs_exe.entries.len)
1284 return .{ .uav_obj = @enumFromInt(uav_index) }
1285 else
1286 uav_index - wasm.uavs_exe.entries.len;
1287
1288 return .{ .nav_exe = @enumFromInt(nav_index) };
1289 }
1290 },
1291 };
1292 }
1293
1294 pub fn category(id: Id, wasm: *const Wasm) Category {
1295 return switch (unpack(id, wasm)) {
1296 .__zig_error_name_table => .data,
1297 .object => |i| {
1298 const ptr = i.ptr(wasm);
1299 if (ptr.flags.tls) return .tls;
1300 if (isBss(wasm, ptr.name)) return .zero;
1301 return .data;
1302 },
1303 inline .uav_exe, .uav_obj => |i| if (i.value(wasm).code.off == .none) .zero else .data,
1304 inline .nav_exe, .nav_obj => |i| {
1305 const zcu = wasm.base.comp.zcu.?;
1306 const ip = &zcu.intern_pool;
1307 const nav = ip.getNav(i.key(wasm).*);
1308 if (nav.isThreadLocal(ip)) return .tls;
1309 const code = i.value(wasm).code;
1310 return if (code.off == .none) .zero else .data;
1311 },
1312 };
1313 }
1314
1315 pub fn isTls(id: Id, wasm: *const Wasm) bool {
1316 return switch (unpack(id, wasm)) {
1317 .__zig_error_name_table => false,
1318 .object => |i| i.ptr(wasm).flags.tls,
1319 .uav_exe, .uav_obj => false,
1320 inline .nav_exe, .nav_obj => |i| {
1321 const zcu = wasm.base.comp.zcu.?;
1322 const ip = &zcu.intern_pool;
1323 const nav = ip.getNav(i.key(wasm).*);
1324 return nav.isThreadLocal(ip);
1325 },
1326 };
1327 }
1328
1329 pub fn name(id: Id, wasm: *const Wasm) []const u8 {
1330 return switch (unpack(id, wasm)) {
1331 .__zig_error_name_table, .uav_exe, .uav_obj => ".data",
1332 .object => |i| i.ptr(wasm).name.unwrap().?.slice(wasm),
1333 inline .nav_exe, .nav_obj => |i| {
1334 const zcu = wasm.base.comp.zcu.?;
1335 const ip = &zcu.intern_pool;
1336 const nav = ip.getNav(i.key(wasm).*);
1337 return nav.status.resolved.@"linksection".toSlice(ip) orelse ".data";
1338 },
1339 };
1340 }
1341
1342 pub fn alignment(id: Id, wasm: *const Wasm) Alignment {
1343 return switch (unpack(id, wasm)) {
1344 .__zig_error_name_table => wasm.pointerAlignment(),
1345 .object => |i| i.ptr(wasm).flags.alignment,
1346 inline .uav_exe, .uav_obj => |i| {
1347 const zcu = wasm.base.comp.zcu.?;
1348 const ip = &zcu.intern_pool;
1349 const ip_index = i.key(wasm).*;
1350 const ty: ZcuType = .fromInterned(ip.typeOf(ip_index));
1351 return ty.abiAlignment(zcu);
1352 },
1353 inline .nav_exe, .nav_obj => |i| {
1354 const zcu = wasm.base.comp.zcu.?;
1355 const ip = &zcu.intern_pool;
1356 const nav = ip.getNav(i.key(wasm).*);
1357 return nav.status.resolved.alignment;
1358 },
1359 };
1360 }
1361
1362 pub fn refCount(id: Id, wasm: *const Wasm) u32 {
1363 return switch (unpack(id, wasm)) {
1364 .__zig_error_name_table => wasm.error_name_table_ref_count,
1365 .object, .uav_obj, .nav_obj => 0,
1366 inline .uav_exe, .nav_exe => |i| i.value(wasm).count,
1367 };
1368 }
1369
1370 pub fn isPassive(id: Id, wasm: *const Wasm) bool {
1371 return switch (unpack(id, wasm)) {
1372 .__zig_error_name_table => true,
1373 .object => |i| i.ptr(wasm).flags.is_passive,
1374 .uav_exe, .uav_obj, .nav_exe, .nav_obj => true,
1375 };
1376 }
1377
1378 pub fn size(id: Id, wasm: *const Wasm) u32 {
1379 return switch (unpack(id, wasm)) {
1380 .__zig_error_name_table => {
1381 const comp = wasm.base.comp;
1382 const zcu = comp.zcu.?;
1383 const errors_len = 1 + zcu.intern_pool.global_error_set.getNamesFromMainThread().len;
1384 const elem_size = ZcuType.slice_const_u8_sentinel_0.abiSize(zcu);
1385 return @intCast(errors_len * elem_size);
1386 },
1387 .object => |i| i.ptr(wasm).payload.len,
1388 inline .uav_exe, .uav_obj, .nav_exe, .nav_obj => |i| i.value(wasm).code.len,
1389 };
11711390 }
11721391 };
11731392};
11741393
1394/// Index into `Wasm.object_data_segments`.
1395pub const ObjectDataSegmentIndex = enum(u32) {
1396 _,
1397
1398 pub fn ptr(i: ObjectDataSegmentIndex, wasm: *const Wasm) *DataSegment {
1399 return &wasm.object_data_segments.items[@intFromEnum(i)];
1400 }
1401};
1402
1403/// Index into `Wasm.uavs`.
1404pub const UavIndex = enum(u32) {
1405 _,
1406};
1407
11751408pub const CustomSegment = extern struct {
11761409 payload: Payload,
11771410 flags: SymbolFlags,
......@@ -1940,6 +2173,8 @@ pub fn deinit(wasm: *Wasm) void {
19402173
19412174 wasm.navs_exe.deinit(gpa);
19422175 wasm.navs_obj.deinit(gpa);
2176 wasm.uavs_exe.deinit(gpa);
2177 wasm.uavs_obj.deinit(gpa);
19432178 wasm.zcu_funcs.deinit(gpa);
19442179 wasm.nav_exports.deinit(gpa);
19452180 wasm.uav_exports.deinit(gpa);
......@@ -1978,6 +2213,7 @@ pub fn deinit(wasm: *Wasm) void {
19782213 wasm.global_exports.deinit(gpa);
19792214 wasm.global_imports.deinit(gpa);
19802215 wasm.table_imports.deinit(gpa);
2216 wasm.data_segments.deinit(gpa);
19812217 wasm.symbol_table.deinit(gpa);
19822218 wasm.out_relocs.deinit(gpa);
19832219 wasm.uav_fixups.deinit(gpa);
......@@ -2053,57 +2289,28 @@ pub fn updateNav(wasm: *Wasm, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index
20532289 return;
20542290 }
20552291
2056 const code_start: u32 = @intCast(wasm.string_bytes.items.len);
2057 const relocs_start: u32 = @intCast(wasm.out_relocs.len);
2058 wasm.string_bytes_lock.lock();
2059
2060 try codegen.generateSymbol(
2061 &wasm.base,
2062 pt,
2063 zcu.navSrcLoc(nav_index),
2064 Value.fromInterned(nav_init),
2065 &wasm.string_bytes,
2066 .none,
2067 );
2068
2069 const code_len: u32 = @intCast(wasm.string_bytes.items.len - code_start);
2070 const relocs_len: u32 = @intCast(wasm.out_relocs.len - relocs_start);
2071 wasm.string_bytes_lock.unlock();
2292 const zcu_data = try lowerZcuData(wasm, pt, nav_init);
20722293
2073 const naive_code: DataSegment.Payload = .{
2074 .off = code_start,
2075 .len = code_len,
2076 };
2077
2078 // Only nonzero init values need to take up space in the output.
2079 const all_zeroes = std.mem.allEqual(u8, naive_code.slice(wasm), 0);
2080 const code: DataSegment.Payload = if (!all_zeroes) naive_code else c: {
2081 wasm.string_bytes.shrinkRetainingCapacity(code_start);
2082 // Indicate empty by making off and len the same value, however, still
2083 // transmit the data size by using the size as that value.
2084 break :c .{
2085 .off = naive_code.len,
2086 .len = naive_code.len,
2087 };
2088 };
2294 try wasm.data_segments.ensureUnusedCapacity(gpa, 1);
20892295
20902296 if (is_obj) {
20912297 const gop = try wasm.navs_obj.getOrPut(gpa, nav_index);
2092 gop.value_ptr.* = .{
2093 .code = code,
2094 .relocs = .{
2095 .off = relocs_start,
2096 .len = relocs_len,
2097 },
2098 };
2298 gop.value_ptr.* = zcu_data;
2299 wasm.data_segments.putAssumeCapacity(.pack(wasm, .{
2300 .nav_obj = @enumFromInt(gop.index),
2301 }), @as(u32, undefined));
20992302 }
21002303
2101 assert(relocs_len == 0);
2304 assert(zcu_data.relocs.len == 0);
21022305
21032306 const gop = try wasm.navs_exe.getOrPut(gpa, nav_index);
21042307 gop.value_ptr.* = .{
2105 .code = code,
2308 .code = zcu_data.code,
2309 .count = 0,
21062310 };
2311 wasm.data_segments.putAssumeCapacity(.pack(wasm, .{
2312 .nav_exe = @enumFromInt(gop.index),
2313 }), @as(u32, undefined));
21072314}
21082315
21092316pub fn updateLineNumber(wasm: *Wasm, pt: Zcu.PerThread, ti_id: InternPool.TrackedInst.Index) !void {
......@@ -2251,6 +2458,12 @@ pub fn prelink(wasm: *Wasm, prog_node: std.Progress.Node) link.File.FlushError!v
22512458 }
22522459 }
22532460
2461 if (comp.zcu != null) {
2462 // Zig always depends on a stack pointer global.
2463 try wasm.globals.put(gpa, .__stack_pointer, {});
2464 assert(wasm.globals.entries.len - 1 == @intFromEnum(GlobalIndex.stack_pointer));
2465 }
2466
22542467 // These loops do both recursive marking of alive symbols well as checking for undefined symbols.
22552468 // At the end, output functions and globals will be populated.
22562469 for (wasm.object_function_imports.keys(), wasm.object_function_imports.values(), 0..) |name, *import, i| {
......@@ -2468,6 +2681,9 @@ pub fn flushModule(
24682681 const globals_end_zcu: u32 = @intCast(wasm.globals.entries.len);
24692682 defer wasm.globals.shrinkRetainingCapacity(globals_end_zcu);
24702683
2684 const data_segments_end_zcu: u32 = @intCast(wasm.data_segments.entries.len);
2685 defer wasm.data_segments.shrinkRetainingCapacity(data_segments_end_zcu);
2686
24712687 wasm.flush_buffer.clear();
24722688
24732689 return wasm.flush_buffer.finish(wasm) catch |err| switch (err) {
......@@ -2999,7 +3215,7 @@ pub fn addRelocatableDataPayload(wasm: *Wasm, bytes: []const u8) Allocator.Error
29993215 const gpa = wasm.base.comp.gpa;
30003216 try wasm.string_bytes.appendSlice(gpa, bytes);
30013217 return .{
3002 .off = @intCast(wasm.string_bytes.items.len - bytes.len),
3218 .off = @enumFromInt(wasm.string_bytes.items.len - bytes.len),
30033219 .len = @intCast(bytes.len),
30043220 };
30053221}
......@@ -3025,6 +3241,65 @@ pub fn navSymbolIndex(wasm: *Wasm, nav_index: InternPool.Nav.Index) Allocator.Er
30253241 return @enumFromInt(gop.index);
30263242}
30273243
3244pub fn errorNameTableSymbolIndex(wasm: *Wasm) Allocator.Error!SymbolTableIndex {
3245 const comp = wasm.base.comp;
3246 assert(comp.config.output_mode == .Obj);
3247 const gpa = comp.gpa;
3248 const gop = try wasm.symbol_table.getOrPut(gpa, wasm.preloaded_strings.__zig_error_name_table);
3249 gop.value_ptr.* = {};
3250 return @enumFromInt(gop.index);
3251}
3252
3253pub fn refUavObj(wasm: *Wasm, pt: Zcu.PerThread, ip_index: InternPool.Index) !UavsObjIndex {
3254 const comp = wasm.base.comp;
3255 const gpa = comp.gpa;
3256 assert(comp.config.output_mode == .Obj);
3257 const gop = try wasm.uavs_obj.getOrPut(gpa, ip_index);
3258 if (!gop.found_existing) gop.value_ptr.* = try lowerZcuData(wasm, pt, ip_index);
3259 const uav_index: UavsObjIndex = @enumFromInt(gop.index);
3260 try wasm.data_segments.put(gpa, .pack(wasm, .{ .uav_obj = uav_index }), @as(u32, undefined));
3261 return uav_index;
3262}
3263
3264pub fn refUavExe(wasm: *Wasm, pt: Zcu.PerThread, ip_index: InternPool.Index) !UavsExeIndex {
3265 const comp = wasm.base.comp;
3266 const gpa = comp.gpa;
3267 assert(comp.config.output_mode != .Obj);
3268 const gop = try wasm.uavs_exe.getOrPut(gpa, ip_index);
3269 if (gop.found_existing) {
3270 gop.value_ptr.count += 1;
3271 } else {
3272 const zcu_data = try lowerZcuData(wasm, pt, ip_index);
3273 gop.value_ptr.* = .{
3274 .code = zcu_data.code,
3275 .count = 1,
3276 };
3277 }
3278 const uav_index: UavsExeIndex = @enumFromInt(gop.index);
3279 wasm.data_segments.putAssumeCapacity(.pack(wasm, .{ .uav_exe = uav_index }), @as(u32, undefined));
3280 return uav_index;
3281}
3282
3283pub fn uavAddr(wasm: *Wasm, uav_index: UavsExeIndex) Allocator.Error!u32 {
3284 const comp = wasm.base.comp;
3285 assert(comp.config.output_mode != .Obj);
3286 const ds_id: DataSegment.Id = .pack(wasm, .{ .uav_exe = uav_index });
3287 return wasm.data_segments.get(ds_id).?;
3288}
3289
3290pub fn navAddr(wasm: *Wasm, nav_index: InternPool.Nav.Index) Allocator.Error!u32 {
3291 const comp = wasm.base.comp;
3292 assert(comp.config.output_mode != .Obj);
3293 const ds_id: DataSegment.Id = .pack(wasm, .{ .nav_exe = @enumFromInt(wasm.navs_exe.getIndex(nav_index).?) });
3294 return wasm.data_segments.get(ds_id).?;
3295}
3296
3297pub fn errorNameTableAddr(wasm: *Wasm) Allocator.Error!u32 {
3298 const comp = wasm.base.comp;
3299 assert(comp.config.output_mode != .Obj);
3300 return wasm.data_segments.get(.__zig_error_name_table).?;
3301}
3302
30283303fn convertZcuFnType(
30293304 comp: *Compilation,
30303305 cc: std.builtin.CallingConvention,
......@@ -3080,3 +3355,54 @@ fn convertZcuFnType(
30803355 }
30813356 }
30823357}
3358
3359pub fn isBss(wasm: *const Wasm, optional_name: OptionalString) bool {
3360 const s = optional_name.slice(wasm) orelse return false;
3361 return mem.eql(u8, s, ".bss") or mem.startsWith(u8, s, ".bss.");
3362}
3363
3364fn lowerZcuData(wasm: *Wasm, pt: Zcu.PerThread, ip_index: InternPool.Index) !ZcuDataObj {
3365 const code_start: u32 = @intCast(wasm.string_bytes.items.len);
3366 const relocs_start: u32 = @intCast(wasm.out_relocs.len);
3367 wasm.string_bytes_lock.lock();
3368
3369 try codegen.generateSymbol(&wasm.base, pt, .unneeded, .fromInterned(ip_index), &wasm.string_bytes, .none);
3370
3371 const code_len: u32 = @intCast(wasm.string_bytes.items.len - code_start);
3372 const relocs_len: u32 = @intCast(wasm.out_relocs.len - relocs_start);
3373 wasm.string_bytes_lock.unlock();
3374
3375 const naive_code: DataSegment.Payload = .{
3376 .off = @enumFromInt(code_start),
3377 .len = code_len,
3378 };
3379
3380 // Only nonzero init values need to take up space in the output.
3381 const all_zeroes = std.mem.allEqual(u8, naive_code.slice(wasm), 0);
3382 const code: DataSegment.Payload = if (!all_zeroes) naive_code else c: {
3383 wasm.string_bytes.shrinkRetainingCapacity(code_start);
3384 // Indicate empty by making off and len the same value, however, still
3385 // transmit the data size by using the size as that value.
3386 break :c .{
3387 .off = .none,
3388 .len = naive_code.len,
3389 };
3390 };
3391
3392 return .{
3393 .code = code,
3394 .relocs = .{
3395 .off = relocs_start,
3396 .len = relocs_len,
3397 },
3398 };
3399}
3400
3401fn pointerAlignment(wasm: *const Wasm) Alignment {
3402 const target = &wasm.base.comp.root_mod.resolved_target.result;
3403 return switch (target.cpu.arch) {
3404 .wasm32 => .@"4",
3405 .wasm64 => .@"8",
3406 else => unreachable,
3407 };
3408}
src/link/Wasm/Flush.zig+93-75
......@@ -19,10 +19,6 @@ const leb = std.leb;
1919const log = std.log.scoped(.link);
2020const assert = std.debug.assert;
2121
22/// Ordered list of data segments that will appear in the final binary.
23/// When sorted, to-be-merged segments will be made adjacent.
24/// Values are offset relative to segment start.
25data_segments: std.AutoArrayHashMapUnmanaged(Wasm.DataSegment.Index, u32) = .empty,
2622/// Each time a `data_segment` offset equals zero it indicates a new group, and
2723/// the next element in this array will contain the total merged segment size.
2824data_segment_groups: std.ArrayListUnmanaged(u32) = .empty,
......@@ -32,21 +28,14 @@ missing_exports: std.AutoArrayHashMapUnmanaged(String, void) = .empty,
3228
3329indirect_function_table: std.AutoArrayHashMapUnmanaged(Wasm.OutputFunctionIndex, u32) = .empty,
3430
35/// 0. Index into `data_segments`.
36const DataSegmentIndex = enum(u32) {
37 _,
38};
39
4031pub fn clear(f: *Flush) void {
4132 f.binary_bytes.clearRetainingCapacity();
42 f.data_segments.clearRetainingCapacity();
4333 f.data_segment_groups.clearRetainingCapacity();
4434 f.indirect_function_table.clearRetainingCapacity();
4535}
4636
4737pub fn deinit(f: *Flush, gpa: Allocator) void {
4838 f.binary_bytes.deinit(gpa);
49 f.data_segments.deinit(gpa);
5039 f.data_segment_groups.deinit(gpa);
5140 f.indirect_function_table.deinit(gpa);
5241 f.* = undefined;
......@@ -141,12 +130,14 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
141130
142131 // Merge and order the data segments. Depends on garbage collection so that
143132 // unused segments can be omitted.
144 try f.data_segments.ensureUnusedCapacity(gpa, wasm.object_data_segments.items.len);
133 try wasm.data_segments.ensureUnusedCapacity(gpa, wasm.object_data_segments.items.len);
145134 for (wasm.object_data_segments.items, 0..) |*ds, i| {
146135 if (!ds.flags.alive) continue;
147 const data_segment_index: Wasm.DataSegment.Index = @enumFromInt(i);
148 any_passive_inits = any_passive_inits or ds.flags.is_passive or (import_memory and !isBss(wasm, ds.name));
149 f.data_segments.putAssumeCapacityNoClobber(data_segment_index, @as(u32, undefined));
136 const data_segment_index: Wasm.ObjectDataSegmentIndex = @enumFromInt(i);
137 any_passive_inits = any_passive_inits or ds.flags.is_passive or (import_memory and !wasm.isBss(ds.name));
138 wasm.data_segments.putAssumeCapacityNoClobber(.pack(wasm, .{
139 .object = data_segment_index,
140 }), @as(u32, undefined));
150141 }
151142
152143 try wasm.functions.ensureUnusedCapacity(gpa, 3);
......@@ -170,48 +161,64 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
170161 }
171162
172163 // Sort order:
173 // 0. Whether the segment is TLS
164 // 0. Segment category (tls, data, zero)
174165 // 1. Segment name prefix
175166 // 2. Segment alignment
176 // 3. Segment name suffix
177 // 4. Segment index (to break ties, keeping it deterministic)
167 // 3. Reference count, descending (optimize for LEB encoding)
168 // 4. Segment name suffix
169 // 5. Segment ID interpreted as an integer (for determinism)
170 //
178171 // TLS segments are intended to be merged with each other, and segments
179172 // with a common prefix name are intended to be merged with each other.
180173 // Sorting ensures the segments intended to be merged will be adjacent.
174 //
175 // Each Zcu Nav and Cau has an independent data segment ID in this logic.
176 // For the purposes of sorting, they are implicitly all named ".data".
181177 const Sort = struct {
182178 wasm: *const Wasm,
183 segments: []const Wasm.DataSegment.Index,
179 segments: []const Wasm.DataSegment.Id,
184180 pub fn lessThan(ctx: @This(), lhs: usize, rhs: usize) bool {
185 const lhs_segment_index = ctx.segments[lhs];
186 const rhs_segment_index = ctx.segments[rhs];
187 const lhs_segment = lhs_segment_index.ptr(ctx.wasm);
188 const rhs_segment = rhs_segment_index.ptr(ctx.wasm);
189 const lhs_tls = @intFromBool(lhs_segment.flags.tls);
190 const rhs_tls = @intFromBool(rhs_segment.flags.tls);
191 if (lhs_tls < rhs_tls) return true;
192 if (lhs_tls > rhs_tls) return false;
193 const lhs_prefix, const lhs_suffix = splitSegmentName(lhs_segment.name.unwrap().?.slice(ctx.wasm));
194 const rhs_prefix, const rhs_suffix = splitSegmentName(rhs_segment.name.unwrap().?.slice(ctx.wasm));
181 const lhs_segment = ctx.segments[lhs];
182 const rhs_segment = ctx.segments[rhs];
183 const lhs_category = @intFromEnum(lhs_segment.category(ctx.wasm));
184 const rhs_category = @intFromEnum(rhs_segment.category(ctx.wasm));
185 switch (std.math.order(lhs_category, rhs_category)) {
186 .lt => return true,
187 .gt => return false,
188 .eq => {},
189 }
190 const lhs_segment_name = lhs_segment.name(ctx.wasm);
191 const rhs_segment_name = rhs_segment.name(ctx.wasm);
192 const lhs_prefix, const lhs_suffix = splitSegmentName(lhs_segment_name);
193 const rhs_prefix, const rhs_suffix = splitSegmentName(rhs_segment_name);
195194 switch (mem.order(u8, lhs_prefix, rhs_prefix)) {
196195 .lt => return true,
197196 .gt => return false,
198197 .eq => {},
199198 }
200 switch (lhs_segment.flags.alignment.order(rhs_segment.flags.alignment)) {
199 const lhs_alignment = lhs_segment.alignment(ctx.wasm);
200 const rhs_alignment = rhs_segment.alignment(ctx.wasm);
201 switch (lhs_alignment.order(rhs_alignment)) {
201202 .lt => return false,
202203 .gt => return true,
203204 .eq => {},
204205 }
205 return switch (mem.order(u8, lhs_suffix, rhs_suffix)) {
206 .lt => true,
207 .gt => false,
208 .eq => @intFromEnum(lhs_segment_index) < @intFromEnum(rhs_segment_index),
209 };
206 switch (std.math.order(lhs_segment.refCount(ctx.wasm), rhs_segment.refCount(ctx.wasm))) {
207 .lt => return false,
208 .gt => return true,
209 .eq => {},
210 }
211 switch (mem.order(u8, lhs_suffix, rhs_suffix)) {
212 .lt => return true,
213 .gt => return false,
214 .eq => {},
215 }
216 return @intFromEnum(lhs_segment) < @intFromEnum(rhs_segment);
210217 }
211218 };
212 f.data_segments.sortUnstable(@as(Sort, .{
219 wasm.data_segments.sortUnstable(@as(Sort, .{
213220 .wasm = wasm,
214 .segments = f.data_segments.keys(),
221 .segments = wasm.data_segments.keys(),
215222 }));
216223
217224 const page_size = std.wasm.page_size; // 64kb
......@@ -246,43 +253,44 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
246253 virtual_addrs.stack_pointer = @intCast(memory_ptr);
247254 }
248255
249 const segment_indexes = f.data_segments.keys();
250 const segment_offsets = f.data_segments.values();
256 const segment_ids = wasm.data_segments.keys();
257 const segment_offsets = wasm.data_segments.values();
251258 assert(f.data_segment_groups.items.len == 0);
252259 {
253260 var seen_tls: enum { before, during, after } = .before;
254261 var offset: u32 = 0;
255 for (segment_indexes, segment_offsets, 0..) |segment_index, *segment_offset, i| {
256 const segment = segment_index.ptr(wasm);
257 memory_ptr = segment.flags.alignment.forward(memory_ptr);
262 for (segment_ids, segment_offsets, 0..) |segment_id, *segment_offset, i| {
263 const alignment = segment_id.alignment(wasm);
264 memory_ptr = alignment.forward(memory_ptr);
258265
259266 const want_new_segment = b: {
260267 if (is_obj) break :b false;
261268 switch (seen_tls) {
262 .before => if (segment.flags.tls) {
269 .before => if (segment_id.isTls(wasm)) {
263270 virtual_addrs.tls_base = if (shared_memory) 0 else @intCast(memory_ptr);
264 virtual_addrs.tls_align = segment.flags.alignment;
271 virtual_addrs.tls_align = alignment;
265272 seen_tls = .during;
266273 break :b true;
267274 },
268 .during => if (!segment.flags.tls) {
275 .during => if (!segment_id.isTls(wasm)) {
269276 virtual_addrs.tls_size = @intCast(memory_ptr - virtual_addrs.tls_base.?);
270 virtual_addrs.tls_align = virtual_addrs.tls_align.maxStrict(segment.flags.alignment);
277 virtual_addrs.tls_align = virtual_addrs.tls_align.maxStrict(alignment);
271278 seen_tls = .after;
272279 break :b true;
273280 },
274281 .after => {},
275282 }
276 break :b i >= 1 and !wantSegmentMerge(wasm, segment_indexes[i - 1], segment_index);
283 break :b i >= 1 and !wantSegmentMerge(wasm, segment_ids[i - 1], segment_id);
277284 };
278285 if (want_new_segment) {
279286 if (offset > 0) try f.data_segment_groups.append(gpa, offset);
280287 offset = 0;
281288 }
282289
290 const size = segment_id.size(wasm);
283291 segment_offset.* = offset;
284 offset += segment.payload.len;
285 memory_ptr += segment.payload.len;
292 offset += size;
293 memory_ptr += size;
286294 }
287295 if (offset > 0) try f.data_segment_groups.append(gpa, offset);
288296 }
......@@ -599,7 +607,6 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
599607 // Code section.
600608 if (wasm.functions.count() != 0) {
601609 const header_offset = try reserveVecSectionHeader(gpa, binary_bytes);
602 const start_offset = binary_bytes.items.len - 5; // minus 5 so start offset is 5 to include entry count
603610
604611 for (wasm.functions.keys()) |resolution| switch (resolution.unpack(wasm)) {
605612 .unresolved => unreachable,
......@@ -610,21 +617,21 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
610617 .__zig_error_names => @panic("TODO lower __zig_error_names "),
611618 .object_function => |i| {
612619 _ = i;
613 _ = start_offset;
614620 @panic("TODO lower object function code and apply relocations");
615621 //try leb.writeUleb128(binary_writer, atom.code.len);
616622 //try binary_bytes.appendSlice(gpa, atom.code.slice(wasm));
617623 },
618624 .zcu_func => |i| {
619 _ = i;
620 _ = start_offset;
621 @panic("TODO lower zcu_func code and apply relocations");
622 //try leb.writeUleb128(binary_writer, atom.code.len);
623 //try binary_bytes.appendSlice(gpa, atom.code.slice(wasm));
625 const code_start = try reserveSize(gpa, binary_bytes);
626 defer replaceSize(binary_bytes, code_start);
627
628 const function = &i.value(wasm).function;
629 try function.lower(wasm, binary_bytes);
624630 },
625631 };
626632
627633 replaceVecSectionHeader(binary_bytes, header_offset, .code, @intCast(wasm.functions.entries.len));
634 if (is_obj) @panic("TODO apply offset to code relocs");
628635 code_section_index = section_index;
629636 section_index += 1;
630637 }
......@@ -635,11 +642,11 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
635642
636643 var group_index: u32 = 0;
637644 var offset: u32 = undefined;
638 for (segment_indexes, segment_offsets) |segment_index, segment_offset| {
639 const segment = segment_index.ptr(wasm);
645 for (segment_ids, segment_offsets) |segment_id, segment_offset| {
646 const segment = segment_id.ptr(wasm);
640647 const segment_payload = segment.payload.slice(wasm);
641648 if (segment_payload.len == 0) continue;
642 if (!import_memory and isBss(wasm, segment.name)) {
649 if (!import_memory and wasm.isBss(segment.name)) {
643650 // It counted for virtual memory but it does not go into the binary.
644651 continue;
645652 }
......@@ -682,7 +689,7 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
682689 // try wasm.emitDataRelocations(binary_bytes, data_index, symbol_table);
683690 //}
684691 } else if (comp.config.debug_format != .strip) {
685 try emitNameSection(wasm, &f.data_segments, binary_bytes);
692 try emitNameSection(wasm, &wasm.data_segments, binary_bytes);
686693 }
687694
688695 if (comp.config.debug_format != .strip) {
......@@ -997,27 +1004,23 @@ fn emitProducerSection(gpa: Allocator, binary_bytes: *std.ArrayListUnmanaged(u8)
9971004// writeCustomSectionHeader(binary_bytes, header_offset);
9981005//}
9991006
1000fn isBss(wasm: *Wasm, optional_name: Wasm.OptionalString) bool {
1001 const s = optional_name.slice(wasm) orelse return false;
1002 return mem.eql(u8, s, ".bss") or mem.startsWith(u8, s, ".bss.");
1003}
1004
10051007fn splitSegmentName(name: []const u8) struct { []const u8, []const u8 } {
10061008 const start = @intFromBool(name.len >= 1 and name[0] == '.');
10071009 const pivot = mem.indexOfScalarPos(u8, name, start, '.') orelse 0;
10081010 return .{ name[0..pivot], name[pivot..] };
10091011}
10101012
1011fn wantSegmentMerge(wasm: *const Wasm, a_index: Wasm.DataSegment.Index, b_index: Wasm.DataSegment.Index) bool {
1012 const a = a_index.ptr(wasm);
1013 const b = b_index.ptr(wasm);
1014 if (a.flags.tls and b.flags.tls) return true;
1015 if (a.flags.tls != b.flags.tls) return false;
1016 if (a.flags.is_passive != b.flags.is_passive) return false;
1017 if (a.name == b.name) return true;
1018 const a_prefix, _ = splitSegmentName(a.name.slice(wasm).?);
1019 const b_prefix, _ = splitSegmentName(b.name.slice(wasm).?);
1020 return a_prefix.len > 0 and mem.eql(u8, a_prefix, b_prefix);
1013fn wantSegmentMerge(wasm: *const Wasm, a_id: Wasm.DataSegment.Id, b_id: Wasm.DataSegment.Id) bool {
1014 const a_category = a_id.category(wasm);
1015 const b_category = b_id.category(wasm);
1016 if (a_category != b_category) return false;
1017 if (a_category == .tls or b_category == .tls) return false;
1018 if (a_id.isPassive(wasm) != b_id.isPassive(wasm)) return false;
1019 const a_name = a_id.name(wasm);
1020 const b_name = b_id.name(wasm);
1021 const a_prefix, _ = splitSegmentName(a_name);
1022 const b_prefix, _ = splitSegmentName(b_name);
1023 return mem.eql(u8, a_prefix, b_prefix);
10211024}
10221025
10231026/// section id + fixed leb contents size + fixed leb vector length
......@@ -1064,6 +1067,21 @@ fn replaceHeader(bytes: *std.ArrayListUnmanaged(u8), offset: u32, tag: u8) void
10641067 bytes.replaceRangeAssumeCapacity(offset, section_header_size, fbw.getWritten());
10651068}
10661069
1070const max_size_encoding = 5;
1071
1072fn reserveSize(gpa: Allocator, bytes: *std.ArrayListUnmanaged(u8)) Allocator.Error!u32 {
1073 try bytes.appendNTimes(gpa, 0, max_size_encoding);
1074 return @intCast(bytes.items.len - max_size_encoding);
1075}
1076
1077fn replaceSize(bytes: *std.ArrayListUnmanaged(u8), offset: u32) void {
1078 const size: u32 = @intCast(bytes.items.len - offset - max_size_encoding);
1079 var buf: [max_size_encoding]u8 = undefined;
1080 var fbw = std.io.fixedBufferStream(&buf);
1081 leb.writeUleb128(fbw.writer(), size) catch unreachable;
1082 bytes.replaceRangeAssumeCapacity(offset, max_size_encoding, fbw.getWritten());
1083}
1084
10671085fn emitLimits(
10681086 gpa: Allocator,
10691087 binary_bytes: *std.ArrayListUnmanaged(u8),
src/link/Wasm/Object.zig+2-2
......@@ -103,7 +103,7 @@ pub const Symbol = struct {
103103 function: Wasm.ObjectFunctionIndex,
104104 function_import: ScratchSpace.FuncImportIndex,
105105 data: struct {
106 segment_index: Wasm.DataSegment.Index,
106 segment_index: Wasm.ObjectDataSegmentIndex,
107107 segment_offset: u32,
108108 size: u32,
109109 },
......@@ -497,7 +497,7 @@ pub fn parse(
497497
498498 try wasm.object_custom_segments.put(gpa, section_index, .{
499499 .payload = .{
500 .off = data_off,
500 .off = @enumFromInt(data_off),
501501 .len = @intCast(debug_content.len),
502502 },
503503 .flags = .{},