authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-05-22 17:34:19+02:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-05-28 12:58:17+02:00
log54810592326e3cd2cad02318aa07dd1d5da2e731
tree98816eb370d69549e3e6b838a64ea3b62b3a78a7
parenta5b5a5532e6500f7458ed5408c0b511a6f306b93
signaturelock-open Commit is signed but in an unrecognized format.

wasm: Implement error unions and unwrapping

- Slightly refactored `Wvalue.multi_value` to also contain the amount of locals it contains, this allows us to set all fields at once.

1 files changed, 109 insertions(+), 9 deletions(-)

src/codegen/wasm.zig+109-9
......@@ -30,7 +30,13 @@ const WValue = union(enum) {
3030 code_offset: usize,
3131 /// Used for variables that create multiple locals on the stack when allocated
3232 /// such as structs and optionals.
33 multi_value: u32,
33 multi_value: struct {
34 /// The index of the first local variable
35 index: u32,
36 /// The count of local variables this `WValue` consists of.
37 /// i.e. an ErrorUnion has a 'count' of 2.
38 count: u32,
39 },
3440};
3541
3642/// Wasm ops, but without input/output/signedness information
......@@ -572,9 +578,9 @@ pub const Context = struct {
572578 },
573579 .Bool,
574580 .Pointer,
575 .Struct,
576581 .ErrorSet,
577582 => wasm.Valtype.i32,
583 .Struct, .ErrorUnion => unreachable, // Multi typed, must be handled individually by genAlloc().
578584 else => self.fail(src, "TODO - Wasm valtype for type '{s}'", .{ty.zigTypeTag()}),
579585 };
580586 }
......@@ -744,6 +750,7 @@ pub const Context = struct {
744750 .constant => unreachable,
745751 .dbg_stmt => WValue.none,
746752 .div => self.genBinOp(inst.castTag(.div).?, .div),
753 .is_err => self.genIsErr(inst.castTag(.is_err).?),
747754 .load => self.genLoad(inst.castTag(.load).?),
748755 .loop => self.genLoop(inst.castTag(.loop).?),
749756 .mul => self.genBinOp(inst.castTag(.mul).?, .mul),
......@@ -755,6 +762,7 @@ pub const Context = struct {
755762 .sub => self.genBinOp(inst.castTag(.sub).?, .sub),
756763 .switchbr => self.genSwitchBr(inst.castTag(.switchbr).?),
757764 .unreach => self.genUnreachable(inst.castTag(.unreach).?),
765 .unwrap_errunion_payload => self.genUnwrapErrUnionPayload(inst.castTag(.unwrap_errunion_payload).?),
758766 .xor => self.genBinOp(inst.castTag(.xor).?, .xor),
759767 else => self.fail(.{ .node_offset = 0 }, "TODO: Implement wasm inst: {s}", .{inst.tag}),
760768 };
......@@ -822,7 +830,27 @@ pub const Context = struct {
822830 self.locals.appendAssumeCapacity(val_type);
823831 self.local_index += 1;
824832 }
825 return WValue{ .multi_value = initial_index };
833 return WValue{ .multi_value = .{
834 .index = initial_index,
835 .count = @intCast(u32, struct_data.fields.count()),
836 } };
837 },
838 .ErrorUnion => {
839 // generate a local for both the error and the payload.
840 const payload_type = elem_type.errorUnionChild();
841
842 // we emit the payload value as the first local, and the error as the second
843 // The first local is also used to find the index of the error.
844 try self.locals.ensureCapacity(self.gpa, self.locals.items.len + 2);
845 const val_type = try self.genValtype(.{ .node_offset = 0 }, payload_type);
846 self.locals.appendAssumeCapacity(val_type);
847 self.locals.appendAssumeCapacity(wasm.valtype(.i32)); // error values are always i32
848 self.local_index += 2;
849
850 return WValue{ .multi_value = .{
851 .index = initial_index,
852 .count = 2,
853 } };
826854 },
827855 else => {
828856 const valtype = try self.genValtype(inst.base.src, elem_type);
......@@ -839,12 +867,43 @@ pub const Context = struct {
839867 const lhs = self.resolveInst(inst.lhs);
840868 const rhs = self.resolveInst(inst.rhs);
841869
870 // switch (lhs) {
871 // // When assigning a value to a multi_value such as a struct,
872 // // we simply assign the local_index to the rhs one.
873 // // This allows us to update struct fields without having to individually
874 // // set each local as each field's index will be calculated off the struct's base index
875 // .multi_value => self.values.put(self.gpa, inst.lhs, rhs) catch unreachable, // Instruction does not dominate all uses!
876 // .local => |local| {
877 // try self.emitWValue(rhs);
878 // try writer.writeByte(wasm.opcode(.local_set));
879 // try leb.writeULEB128(writer, lhs.local);
880 // },
881 // else => unreachable,
882 // }
883 // return .none;
884
842885 switch (lhs) {
843 // When assigning a value to a multi_value such as a struct,
844 // we simply assign the local_index to the rhs one.
845 // This allows us to update struct fields without having to individually
846 // set each local as each field's index will be calculated off the struct's base index
847 .multi_value => self.values.put(self.gpa, inst.lhs, rhs) catch unreachable, // Instruction does not dominate all uses!
886 .multi_value => |multi_value| switch (rhs) {
887 // When assigning a value to a multi_value such as a struct,
888 // we simply assign the local_index to the rhs one.
889 // This allows us to update struct fields without having to individually
890 // set each local as each field's index will be calculated off the struct's base index
891 .multi_value => self.values.put(self.gpa, inst.lhs, rhs) catch unreachable, // Instruction does not dominate all uses!
892 .constant => {
893 // emit all values onto the stack
894 try self.emitWValue(rhs);
895
896 // for each local, pop the stack value into the local
897 // As the last element is on top of the stack, we must populate the locals
898 // in reverse.
899 var i: u32 = multi_value.count;
900 while (i > 0) : (i -= 1) {
901 try writer.writeByte(wasm.opcode(.local_set));
902 try leb.writeULEB128(writer, multi_value.index + i - 1);
903 }
904 },
905 else => unreachable,
906 },
848907 .local => |local| {
849908 try self.emitWValue(rhs);
850909 try writer.writeByte(wasm.opcode(.local_set));
......@@ -972,6 +1031,23 @@ pub const Context = struct {
9721031 try writer.writeByte(wasm.opcode(.i32_const));
9731032 try leb.writeULEB128(writer, error_index);
9741033 },
1034 .ErrorUnion => {
1035 const data = value.castTag(.error_union).?.data;
1036 const error_type = ty.errorUnionSet();
1037 const payload_type = ty.errorUnionChild();
1038 if (value.getError()) |_| {
1039 // no payload, so write a '0' const
1040 try writer.writeByte(wasm.opcode(.i32_const));
1041 try leb.writeULEB128(writer, @as(u32, 0));
1042 try self.emitConstant(src, data, error_type);
1043 } else {
1044 // payload first
1045 try self.emitConstant(src, data, payload_type);
1046 // no error, so write a '0' const
1047 try writer.writeByte(wasm.opcode(.i32_const));
1048 try leb.writeULEB128(writer, @as(u32, 0));
1049 }
1050 },
9751051 else => |zig_type| return self.fail(src, "Wasm TODO: emitConstant for zigTypeTag {s}", .{zig_type}),
9761052 }
9771053 }
......@@ -1144,7 +1220,7 @@ pub const Context = struct {
11441220 fn genStructFieldPtr(self: *Context, inst: *Inst.StructFieldPtr) InnerError!WValue {
11451221 const struct_ptr = self.resolveInst(inst.struct_ptr);
11461222
1147 return WValue{ .local = struct_ptr.multi_value + @intCast(u32, inst.field_index) };
1223 return WValue{ .local = struct_ptr.multi_value.index + @intCast(u32, inst.field_index) };
11481224 }
11491225
11501226 fn genSwitchBr(self: *Context, inst: *Inst.SwitchBr) InnerError!WValue {
......@@ -1187,4 +1263,28 @@ pub const Context = struct {
11871263
11881264 return .none;
11891265 }
1266
1267 fn genIsErr(self: *Context, inst: *Inst.UnOp) InnerError!WValue {
1268 const operand = self.resolveInst(inst.operand);
1269 const offset = self.code.items.len;
1270 const writer = self.code.writer();
1271
1272 // load the error value which is the payload's multi_value index + 1
1273 try self.emitWValue(.{ .local = operand.multi_value.index + 1 });
1274 // Compare the error value with '0'
1275 try writer.writeByte(wasm.opcode(.i32_const));
1276 try leb.writeILEB128(writer, @as(i32, 0));
1277
1278 // we want to break out of the condition if they're *not* equal,
1279 // because that means there's an error.
1280 try writer.writeByte(wasm.opcode(.i32_ne));
1281
1282 return WValue{ .code_offset = offset };
1283 }
1284
1285 fn genUnwrapErrUnionPayload(self: *Context, inst: *Inst.UnOp) InnerError!WValue {
1286 const operand = self.resolveInst(inst.operand);
1287 // payload's local index is that of its multi_value index, so convert it to a `WValue.local`
1288 return WValue{ .local = operand.multi_value.index };
1289 }
11901290};