mirror of
https://github.com/ziglang/zig.git
synced 2026-01-20 14:25:16 +00:00
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:
parent
fe3aa4ccd0
commit
6242ae35f3
@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user