authorgravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2021-02-21 12:22:01+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-02-21 12:22:01+02:00
logef6aa3d027f781acd577134ffab4b235cff91582
treea2634d50e1db990816514d52d30f6a9717b62458
parent057bf1afc9933e32bd35842d2464dab2164f06fb
parent36df6a008fb009fd8fd1d3362fc850cf97723dcd
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #7960 from Luukdegram/wasm-extern

stage2: Add support for extern functions for the wasm backend

3 files changed, 125 insertions(+), 23 deletions(-)

lib/std/wasm.zig+14
...@@ -253,6 +253,20 @@ pub fn section(val: Section) u8 {...@@ -253,6 +253,20 @@ pub fn section(val: Section) u8 {
253 return @enumToInt(val);253 return @enumToInt(val);
254}254}
255255
256/// The kind of the type when importing or exporting to/from the host environment
257/// https://webassembly.github.io/spec/core/syntax/modules.html
258pub const ExternalKind = enum(u8) {
259 function,
260 table,
261 memory,
262 global,
263};
264
265/// Returns the integer value of a given `ExternalKind`
266pub fn externalKind(val: ExternalKind) u8 {
267 return @enumToInt(val);
268}
269
256// types270// types
257pub const element_type: u8 = 0x70;271pub const element_type: u8 = 0x70;
258pub const function_type: u8 = 0x60;272pub const function_type: u8 = 0x60;
src/codegen/wasm.zig+21-9
...@@ -161,15 +161,19 @@ pub const Context = struct {...@@ -161,15 +161,19 @@ 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();
165
166 // Reserve space to write the size after generating the code as well as space for locals count
167 try self.code.resize(10);
168164
169 // Write instructions165 // Write instructions
170 // TODO: check for and handle death of instructions166 // TODO: check for and handle death of instructions
171 const tv = self.decl.typed_value.most_recent.typed_value;167 const tv = self.decl.typed_value.most_recent.typed_value;
172 const mod_fn = tv.val.castTag(.function).?.data;168 const mod_fn = blk: {
169 if (tv.val.castTag(.function)) |func| break :blk func.data;
170 if (tv.val.castTag(.extern_fn)) |ext_fn| return; // don't need codegen for extern functions
171 return self.fail(self.decl.src(), "TODO: Wasm codegen for decl type '{s}'", .{tv.ty.tag()});
172 };
173
174 // Reserve space to write the size after generating the code as well as space for locals count
175 try self.code.resize(10);
176
173 try self.genBody(mod_fn.body);177 try self.genBody(mod_fn.body);
174178
175 // finally, write our local types at the 'offset' position179 // finally, write our local types at the 'offset' position
...@@ -189,6 +193,7 @@ pub const Context = struct {...@@ -189,6 +193,7 @@ pub const Context = struct {
189 }193 }
190 }194 }
191195
196 const writer = self.code.writer();
192 try writer.writeByte(wasm.opcode(.end));197 try writer.writeByte(wasm.opcode(.end));
193198
194 // 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
...@@ -239,9 +244,16 @@ pub const Context = struct {...@@ -239,9 +244,16 @@ pub const Context = struct {
239244
240 fn genCall(self: *Context, inst: *Inst.Call) InnerError!WValue {245 fn genCall(self: *Context, inst: *Inst.Call) InnerError!WValue {
241 const func_inst = inst.func.castTag(.constant).?;246 const func_inst = inst.func.castTag(.constant).?;
242 const func = func_inst.val.castTag(.function).?.data;247 const func_val = inst.func.value().?;
243 const target = func.owner_decl;248
244 const target_ty = target.typed_value.most_recent.typed_value.ty;249 const target = blk: {
250 if (func_val.castTag(.function)) |func| {
251 break :blk func.data.owner_decl;
252 } else if (func_val.castTag(.extern_fn)) |ext_fn| {
253 break :blk ext_fn.data;
254 }
255 return self.fail(inst.base.src, "Expected a function, but instead found type '{s}'", .{func_val.tag()});
256 };
245257
246 for (inst.args) |arg| {258 for (inst.args) |arg| {
247 const arg_val = self.resolveInst(arg);259 const arg_val = self.resolveInst(arg);
...@@ -495,7 +507,7 @@ pub const Context = struct {...@@ -495,7 +507,7 @@ pub const Context = struct {
495 }507 }
496508
497 fn genBr(self: *Context, br: *Inst.Br) InnerError!WValue {509 fn genBr(self: *Context, br: *Inst.Br) InnerError!WValue {
498 // of operand has codegen bits we should break with a value510 // if operand has codegen bits we should break with a value
499 if (br.operand.ty.hasCodeGenBits()) {511 if (br.operand.ty.hasCodeGenBits()) {
500 const operand = self.resolveInst(br.operand);512 const operand = self.resolveInst(br.operand);
501 try self.emitWValue(operand);513 try self.emitWValue(operand);
src/link/Wasm.zig+90-14
...@@ -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
...@@ -85,8 +100,6 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {...@@ -85,8 +100,6 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
85 const typed_value = decl.typed_value.most_recent.typed_value;100 const typed_value = decl.typed_value.most_recent.typed_value;
86 if (typed_value.ty.zigTypeTag() != .Fn)101 if (typed_value.ty.zigTypeTag() != .Fn)
87 return error.TODOImplementNonFnDeclsForWasm;102 return error.TODOImplementNonFnDeclsForWasm;
88 if (typed_value.val.tag() == .extern_fn)
89 return error.TODOImplementExternFnDeclsForWasm;
90103
91 if (decl.fn_link.wasm) |*fn_data| {104 if (decl.fn_link.wasm) |*fn_data| {
92 fn_data.functype.items.len = 0;105 fn_data.functype.items.len = 0;
...@@ -94,7 +107,12 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {...@@ -94,7 +107,12 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
94 fn_data.idx_refs.items.len = 0;107 fn_data.idx_refs.items.len = 0;
95 } else {108 } else {
96 decl.fn_link.wasm = .{};109 decl.fn_link.wasm = .{};
97 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 }
98 }116 }
99 const fn_data = &decl.fn_link.wasm.?;117 const fn_data = &decl.fn_link.wasm.?;
100118
...@@ -143,7 +161,12 @@ pub fn updateDeclExports(...@@ -143,7 +161,12 @@ pub fn updateDeclExports(
143pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {161pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {
144 // TODO: remove this assert when non-function Decls are implemented162 // TODO: remove this assert when non-function Decls are implemented
145 assert(decl.typed_value.most_recent.typed_value.ty.zigTypeTag() == .Fn);163 assert(decl.typed_value.most_recent.typed_value.ty.zigTypeTag() == .Fn);
146 _ = 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 }
147 decl.fn_link.wasm.?.functype.deinit(self.base.allocator);170 decl.fn_link.wasm.?.functype.deinit(self.base.allocator);
148 decl.fn_link.wasm.?.code.deinit(self.base.allocator);171 decl.fn_link.wasm.?.code.deinit(self.base.allocator);
149 decl.fn_link.wasm.?.idx_refs.deinit(self.base.allocator);172 decl.fn_link.wasm.?.idx_refs.deinit(self.base.allocator);
...@@ -172,15 +195,46 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -172,15 +195,46 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
172 // Type section195 // Type section
173 {196 {
174 const header_offset = try reserveVecSectionHeader(file);197 const header_offset = try reserveVecSectionHeader(file);
175 for (self.funcs.items) |decl| {198
176 try file.writeAll(decl.fn_link.wasm.?.functype.items);199 // extern functions are defined in the wasm binary first through the `import`
177 }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
178 try writeVecSectionHeader(204 try writeVecSectionHeader(
179 file,205 file,
180 header_offset,206 header_offset,
181 .type,207 .type,
182 @intCast(u32, (try file.getPos()) - header_offset - header_size),208 @intCast(u32, (try file.getPos()) - header_offset - header_size),
183 @intCast(u32, self.funcs.items.len),209 @intCast(u32, self.ext_funcs.items.len + self.funcs.items.len),
210 );
211 }
212
213 // Import section
214 {
215 // TODO: implement non-functions imports
216 const header_offset = try reserveVecSectionHeader(file);
217 const writer = file.writer();
218 for (self.ext_funcs.items) |decl, typeidx| {
219 try leb.writeULEB128(writer, @intCast(u32, self.host_name.len));
220 try writer.writeAll(self.host_name);
221
222 // wasm requires the length of the import name with no null-termination
223 const decl_len = mem.len(decl.name);
224 try leb.writeULEB128(writer, @intCast(u32, decl_len));
225 try writer.writeAll(decl.name[0..decl_len]);
226
227 // emit kind and the function type
228 try writer.writeByte(wasm.externalKind(.function));
229 try leb.writeULEB128(writer, @intCast(u32, typeidx));
230 }
231
232 try writeVecSectionHeader(
233 file,
234 header_offset,
235 .import,
236 @intCast(u32, (try file.getPos()) - header_offset - header_size),
237 @intCast(u32, self.ext_funcs.items.len),
184 );238 );
185 }239 }
186240
...@@ -188,7 +242,11 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -188,7 +242,11 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
188 {242 {
189 const header_offset = try reserveVecSectionHeader(file);243 const header_offset = try reserveVecSectionHeader(file);
190 const writer = file.writer();244 const writer = file.writer();
191 for (self.funcs.items) |_, typeidx| try leb.writeULEB128(writer, @intCast(u32, typeidx));245 for (self.funcs.items) |_, typeidx| {
246 const func_idx = @intCast(u32, self.getFuncIdxOffset() + typeidx);
247 try leb.writeULEB128(writer, func_idx);
248 }
249
192 try writeVecSectionHeader(250 try writeVecSectionHeader(
193 file,251 file,
194 header_offset,252 header_offset,
...@@ -212,7 +270,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -212,7 +270,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
212 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()) {
213 .Fn => {271 .Fn => {
214 // Type of the export272 // Type of the export
215 try writer.writeByte(0x00);273 try writer.writeByte(wasm.externalKind(.function));
216 // Exported function index274 // Exported function index
217 try leb.writeULEB128(writer, self.getFuncidx(exprt.exported_decl).?);275 try leb.writeULEB128(writer, self.getFuncidx(exprt.exported_decl).?);
218 },276 },
...@@ -523,13 +581,31 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {...@@ -523,13 +581,31 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {
523}581}
524582
525/// Get the current index of a given Decl in the function list583/// Get the current index of a given Decl in the function list
526/// 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
527fn getFuncidx(self: Wasm, decl: *Module.Decl) ?u32 {586fn getFuncidx(self: Wasm, decl: *Module.Decl) ?u32 {
528 return for (self.funcs.items) |func, idx| {587 var offset: u32 = 0;
529 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);
530 } else null;600 } else null;
531}601}
532602
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
533fn reserveVecSectionHeader(file: fs.File) !u64 {609fn reserveVecSectionHeader(file: fs.File) !u64 {
534 // section id + fixed leb contents size + fixed leb vector length610 // section id + fixed leb contents size + fixed leb vector length
535 const header_size = 1 + 5 + 5;611 const header_size = 1 + 5 + 5;