authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-02-04 21:08:19+01:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-02-05 18:06:25+01:00
log36df6a008fb009fd8fd1d3362fc850cf97723dcd
tree801a88c25477032a96f23f60506c1a6e6d16aaba
parentaa3e0ff454d06407b4ee347c1cd0c1e09444c52c
signaturelock-open Commit is signed but in an unrecognized format.

Ensure function indices are correct and fix a memory leak


3 files changed, 73 insertions(+), 49 deletions(-)

lib/std/wasm.zig+1-1
...@@ -263,7 +263,7 @@ pub const ExternalKind = enum(u8) {...@@ -263,7 +263,7 @@ pub const ExternalKind = enum(u8) {
263};263};
264264
265/// Returns the integer value of a given `ExternalKind`265/// Returns the integer value of a given `ExternalKind`
266pub fn kind(val: ExternalKind) u8 {266pub fn externalKind(val: ExternalKind) u8 {
267 return @enumToInt(val);267 return @enumToInt(val);
268}268}
269269
src/codegen/wasm.zig+1-1
...@@ -161,7 +161,6 @@ pub const Context = struct {...@@ -161,7 +161,6 @@ pub const Context = struct {
161 pub fn gen(self: *Context) InnerError!void {161 pub fn gen(self: *Context) InnerError!void {
162 assert(self.code.items.len == 0);162 assert(self.code.items.len == 0);
163 try self.genFunctype();163 try self.genFunctype();
164 const writer = self.code.writer();
165164
166 // Write instructions165 // Write instructions
167 // TODO: check for and handle death of instructions166 // TODO: check for and handle death of instructions
...@@ -194,6 +193,7 @@ pub const Context = struct {...@@ -194,6 +193,7 @@ pub const Context = struct {
194 }193 }
195 }194 }
196195
196 const writer = self.code.writer();
197 try writer.writeByte(wasm.opcode(.end));197 try writer.writeByte(wasm.opcode(.end));
198198
199 // Fill in the size of the generated code to the reserved space at the199 // Fill in the size of the generated code to the reserved space at the
src/link/Wasm.zig+71-47
...@@ -33,9 +33,18 @@ base: link.File,...@@ -33,9 +33,18 @@ base: link.File,
3333
34/// List of all function Decls to be written to the output file. The index of34/// List of all function Decls to be written to the output file. The index of
35/// each Decl in this list at the time of writing the binary is used as the35/// each Decl in this list at the time of writing the binary is used as the
36/// function index.36/// function index. In the event where ext_funcs' size is not 0, the index of
37/// each function is added on top of the ext_funcs' length.
37/// TODO: can/should we access some data structure in Module directly?38/// TODO: can/should we access some data structure in Module directly?
38funcs: std.ArrayListUnmanaged(*Module.Decl) = .{},39funcs: std.ArrayListUnmanaged(*Module.Decl) = .{},
40/// List of all extern function Decls to be written to the `import` section of the
41/// wasm binary. The positin in the list defines the function index
42ext_funcs: std.ArrayListUnmanaged(*Module.Decl) = .{},
43/// When importing objects from the host environment, a name must be supplied.
44/// LLVM uses "env" by default when none is given. This would be a good default for Zig
45/// to support existing code.
46/// TODO: Allow setting this through a flag?
47host_name: []const u8 = "env",
3948
40pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*Wasm {49pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*Wasm {
41 assert(options.object_format == .wasm);50 assert(options.object_format == .wasm);
...@@ -76,7 +85,13 @@ pub fn deinit(self: *Wasm) void {...@@ -76,7 +85,13 @@ pub fn deinit(self: *Wasm) void {
76 decl.fn_link.wasm.?.code.deinit(self.base.allocator);85 decl.fn_link.wasm.?.code.deinit(self.base.allocator);
77 decl.fn_link.wasm.?.idx_refs.deinit(self.base.allocator);86 decl.fn_link.wasm.?.idx_refs.deinit(self.base.allocator);
78 }87 }
88 for (self.ext_funcs.items) |decl| {
89 decl.fn_link.wasm.?.functype.deinit(self.base.allocator);
90 decl.fn_link.wasm.?.code.deinit(self.base.allocator);
91 decl.fn_link.wasm.?.idx_refs.deinit(self.base.allocator);
92 }
79 self.funcs.deinit(self.base.allocator);93 self.funcs.deinit(self.base.allocator);
94 self.ext_funcs.deinit(self.base.allocator);
80}95}
8196
82// Generate code for the Decl, storing it in memory to be later written to97// Generate code for the Decl, storing it in memory to be later written to
...@@ -92,7 +107,12 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {...@@ -92,7 +107,12 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
92 fn_data.idx_refs.items.len = 0;107 fn_data.idx_refs.items.len = 0;
93 } else {108 } else {
94 decl.fn_link.wasm = .{};109 decl.fn_link.wasm = .{};
95 try self.funcs.append(self.base.allocator, decl);110 // dependent on function type, appends it to the correct list
111 switch (decl.typed_value.most_recent.typed_value.val.tag()) {
112 .function => try self.funcs.append(self.base.allocator, decl),
113 .extern_fn => try self.ext_funcs.append(self.base.allocator, decl),
114 else => return error.TODOImplementNonFnDeclsForWasm,
115 }
96 }116 }
97 const fn_data = &decl.fn_link.wasm.?;117 const fn_data = &decl.fn_link.wasm.?;
98118
...@@ -141,7 +161,12 @@ pub fn updateDeclExports(...@@ -141,7 +161,12 @@ pub fn updateDeclExports(
141pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {161pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {
142 // TODO: remove this assert when non-function Decls are implemented162 // TODO: remove this assert when non-function Decls are implemented
143 assert(decl.typed_value.most_recent.typed_value.ty.zigTypeTag() == .Fn);163 assert(decl.typed_value.most_recent.typed_value.ty.zigTypeTag() == .Fn);
144 _ = self.funcs.swapRemove(self.getFuncidx(decl).?);164 const func_idx = self.getFuncidx(decl).?;
165 switch (decl.typed_value.most_recent.typed_value.val.tag()) {
166 .function => _ = self.funcs.swapRemove(func_idx),
167 .extern_fn => _ = self.ext_funcs.swapRemove(func_idx),
168 else => unreachable,
169 }
145 decl.fn_link.wasm.?.functype.deinit(self.base.allocator);170 decl.fn_link.wasm.?.functype.deinit(self.base.allocator);
146 decl.fn_link.wasm.?.code.deinit(self.base.allocator);171 decl.fn_link.wasm.?.code.deinit(self.base.allocator);
147 decl.fn_link.wasm.?.idx_refs.deinit(self.base.allocator);172 decl.fn_link.wasm.?.idx_refs.deinit(self.base.allocator);
...@@ -170,15 +195,18 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -170,15 +195,18 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
170 // Type section195 // Type section
171 {196 {
172 const header_offset = try reserveVecSectionHeader(file);197 const header_offset = try reserveVecSectionHeader(file);
173 for (self.funcs.items) |decl| {198
174 try file.writeAll(decl.fn_link.wasm.?.functype.items);199 // extern functions are defined in the wasm binary first through the `import`
175 }200 // section, so define their func types first
201 for (self.ext_funcs.items) |decl| try file.writeAll(decl.fn_link.wasm.?.functype.items);
202 for (self.funcs.items) |decl| try file.writeAll(decl.fn_link.wasm.?.functype.items);
203
176 try writeVecSectionHeader(204 try writeVecSectionHeader(
177 file,205 file,
178 header_offset,206 header_offset,
179 .type,207 .type,
180 @intCast(u32, (try file.getPos()) - header_offset - header_size),208 @intCast(u32, (try file.getPos()) - header_offset - header_size),
181 @intCast(u32, self.funcs.items.len),209 @intCast(u32, self.ext_funcs.items.len + self.funcs.items.len),
182 );210 );
183 }211 }
184212
...@@ -187,28 +215,18 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -187,28 +215,18 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
187 // TODO: implement non-functions imports215 // TODO: implement non-functions imports
188 const header_offset = try reserveVecSectionHeader(file);216 const header_offset = try reserveVecSectionHeader(file);
189 const writer = file.writer();217 const writer = file.writer();
190 var count: u32 = 0;218 for (self.ext_funcs.items) |decl, typeidx| {
191 for (self.funcs.items) |decl, typeidx| {219 try leb.writeULEB128(writer, @intCast(u32, self.host_name.len));
192 if (decl.typed_value.most_recent.typed_value.val.tag() != .extern_fn) {220 try writer.writeAll(self.host_name);
193 continue;
194 }
195
196 // TODO: can we set/save the module name somewhere?
197 // For now, emit "env" like LLVM does
198 const module_name = "env";
199 try leb.writeULEB128(writer, @intCast(u32, module_name.len));
200 try writer.writeAll(module_name);
201221
202 // wasm requires the length of the import name and doesn't require a null-termination222 // wasm requires the length of the import name with no null-termination
203 const decl_len = mem.len(decl.name);223 const decl_len = mem.len(decl.name);
204 try leb.writeULEB128(writer, @intCast(u32, decl_len));224 try leb.writeULEB128(writer, @intCast(u32, decl_len));
205 try writer.writeAll(decl.name[0..decl_len]);225 try writer.writeAll(decl.name[0..decl_len]);
206226
207 // emit kind and the function type227 // emit kind and the function type
208 try writer.writeByte(wasm.kind(.function));228 try writer.writeByte(wasm.externalKind(.function));
209 try leb.writeULEB128(writer, @intCast(u32, typeidx));229 try leb.writeULEB128(writer, @intCast(u32, typeidx));
210
211 count += 1;
212 }230 }
213231
214 try writeVecSectionHeader(232 try writeVecSectionHeader(
...@@ -216,7 +234,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -216,7 +234,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
216 header_offset,234 header_offset,
217 .import,235 .import,
218 @intCast(u32, (try file.getPos()) - header_offset - header_size),236 @intCast(u32, (try file.getPos()) - header_offset - header_size),
219 count,237 @intCast(u32, self.ext_funcs.items.len),
220 );238 );
221 }239 }
222240
...@@ -224,21 +242,17 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -224,21 +242,17 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
224 {242 {
225 const header_offset = try reserveVecSectionHeader(file);243 const header_offset = try reserveVecSectionHeader(file);
226 const writer = file.writer();244 const writer = file.writer();
227 var count: u32 = 0;245 for (self.funcs.items) |_, typeidx| {
228 for (self.funcs.items) |decl, typeidx| {246 const func_idx = @intCast(u32, self.getFuncIdxOffset() + typeidx);
229 // Extern functions only have a type, so skip the function signature section247 try leb.writeULEB128(writer, func_idx);
230 if (decl.typed_value.most_recent.typed_value.val.tag() != .function) {
231 continue;
232 }
233 try leb.writeULEB128(writer, @intCast(u32, typeidx));
234 count += 1;
235 }248 }
249
236 try writeVecSectionHeader(250 try writeVecSectionHeader(
237 file,251 file,
238 header_offset,252 header_offset,
239 .function,253 .function,
240 @intCast(u32, (try file.getPos()) - header_offset - header_size),254 @intCast(u32, (try file.getPos()) - header_offset - header_size),
241 count,255 @intCast(u32, self.funcs.items.len),
242 );256 );
243 }257 }
244258
...@@ -256,9 +270,9 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -256,9 +270,9 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
256 switch (exprt.exported_decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) {270 switch (exprt.exported_decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) {
257 .Fn => {271 .Fn => {
258 // Type of the export272 // Type of the export
259 try writer.writeByte(0x00);273 try writer.writeByte(wasm.externalKind(.function));
260 // Exported function index274 // Exported function index
261 try leb.writeULEB128(writer, self.getFuncidx(exprt.exported_decl).? + 1);275 try leb.writeULEB128(writer, self.getFuncidx(exprt.exported_decl).?);
262 },276 },
263 else => return error.TODOImplementNonFnDeclsForWasm,277 else => return error.TODOImplementNonFnDeclsForWasm,
264 }278 }
...@@ -279,13 +293,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -279,13 +293,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
279 {293 {
280 const header_offset = try reserveVecSectionHeader(file);294 const header_offset = try reserveVecSectionHeader(file);
281 const writer = file.writer();295 const writer = file.writer();
282 var count: u32 = 0;
283 for (self.funcs.items) |decl| {296 for (self.funcs.items) |decl| {
284 // Do not emit any code for extern functions
285 if (decl.typed_value.most_recent.typed_value.val.tag() != .function) {
286 std.debug.print("Skipping decl: {s}\n", .{decl.name});
287 continue;
288 }
289 const fn_data = &decl.fn_link.wasm.?;297 const fn_data = &decl.fn_link.wasm.?;
290298
291 // Write the already generated code to the file, inserting299 // Write the already generated code to the file, inserting
...@@ -297,20 +305,18 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -297,20 +305,18 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
297 // Use a fixed width here to make calculating the code size305 // Use a fixed width here to make calculating the code size
298 // in codegen.wasm.gen() simpler.306 // in codegen.wasm.gen() simpler.
299 var buf: [5]u8 = undefined;307 var buf: [5]u8 = undefined;
300 std.debug.print("idx_ref: {s} - {d}\n", .{ idx_ref.decl.name, self.getFuncidx(idx_ref.decl).? });308 leb.writeUnsignedFixed(5, &buf, self.getFuncidx(idx_ref.decl).?);
301 leb.writeUnsignedFixed(5, &buf, self.getFuncidx(idx_ref.decl).? - 1);
302 try writer.writeAll(&buf);309 try writer.writeAll(&buf);
303 }310 }
304311
305 try writer.writeAll(fn_data.code.items[current..]);312 try writer.writeAll(fn_data.code.items[current..]);
306 count += 1;
307 }313 }
308 try writeVecSectionHeader(314 try writeVecSectionHeader(
309 file,315 file,
310 header_offset,316 header_offset,
311 .code,317 .code,
312 @intCast(u32, (try file.getPos()) - header_offset - header_size),318 @intCast(u32, (try file.getPos()) - header_offset - header_size),
313 count,319 @intCast(u32, self.funcs.items.len),
314 );320 );
315 }321 }
316}322}
...@@ -575,13 +581,31 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {...@@ -575,13 +581,31 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {
575}581}
576582
577/// Get the current index of a given Decl in the function list583/// Get the current index of a given Decl in the function list
578/// TODO: we could maintain a hash map to potentially make this584/// This will correctly provide the index, regardless whether the function is extern or not
585/// TODO: we could maintain a hash map to potentially make this simpler
579fn getFuncidx(self: Wasm, decl: *Module.Decl) ?u32 {586fn getFuncidx(self: Wasm, decl: *Module.Decl) ?u32 {
580 return for (self.funcs.items) |func, idx| {587 var offset: u32 = 0;
581 if (func == decl) break @intCast(u32, idx);588 const slice = switch (decl.typed_value.most_recent.typed_value.val.tag()) {
589 .function => blk: {
590 // when the target is a regular function, we have to calculate
591 // the offset of where the index starts
592 offset += self.getFuncIdxOffset();
593 break :blk self.funcs.items;
594 },
595 .extern_fn => self.ext_funcs.items,
596 else => return null,
597 };
598 return for (slice) |func, idx| {
599 if (func == decl) break @intCast(u32, offset + idx);
582 } else null;600 } else null;
583}601}
584602
603/// Based on the size of `ext_funcs` returns the
604/// offset of the function indices
605fn getFuncIdxOffset(self: Wasm) u32 {
606 return @intCast(u32, self.ext_funcs.items.len);
607}
608
585fn reserveVecSectionHeader(file: fs.File) !u64 {609fn reserveVecSectionHeader(file: fs.File) !u64 {
586 // section id + fixed leb contents size + fixed leb vector length610 // section id + fixed leb contents size + fixed leb vector length
587 const header_size = 1 + 5 + 5;611 const header_size = 1 + 5 + 5;