authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-05-28 18:45:36-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-05-28 18:45:36-04:00
log3483931d2a8e9cd565e2c03ee34ac9a7db538a7e
treeefd954ceaff56278e7b5197f068dda475435ba4a
parent33a779c7f65b9eb5115253bfe1cf4f4ea86336af
parent5cbe930e36d9ccd6ac90d15a2354aced5d54f0dc
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #8923 from Luukdegram/wasm-errors

stage2: wasm backend - Error sets and error unions

3 files changed, 233 insertions(+), 35 deletions(-)

src/codegen/wasm.zig+168-35
...@@ -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
...@@ -510,6 +516,10 @@ pub const Context = struct {...@@ -510,6 +516,10 @@ pub const Context = struct {
510 locals: std.ArrayListUnmanaged(u8),516 locals: std.ArrayListUnmanaged(u8),
511 /// The Target we're emitting (used to call intInfo)517 /// The Target we're emitting (used to call intInfo)
512 target: std.Target,518 target: std.Target,
519 /// Table with the global error set. Consists of every error found in
520 /// the compiled code. Each error name maps to a `Module.ErrorInt` which is emitted
521 /// during codegen to determine the error value.
522 global_error_set: std.StringHashMapUnmanaged(Module.ErrorInt),
513523
514 const InnerError = error{524 const InnerError = error{
515 OutOfMemory,525 OutOfMemory,
...@@ -559,7 +569,6 @@ pub const Context = struct {...@@ -559,7 +569,6 @@ pub const Context = struct {
559 if (info.bits > 32 and info.bits <= 64) break :blk wasm.Valtype.i64;569 if (info.bits > 32 and info.bits <= 64) break :blk wasm.Valtype.i64;
560 return self.fail(src, "Integer bit size not supported by wasm: '{d}'", .{info.bits});570 return self.fail(src, "Integer bit size not supported by wasm: '{d}'", .{info.bits});
561 },571 },
562 .Bool, .Pointer, .Struct => wasm.Valtype.i32,
563 .Enum => switch (ty.tag()) {572 .Enum => switch (ty.tag()) {
564 .enum_simple => wasm.Valtype.i32,573 .enum_simple => wasm.Valtype.i32,
565 else => self.typeToValtype(574 else => self.typeToValtype(
...@@ -567,6 +576,11 @@ pub const Context = struct {...@@ -567,6 +576,11 @@ pub const Context = struct {
567 ty.cast(Type.Payload.EnumFull).?.data.tag_ty,576 ty.cast(Type.Payload.EnumFull).?.data.tag_ty,
568 ),577 ),
569 },578 },
579 .Bool,
580 .Pointer,
581 .ErrorSet,
582 => wasm.Valtype.i32,
583 .Struct, .ErrorUnion => unreachable, // Multi typed, must be handled individually.
570 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()}),
571 };585 };
572 }586 }
...@@ -600,6 +614,57 @@ pub const Context = struct {...@@ -600,6 +614,57 @@ pub const Context = struct {
600 }614 }
601 }615 }
602616
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 const fields_len = @intCast(u32, struct_data.fields.count());
627 try self.locals.ensureCapacity(self.gpa, self.locals.items.len + fields_len);
628 for (struct_data.fields.items()) |entry| {
629 const val_type = try self.genValtype(
630 .{ .node_offset = struct_data.node_offset },
631 entry.value.ty,
632 );
633 self.locals.appendAssumeCapacity(val_type);
634 self.local_index += 1;
635 }
636 return WValue{ .multi_value = .{
637 .index = initial_index,
638 .count = fields_len,
639 } };
640 },
641 .ErrorUnion => {
642 const payload_type = ty.errorUnionChild();
643 const val_type = try self.genValtype(.{ .node_offset = 0 }, payload_type);
644
645 // we emit the error value as the first local, and the payload as the following.
646 // The first local is also used to find the index of the error and payload.
647 //
648 // TODO: Add support where the payload is a type that contains multiple locals such as a struct.
649 try self.locals.ensureCapacity(self.gpa, self.locals.items.len + 2);
650 self.locals.appendAssumeCapacity(wasm.valtype(.i32)); // error values are always i32
651 self.locals.appendAssumeCapacity(val_type);
652 self.local_index += 2;
653
654 return WValue{ .multi_value = .{
655 .index = initial_index,
656 .count = 2,
657 } };
658 },
659 else => {
660 const valtype = try self.genValtype(.{ .node_offset = 0 }, ty);
661 try self.locals.append(self.gpa, valtype);
662 self.local_index += 1;
663 return WValue{ .local = initial_index };
664 },
665 }
666 }
667
603 fn genFunctype(self: *Context) InnerError!void {668 fn genFunctype(self: *Context) InnerError!void {
604 assert(self.decl.has_tv);669 assert(self.decl.has_tv);
605 const ty = self.decl.ty;670 const ty = self.decl.ty;
...@@ -622,8 +687,21 @@ pub const Context = struct {...@@ -622,8 +687,21 @@ pub const Context = struct {
622687
623 // return type688 // return type
624 const return_type = ty.fnReturnType();689 const return_type = ty.fnReturnType();
625 switch (return_type.tag()) {690 switch (return_type.zigTypeTag()) {
626 .void, .noreturn => try leb.writeULEB128(writer, @as(u32, 0)),691 .Void, .NoReturn => try leb.writeULEB128(writer, @as(u32, 0)),
692 .Struct => return self.fail(.{ .node_offset = 0 }, "TODO: Implement struct as return type for wasm", .{}),
693 .Optional => return self.fail(.{ .node_offset = 0 }, "TODO: Implement optionals as return type for wasm", .{}),
694 .ErrorUnion => {
695 const val_type = try self.genValtype(
696 .{ .node_offset = 0 },
697 return_type.errorUnionChild(),
698 );
699
700 // write down the amount of return values
701 try leb.writeULEB128(writer, @as(u32, 2));
702 try writer.writeByte(wasm.valtype(.i32)); // error code is always an i32 integer.
703 try writer.writeByte(val_type);
704 },
627 else => |ret_type| {705 else => |ret_type| {
628 try leb.writeULEB128(writer, @as(u32, 1));706 try leb.writeULEB128(writer, @as(u32, 1));
629 // Can we maybe get the source index of the return type?707 // Can we maybe get the source index of the return type?
...@@ -736,6 +814,7 @@ pub const Context = struct {...@@ -736,6 +814,7 @@ pub const Context = struct {
736 .constant => unreachable,814 .constant => unreachable,
737 .dbg_stmt => WValue.none,815 .dbg_stmt => WValue.none,
738 .div => self.genBinOp(inst.castTag(.div).?, .div),816 .div => self.genBinOp(inst.castTag(.div).?, .div),
817 .is_err => self.genIsErr(inst.castTag(.is_err).?),
739 .load => self.genLoad(inst.castTag(.load).?),818 .load => self.genLoad(inst.castTag(.load).?),
740 .loop => self.genLoop(inst.castTag(.loop).?),819 .loop => self.genLoop(inst.castTag(.loop).?),
741 .mul => self.genBinOp(inst.castTag(.mul).?, .mul),820 .mul => self.genBinOp(inst.castTag(.mul).?, .mul),
...@@ -747,6 +826,8 @@ pub const Context = struct {...@@ -747,6 +826,8 @@ pub const Context = struct {
747 .sub => self.genBinOp(inst.castTag(.sub).?, .sub),826 .sub => self.genBinOp(inst.castTag(.sub).?, .sub),
748 .switchbr => self.genSwitchBr(inst.castTag(.switchbr).?),827 .switchbr => self.genSwitchBr(inst.castTag(.switchbr).?),
749 .unreach => self.genUnreachable(inst.castTag(.unreach).?),828 .unreach => self.genUnreachable(inst.castTag(.unreach).?),
829 .unwrap_errunion_payload => self.genUnwrapErrUnionPayload(inst.castTag(.unwrap_errunion_payload).?),
830 .wrap_errunion_payload => self.genWrapErrUnionPayload(inst.castTag(.wrap_errunion_payload).?),
750 .xor => self.genBinOp(inst.castTag(.xor).?, .xor),831 .xor => self.genBinOp(inst.castTag(.xor).?, .xor),
751 else => self.fail(.{ .node_offset = 0 }, "TODO: Implement wasm inst: {s}", .{inst.tag}),832 else => self.fail(.{ .node_offset = 0 }, "TODO: Implement wasm inst: {s}", .{inst.tag}),
752 };833 };
...@@ -771,7 +852,7 @@ pub const Context = struct {...@@ -771,7 +852,7 @@ pub const Context = struct {
771 const func_inst = inst.func.castTag(.constant).?;852 const func_inst = inst.func.castTag(.constant).?;
772 const func_val = inst.func.value().?;853 const func_val = inst.func.value().?;
773854
774 const target = blk: {855 const target: *Decl = blk: {
775 if (func_val.castTag(.function)) |func| {856 if (func_val.castTag(.function)) |func| {
776 break :blk func.data.owner_decl;857 break :blk func.data.owner_decl;
777 } else if (func_val.castTag(.extern_fn)) |ext_fn| {858 } else if (func_val.castTag(.extern_fn)) |ext_fn| {
...@@ -799,30 +880,7 @@ pub const Context = struct {...@@ -799,30 +880,7 @@ pub const Context = struct {
799880
800 fn genAlloc(self: *Context, inst: *Inst.NoOp) InnerError!WValue {881 fn genAlloc(self: *Context, inst: *Inst.NoOp) InnerError!WValue {
801 const elem_type = inst.base.ty.elemType();882 const elem_type = inst.base.ty.elemType();
802 const initial_index = self.local_index;883 return self.allocLocal(elem_type);
803
804 switch (elem_type.zigTypeTag()) {
805 .Struct => {
806 // for each struct field, generate a local
807 const struct_data: *Module.Struct = elem_type.castTag(.@"struct").?.data;
808 try self.locals.ensureCapacity(self.gpa, self.locals.items.len + struct_data.fields.count());
809 for (struct_data.fields.items()) |entry| {
810 const val_type = try self.genValtype(
811 .{ .node_offset = struct_data.node_offset },
812 entry.value.ty,
813 );
814 self.locals.appendAssumeCapacity(val_type);
815 self.local_index += 1;
816 }
817 return WValue{ .multi_value = initial_index };
818 },
819 else => {
820 const valtype = try self.genValtype(inst.base.src, elem_type);
821 try self.locals.append(self.gpa, valtype);
822 self.local_index += 1;
823 return WValue{ .local = initial_index };
824 },
825 }
826 }884 }
827885
828 fn genStore(self: *Context, inst: *Inst.BinOp) InnerError!WValue {886 fn genStore(self: *Context, inst: *Inst.BinOp) InnerError!WValue {
...@@ -832,11 +890,27 @@ pub const Context = struct {...@@ -832,11 +890,27 @@ pub const Context = struct {
832 const rhs = self.resolveInst(inst.rhs);890 const rhs = self.resolveInst(inst.rhs);
833891
834 switch (lhs) {892 switch (lhs) {
835 // When assigning a value to a multi_value such as a struct,893 .multi_value => |multi_value| switch (rhs) {
836 // we simply assign the local_index to the rhs one.894 // When assigning a value to a multi_value such as a struct,
837 // This allows us to update struct fields without having to individually895 // we simply assign the local_index to the rhs one.
838 // set each local as each field's index will be calculated off the struct's base index896 // This allows us to update struct fields without having to individually
839 .multi_value => self.values.put(self.gpa, inst.lhs, rhs) catch unreachable, // Instruction does not dominate all uses!897 // set each local as each field's index will be calculated off the struct's base index
898 .multi_value => self.values.put(self.gpa, inst.lhs, rhs) catch unreachable, // Instruction does not dominate all uses!
899 .constant, .none => {
900 // emit all values onto the stack if constant
901 try self.emitWValue(rhs);
902
903 // for each local, pop the stack value into the local
904 // As the last element is on top of the stack, we must populate the locals
905 // in reverse.
906 var i: u32 = multi_value.count;
907 while (i > 0) : (i -= 1) {
908 try writer.writeByte(wasm.opcode(.local_set));
909 try leb.writeULEB128(writer, multi_value.index + i - 1);
910 }
911 },
912 else => unreachable,
913 },
840 .local => |local| {914 .local => |local| {
841 try self.emitWValue(rhs);915 try self.emitWValue(rhs);
842 try writer.writeByte(wasm.opcode(.local_set));916 try writer.writeByte(wasm.opcode(.local_set));
...@@ -959,6 +1033,34 @@ pub const Context = struct {...@@ -959,6 +1033,34 @@ pub const Context = struct {
959 try self.emitConstant(src, value, int_tag_ty);1033 try self.emitConstant(src, value, int_tag_ty);
960 }1034 }
961 },1035 },
1036 .ErrorSet => {
1037 const error_index = self.global_error_set.get(value.getError().?).?;
1038 try writer.writeByte(wasm.opcode(.i32_const));
1039 try leb.writeULEB128(writer, error_index);
1040 },
1041 .ErrorUnion => {
1042 const data = value.castTag(.error_union).?.data;
1043 const error_type = ty.errorUnionSet();
1044 const payload_type = ty.errorUnionChild();
1045 if (value.getError()) |_| {
1046 // write the error value
1047 try self.emitConstant(src, data, error_type);
1048
1049 // no payload, so write a '0' const
1050 const opcode: wasm.Opcode = buildOpcode(.{
1051 .op = .@"const",
1052 .valtype1 = try self.typeToValtype(src, payload_type),
1053 });
1054 try writer.writeByte(wasm.opcode(opcode));
1055 try leb.writeULEB128(writer, @as(u32, 0));
1056 } else {
1057 // no error, so write a '0' const
1058 try writer.writeByte(wasm.opcode(.i32_const));
1059 try leb.writeULEB128(writer, @as(u32, 0));
1060 // after the error code, we emit the payload
1061 try self.emitConstant(src, data, payload_type);
1062 }
1063 },
962 else => |zig_type| return self.fail(src, "Wasm TODO: emitConstant for zigTypeTag {s}", .{zig_type}),1064 else => |zig_type| return self.fail(src, "Wasm TODO: emitConstant for zigTypeTag {s}", .{zig_type}),
963 }1065 }
964 }1066 }
...@@ -1131,7 +1233,7 @@ pub const Context = struct {...@@ -1131,7 +1233,7 @@ pub const Context = struct {
1131 fn genStructFieldPtr(self: *Context, inst: *Inst.StructFieldPtr) InnerError!WValue {1233 fn genStructFieldPtr(self: *Context, inst: *Inst.StructFieldPtr) InnerError!WValue {
1132 const struct_ptr = self.resolveInst(inst.struct_ptr);1234 const struct_ptr = self.resolveInst(inst.struct_ptr);
11331235
1134 return WValue{ .local = struct_ptr.multi_value + @intCast(u32, inst.field_index) };1236 return WValue{ .local = struct_ptr.multi_value.index + @intCast(u32, inst.field_index) };
1135 }1237 }
11361238
1137 fn genSwitchBr(self: *Context, inst: *Inst.SwitchBr) InnerError!WValue {1239 fn genSwitchBr(self: *Context, inst: *Inst.SwitchBr) InnerError!WValue {
...@@ -1174,4 +1276,35 @@ pub const Context = struct {...@@ -1174,4 +1276,35 @@ pub const Context = struct {
11741276
1175 return .none;1277 return .none;
1176 }1278 }
1279
1280 fn genIsErr(self: *Context, inst: *Inst.UnOp) InnerError!WValue {
1281 const operand = self.resolveInst(inst.operand);
1282 const offset = self.code.items.len;
1283 const writer = self.code.writer();
1284
1285 // load the error value which is positioned at multi_value's index
1286 try self.emitWValue(.{ .local = operand.multi_value.index });
1287 // Compare the error value with '0'
1288 try writer.writeByte(wasm.opcode(.i32_const));
1289 try leb.writeILEB128(writer, @as(i32, 0));
1290
1291 // we want to break out of the condition if they're *not* equal,
1292 // because that means there's an error.
1293 try writer.writeByte(wasm.opcode(.i32_ne));
1294
1295 return WValue{ .code_offset = offset };
1296 }
1297
1298 fn genUnwrapErrUnionPayload(self: *Context, inst: *Inst.UnOp) InnerError!WValue {
1299 const operand = self.resolveInst(inst.operand);
1300 // The index of multi_value contains the error code. To get the initial index of the payload we get
1301 // the following index. Next, convert it to a `WValue.local`
1302 //
1303 // TODO: Check if payload is a type that requires a multi_value as well and emit that instead. i.e. a struct.
1304 return WValue{ .local = operand.multi_value.index + 1 };
1305 }
1306
1307 fn genWrapErrUnionPayload(self: *Context, inst: *Inst.UnOp) InnerError!WValue {
1308 return self.resolveInst(inst.operand);
1309 }
1177};1310};
src/link/Wasm.zig+1
...@@ -204,6 +204,7 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {...@@ -204,6 +204,7 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
204 .err_msg = undefined,204 .err_msg = undefined,
205 .locals = .{},205 .locals = .{},
206 .target = self.base.options.target,206 .target = self.base.options.target,
207 .global_error_set = self.base.options.module.?.global_error_set,
207 };208 };
208 defer context.deinit();209 defer context.deinit();
209210
test/stage2/wasm.zig+64
...@@ -535,4 +535,68 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -535,4 +535,68 @@ pub fn addCases(ctx: *TestContext) !void {
535 \\}535 \\}
536 , "2\n");536 , "2\n");
537 }537 }
538
539 {
540 var case = ctx.exe("wasm error unions", wasi);
541
542 case.addCompareOutput(
543 \\pub export fn _start() void {
544 \\ var e1 = error.Foo;
545 \\ var e2 = error.Bar;
546 \\ assert(e1 != e2);
547 \\ assert(e1 == error.Foo);
548 \\ assert(e2 == error.Bar);
549 \\}
550 \\
551 \\fn assert(b: bool) void {
552 \\ if (!b) unreachable;
553 \\}
554 , "");
555
556 case.addCompareOutput(
557 \\pub export fn _start() u32 {
558 \\ var e: anyerror!u32 = 5;
559 \\ const i = e catch 10;
560 \\ return i;
561 \\}
562 , "5\n");
563
564 case.addCompareOutput(
565 \\pub export fn _start() u32 {
566 \\ var e: anyerror!u32 = error.Foo;
567 \\ const i = e catch 10;
568 \\ return i;
569 \\}
570 , "10\n");
571
572 case.addCompareOutput(
573 \\pub export fn _start() u32 {
574 \\ var e = foo();
575 \\ const i = e catch 69;
576 \\ return i;
577 \\}
578 \\
579 \\fn foo() anyerror!u32 {
580 \\ return 5;
581 \\}
582 , "5\n");
583 }
584
585 {
586 // TODO implement Type equality comparison of error unions in SEMA
587 // before we can incrementally compile functions with an error union as return type
588 var case = ctx.exe("wasm error union part 2", wasi);
589
590 case.addCompareOutput(
591 \\pub export fn _start() u32 {
592 \\ var e = foo();
593 \\ const i = e catch 69;
594 \\ return i;
595 \\}
596 \\
597 \\fn foo() anyerror!u32 {
598 \\ return error.Bruh;
599 \\}
600 , "69\n");
601 }
538}602}