From aa3e0ff454d06407b4ee347c1cd0c1e09444c52c Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Wed, 3 Feb 2021 22:15:18 +0100 Subject: Create type declarations for extern functions and write the 'import' section --- src/codegen/wasm.zig | 28 +++++++++++++++------- src/link/Wasm.zig | 66 ++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 79 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index abc411b551..5be26de7cb 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -163,13 +163,18 @@ pub const Context = struct { try self.genFunctype(); const writer = self.code.writer(); - // Reserve space to write the size after generating the code as well as space for locals count - try self.code.resize(10); - // Write instructions // TODO: check for and handle death of instructions const tv = self.decl.typed_value.most_recent.typed_value; - const mod_fn = tv.val.castTag(.function).?.data; + const mod_fn = blk: { + if (tv.val.castTag(.function)) |func| break :blk func.data; + if (tv.val.castTag(.extern_fn)) |ext_fn| return; // don't need codegen for extern functions + return self.fail(self.decl.src(), "TODO: Wasm codegen for decl type '{s}'", .{tv.ty.tag()}); + }; + + // Reserve space to write the size after generating the code as well as space for locals count + try self.code.resize(10); + try self.genBody(mod_fn.body); // finally, write our local types at the 'offset' position @@ -239,9 +244,16 @@ pub const Context = struct { fn genCall(self: *Context, inst: *Inst.Call) InnerError!WValue { const func_inst = inst.func.castTag(.constant).?; - const func = func_inst.val.castTag(.function).?.data; - const target = func.owner_decl; - const target_ty = target.typed_value.most_recent.typed_value.ty; + const func_val = inst.func.value().?; + + const target = blk: { + if (func_val.castTag(.function)) |func| { + break :blk func.data.owner_decl; + } else if (func_val.castTag(.extern_fn)) |ext_fn| { + break :blk ext_fn.data; + } + return self.fail(inst.base.src, "Expected a function, but instead found type '{s}'", .{func_val.tag()}); + }; for (inst.args) |arg| { const arg_val = self.resolveInst(arg); @@ -495,7 +507,7 @@ pub const Context = struct { } fn genBr(self: *Context, br: *Inst.Br) InnerError!WValue { - // of operand has codegen bits we should break with a value + // if operand has codegen bits we should break with a value if (br.operand.ty.hasCodeGenBits()) { const operand = self.resolveInst(br.operand); try self.emitWValue(operand); diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 547ab2a012..9660ff0a43 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -85,8 +85,6 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { const typed_value = decl.typed_value.most_recent.typed_value; if (typed_value.ty.zigTypeTag() != .Fn) return error.TODOImplementNonFnDeclsForWasm; - if (typed_value.val.tag() == .extern_fn) - return error.TODOImplementExternFnDeclsForWasm; if (decl.fn_link.wasm) |*fn_data| { fn_data.functype.items.len = 0; @@ -184,17 +182,63 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { ); } + // Import section + { + // TODO: implement non-functions imports + const header_offset = try reserveVecSectionHeader(file); + const writer = file.writer(); + var count: u32 = 0; + for (self.funcs.items) |decl, typeidx| { + if (decl.typed_value.most_recent.typed_value.val.tag() != .extern_fn) { + continue; + } + + // TODO: can we set/save the module name somewhere? + // For now, emit "env" like LLVM does + const module_name = "env"; + try leb.writeULEB128(writer, @intCast(u32, module_name.len)); + try writer.writeAll(module_name); + + // wasm requires the length of the import name and doesn't require a null-termination + const decl_len = mem.len(decl.name); + try leb.writeULEB128(writer, @intCast(u32, decl_len)); + try writer.writeAll(decl.name[0..decl_len]); + + // emit kind and the function type + try writer.writeByte(wasm.kind(.function)); + try leb.writeULEB128(writer, @intCast(u32, typeidx)); + + count += 1; + } + + try writeVecSectionHeader( + file, + header_offset, + .import, + @intCast(u32, (try file.getPos()) - header_offset - header_size), + count, + ); + } + // Function section { const header_offset = try reserveVecSectionHeader(file); const writer = file.writer(); - for (self.funcs.items) |_, typeidx| try leb.writeULEB128(writer, @intCast(u32, typeidx)); + var count: u32 = 0; + for (self.funcs.items) |decl, typeidx| { + // Extern functions only have a type, so skip the function signature section + if (decl.typed_value.most_recent.typed_value.val.tag() != .function) { + continue; + } + try leb.writeULEB128(writer, @intCast(u32, typeidx)); + count += 1; + } try writeVecSectionHeader( file, header_offset, .function, @intCast(u32, (try file.getPos()) - header_offset - header_size), - @intCast(u32, self.funcs.items.len), + count, ); } @@ -214,7 +258,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { // Type of the export try writer.writeByte(0x00); // Exported function index - try leb.writeULEB128(writer, self.getFuncidx(exprt.exported_decl).?); + try leb.writeULEB128(writer, self.getFuncidx(exprt.exported_decl).? + 1); }, else => return error.TODOImplementNonFnDeclsForWasm, } @@ -235,7 +279,13 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { { const header_offset = try reserveVecSectionHeader(file); const writer = file.writer(); + var count: u32 = 0; for (self.funcs.items) |decl| { + // Do not emit any code for extern functions + if (decl.typed_value.most_recent.typed_value.val.tag() != .function) { + std.debug.print("Skipping decl: {s}\n", .{decl.name}); + continue; + } const fn_data = &decl.fn_link.wasm.?; // Write the already generated code to the file, inserting @@ -247,18 +297,20 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { // Use a fixed width here to make calculating the code size // in codegen.wasm.gen() simpler. var buf: [5]u8 = undefined; - leb.writeUnsignedFixed(5, &buf, self.getFuncidx(idx_ref.decl).?); + std.debug.print("idx_ref: {s} - {d}\n", .{ idx_ref.decl.name, self.getFuncidx(idx_ref.decl).? }); + leb.writeUnsignedFixed(5, &buf, self.getFuncidx(idx_ref.decl).? - 1); try writer.writeAll(&buf); } try writer.writeAll(fn_data.code.items[current..]); + count += 1; } try writeVecSectionHeader( file, header_offset, .code, @intCast(u32, (try file.getPos()) - header_offset - header_size), - @intCast(u32, self.funcs.items.len), + count, ); } } -- cgit v1.2.3 From 36df6a008fb009fd8fd1d3362fc850cf97723dcd Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Thu, 4 Feb 2021 21:08:19 +0100 Subject: Ensure function indices are correct and fix a memory leak --- lib/std/wasm.zig | 2 +- src/codegen/wasm.zig | 2 +- src/link/Wasm.zig | 118 +++++++++++++++++++++++++++++++-------------------- 3 files changed, 73 insertions(+), 49 deletions(-) (limited to 'src') diff --git a/lib/std/wasm.zig b/lib/std/wasm.zig index c59117d65b..a04378d283 100644 --- a/lib/std/wasm.zig +++ b/lib/std/wasm.zig @@ -263,7 +263,7 @@ pub const ExternalKind = enum(u8) { }; /// Returns the integer value of a given `ExternalKind` -pub fn kind(val: ExternalKind) u8 { +pub fn externalKind(val: ExternalKind) u8 { return @enumToInt(val); } diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index 5be26de7cb..34e0b2f9b5 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -161,7 +161,6 @@ pub const Context = struct { pub fn gen(self: *Context) InnerError!void { assert(self.code.items.len == 0); try self.genFunctype(); - const writer = self.code.writer(); // Write instructions // TODO: check for and handle death of instructions @@ -194,6 +193,7 @@ pub const Context = struct { } } + const writer = self.code.writer(); try writer.writeByte(wasm.opcode(.end)); // Fill in the size of the generated code to the reserved space at the diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 9660ff0a43..bd89ce3345 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -33,9 +33,18 @@ base: link.File, /// List of all function Decls to be written to the output file. The index of /// each Decl in this list at the time of writing the binary is used as the -/// function index. +/// function index. In the event where ext_funcs' size is not 0, the index of +/// each function is added on top of the ext_funcs' length. /// TODO: can/should we access some data structure in Module directly? funcs: std.ArrayListUnmanaged(*Module.Decl) = .{}, +/// List of all extern function Decls to be written to the `import` section of the +/// wasm binary. The positin in the list defines the function index +ext_funcs: std.ArrayListUnmanaged(*Module.Decl) = .{}, +/// When importing objects from the host environment, a name must be supplied. +/// LLVM uses "env" by default when none is given. This would be a good default for Zig +/// to support existing code. +/// TODO: Allow setting this through a flag? +host_name: []const u8 = "env", pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*Wasm { assert(options.object_format == .wasm); @@ -76,7 +85,13 @@ pub fn deinit(self: *Wasm) void { decl.fn_link.wasm.?.code.deinit(self.base.allocator); decl.fn_link.wasm.?.idx_refs.deinit(self.base.allocator); } + for (self.ext_funcs.items) |decl| { + decl.fn_link.wasm.?.functype.deinit(self.base.allocator); + decl.fn_link.wasm.?.code.deinit(self.base.allocator); + decl.fn_link.wasm.?.idx_refs.deinit(self.base.allocator); + } self.funcs.deinit(self.base.allocator); + self.ext_funcs.deinit(self.base.allocator); } // 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 { fn_data.idx_refs.items.len = 0; } else { decl.fn_link.wasm = .{}; - try self.funcs.append(self.base.allocator, decl); + // dependent on function type, appends it to the correct list + switch (decl.typed_value.most_recent.typed_value.val.tag()) { + .function => try self.funcs.append(self.base.allocator, decl), + .extern_fn => try self.ext_funcs.append(self.base.allocator, decl), + else => return error.TODOImplementNonFnDeclsForWasm, + } } const fn_data = &decl.fn_link.wasm.?; @@ -141,7 +161,12 @@ pub fn updateDeclExports( pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void { // TODO: remove this assert when non-function Decls are implemented assert(decl.typed_value.most_recent.typed_value.ty.zigTypeTag() == .Fn); - _ = self.funcs.swapRemove(self.getFuncidx(decl).?); + const func_idx = self.getFuncidx(decl).?; + switch (decl.typed_value.most_recent.typed_value.val.tag()) { + .function => _ = self.funcs.swapRemove(func_idx), + .extern_fn => _ = self.ext_funcs.swapRemove(func_idx), + else => unreachable, + } decl.fn_link.wasm.?.functype.deinit(self.base.allocator); decl.fn_link.wasm.?.code.deinit(self.base.allocator); decl.fn_link.wasm.?.idx_refs.deinit(self.base.allocator); @@ -170,15 +195,18 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { // Type section { const header_offset = try reserveVecSectionHeader(file); - for (self.funcs.items) |decl| { - try file.writeAll(decl.fn_link.wasm.?.functype.items); - } + + // extern functions are defined in the wasm binary first through the `import` + // section, so define their func types first + for (self.ext_funcs.items) |decl| try file.writeAll(decl.fn_link.wasm.?.functype.items); + for (self.funcs.items) |decl| try file.writeAll(decl.fn_link.wasm.?.functype.items); + try writeVecSectionHeader( file, header_offset, .type, @intCast(u32, (try file.getPos()) - header_offset - header_size), - @intCast(u32, self.funcs.items.len), + @intCast(u32, self.ext_funcs.items.len + self.funcs.items.len), ); } @@ -187,28 +215,18 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { // TODO: implement non-functions imports const header_offset = try reserveVecSectionHeader(file); const writer = file.writer(); - var count: u32 = 0; - for (self.funcs.items) |decl, typeidx| { - if (decl.typed_value.most_recent.typed_value.val.tag() != .extern_fn) { - continue; - } - - // TODO: can we set/save the module name somewhere? - // For now, emit "env" like LLVM does - const module_name = "env"; - try leb.writeULEB128(writer, @intCast(u32, module_name.len)); - try writer.writeAll(module_name); + for (self.ext_funcs.items) |decl, typeidx| { + try leb.writeULEB128(writer, @intCast(u32, self.host_name.len)); + try writer.writeAll(self.host_name); - // wasm requires the length of the import name and doesn't require a null-termination + // wasm requires the length of the import name with no null-termination const decl_len = mem.len(decl.name); try leb.writeULEB128(writer, @intCast(u32, decl_len)); try writer.writeAll(decl.name[0..decl_len]); // emit kind and the function type - try writer.writeByte(wasm.kind(.function)); + try writer.writeByte(wasm.externalKind(.function)); try leb.writeULEB128(writer, @intCast(u32, typeidx)); - - count += 1; } try writeVecSectionHeader( @@ -216,7 +234,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { header_offset, .import, @intCast(u32, (try file.getPos()) - header_offset - header_size), - count, + @intCast(u32, self.ext_funcs.items.len), ); } @@ -224,21 +242,17 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { { const header_offset = try reserveVecSectionHeader(file); const writer = file.writer(); - var count: u32 = 0; - for (self.funcs.items) |decl, typeidx| { - // Extern functions only have a type, so skip the function signature section - if (decl.typed_value.most_recent.typed_value.val.tag() != .function) { - continue; - } - try leb.writeULEB128(writer, @intCast(u32, typeidx)); - count += 1; + for (self.funcs.items) |_, typeidx| { + const func_idx = @intCast(u32, self.getFuncIdxOffset() + typeidx); + try leb.writeULEB128(writer, func_idx); } + try writeVecSectionHeader( file, header_offset, .function, @intCast(u32, (try file.getPos()) - header_offset - header_size), - count, + @intCast(u32, self.funcs.items.len), ); } @@ -256,9 +270,9 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { switch (exprt.exported_decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) { .Fn => { // Type of the export - try writer.writeByte(0x00); + try writer.writeByte(wasm.externalKind(.function)); // Exported function index - try leb.writeULEB128(writer, self.getFuncidx(exprt.exported_decl).? + 1); + try leb.writeULEB128(writer, self.getFuncidx(exprt.exported_decl).?); }, else => return error.TODOImplementNonFnDeclsForWasm, } @@ -279,13 +293,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { { const header_offset = try reserveVecSectionHeader(file); const writer = file.writer(); - var count: u32 = 0; for (self.funcs.items) |decl| { - // Do not emit any code for extern functions - if (decl.typed_value.most_recent.typed_value.val.tag() != .function) { - std.debug.print("Skipping decl: {s}\n", .{decl.name}); - continue; - } const fn_data = &decl.fn_link.wasm.?; // Write the already generated code to the file, inserting @@ -297,20 +305,18 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { // Use a fixed width here to make calculating the code size // in codegen.wasm.gen() simpler. var buf: [5]u8 = undefined; - std.debug.print("idx_ref: {s} - {d}\n", .{ idx_ref.decl.name, self.getFuncidx(idx_ref.decl).? }); - leb.writeUnsignedFixed(5, &buf, self.getFuncidx(idx_ref.decl).? - 1); + leb.writeUnsignedFixed(5, &buf, self.getFuncidx(idx_ref.decl).?); try writer.writeAll(&buf); } try writer.writeAll(fn_data.code.items[current..]); - count += 1; } try writeVecSectionHeader( file, header_offset, .code, @intCast(u32, (try file.getPos()) - header_offset - header_size), - count, + @intCast(u32, self.funcs.items.len), ); } } @@ -575,13 +581,31 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void { } /// Get the current index of a given Decl in the function list -/// TODO: we could maintain a hash map to potentially make this +/// This will correctly provide the index, regardless whether the function is extern or not +/// TODO: we could maintain a hash map to potentially make this simpler fn getFuncidx(self: Wasm, decl: *Module.Decl) ?u32 { - return for (self.funcs.items) |func, idx| { - if (func == decl) break @intCast(u32, idx); + var offset: u32 = 0; + const slice = switch (decl.typed_value.most_recent.typed_value.val.tag()) { + .function => blk: { + // when the target is a regular function, we have to calculate + // the offset of where the index starts + offset += self.getFuncIdxOffset(); + break :blk self.funcs.items; + }, + .extern_fn => self.ext_funcs.items, + else => return null, + }; + return for (slice) |func, idx| { + if (func == decl) break @intCast(u32, offset + idx); } else null; } +/// Based on the size of `ext_funcs` returns the +/// offset of the function indices +fn getFuncIdxOffset(self: Wasm) u32 { + return @intCast(u32, self.ext_funcs.items.len); +} + fn reserveVecSectionHeader(file: fs.File) !u64 { // section id + fixed leb contents size + fixed leb vector length const header_size = 1 + 5 + 5; -- cgit v1.2.3