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) {...@@ -30,7 +30,13 @@ const WValue = union(enum) {
30 code_offset: usize,30 code_offset: usize,
31 /// Used for variables that create multiple locals on the stack when allocated31 /// Used for variables that create multiple locals on the stack when allocated
32 /// such as structs and optionals.32 /// 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 },
34};40};
3541
36/// Wasm ops, but without input/output/signedness information42/// Wasm ops, but without input/output/signedness information
...@@ -572,9 +578,9 @@ pub const Context = struct {...@@ -572,9 +578,9 @@ pub const Context = struct {
572 },578 },
573 .Bool,579 .Bool,
574 .Pointer,580 .Pointer,
575 .Struct,
576 .ErrorSet,581 .ErrorSet,
577 => wasm.Valtype.i32,582 => wasm.Valtype.i32,
583 .Struct, .ErrorUnion => unreachable, // Multi typed, must be handled individually by genAlloc().
578 else => self.fail(src, "TODO - Wasm valtype for type '{s}'", .{ty.zigTypeTag()}),584 else => self.fail(src, "TODO - Wasm valtype for type '{s}'", .{ty.zigTypeTag()}),
579 };585 };
580 }586 }
...@@ -744,6 +750,7 @@ pub const Context = struct {...@@ -744,6 +750,7 @@ pub const Context = struct {
744 .constant => unreachable,750 .constant => unreachable,
745 .dbg_stmt => WValue.none,751 .dbg_stmt => WValue.none,
746 .div => self.genBinOp(inst.castTag(.div).?, .div),752 .div => self.genBinOp(inst.castTag(.div).?, .div),
753 .is_err => self.genIsErr(inst.castTag(.is_err).?),
747 .load => self.genLoad(inst.castTag(.load).?),754 .load => self.genLoad(inst.castTag(.load).?),
748 .loop => self.genLoop(inst.castTag(.loop).?),755 .loop => self.genLoop(inst.castTag(.loop).?),
749 .mul => self.genBinOp(inst.castTag(.mul).?, .mul),756 .mul => self.genBinOp(inst.castTag(.mul).?, .mul),
...@@ -755,6 +762,7 @@ pub const Context = struct {...@@ -755,6 +762,7 @@ pub const Context = struct {
755 .sub => self.genBinOp(inst.castTag(.sub).?, .sub),762 .sub => self.genBinOp(inst.castTag(.sub).?, .sub),
756 .switchbr => self.genSwitchBr(inst.castTag(.switchbr).?),763 .switchbr => self.genSwitchBr(inst.castTag(.switchbr).?),
757 .unreach => self.genUnreachable(inst.castTag(.unreach).?),764 .unreach => self.genUnreachable(inst.castTag(.unreach).?),
765 .unwrap_errunion_payload => self.genUnwrapErrUnionPayload(inst.castTag(.unwrap_errunion_payload).?),
758 .xor => self.genBinOp(inst.castTag(.xor).?, .xor),766 .xor => self.genBinOp(inst.castTag(.xor).?, .xor),
759 else => self.fail(.{ .node_offset = 0 }, "TODO: Implement wasm inst: {s}", .{inst.tag}),767 else => self.fail(.{ .node_offset = 0 }, "TODO: Implement wasm inst: {s}", .{inst.tag}),
760 };768 };
...@@ -822,7 +830,27 @@ pub const Context = struct {...@@ -822,7 +830,27 @@ pub const Context = struct {
822 self.locals.appendAssumeCapacity(val_type);830 self.locals.appendAssumeCapacity(val_type);
823 self.local_index += 1;831 self.local_index += 1;
824 }832 }
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 } };
826 },854 },
827 else => {855 else => {
828 const valtype = try self.genValtype(inst.base.src, elem_type);856 const valtype = try self.genValtype(inst.base.src, elem_type);
...@@ -839,12 +867,43 @@ pub const Context = struct {...@@ -839,12 +867,43 @@ pub const Context = struct {
839 const lhs = self.resolveInst(inst.lhs);867 const lhs = self.resolveInst(inst.lhs);
840 const rhs = self.resolveInst(inst.rhs);868 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
842 switch (lhs) {885 switch (lhs) {
843 // When assigning a value to a multi_value such as a struct,886 .multi_value => |multi_value| switch (rhs) {
844 // we simply assign the local_index to the rhs one.887 // When assigning a value to a multi_value such as a struct,
845 // This allows us to update struct fields without having to individually888 // we simply assign the local_index to the rhs one.
846 // set each local as each field's index will be calculated off the struct's base index889 // This allows us to update struct fields without having to individually
847 .multi_value => self.values.put(self.gpa, inst.lhs, rhs) catch unreachable, // Instruction does not dominate all uses!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 },
848 .local => |local| {907 .local => |local| {
849 try self.emitWValue(rhs);908 try self.emitWValue(rhs);
850 try writer.writeByte(wasm.opcode(.local_set));909 try writer.writeByte(wasm.opcode(.local_set));
...@@ -972,6 +1031,23 @@ pub const Context = struct {...@@ -972,6 +1031,23 @@ pub const Context = struct {
972 try writer.writeByte(wasm.opcode(.i32_const));1031 try writer.writeByte(wasm.opcode(.i32_const));
973 try leb.writeULEB128(writer, error_index);1032 try leb.writeULEB128(writer, error_index);
974 },1033 },
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 },
975 else => |zig_type| return self.fail(src, "Wasm TODO: emitConstant for zigTypeTag {s}", .{zig_type}),1051 else => |zig_type| return self.fail(src, "Wasm TODO: emitConstant for zigTypeTag {s}", .{zig_type}),
976 }1052 }
977 }1053 }
...@@ -1144,7 +1220,7 @@ pub const Context = struct {...@@ -1144,7 +1220,7 @@ pub const Context = struct {
1144 fn genStructFieldPtr(self: *Context, inst: *Inst.StructFieldPtr) InnerError!WValue {1220 fn genStructFieldPtr(self: *Context, inst: *Inst.StructFieldPtr) InnerError!WValue {
1145 const struct_ptr = self.resolveInst(inst.struct_ptr);1221 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) };
1148 }1224 }
11491225
1150 fn genSwitchBr(self: *Context, inst: *Inst.SwitchBr) InnerError!WValue {1226 fn genSwitchBr(self: *Context, inst: *Inst.SwitchBr) InnerError!WValue {
...@@ -1187,4 +1263,28 @@ pub const Context = struct {...@@ -1187,4 +1263,28 @@ pub const Context = struct {
11871263
1188 return .none;1264 return .none;
1189 }1265 }
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 }
1190};1290};