authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-05-23 16:43:44+02:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-05-28 12:58:17+02:00
log967a299c346937316158f58f3d3ae1be7ee0f551
tree71fddc09063d6802f57369df074f7e3409c37068
parent54810592326e3cd2cad02318aa07dd1d5da2e731
signaturelock-open Commit is signed but in an unrecognized format.

wasm: Add support for error union as return type

- This currently uses the multi-value feature to return both the possible error, and its payload. - Also genAlloc and the logic to allocate the locals itself have been seperated, so we can create more locals whenever needed, and not only when `genAlloc` is called.

1 files changed, 79 insertions(+), 66 deletions(-)

src/codegen/wasm.zig+79-66
...@@ -580,7 +580,7 @@ pub const Context = struct {...@@ -580,7 +580,7 @@ pub const Context = struct {
580 .Pointer,580 .Pointer,
581 .ErrorSet,581 .ErrorSet,
582 => wasm.Valtype.i32,582 => wasm.Valtype.i32,
583 .Struct, .ErrorUnion => unreachable, // Multi typed, must be handled individually by genAlloc().583 .Struct, .ErrorUnion => unreachable, // Multi typed, must be handled individually.
584 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()}),
585 };585 };
586 }586 }
...@@ -614,6 +614,55 @@ pub const Context = struct {...@@ -614,6 +614,55 @@ pub const Context = struct {
614 }614 }
615 }615 }
616616
617 /// Creates one or multiple locals for a given `Type`.
618 /// Returns a corresponding `Wvalue` that can either be of tag
619 /// local or multi_value
620 fn allocLocal(self: *Context, ty: Type) InnerError!WValue {
621 const initial_index = self.local_index;
622 switch (ty.zigTypeTag()) {
623 .Struct => {
624 // for each struct field, generate a local
625 const struct_data: *Module.Struct = ty.castTag(.@"struct").?.data;
626 try self.locals.ensureCapacity(self.gpa, self.locals.items.len + struct_data.fields.count());
627 for (struct_data.fields.items()) |entry| {
628 const val_type = try self.genValtype(
629 .{ .node_offset = struct_data.node_offset },
630 entry.value.ty,
631 );
632 self.locals.appendAssumeCapacity(val_type);
633 self.local_index += 1;
634 }
635 return WValue{ .multi_value = .{
636 .index = initial_index,
637 .count = @intCast(u32, struct_data.fields.count()),
638 } };
639 },
640 .ErrorUnion => {
641 // generate a local for both the error and the payload.
642 const payload_type = ty.errorUnionChild();
643
644 // we emit the payload value as the first local, and the error as the second
645 // The first local is also used to find the index of the error.
646 try self.locals.ensureCapacity(self.gpa, self.locals.items.len + 2);
647 const val_type = try self.genValtype(.{ .node_offset = 0 }, payload_type);
648 self.locals.appendAssumeCapacity(val_type);
649 self.locals.appendAssumeCapacity(wasm.valtype(.i32)); // error values are always i32
650 self.local_index += 2;
651
652 return WValue{ .multi_value = .{
653 .index = initial_index,
654 .count = 2,
655 } };
656 },
657 else => {
658 const valtype = try self.genValtype(.{ .node_offset = 0 }, ty);
659 try self.locals.append(self.gpa, valtype);
660 self.local_index += 1;
661 return WValue{ .local = initial_index };
662 },
663 }
664 }
665
617 fn genFunctype(self: *Context) InnerError!void {666 fn genFunctype(self: *Context) InnerError!void {
618 assert(self.decl.has_tv);667 assert(self.decl.has_tv);
619 const ty = self.decl.ty;668 const ty = self.decl.ty;
...@@ -636,8 +685,19 @@ pub const Context = struct {...@@ -636,8 +685,19 @@ pub const Context = struct {
636685
637 // return type686 // return type
638 const return_type = ty.fnReturnType();687 const return_type = ty.fnReturnType();
639 switch (return_type.tag()) {688 switch (return_type.zigTypeTag()) {
640 .void, .noreturn => try leb.writeULEB128(writer, @as(u32, 0)),689 .Void, .NoReturn => try leb.writeULEB128(writer, @as(u32, 0)),
690 .Struct => return self.fail(.{ .node_offset = 0 }, "TODO: Implement struct as return type for wasm", .{}),
691 .Optional => return self.fail(.{ .node_offset = 0 }, "TODO: Implement optionals as return type for wasm", .{}),
692 .ErrorUnion => {
693 try leb.writeULEB128(writer, @as(u32, 2));
694 const val_type = try self.genValtype(
695 .{ .node_offset = 0 },
696 return_type.errorUnionChild(),
697 );
698 try writer.writeByte(val_type);
699 try writer.writeByte(wasm.valtype(.i32)); // error code is always an i32 integer.
700 },
641 else => |ret_type| {701 else => |ret_type| {
642 try leb.writeULEB128(writer, @as(u32, 1));702 try leb.writeULEB128(writer, @as(u32, 1));
643 // Can we maybe get the source index of the return type?703 // Can we maybe get the source index of the return type?
...@@ -763,6 +823,7 @@ pub const Context = struct {...@@ -763,6 +823,7 @@ pub const Context = struct {
763 .switchbr => self.genSwitchBr(inst.castTag(.switchbr).?),823 .switchbr => self.genSwitchBr(inst.castTag(.switchbr).?),
764 .unreach => self.genUnreachable(inst.castTag(.unreach).?),824 .unreach => self.genUnreachable(inst.castTag(.unreach).?),
765 .unwrap_errunion_payload => self.genUnwrapErrUnionPayload(inst.castTag(.unwrap_errunion_payload).?),825 .unwrap_errunion_payload => self.genUnwrapErrUnionPayload(inst.castTag(.unwrap_errunion_payload).?),
826 .wrap_errunion_payload => self.genWrapErrUnionPayload(inst.castTag(.wrap_errunion_payload).?),
766 .xor => self.genBinOp(inst.castTag(.xor).?, .xor),827 .xor => self.genBinOp(inst.castTag(.xor).?, .xor),
767 else => self.fail(.{ .node_offset = 0 }, "TODO: Implement wasm inst: {s}", .{inst.tag}),828 else => self.fail(.{ .node_offset = 0 }, "TODO: Implement wasm inst: {s}", .{inst.tag}),
768 };829 };
...@@ -787,7 +848,7 @@ pub const Context = struct {...@@ -787,7 +848,7 @@ pub const Context = struct {
787 const func_inst = inst.func.castTag(.constant).?;848 const func_inst = inst.func.castTag(.constant).?;
788 const func_val = inst.func.value().?;849 const func_val = inst.func.value().?;
789850
790 const target = blk: {851 const target: *Decl = blk: {
791 if (func_val.castTag(.function)) |func| {852 if (func_val.castTag(.function)) |func| {
792 break :blk func.data.owner_decl;853 break :blk func.data.owner_decl;
793 } else if (func_val.castTag(.extern_fn)) |ext_fn| {854 } else if (func_val.castTag(.extern_fn)) |ext_fn| {
...@@ -815,50 +876,7 @@ pub const Context = struct {...@@ -815,50 +876,7 @@ pub const Context = struct {
815876
816 fn genAlloc(self: *Context, inst: *Inst.NoOp) InnerError!WValue {877 fn genAlloc(self: *Context, inst: *Inst.NoOp) InnerError!WValue {
817 const elem_type = inst.base.ty.elemType();878 const elem_type = inst.base.ty.elemType();
818 const initial_index = self.local_index;879 return self.allocLocal(elem_type);
819
820 switch (elem_type.zigTypeTag()) {
821 .Struct => {
822 // for each struct field, generate a local
823 const struct_data: *Module.Struct = elem_type.castTag(.@"struct").?.data;
824 try self.locals.ensureCapacity(self.gpa, self.locals.items.len + struct_data.fields.count());
825 for (struct_data.fields.items()) |entry| {
826 const val_type = try self.genValtype(
827 .{ .node_offset = struct_data.node_offset },
828 entry.value.ty,
829 );
830 self.locals.appendAssumeCapacity(val_type);
831 self.local_index += 1;
832 }
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 } };
854 },
855 else => {
856 const valtype = try self.genValtype(inst.base.src, elem_type);
857 try self.locals.append(self.gpa, valtype);
858 self.local_index += 1;
859 return WValue{ .local = initial_index };
860 },
861 }
862 }880 }
863881
864 fn genStore(self: *Context, inst: *Inst.BinOp) InnerError!WValue {882 fn genStore(self: *Context, inst: *Inst.BinOp) InnerError!WValue {
...@@ -867,21 +885,6 @@ pub const Context = struct {...@@ -867,21 +885,6 @@ pub const Context = struct {
867 const lhs = self.resolveInst(inst.lhs);885 const lhs = self.resolveInst(inst.lhs);
868 const rhs = self.resolveInst(inst.rhs);886 const rhs = self.resolveInst(inst.rhs);
869887
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
885 switch (lhs) {888 switch (lhs) {
886 .multi_value => |multi_value| switch (rhs) {889 .multi_value => |multi_value| switch (rhs) {
887 // When assigning a value to a multi_value such as a struct,890 // When assigning a value to a multi_value such as a struct,
...@@ -889,8 +892,8 @@ pub const Context = struct {...@@ -889,8 +892,8 @@ pub const Context = struct {
889 // This allows us to update struct fields without having to individually892 // 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 index893 // 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!894 .multi_value => self.values.put(self.gpa, inst.lhs, rhs) catch unreachable, // Instruction does not dominate all uses!
892 .constant => {895 .constant, .none => {
893 // emit all values onto the stack896 // emit all values onto the stack if constant
894 try self.emitWValue(rhs);897 try self.emitWValue(rhs);
895898
896 // for each local, pop the stack value into the local899 // for each local, pop the stack value into the local
...@@ -1037,8 +1040,14 @@ pub const Context = struct {...@@ -1037,8 +1040,14 @@ pub const Context = struct {
1037 const payload_type = ty.errorUnionChild();1040 const payload_type = ty.errorUnionChild();
1038 if (value.getError()) |_| {1041 if (value.getError()) |_| {
1039 // no payload, so write a '0' const1042 // no payload, so write a '0' const
1040 try writer.writeByte(wasm.opcode(.i32_const));1043 const opcode: wasm.Opcode = buildOpcode(.{
1044 .op = .@"const",
1045 .valtype1 = try self.typeToValtype(src, payload_type),
1046 });
1047 try writer.writeByte(wasm.opcode(opcode));
1041 try leb.writeULEB128(writer, @as(u32, 0));1048 try leb.writeULEB128(writer, @as(u32, 0));
1049
1050 // write the error value
1042 try self.emitConstant(src, data, error_type);1051 try self.emitConstant(src, data, error_type);
1043 } else {1052 } else {
1044 // payload first1053 // payload first
...@@ -1287,4 +1296,8 @@ pub const Context = struct {...@@ -1287,4 +1296,8 @@ pub const Context = struct {
1287 // payload's local index is that of its multi_value index, so convert it to a `WValue.local`1296 // 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 };1297 return WValue{ .local = operand.multi_value.index };
1289 }1298 }
1299
1300 fn genWrapErrUnionPayload(self: *Context, inst: *Inst.UnOp) InnerError!WValue {
1301 return self.resolveInst(inst.operand);
1302 }
1290};1303};