authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-04-08 22:44:29+02:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-04-08 22:47:08+02:00
logff5774d93d9d952a74fab3666d4480f534c770db
tree910dfe9685f5acd65efead91c86ef758a452e702
parent47f36427887fc7d1646cfb7ed8eeeeb14cd3555b
signaturelock-open Commit is signed but in an unrecognized format.

Refactor link/wasm.zig to use offset table

This refactor inserts an offset table into wasm's data section where each offset points to the actual data region. This means we can keep offset indexes consistant and do not have to perform any computer to determine where in the data section something like a static string exists. Instead during runtime it will load the data offset onto the stack.

5 files changed, 238 insertions(+), 176 deletions(-)

lib/std/wasm.zig+3
...@@ -280,3 +280,6 @@ pub const block_empty: u8 = 0x40;...@@ -280,3 +280,6 @@ pub const block_empty: u8 = 0x40;
280// binary constants280// binary constants
281pub const magic = [_]u8{ 0x00, 0x61, 0x73, 0x6D }; // \0asm281pub const magic = [_]u8{ 0x00, 0x61, 0x73, 0x6D }; // \0asm
282pub const version = [_]u8{ 0x01, 0x00, 0x00, 0x00 }; // version 1282pub const version = [_]u8{ 0x01, 0x00, 0x00, 0x00 }; // version 1
283
284// Each wasm page size is 64kB
285pub const page_size = 64 * 1024;
src/Module.zig+8-5
...@@ -3029,9 +3029,12 @@ fn astgenAndSemaVarDecl(...@@ -3029,9 +3029,12 @@ fn astgenAndSemaVarDecl(
3029 };3029 };
3030 defer gen_scope.instructions.deinit(mod.gpa);3030 defer gen_scope.instructions.deinit(mod.gpa);
30313031
3032 const init_result_loc: AstGen.ResultLoc = if (var_decl.ast.type_node != 0) .{3032 const init_result_loc: AstGen.ResultLoc = if (var_decl.ast.type_node != 0)
3033 .ty = try AstGen.expr(&gen_scope, &gen_scope.base, .{ .ty = .type_type }, var_decl.ast.type_node),3033 .{
3034 } else .none;3034 .ty = try AstGen.expr(&gen_scope, &gen_scope.base, .{ .ty = .type_type }, var_decl.ast.type_node),
3035 }
3036 else
3037 .none;
30353038
3036 const init_inst = try AstGen.comptimeExpr(3039 const init_inst = try AstGen.comptimeExpr(
3037 &gen_scope,3040 &gen_scope,
...@@ -3834,7 +3837,7 @@ fn allocateNewDecl(...@@ -3834,7 +3837,7 @@ fn allocateNewDecl(
3834 .elf => .{ .elf = link.File.Elf.TextBlock.empty },3837 .elf => .{ .elf = link.File.Elf.TextBlock.empty },
3835 .macho => .{ .macho = link.File.MachO.TextBlock.empty },3838 .macho => .{ .macho = link.File.MachO.TextBlock.empty },
3836 .c => .{ .c = link.File.C.DeclBlock.empty },3839 .c => .{ .c = link.File.C.DeclBlock.empty },
3837 .wasm => .{ .wasm = {} },3840 .wasm => .{ .wasm = link.File.Wasm.DeclBlock.empty },
3838 .spirv => .{ .spirv = {} },3841 .spirv => .{ .spirv = {} },
3839 },3842 },
3840 .fn_link = switch (mod.comp.bin_file.tag) {3843 .fn_link = switch (mod.comp.bin_file.tag) {
...@@ -3842,7 +3845,7 @@ fn allocateNewDecl(...@@ -3842,7 +3845,7 @@ fn allocateNewDecl(
3842 .elf => .{ .elf = link.File.Elf.SrcFn.empty },3845 .elf => .{ .elf = link.File.Elf.SrcFn.empty },
3843 .macho => .{ .macho = link.File.MachO.SrcFn.empty },3846 .macho => .{ .macho = link.File.MachO.SrcFn.empty },
3844 .c => .{ .c = link.File.C.FnBlock.empty },3847 .c => .{ .c = link.File.C.FnBlock.empty },
3845 .wasm => .{ .wasm = .{} },3848 .wasm => .{ .wasm = link.File.Wasm.FnData.empty },
3846 .spirv => .{ .spirv = .{} },3849 .spirv => .{ .spirv = .{} },
3847 },3850 },
3848 .generation = 0,3851 .generation = 0,
src/codegen/wasm.zig+32-7
...@@ -543,11 +543,20 @@ pub const Context = struct {...@@ -543,11 +543,20 @@ pub const Context = struct {
543543
544 /// Using a given `Type`, returns the corresponding wasm Valtype544 /// Using a given `Type`, returns the corresponding wasm Valtype
545 fn typeToValtype(self: *Context, src: LazySrcLoc, ty: Type) InnerError!wasm.Valtype {545 fn typeToValtype(self: *Context, src: LazySrcLoc, ty: Type) InnerError!wasm.Valtype {
546 return switch (ty.tag()) {546 return switch (ty.zigTypeTag()) {
547 .f32 => .f32,547 .Float => blk: {
548 .f64 => .f64,548 const bits = ty.floatBits(self.target);
549 .u32, .i32, .bool => .i32,549 if (bits == 16 or bits == 32) break :blk wasm.Valtype.f32;
550 .u64, .i64 => .i64,550 if (bits == 64) break :blk wasm.Valtype.f64;
551 return self.fail(src, "Float bit size not supported by wasm: '{d}'", .{bits});
552 },
553 .Int => blk: {
554 const info = ty.intInfo(self.target);
555 if (info.bits <= 32) break :blk wasm.Valtype.i32;
556 if (info.bits > 32 and info.bits <= 64) break :blk wasm.Valtype.i64;
557 return self.fail(src, "Integer bit size not supported by wasm: '{d}'", .{info.bits});
558 },
559 .Bool, .Pointer => wasm.Valtype.i32,
551 else => self.fail(src, "TODO - Wasm valtype for type '{s}'", .{ty.tag()}),560 else => self.fail(src, "TODO - Wasm valtype for type '{s}'", .{ty.tag()}),
552 };561 };
553 }562 }
...@@ -624,7 +633,7 @@ pub const Context = struct {...@@ -624,7 +633,7 @@ pub const Context = struct {
624 const mod_fn = blk: {633 const mod_fn = blk: {
625 if (typed_value.val.castTag(.function)) |func| break :blk func.data;634 if (typed_value.val.castTag(.function)) |func| break :blk func.data;
626 if (typed_value.val.castTag(.extern_fn)) |ext_fn| return Result.appended; // don't need code body for extern functions635 if (typed_value.val.castTag(.extern_fn)) |ext_fn| return Result.appended; // don't need code body for extern functions
627 return self.fail(.{ .node_offset = 0 }, "TODO: Wasm codegen for decl type '{s}'", .{typed_value.ty.tag()});636 unreachable;
628 };637 };
629638
630 // Reserve space to write the size after generating the code as well as space for locals count639 // Reserve space to write the size after generating the code as well as space for locals count
...@@ -680,7 +689,7 @@ pub const Context = struct {...@@ -680,7 +689,7 @@ pub const Context = struct {
680 } else return self.fail(.{ .node_offset = 0 }, "TODO implement gen for more kinds of arrays", .{});689 } else return self.fail(.{ .node_offset = 0 }, "TODO implement gen for more kinds of arrays", .{});
681 },690 },
682 .Int => {691 .Int => {
683 const info = typed_value.ty.intInfo(self.bin_file.base.options.target);692 const info = typed_value.ty.intInfo(self.target);
684 if (info.bits == 8 and info.signedness == .unsigned) {693 if (info.bits == 8 and info.signedness == .unsigned) {
685 const int_byte = typed_value.val.toUnsignedInt();694 const int_byte = typed_value.val.toUnsignedInt();
686 try self.code.append(@intCast(u8, int_byte));695 try self.code.append(@intCast(u8, int_byte));
...@@ -856,6 +865,22 @@ pub const Context = struct {...@@ -856,6 +865,22 @@ pub const Context = struct {
856 else => |bits| return self.fail(inst.base.src, "Wasm TODO: emitConstant for float with {d} bits", .{bits}),865 else => |bits| return self.fail(inst.base.src, "Wasm TODO: emitConstant for float with {d} bits", .{bits}),
857 }866 }
858 },867 },
868 .Pointer => {
869 if (inst.val.castTag(.decl_ref)) |payload| {
870 const decl = payload.data;
871
872 // offset into the offset table within the 'data' section
873 const ptr_width = self.target.cpu.arch.ptrBitWidth() / 8;
874 try writer.writeByte(wasm.opcode(.i32_const));
875 try leb.writeULEB128(writer, decl.link.wasm.offset_index * ptr_width);
876
877 // memory instruction followed by their memarg immediate
878 // memarg ::== x:u32, y:u32 => {align x, offset y}
879 try writer.writeByte(wasm.opcode(.i32_load));
880 try leb.writeULEB128(writer, @as(u32, 0));
881 try leb.writeULEB128(writer, @as(u32, 0));
882 } else return self.fail(inst.base.src, "Wasm TODO: emitConstant for other const pointer tag {s}", .{inst.val.tag()});
883 },
859 .Void => {},884 .Void => {},
860 else => |ty| return self.fail(inst.base.src, "Wasm TODO: emitConstant for zigTypeTag {s}", .{ty}),885 else => |ty| return self.fail(inst.base.src, "Wasm TODO: emitConstant for zigTypeTag {s}", .{ty}),
861 }886 }
src/link.zig+1-1
...@@ -138,7 +138,7 @@ pub const File = struct {...@@ -138,7 +138,7 @@ pub const File = struct {
138 coff: Coff.TextBlock,138 coff: Coff.TextBlock,
139 macho: MachO.TextBlock,139 macho: MachO.TextBlock,
140 c: C.DeclBlock,140 c: C.DeclBlock,
141 wasm: void,141 wasm: Wasm.DeclBlock,
142 spirv: void,142 spirv: void,
143 };143 };
144144
src/link/Wasm.zig+194-163
...@@ -20,70 +20,7 @@ const TypedValue = @import("../TypedValue.zig");...@@ -20,70 +20,7 @@ const TypedValue = @import("../TypedValue.zig");
2020
21pub const base_tag = link.File.Tag.wasm;21pub const base_tag = link.File.Tag.wasm;
2222
23pub const FnData = struct {
24 /// Generated code for the type of the function
25 functype: std.ArrayListUnmanaged(u8) = .{},
26 /// Generated code for the body of the function
27 code: std.ArrayListUnmanaged(u8) = .{},
28 /// Locations in the generated code where function indexes must be filled in.
29 /// This must be kept ordered by offset.
30 idx_refs: std.ArrayListUnmanaged(struct { offset: u32, decl: *Module.Decl }) = .{},
31};
32
33/// Data section of the wasm binary
34/// Each declaration will have its own 'data_segment' within the section
35/// where the offset is calculated using the previous segments and the content length
36/// of the data
37pub const DataSection = struct {
38 /// Every data object will be appended to this list,
39 /// containing its `Decl`, the data in bytes, and its length.
40 segments: std.ArrayListUnmanaged(struct {
41 /// The decl that lives inside the 'data' section such as an array
42 decl: *Module.Decl,
43 /// The contents of the data in bytes
44 data: [*]const u8,
45 /// The length of the contents inside the 'data' section
46 len: u32,
47 }) = .{},
48
49 /// Returns the offset into the data segment based on a given `Decl`
50 pub fn offset(self: DataSection, decl: *const Module.Decl) u32 {
51 var cur_offset: u32 = 0;
52 return for (self.segments.items) |entry| {
53 if (entry.decl == decl) break cur_offset;
54 cur_offset += entry.len;
55 } else unreachable; // offset() called on declaration that does not live inside 'data' section
56 }
57
58 /// Returns the total payload size of the data section
59 pub fn size(self: DataSection) u32 {
60 var total: u32 = 0;
61 for (self.segments.items) |entry| {
62 total += entry.len;
63 }
64 return total;
65 }
66
67 /// Updates the data in the data segment belonging to the given decl.
68 /// It's illegal behaviour to call this before allocateDeclIndexes was called
69 /// `data` must be managed externally with a lifetime that last as long as codegen does.
70 pub fn updateData(self: DataSection, decl: *Module.Decl, data: []const u8) void {
71 const entry = for (self.segments.items) |*item| {
72 if (item.decl == decl) break item;
73 } else unreachable; // called updateData before the declaration was added to data segments
74 entry.data = data.ptr;
75 }
76
77 /// Returns the index of a declaration and `null` when not found
78 pub fn getIdx(self: DataSection, decl: *Module.Decl) ?usize {
79 return for (self.segments.items) |entry, i| {
80 if (entry.decl == decl) break i;
81 } else null;
82 }
83};
84
85base: link.File,23base: link.File,
86
87/// List of all function Decls to be written to the output file. The index of24/// List of all function Decls to be written to the output file. The index of
88/// each Decl in this list at the time of writing the binary is used as the25/// each Decl in this list at the time of writing the binary is used as the
89/// function index. In the event where ext_funcs' size is not 0, the index of26/// function index. In the event where ext_funcs' size is not 0, the index of
...@@ -98,10 +35,67 @@ ext_funcs: std.ArrayListUnmanaged(*Module.Decl) = .{},...@@ -98,10 +35,67 @@ ext_funcs: std.ArrayListUnmanaged(*Module.Decl) = .{},
98/// to support existing code.35/// to support existing code.
99/// TODO: Allow setting this through a flag?36/// TODO: Allow setting this through a flag?
100host_name: []const u8 = "env",37host_name: []const u8 = "env",
101/// Map of declarations with its bytes payload, used to keep track of all data segments38/// The last `DeclBlock` that was initialized will be saved here.
102/// that needs to be emit when creating the wasm binary.39last_block: ?*DeclBlock = null,
103/// The `DataSection`'s lifetime must be kept alive until the linking stage.40/// Table with offsets, each element represents an offset with the value being
104data: DataSection = .{},41/// the offset into the 'data' section where the data lives
42offset_table: std.ArrayListUnmanaged(u32) = .{},
43/// List of offset indexes which are free to be used for new decl's.
44/// Each element's value points to an index into the offset_table.
45offset_table_free_list: std.ArrayListUnmanaged(u32) = .{},
46/// List of all `Decl` that are currently alive.
47/// This is ment for bookkeeping so we can safely cleanup all codegen memory
48/// when calling `deinit`
49symbols: std.ArrayListUnmanaged(*Module.Decl) = .{},
50/// Contains indexes into `symbols` that are no longer used and can be populated instead,
51/// removing the need to search for a symbol and remove it when it's dereferenced.
52symbols_free_list: std.ArrayListUnmanaged(u32) = .{},
53
54pub const FnData = struct {
55 /// Generated code for the type of the function
56 functype: std.ArrayListUnmanaged(u8),
57 /// Generated code for the body of the function
58 code: std.ArrayListUnmanaged(u8),
59 /// Locations in the generated code where function indexes must be filled in.
60 /// This must be kept ordered by offset.
61 idx_refs: std.ArrayListUnmanaged(struct { offset: u32, decl: *Module.Decl }),
62
63 pub const empty: FnData = .{
64 .functype = .{},
65 .code = .{},
66 .idx_refs = .{},
67 };
68};
69
70pub const DeclBlock = struct {
71 /// Determines whether the `DeclBlock` has been initialized for codegen.
72 init: bool,
73 /// Index into the `symbols` list.
74 symbol_index: u32,
75 /// Index into the offset table
76 offset_index: u32,
77 /// The size of the block and how large part of the data section it occupies.
78 /// Will be 0 when the Decl will not live inside the data section and `data` will be undefined.
79 size: u32,
80 /// Points to the previous and next blocks.
81 /// Can be used to find the total size, and used to calculate the `offset` based on the previous block.
82 prev: ?*DeclBlock,
83 next: ?*DeclBlock,
84 /// Pointer to data that will be written to the 'data' section.
85 /// This data either lives in `FnData.code` or is externally managed.
86 /// For data that does not live inside the 'data' section, this field will be undefined. (size == 0).
87 data: [*]const u8,
88
89 pub const empty: DeclBlock = .{
90 .init = false,
91 .symbol_index = 0,
92 .offset_index = 0,
93 .size = 0,
94 .prev = null,
95 .next = null,
96 .data = undefined,
97 };
98};
10599
106pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*Wasm {100pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*Wasm {
107 assert(options.object_format == .wasm);101 assert(options.object_format == .wasm);
...@@ -137,94 +131,66 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*Wasm {...@@ -137,94 +131,66 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*Wasm {
137}131}
138132
139pub fn deinit(self: *Wasm) void {133pub fn deinit(self: *Wasm) void {
140 for (self.funcs.items) |decl| {134 while (self.symbols_free_list.popOrNull()) |idx| {
141 decl.fn_link.wasm.functype.deinit(self.base.allocator);135 //dead decl's so remove them from symbol list before trying to clean them up
142 decl.fn_link.wasm.code.deinit(self.base.allocator);136 _ = self.symbols.swapRemove(idx);
143 decl.fn_link.wasm.idx_refs.deinit(self.base.allocator);
144 }137 }
145 for (self.ext_funcs.items) |decl| {138 for (self.symbols.items) |decl| {
146 decl.fn_link.wasm.functype.deinit(self.base.allocator);139 decl.fn_link.wasm.functype.deinit(self.base.allocator);
147 decl.fn_link.wasm.code.deinit(self.base.allocator);140 decl.fn_link.wasm.code.deinit(self.base.allocator);
148 decl.fn_link.wasm.idx_refs.deinit(self.base.allocator);141 decl.fn_link.wasm.idx_refs.deinit(self.base.allocator);
149 }142 }
150 for (self.data.segments.items) |entry| {143
151 // decl's that live in data section do not generate idx_refs or func types
152 entry.decl.fn_link.wasm.code.deinit(self.base.allocator);
153 }
154 self.funcs.deinit(self.base.allocator);144 self.funcs.deinit(self.base.allocator);
155 self.ext_funcs.deinit(self.base.allocator);145 self.ext_funcs.deinit(self.base.allocator);
156 self.data.segments.deinit(self.base.allocator);146 self.offset_table.deinit(self.base.allocator);
147 self.offset_table_free_list.deinit(self.base.allocator);
148 self.symbols.deinit(self.base.allocator);
149 self.symbols_free_list.deinit(self.base.allocator);
157}150}
158151
159pub fn allocateDeclIndexes(self: *Wasm, decl: *Module.Decl) !void {152pub fn allocateDeclIndexes(self: *Wasm, decl: *Module.Decl) !void {
160 const typed_value = decl.typed_value.most_recent.typed_value;153 if (decl.link.wasm.init) return;
161154
162 switch (typed_value.ty.zigTypeTag()) {155 try self.offset_table.ensureCapacity(self.base.allocator, self.offset_table.items.len + 1);
163 .Array => {156 try self.symbols.ensureCapacity(self.base.allocator, self.symbols.items.len + 1);
164 // if the codegen of the given decl contributes to the data segment
165 // we must calculate its data length now so that the data offsets are available
166 // to other decls when called
167 const data_len = self.calcDataLen(typed_value);
168 try self.data.segments.append(self.base.allocator, .{
169 .decl = decl,
170 .data = undefined,
171 .len = data_len,
172 });
173157
174 // detect if we can replace it into a to-be-deleted decl's spot to ensure no gaps are158 const block = &decl.link.wasm;
175 // made in our data segment159 block.init = true;
176 const idx: ?usize = for (self.data.segments.items) |entry, i| {160
177 if (entry.decl.deletion_flag) break i;161 if (self.symbols_free_list.popOrNull()) |index| {
178 } else null;162 block.symbol_index = index;
179163 } else {
180 if (idx) |id| {164 block.symbol_index = @intCast(u32, self.symbols.items.len);
181 // swap to-be-removed decl with newly added to create a contigious valid data segment165 _ = self.symbols.addOneAssumeCapacity();
182 const items = self.data.segments.items;166 }
183 std.mem.swap(167
184 std.meta.Child(@TypeOf(items)),168 if (self.offset_table_free_list.popOrNull()) |index| {
185 &items[id],169 block.offset_index = index;
186 &items[items.len - 1],170 } else {
187 );171 block.offset_index = @intCast(u32, self.offset_table.items.len);
188 }172 _ = self.offset_table.addOneAssumeCapacity();
189 },173 }
190 .Fn => if (self.getFuncidx(decl) == null) switch (typed_value.val.tag()) {174
175 self.offset_table.items[block.offset_index] = 0;
176
177 const typed_value = decl.typed_value.most_recent.typed_value;
178 if (typed_value.ty.zigTypeTag() == .Fn) {
179 switch (typed_value.val.tag()) {
191 // dependent on function type, appends it to the correct list180 // dependent on function type, appends it to the correct list
192 .function => try self.funcs.append(self.base.allocator, decl),181 .function => try self.funcs.append(self.base.allocator, decl),
193 .extern_fn => try self.ext_funcs.append(self.base.allocator, decl),182 .extern_fn => try self.ext_funcs.append(self.base.allocator, decl),
194 else => unreachable,183 else => unreachable,
195 },184 }
196 else => {},
197 }
198}
199
200/// Calculates the length of the data segment that will be occupied by the given `TypedValue`
201fn calcDataLen(self: *Wasm, typed_value: TypedValue) u32 {
202 switch (typed_value.ty.zigTypeTag()) {
203 .Array => {
204 if (typed_value.val.castTag(.bytes)) |payload| {
205 if (typed_value.ty.sentinel()) |sentinel| {
206 return @intCast(u32, payload.data.len) + self.calcDataLen(.{
207 .ty = typed_value.ty.elemType(),
208 .val = sentinel,
209 });
210 }
211 }
212 return @intCast(u32, typed_value.ty.arrayLen());
213 },
214 .Int => {
215 const info = typed_value.ty.intInfo(self.base.options.target);
216 return std.math.divCeil(u32, info.bits, 8) catch unreachable;
217 },
218 .Pointer => return 4,
219 else => unreachable,
220 }185 }
221}186}
222187
223// Generate code for the Decl, storing it in memory to be later written to188// Generate code for the Decl, storing it in memory to be later written to
224// the file on flush().189// the file on flush().
225pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {190pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
226 const typed_value = decl.typed_value.most_recent.typed_value;191 std.debug.assert(decl.link.wasm.init); // Must call allocateDeclIndexes()
227192
193 const typed_value = decl.typed_value.most_recent.typed_value;
228 const fn_data = &decl.fn_link.wasm;194 const fn_data = &decl.fn_link.wasm;
229 fn_data.functype.items.len = 0;195 fn_data.functype.items.len = 0;
230 fn_data.code.items.len = 0;196 fn_data.code.items.len = 0;
...@@ -252,28 +218,43 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {...@@ -252,28 +218,43 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
252 else => |e| return err,218 else => |e| return err,
253 };219 };
254220
255 switch (typed_value.ty.zigTypeTag()) {221 const code: []const u8 = switch (result) {
256 .Fn => {222 .appended => @as([]const u8, context.code.items),
257 // as locals are patched afterwards, the offsets of funcidx's are off,223 .externally_managed => |payload| payload,
258 // here we update them to correct them224 };
259 for (fn_data.idx_refs.items) |*func| {
260 // For each local, add 6 bytes (count + type)
261 func.offset += @intCast(u32, context.locals.items.len * 6);
262 }
263225
264 fn_data.functype = context.func_type_data.toUnmanaged();226 fn_data.code = context.code.toUnmanaged();
265 fn_data.code = context.code.toUnmanaged();227 fn_data.functype = context.func_type_data.toUnmanaged();
266 },228
267 .Array => switch (result) {229 const block = &decl.link.wasm;
268 .appended => {230 if (typed_value.ty.zigTypeTag() == .Fn) {
269 fn_data.functype = context.func_type_data.toUnmanaged();231 // as locals are patched afterwards, the offsets of funcidx's are off,
270 fn_data.code = context.code.toUnmanaged();232 // here we update them to correct them
271 self.data.updateData(decl, fn_data.code.items);233 for (fn_data.idx_refs.items) |*func| {
272 },234 // For each local, add 6 bytes (count + type)
273 .externally_managed => |payload| self.data.updateData(decl, payload),235 func.offset += @intCast(u32, context.locals.items.len * 6);
274 },236 }
275 else => return error.TODO,237 } else {
238 block.size = @intCast(u32, code.len);
239 block.data = code.ptr;
240 }
241
242 // If we're updating an existing decl, unplug it first
243 // to avoid infinite loops due to earlier links
244 if (block.prev) |prev| {
245 prev.next = block.next;
246 }
247 if (block.next) |next| {
248 next.prev = block.prev;
276 }249 }
250
251 if (self.last_block) |last| {
252 if (last != block) {
253 last.next = block;
254 block.prev = last;
255 }
256 }
257 self.last_block = block;
277}258}
278259
279pub fn updateDeclExports(260pub fn updateDeclExports(
...@@ -291,9 +272,24 @@ pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {...@@ -291,9 +272,24 @@ pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {
291 else => unreachable,272 else => unreachable,
292 }273 }
293 }274 }
294 if (self.data.getIdx(decl)) |idx| {275 const block = &decl.link.wasm;
295 _ = self.data.segments.swapRemove(idx);276
277 if (self.last_block == block) {
278 self.last_block = block.prev;
279 }
280
281 if (block.prev) |prev| {
282 prev.next = block.next;
296 }283 }
284
285 if (block.next) |next| {
286 next.prev = block.prev;
287 }
288
289 self.offset_table_free_list.append(self.base.allocator, decl.link.wasm.offset_index) catch {};
290 self.symbols_free_list.append(self.base.allocator, decl.link.wasm.symbol_index) catch {};
291 block.init = false;
292
297 decl.fn_link.wasm.functype.deinit(self.base.allocator);293 decl.fn_link.wasm.functype.deinit(self.base.allocator);
298 decl.fn_link.wasm.code.deinit(self.base.allocator);294 decl.fn_link.wasm.code.deinit(self.base.allocator);
299 decl.fn_link.wasm.idx_refs.deinit(self.base.allocator);295 decl.fn_link.wasm.idx_refs.deinit(self.base.allocator);
...@@ -314,7 +310,25 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -314,7 +310,25 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
314310
315 const file = self.base.file.?;311 const file = self.base.file.?;
316 const header_size = 5 + 1;312 const header_size = 5 + 1;
317 const data_size = self.data.size();313 // ptr_width in bytes
314 const ptr_width = self.base.options.target.cpu.arch.ptrBitWidth() / 8;
315 // The size of the offset table in bytes
316 // The table contains all decl's with its corresponding offset into
317 // the 'data' section
318 const offset_table_size = @intCast(u32, self.offset_table.items.len * ptr_width);
319
320 // The size of the data, this together with `offset_table_size` amounts to the
321 // total size of the 'data' section
322 var first_decl: ?*DeclBlock = null;
323 const data_size: u32 = if (self.last_block) |last| blk: {
324 var size = last.size;
325 var cur = last;
326 while (cur.prev) |prev| : (cur = prev) {
327 size += prev.size;
328 }
329 first_decl = cur;
330 break :blk size;
331 } else 0;
318332
319 // No need to rewrite the magic/version header333 // No need to rewrite the magic/version header
320 try file.setEndPos(@sizeOf(@TypeOf(wasm.magic ++ wasm.version)));334 try file.setEndPos(@sizeOf(@TypeOf(wasm.magic ++ wasm.version)));
...@@ -396,8 +410,8 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -396,8 +410,8 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
396 writer,410 writer,
397 try std.math.divCeil(411 try std.math.divCeil(
398 u32,412 u32,
399 self.data.size(),413 offset_table_size + data_size,
400 std.mem.page_size,414 std.wasm.page_size,
401 ),415 ),
402 );416 );
403 try writeVecSectionHeader(417 try writeVecSectionHeader(
...@@ -496,18 +510,35 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -496,18 +510,35 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
496 try leb.writeILEB128(writer, @as(i32, 0));510 try leb.writeILEB128(writer, @as(i32, 0));
497 try writer.writeByte(wasm.opcode(.end));511 try writer.writeByte(wasm.opcode(.end));
498512
499 // payload size513 const total_size = offset_table_size + data_size;
500 try leb.writeULEB128(writer, data_size);
501514
502 // write payload515 // offset table + data size
503 for (self.data.segments.items) |entry| try writer.writeAll(entry.data[0..entry.len]);516 try leb.writeULEB128(writer, total_size);
504517
518 // fill in the offset table and the data segments
519 const file_offset = try file.getPos();
520 var cur = first_decl;
521 var data_offset = offset_table_size;
522 while (cur) |cur_block| : (cur = cur_block.next) {
523 if (cur_block.size == 0) continue;
524 std.debug.assert(cur_block.init);
525
526 const offset = (cur_block.offset_index) * ptr_width;
527 var buf: [4]u8 = undefined;
528 std.mem.writeIntLittle(u32, &buf, data_offset);
529
530 try file.pwriteAll(&buf, file_offset + offset);
531 try file.pwriteAll(cur_block.data[0..cur_block.size], file_offset + data_offset);
532 data_offset += cur_block.size;
533 }
534
535 try file.seekTo(file_offset + data_offset);
505 try writeVecSectionHeader(536 try writeVecSectionHeader(
506 file,537 file,
507 header_offset,538 header_offset,
508 .data,539 .data,
509 @intCast(u32, (try file.getPos()) - header_offset - header_size),540 @intCast(u32, (file_offset + data_offset) - header_offset - header_size),
510 @intCast(u32, 1),541 @intCast(u32, 1), // only 1 data section
511 );542 );
512 }543 }
513}544}