From 87a9c6946dd536de562bc86ac4819a33df3442df Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Wed, 19 May 2021 17:36:23 +0200 Subject: [PATCH] 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. --- src/codegen/wasm.zig | 33 +++++++++++++++++++++++---------- test/stage2/wasm.zig | 37 +++++++++++++++++++++++++++++++++++-- 2 files changed, 58 insertions(+), 12 deletions(-) diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index da451b62c2..7c3fecd496 100644 --- a/src/codegen/wasm.zig +++ b/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) }; } }; diff --git a/test/stage2/wasm.zig b/test/stage2/wasm.zig index 442f4d917d..2b99ec5cb7 100644 --- a/test/stage2/wasm.zig +++ b/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"); } }