Commit 6242ae35f3

Isaac Freund <ifreund@ifreund.xyz>
2020-08-19 01:48:09
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.
1 parent fe3aa4c
Changed files (3)
src-self-hosted
codegen
link
test
src-self-hosted/codegen/wasm.zig
@@ -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,
+    });
+}
src-self-hosted/link/Wasm.zig
@@ -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,
test/stage2/compare_output.zig
@@ -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