Merge pull request #7960 from Luukdegram/wasm-extern

stage2: Add support for extern functions for the wasm backend
This commit is contained in:
Veikka Tuominen 2021-02-21 12:22:01 +02:00 committed by GitHub
commit ef6aa3d027
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 125 additions and 23 deletions

View File

@ -253,6 +253,20 @@ pub fn section(val: Section) u8 {
return @enumToInt(val);
}
/// The kind of the type when importing or exporting to/from the host environment
/// https://webassembly.github.io/spec/core/syntax/modules.html
pub const ExternalKind = enum(u8) {
function,
table,
memory,
global,
};
/// Returns the integer value of a given `ExternalKind`
pub fn externalKind(val: ExternalKind) u8 {
return @enumToInt(val);
}
// types
pub const element_type: u8 = 0x70;
pub const function_type: u8 = 0x60;

View File

@ -161,15 +161,19 @@ 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();
// 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
@ -189,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
@ -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);

View File

@ -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
@ -85,8 +100,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;
@ -94,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.?;
@ -143,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);
@ -172,15 +195,46 @@ 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),
);
}
// Import section
{
// TODO: implement non-functions imports
const header_offset = try reserveVecSectionHeader(file);
const writer = file.writer();
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 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.externalKind(.function));
try leb.writeULEB128(writer, @intCast(u32, typeidx));
}
try writeVecSectionHeader(
file,
header_offset,
.import,
@intCast(u32, (try file.getPos()) - header_offset - header_size),
@intCast(u32, self.ext_funcs.items.len),
);
}
@ -188,7 +242,11 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
{
const header_offset = try reserveVecSectionHeader(file);
const writer = file.writer();
for (self.funcs.items) |_, typeidx| try leb.writeULEB128(writer, @intCast(u32, typeidx));
for (self.funcs.items) |_, typeidx| {
const func_idx = @intCast(u32, self.getFuncIdxOffset() + typeidx);
try leb.writeULEB128(writer, func_idx);
}
try writeVecSectionHeader(
file,
header_offset,
@ -212,7 +270,7 @@ 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).?);
},
@ -523,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;