stage2/wasm: implement function calls

During codegen we do not yet know the indexes that will be used for
called functions. Therefore, we store the offset into the in-memory
code where the index is needed with a pointer to the Decl and use this
data to insert the proper indexes while writing the binary in the flush
function.
This commit is contained in:
Isaac Freund 2020-08-19 01:48:09 +02:00
parent fe3aa4ccd0
commit 6242ae35f3
No known key found for this signature in database
GPG Key ID: 86DED400DDFD7A11
3 changed files with 110 additions and 39 deletions

View File

@ -62,58 +62,80 @@ pub fn genCode(buf: *ArrayList(u8), decl: *Decl) !void {
// TODO: check for and handle death of instructions
const tv = decl.typed_value.most_recent.typed_value;
const mod_fn = tv.val.cast(Value.Payload.Function).?.func;
for (mod_fn.analysis.success.instructions) |inst| try genInst(writer, inst);
for (mod_fn.analysis.success.instructions) |inst| try genInst(buf, decl, inst);
// Write 'end' opcode
try writer.writeByte(0x0B);
// Fill in the size of the generated code to the reserved space at the
// beginning of the buffer.
leb.writeUnsignedFixed(5, buf.items[0..5], @intCast(u32, buf.items.len - 5));
const size = buf.items.len - 5 + decl.fn_link.wasm.?.idx_refs.items.len * 5;
leb.writeUnsignedFixed(5, buf.items[0..5], @intCast(u32, size));
}
fn genInst(writer: ArrayList(u8).Writer, inst: *Inst) !void {
fn genInst(buf: *ArrayList(u8), decl: *Decl, inst: *Inst) !void {
return switch (inst.tag) {
.call => genCall(buf, decl, inst.castTag(.call).?),
.constant => genConstant(buf, decl, inst.castTag(.constant).?),
.dbg_stmt => {},
.ret => genRet(writer, inst.castTag(.ret).?),
.ret => genRet(buf, decl, inst.castTag(.ret).?),
.retvoid => {},
else => error.TODOImplementMoreWasmCodegen,
};
}
fn genRet(writer: ArrayList(u8).Writer, inst: *Inst.UnOp) !void {
switch (inst.operand.tag) {
.constant => {
const constant = inst.operand.castTag(.constant).?;
switch (inst.operand.ty.tag()) {
.u32 => {
try writer.writeByte(0x41); // i32.const
try leb.writeILEB128(writer, constant.val.toUnsignedInt());
},
.i32 => {
try writer.writeByte(0x41); // i32.const
try leb.writeILEB128(writer, constant.val.toSignedInt());
},
.u64 => {
try writer.writeByte(0x42); // i64.const
try leb.writeILEB128(writer, constant.val.toUnsignedInt());
},
.i64 => {
try writer.writeByte(0x42); // i64.const
try leb.writeILEB128(writer, constant.val.toSignedInt());
},
.f32 => {
try writer.writeByte(0x43); // f32.const
// TODO: enforce LE byte order
try writer.writeAll(mem.asBytes(&constant.val.toFloat(f32)));
},
.f64 => {
try writer.writeByte(0x44); // f64.const
// TODO: enforce LE byte order
try writer.writeAll(mem.asBytes(&constant.val.toFloat(f64)));
},
else => return error.TODOImplementMoreWasmCodegen,
}
fn genConstant(buf: *ArrayList(u8), decl: *Decl, inst: *Inst.Constant) !void {
const writer = buf.writer();
switch (inst.base.ty.tag()) {
.u32 => {
try writer.writeByte(0x41); // i32.const
try leb.writeILEB128(writer, inst.val.toUnsignedInt());
},
.i32 => {
try writer.writeByte(0x41); // i32.const
try leb.writeILEB128(writer, inst.val.toSignedInt());
},
.u64 => {
try writer.writeByte(0x42); // i64.const
try leb.writeILEB128(writer, inst.val.toUnsignedInt());
},
.i64 => {
try writer.writeByte(0x42); // i64.const
try leb.writeILEB128(writer, inst.val.toSignedInt());
},
.f32 => {
try writer.writeByte(0x43); // f32.const
// TODO: enforce LE byte order
try writer.writeAll(mem.asBytes(&inst.val.toFloat(f32)));
},
.f64 => {
try writer.writeByte(0x44); // f64.const
// TODO: enforce LE byte order
try writer.writeAll(mem.asBytes(&inst.val.toFloat(f64)));
},
.void => {},
else => return error.TODOImplementMoreWasmCodegen,
}
}
fn genRet(buf: *ArrayList(u8), decl: *Decl, inst: *Inst.UnOp) !void {
try genInst(buf, decl, inst.operand);
}
fn genCall(buf: *ArrayList(u8), decl: *Decl, inst: *Inst.Call) !void {
const func_inst = inst.func.castTag(.constant).?;
const func_val = func_inst.val.cast(Value.Payload.Function).?;
const target = func_val.func.owner_decl;
const target_ty = target.typed_value.most_recent.typed_value.ty;
if (inst.args.len != 0) return error.TODOImplementMoreWasmCodegen;
try buf.append(0x10); // call
// The function index immediate argument will be filled in using this data
// in link.Wasm.flush().
try decl.fn_link.wasm.?.idx_refs.append(buf.allocator, .{
.offset = @intCast(u32, buf.items.len),
.decl = target,
});
}

View File

@ -36,6 +36,9 @@ pub const FnData = struct {
functype: std.ArrayListUnmanaged(u8) = .{},
/// Generated code for the body of the function
code: std.ArrayListUnmanaged(u8) = .{},
/// Locations in the generated code where function indexes must be filled in.
/// This must be kept ordered by offset.
idx_refs: std.ArrayListUnmanaged(struct { offset: u32, decl: *Module.Decl }) = .{},
};
base: link.File,
@ -74,6 +77,7 @@ pub fn deinit(self: *Wasm) void {
for (self.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);
}
@ -87,6 +91,7 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
if (decl.fn_link.wasm) |*fn_data| {
fn_data.functype.items.len = 0;
fn_data.code.items.len = 0;
fn_data.idx_refs.items.len = 0;
} else {
decl.fn_link.wasm = .{};
try self.funcs.append(self.base.allocator, decl);
@ -114,6 +119,7 @@ pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {
_ = self.funcs.swapRemove(self.getFuncidx(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);
decl.fn_link.wasm = null;
}
@ -190,7 +196,25 @@ pub fn flush(self: *Wasm, module: *Module) !void {
// Code section
{
const header_offset = try reserveVecSectionHeader(file);
for (self.funcs.items) |decl| try file.writeAll(decl.fn_link.wasm.?.code.items);
const writer = file.writer();
for (self.funcs.items) |decl| {
const fn_data = &decl.fn_link.wasm.?;
// Write the already generated code to the file, inserting
// function indexes where required.
var current: u32 = 0;
for (fn_data.idx_refs.items) |idx_ref| {
try writer.writeAll(fn_data.code.items[current..idx_ref.offset]);
current = idx_ref.offset;
// Use a fixed width here to make calculating the code size
// in codegen.wasm.genCode() simpler.
var buf: [5]u8 = undefined;
leb.writeUnsignedFixed(5, &buf, self.getFuncidx(idx_ref.decl).?);
try writer.writeAll(&buf);
}
try writer.writeAll(fn_data.code.items[current..]);
}
try writeVecSectionHeader(
file,
header_offset,

View File

@ -546,28 +546,53 @@ pub fn addCases(ctx: *TestContext) !void {
}
{
var case = ctx.exe("wasm returns", wasi);
var case = ctx.exe("wasm function calls", wasi);
case.addCompareOutput(
\\export fn _start() u32 {
\\ foo();
\\ bar();
\\ return 42;
\\}
\\fn foo() void {
\\ bar();
\\ bar();
\\}
\\fn bar() void {}
,
"42\n",
);
case.addCompareOutput(
\\export fn _start() i64 {
\\ bar();
\\ foo();
\\ foo();
\\ bar();
\\ foo();
\\ bar();
\\ return 42;
\\}
\\fn foo() void {
\\ bar();
\\}
\\fn bar() void {}
,
"42\n",
);
case.addCompareOutput(
\\export fn _start() f32 {
\\ bar();
\\ foo();
\\ return 42.0;
\\}
\\fn foo() void {
\\ bar();
\\ bar();
\\ bar();
\\}
\\fn bar() void {}
,
// This is what you get when you take the bits of the IEE-754
// representation of 42.0 and reinterpret them as an unsigned