authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-05-19 17:36:23+02:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-05-20 09:25:02+02:00
log87a9c6946dd536de562bc86ac4819a33df3442df
tree7a6cd4ba08b8246625f65e39675e9f7b1a2d2c1d
parent69626478626aee18953b2fbfa42d509c7dfff1e4
signaturelock-open Commit is signed but in an unrecognized format.

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.

2 files changed, 58 insertions(+), 12 deletions(-)

src/codegen/wasm.zig+23-10
......@@ -31,6 +31,9 @@ const WValue = union(enum) {
3131 code_offset: usize,
3232 /// The label of the block, used by breaks to find its relative distance
3333 block_idx: u32,
34 /// Used for variables that create multiple locals on the stack when allocated
35 /// such as structs and optionals.
36 multi_value: u32,
3437};
3538
3639/// Wasm ops, but without input/output/signedness information
......@@ -587,8 +590,9 @@ pub const Context = struct {
587590 fn emitWValue(self: *Context, val: WValue) InnerError!void {
588591 const writer = self.code.writer();
589592 switch (val) {
590 .block_idx => unreachable,
591 .none, .code_offset => {},
593 .block_idx => unreachable, // block_idx cannot be referenced
594 .multi_value => unreachable, // multi_value can never be written directly, and must be accessed individually
595 .none, .code_offset => {}, // no-op
592596 .local => |idx| {
593597 try writer.writeByte(wasm.opcode(.local_get));
594598 try leb.writeULEB128(writer, idx);
......@@ -795,7 +799,7 @@ pub const Context = struct {
795799
796800 fn genAlloc(self: *Context, inst: *Inst.NoOp) InnerError!WValue {
797801 const elem_type = inst.base.ty.elemType();
798 const local_value = WValue{ .local = self.local_index };
802 const initial_index = self.local_index;
799803
800804 switch (elem_type.zigTypeTag()) {
801805 .Struct => {
......@@ -810,16 +814,15 @@ pub const Context = struct {
810814 self.locals.appendAssumeCapacity(val_type);
811815 self.local_index += 1;
812816 }
817 return WValue{ .multi_value = initial_index };
813818 },
814 // TODO: Add more types that require extra locals such as optionals
815819 else => {
816820 const valtype = try self.genValtype(inst.base.src, elem_type);
817821 try self.locals.append(self.gpa, valtype);
818822 self.local_index += 1;
823 return WValue{ .local = initial_index };
819824 },
820825 }
821
822 return local_value;
823826 }
824827
825828 fn genStore(self: *Context, inst: *Inst.BinOp) InnerError!WValue {
......@@ -827,10 +830,20 @@ pub const Context = struct {
827830
828831 const lhs = self.resolveInst(inst.lhs);
829832 const rhs = self.resolveInst(inst.rhs);
830 try self.emitWValue(rhs);
831833
832 try writer.writeByte(wasm.opcode(.local_set));
833 try leb.writeULEB128(writer, lhs.local);
834 switch (lhs) {
835 // When assigning a value to a multi_value such as a struct,
836 // we simply assign the local_index to the rhs one.
837 // This allows us to update struct fields without having to individually
838 // set each local as each field's index will be calculated off the struct's base index
839 .multi_value => self.values.put(self.gpa, inst.lhs, rhs) catch unreachable, // Instruction does not dominate all uses!
840 .local => |local| {
841 try self.emitWValue(rhs);
842 try writer.writeByte(wasm.opcode(.local_set));
843 try leb.writeULEB128(writer, lhs.local);
844 },
845 else => unreachable,
846 }
834847 return .none;
835848 }
836849
......@@ -1115,6 +1128,6 @@ pub const Context = struct {
11151128 fn genStructFieldPtr(self: *Context, inst: *Inst.StructFieldPtr) InnerError!WValue {
11161129 const struct_ptr = self.resolveInst(inst.struct_ptr);
11171130
1118 return WValue{ .local = struct_ptr.local + @intCast(u32, inst.field_index) };
1131 return WValue{ .local = struct_ptr.multi_value + @intCast(u32, inst.field_index) };
11191132 }
11201133};
test/stage2/wasm.zig+35-2
......@@ -426,19 +426,52 @@ pub fn addCases(ctx: *TestContext) !void {
426426 case.addCompareOutput(
427427 \\const Example = struct { x: u32 };
428428 \\
429 \\export fn _start() u32 {
429 \\pub export fn _start() u32 {
430430 \\ var example: Example = .{ .x = 5 };
431431 \\ return example.x;
432432 \\}
433433 , "5\n");
434434
435 case.addCompareOutput(
436 \\const Example = struct { x: u32 };
437 \\
438 \\pub export fn _start() u32 {
439 \\ var example: Example = .{ .x = 5 };
440 \\ example.x = 10;
441 \\ return example.x;
442 \\}
443 , "10\n");
444
435445 case.addCompareOutput(
436446 \\const Example = struct { x: u32, y: u32 };
437447 \\
438 \\export fn _start() u32 {
448 \\pub export fn _start() u32 {
439449 \\ var example: Example = .{ .x = 5, .y = 10 };
440450 \\ return example.y + example.x;
441451 \\}
442452 , "15\n");
453
454 case.addCompareOutput(
455 \\const Example = struct { x: u32, y: u32 };
456 \\
457 \\pub export fn _start() u32 {
458 \\ var example: Example = .{ .x = 5, .y = 10 };
459 \\ var example2: Example = .{ .x = 10, .y = 20 };
460 \\
461 \\ example = example2;
462 \\ return example.y + example.x;
463 \\}
464 , "30\n");
465
466 case.addCompareOutput(
467 \\const Example = struct { x: u32, y: u32 };
468 \\
469 \\pub export fn _start() u32 {
470 \\ var example: Example = .{ .x = 5, .y = 10 };
471 \\
472 \\ example = .{ .x = 10, .y = 20 };
473 \\ return example.y + example.x;
474 \\}
475 , "30\n");
443476 }
444477}