Commit 87a9c6946d

Luuk de Gram <Luukdegram@users.noreply.github.com>
2021-05-19 17:36:23
wasm backend: implement `multi_value` for `WValue`
This allows us to differentiate between regular locals and variables that create multiple locals on the stack such as optionals and structs. Now `struct_a = struct_b;` works and only updates a reference, rather than update all local's values. Also created more test cases to test against this.
1 parent 6962647
Changed files (2)
src
codegen
test
stage2
src/codegen/wasm.zig
@@ -31,6 +31,9 @@ const WValue = union(enum) {
     code_offset: usize,
     /// The label of the block, used by breaks to find its relative distance
     block_idx: u32,
+    /// Used for variables that create multiple locals on the stack when allocated
+    /// such as structs and optionals.
+    multi_value: u32,
 };
 
 /// Wasm ops, but without input/output/signedness information
@@ -587,8 +590,9 @@ pub const Context = struct {
     fn emitWValue(self: *Context, val: WValue) InnerError!void {
         const writer = self.code.writer();
         switch (val) {
-            .block_idx => unreachable,
-            .none, .code_offset => {},
+            .block_idx => unreachable, // block_idx cannot be referenced
+            .multi_value => unreachable, // multi_value can never be written directly, and must be accessed individually
+            .none, .code_offset => {}, // no-op
             .local => |idx| {
                 try writer.writeByte(wasm.opcode(.local_get));
                 try leb.writeULEB128(writer, idx);
@@ -795,7 +799,7 @@ pub const Context = struct {
 
     fn genAlloc(self: *Context, inst: *Inst.NoOp) InnerError!WValue {
         const elem_type = inst.base.ty.elemType();
-        const local_value = WValue{ .local = self.local_index };
+        const initial_index = self.local_index;
 
         switch (elem_type.zigTypeTag()) {
             .Struct => {
@@ -810,16 +814,15 @@ pub const Context = struct {
                     self.locals.appendAssumeCapacity(val_type);
                     self.local_index += 1;
                 }
+                return WValue{ .multi_value = initial_index };
             },
-            // TODO: Add more types that require extra locals such as optionals
             else => {
                 const valtype = try self.genValtype(inst.base.src, elem_type);
                 try self.locals.append(self.gpa, valtype);
                 self.local_index += 1;
+                return WValue{ .local = initial_index };
             },
         }
-
-        return local_value;
     }
 
     fn genStore(self: *Context, inst: *Inst.BinOp) InnerError!WValue {
@@ -827,10 +830,20 @@ pub const Context = struct {
 
         const lhs = self.resolveInst(inst.lhs);
         const rhs = self.resolveInst(inst.rhs);
-        try self.emitWValue(rhs);
 
-        try writer.writeByte(wasm.opcode(.local_set));
-        try leb.writeULEB128(writer, lhs.local);
+        switch (lhs) {
+            // When assigning a value to a multi_value such as a struct,
+            // we simply assign the local_index to the rhs one.
+            // This allows us to update struct fields without having to individually
+            // set each local as each field's index will be calculated off the struct's base index
+            .multi_value => self.values.put(self.gpa, inst.lhs, rhs) catch unreachable, // Instruction does not dominate all uses!
+            .local => |local| {
+                try self.emitWValue(rhs);
+                try writer.writeByte(wasm.opcode(.local_set));
+                try leb.writeULEB128(writer, lhs.local);
+            },
+            else => unreachable,
+        }
         return .none;
     }
 
@@ -1115,6 +1128,6 @@ pub const Context = struct {
     fn genStructFieldPtr(self: *Context, inst: *Inst.StructFieldPtr) InnerError!WValue {
         const struct_ptr = self.resolveInst(inst.struct_ptr);
 
-        return WValue{ .local = struct_ptr.local + @intCast(u32, inst.field_index) };
+        return WValue{ .local = struct_ptr.multi_value + @intCast(u32, inst.field_index) };
     }
 };
test/stage2/wasm.zig
@@ -426,19 +426,52 @@ pub fn addCases(ctx: *TestContext) !void {
         case.addCompareOutput(
             \\const Example = struct { x: u32 };
             \\
-            \\export fn _start() u32 {
+            \\pub export fn _start() u32 {
             \\    var example: Example = .{ .x = 5 };
             \\    return example.x;
             \\}
         , "5\n");
 
+        case.addCompareOutput(
+            \\const Example = struct { x: u32 };
+            \\
+            \\pub export fn _start() u32 {
+            \\    var example: Example = .{ .x = 5 };
+            \\    example.x = 10;
+            \\    return example.x;
+            \\}
+        , "10\n");
+
         case.addCompareOutput(
             \\const Example = struct { x: u32, y: u32 };
             \\
-            \\export fn _start() u32 {
+            \\pub export fn _start() u32 {
             \\    var example: Example = .{ .x = 5, .y = 10 };
             \\    return example.y + example.x;
             \\}
         , "15\n");
+
+        case.addCompareOutput(
+            \\const Example = struct { x: u32, y: u32 };
+            \\
+            \\pub export fn _start() u32 {
+            \\    var example: Example = .{ .x = 5, .y = 10 };
+            \\    var example2: Example = .{ .x = 10, .y = 20 };
+            \\
+            \\    example = example2;
+            \\    return example.y + example.x;
+            \\}
+        , "30\n");
+
+        case.addCompareOutput(
+            \\const Example = struct { x: u32, y: u32 };
+            \\
+            \\pub export fn _start() u32 {
+            \\    var example: Example = .{ .x = 5, .y = 10 };
+            \\
+            \\    example = .{ .x = 10, .y = 20 };
+            \\    return example.y + example.x;
+            \\}
+        , "30\n");
     }
 }