From a5b5a5532e6500f7458ed5408c0b511a6f306b93 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Fri, 21 May 2021 17:07:46 +0200 Subject: wasm: Support error sets --- src/codegen/wasm.zig | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) (limited to 'src/codegen') diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index 36b131e9b1..8516c7aece 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -510,6 +510,10 @@ pub const Context = struct { locals: std.ArrayListUnmanaged(u8), /// The Target we're emitting (used to call intInfo) target: std.Target, + /// Table with the global error set. Consists of every error found in + /// the compiled code. Each error name maps to a `Module.ErrorInt` which is emitted + /// during codegen to determine the error value. + global_error_set: std.StringHashMapUnmanaged(Module.ErrorInt), const InnerError = error{ OutOfMemory, @@ -559,7 +563,6 @@ pub const Context = struct { if (info.bits > 32 and info.bits <= 64) break :blk wasm.Valtype.i64; return self.fail(src, "Integer bit size not supported by wasm: '{d}'", .{info.bits}); }, - .Bool, .Pointer, .Struct => wasm.Valtype.i32, .Enum => switch (ty.tag()) { .enum_simple => wasm.Valtype.i32, else => self.typeToValtype( @@ -567,6 +570,11 @@ pub const Context = struct { ty.cast(Type.Payload.EnumFull).?.data.tag_ty, ), }, + .Bool, + .Pointer, + .Struct, + .ErrorSet, + => wasm.Valtype.i32, else => self.fail(src, "TODO - Wasm valtype for type '{s}'", .{ty.zigTypeTag()}), }; } @@ -959,6 +967,11 @@ pub const Context = struct { try self.emitConstant(src, value, int_tag_ty); } }, + .ErrorSet => { + const error_index = self.global_error_set.get(value.getError().?).?; + try writer.writeByte(wasm.opcode(.i32_const)); + try leb.writeULEB128(writer, error_index); + }, else => |zig_type| return self.fail(src, "Wasm TODO: emitConstant for zigTypeTag {s}", .{zig_type}), } } -- cgit v1.2.3 From 54810592326e3cd2cad02318aa07dd1d5da2e731 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Sat, 22 May 2021 17:34:19 +0200 Subject: 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. --- src/codegen/wasm.zig | 118 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 109 insertions(+), 9 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index 8516c7aece..88bb6ec5a0 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -30,7 +30,13 @@ const WValue = union(enum) { code_offset: usize, /// Used for variables that create multiple locals on the stack when allocated /// such as structs and optionals. - multi_value: u32, + multi_value: struct { + /// The index of the first local variable + index: u32, + /// The count of local variables this `WValue` consists of. + /// i.e. an ErrorUnion has a 'count' of 2. + count: u32, + }, }; /// Wasm ops, but without input/output/signedness information @@ -572,9 +578,9 @@ pub const Context = struct { }, .Bool, .Pointer, - .Struct, .ErrorSet, => wasm.Valtype.i32, + .Struct, .ErrorUnion => unreachable, // Multi typed, must be handled individually by genAlloc(). else => self.fail(src, "TODO - Wasm valtype for type '{s}'", .{ty.zigTypeTag()}), }; } @@ -744,6 +750,7 @@ pub const Context = struct { .constant => unreachable, .dbg_stmt => WValue.none, .div => self.genBinOp(inst.castTag(.div).?, .div), + .is_err => self.genIsErr(inst.castTag(.is_err).?), .load => self.genLoad(inst.castTag(.load).?), .loop => self.genLoop(inst.castTag(.loop).?), .mul => self.genBinOp(inst.castTag(.mul).?, .mul), @@ -755,6 +762,7 @@ pub const Context = struct { .sub => self.genBinOp(inst.castTag(.sub).?, .sub), .switchbr => self.genSwitchBr(inst.castTag(.switchbr).?), .unreach => self.genUnreachable(inst.castTag(.unreach).?), + .unwrap_errunion_payload => self.genUnwrapErrUnionPayload(inst.castTag(.unwrap_errunion_payload).?), .xor => self.genBinOp(inst.castTag(.xor).?, .xor), else => self.fail(.{ .node_offset = 0 }, "TODO: Implement wasm inst: {s}", .{inst.tag}), }; @@ -822,7 +830,27 @@ pub const Context = struct { self.locals.appendAssumeCapacity(val_type); self.local_index += 1; } - return WValue{ .multi_value = initial_index }; + return WValue{ .multi_value = .{ + .index = initial_index, + .count = @intCast(u32, struct_data.fields.count()), + } }; + }, + .ErrorUnion => { + // generate a local for both the error and the payload. + const payload_type = elem_type.errorUnionChild(); + + // we emit the payload value as the first local, and the error as the second + // The first local is also used to find the index of the error. + try self.locals.ensureCapacity(self.gpa, self.locals.items.len + 2); + const val_type = try self.genValtype(.{ .node_offset = 0 }, payload_type); + self.locals.appendAssumeCapacity(val_type); + self.locals.appendAssumeCapacity(wasm.valtype(.i32)); // error values are always i32 + self.local_index += 2; + + return WValue{ .multi_value = .{ + .index = initial_index, + .count = 2, + } }; }, else => { const valtype = try self.genValtype(inst.base.src, elem_type); @@ -839,12 +867,43 @@ pub const Context = struct { const lhs = self.resolveInst(inst.lhs); const rhs = self.resolveInst(inst.rhs); + // switch (lhs) { + // // When assigning a value to a multi_value such as a struct, + // // we simply assign the local_index to the rhs one. + // // This allows us to update struct fields without having to individually + // // set each local as each field's index will be calculated off the struct's base index + // .multi_value => self.values.put(self.gpa, inst.lhs, rhs) catch unreachable, // Instruction does not dominate all uses! + // .local => |local| { + // try self.emitWValue(rhs); + // try writer.writeByte(wasm.opcode(.local_set)); + // try leb.writeULEB128(writer, lhs.local); + // }, + // else => unreachable, + // } + // return .none; + switch (lhs) { - // When assigning a value to a multi_value such as a struct, - // we simply assign the local_index to the rhs one. - // This allows us to update struct fields without having to individually - // set each local as each field's index will be calculated off the struct's base index - .multi_value => self.values.put(self.gpa, inst.lhs, rhs) catch unreachable, // Instruction does not dominate all uses! + .multi_value => |multi_value| switch (rhs) { + // When assigning a value to a multi_value such as a struct, + // we simply assign the local_index to the rhs one. + // This allows us to update struct fields without having to individually + // set each local as each field's index will be calculated off the struct's base index + .multi_value => self.values.put(self.gpa, inst.lhs, rhs) catch unreachable, // Instruction does not dominate all uses! + .constant => { + // emit all values onto the stack + try self.emitWValue(rhs); + + // for each local, pop the stack value into the local + // As the last element is on top of the stack, we must populate the locals + // in reverse. + var i: u32 = multi_value.count; + while (i > 0) : (i -= 1) { + try writer.writeByte(wasm.opcode(.local_set)); + try leb.writeULEB128(writer, multi_value.index + i - 1); + } + }, + else => unreachable, + }, .local => |local| { try self.emitWValue(rhs); try writer.writeByte(wasm.opcode(.local_set)); @@ -972,6 +1031,23 @@ pub const Context = struct { try writer.writeByte(wasm.opcode(.i32_const)); try leb.writeULEB128(writer, error_index); }, + .ErrorUnion => { + const data = value.castTag(.error_union).?.data; + const error_type = ty.errorUnionSet(); + const payload_type = ty.errorUnionChild(); + if (value.getError()) |_| { + // no payload, so write a '0' const + try writer.writeByte(wasm.opcode(.i32_const)); + try leb.writeULEB128(writer, @as(u32, 0)); + try self.emitConstant(src, data, error_type); + } else { + // payload first + try self.emitConstant(src, data, payload_type); + // no error, so write a '0' const + try writer.writeByte(wasm.opcode(.i32_const)); + try leb.writeULEB128(writer, @as(u32, 0)); + } + }, else => |zig_type| return self.fail(src, "Wasm TODO: emitConstant for zigTypeTag {s}", .{zig_type}), } } @@ -1144,7 +1220,7 @@ pub const Context = struct { fn genStructFieldPtr(self: *Context, inst: *Inst.StructFieldPtr) InnerError!WValue { const struct_ptr = self.resolveInst(inst.struct_ptr); - return WValue{ .local = struct_ptr.multi_value + @intCast(u32, inst.field_index) }; + return WValue{ .local = struct_ptr.multi_value.index + @intCast(u32, inst.field_index) }; } fn genSwitchBr(self: *Context, inst: *Inst.SwitchBr) InnerError!WValue { @@ -1187,4 +1263,28 @@ pub const Context = struct { return .none; } + + fn genIsErr(self: *Context, inst: *Inst.UnOp) InnerError!WValue { + const operand = self.resolveInst(inst.operand); + const offset = self.code.items.len; + const writer = self.code.writer(); + + // load the error value which is the payload's multi_value index + 1 + try self.emitWValue(.{ .local = operand.multi_value.index + 1 }); + // Compare the error value with '0' + try writer.writeByte(wasm.opcode(.i32_const)); + try leb.writeILEB128(writer, @as(i32, 0)); + + // we want to break out of the condition if they're *not* equal, + // because that means there's an error. + try writer.writeByte(wasm.opcode(.i32_ne)); + + return WValue{ .code_offset = offset }; + } + + fn genUnwrapErrUnionPayload(self: *Context, inst: *Inst.UnOp) InnerError!WValue { + const operand = self.resolveInst(inst.operand); + // payload's local index is that of its multi_value index, so convert it to a `WValue.local` + return WValue{ .local = operand.multi_value.index }; + } }; -- cgit v1.2.3 From 967a299c346937316158f58f3d3ae1be7ee0f551 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Sun, 23 May 2021 16:43:44 +0200 Subject: 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. --- src/codegen/wasm.zig | 145 ++++++++++++++++++++++++++++----------------------- 1 file changed, 79 insertions(+), 66 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index 88bb6ec5a0..5c902ea703 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -580,7 +580,7 @@ pub const Context = struct { .Pointer, .ErrorSet, => wasm.Valtype.i32, - .Struct, .ErrorUnion => unreachable, // Multi typed, must be handled individually by genAlloc(). + .Struct, .ErrorUnion => unreachable, // Multi typed, must be handled individually. else => self.fail(src, "TODO - Wasm valtype for type '{s}'", .{ty.zigTypeTag()}), }; } @@ -614,6 +614,55 @@ pub const Context = struct { } } + /// Creates one or multiple locals for a given `Type`. + /// Returns a corresponding `Wvalue` that can either be of tag + /// local or multi_value + fn allocLocal(self: *Context, ty: Type) InnerError!WValue { + const initial_index = self.local_index; + switch (ty.zigTypeTag()) { + .Struct => { + // for each struct field, generate a local + const struct_data: *Module.Struct = ty.castTag(.@"struct").?.data; + try self.locals.ensureCapacity(self.gpa, self.locals.items.len + struct_data.fields.count()); + for (struct_data.fields.items()) |entry| { + const val_type = try self.genValtype( + .{ .node_offset = struct_data.node_offset }, + entry.value.ty, + ); + self.locals.appendAssumeCapacity(val_type); + self.local_index += 1; + } + return WValue{ .multi_value = .{ + .index = initial_index, + .count = @intCast(u32, struct_data.fields.count()), + } }; + }, + .ErrorUnion => { + // generate a local for both the error and the payload. + const payload_type = ty.errorUnionChild(); + + // we emit the payload value as the first local, and the error as the second + // The first local is also used to find the index of the error. + try self.locals.ensureCapacity(self.gpa, self.locals.items.len + 2); + const val_type = try self.genValtype(.{ .node_offset = 0 }, payload_type); + self.locals.appendAssumeCapacity(val_type); + self.locals.appendAssumeCapacity(wasm.valtype(.i32)); // error values are always i32 + self.local_index += 2; + + return WValue{ .multi_value = .{ + .index = initial_index, + .count = 2, + } }; + }, + else => { + const valtype = try self.genValtype(.{ .node_offset = 0 }, ty); + try self.locals.append(self.gpa, valtype); + self.local_index += 1; + return WValue{ .local = initial_index }; + }, + } + } + fn genFunctype(self: *Context) InnerError!void { assert(self.decl.has_tv); const ty = self.decl.ty; @@ -636,8 +685,19 @@ pub const Context = struct { // return type const return_type = ty.fnReturnType(); - switch (return_type.tag()) { - .void, .noreturn => try leb.writeULEB128(writer, @as(u32, 0)), + switch (return_type.zigTypeTag()) { + .Void, .NoReturn => try leb.writeULEB128(writer, @as(u32, 0)), + .Struct => return self.fail(.{ .node_offset = 0 }, "TODO: Implement struct as return type for wasm", .{}), + .Optional => return self.fail(.{ .node_offset = 0 }, "TODO: Implement optionals as return type for wasm", .{}), + .ErrorUnion => { + try leb.writeULEB128(writer, @as(u32, 2)); + const val_type = try self.genValtype( + .{ .node_offset = 0 }, + return_type.errorUnionChild(), + ); + try writer.writeByte(val_type); + try writer.writeByte(wasm.valtype(.i32)); // error code is always an i32 integer. + }, else => |ret_type| { try leb.writeULEB128(writer, @as(u32, 1)); // Can we maybe get the source index of the return type? @@ -763,6 +823,7 @@ pub const Context = struct { .switchbr => self.genSwitchBr(inst.castTag(.switchbr).?), .unreach => self.genUnreachable(inst.castTag(.unreach).?), .unwrap_errunion_payload => self.genUnwrapErrUnionPayload(inst.castTag(.unwrap_errunion_payload).?), + .wrap_errunion_payload => self.genWrapErrUnionPayload(inst.castTag(.wrap_errunion_payload).?), .xor => self.genBinOp(inst.castTag(.xor).?, .xor), else => self.fail(.{ .node_offset = 0 }, "TODO: Implement wasm inst: {s}", .{inst.tag}), }; @@ -787,7 +848,7 @@ pub const Context = struct { const func_inst = inst.func.castTag(.constant).?; const func_val = inst.func.value().?; - const target = blk: { + const target: *Decl = blk: { if (func_val.castTag(.function)) |func| { break :blk func.data.owner_decl; } else if (func_val.castTag(.extern_fn)) |ext_fn| { @@ -815,50 +876,7 @@ pub const Context = struct { fn genAlloc(self: *Context, inst: *Inst.NoOp) InnerError!WValue { const elem_type = inst.base.ty.elemType(); - const initial_index = self.local_index; - - switch (elem_type.zigTypeTag()) { - .Struct => { - // for each struct field, generate a local - const struct_data: *Module.Struct = elem_type.castTag(.@"struct").?.data; - try self.locals.ensureCapacity(self.gpa, self.locals.items.len + struct_data.fields.count()); - for (struct_data.fields.items()) |entry| { - const val_type = try self.genValtype( - .{ .node_offset = struct_data.node_offset }, - entry.value.ty, - ); - self.locals.appendAssumeCapacity(val_type); - self.local_index += 1; - } - return WValue{ .multi_value = .{ - .index = initial_index, - .count = @intCast(u32, struct_data.fields.count()), - } }; - }, - .ErrorUnion => { - // generate a local for both the error and the payload. - const payload_type = elem_type.errorUnionChild(); - - // we emit the payload value as the first local, and the error as the second - // The first local is also used to find the index of the error. - try self.locals.ensureCapacity(self.gpa, self.locals.items.len + 2); - const val_type = try self.genValtype(.{ .node_offset = 0 }, payload_type); - self.locals.appendAssumeCapacity(val_type); - self.locals.appendAssumeCapacity(wasm.valtype(.i32)); // error values are always i32 - self.local_index += 2; - - return WValue{ .multi_value = .{ - .index = initial_index, - .count = 2, - } }; - }, - else => { - const valtype = try self.genValtype(inst.base.src, elem_type); - try self.locals.append(self.gpa, valtype); - self.local_index += 1; - return WValue{ .local = initial_index }; - }, - } + return self.allocLocal(elem_type); } fn genStore(self: *Context, inst: *Inst.BinOp) InnerError!WValue { @@ -867,21 +885,6 @@ pub const Context = struct { const lhs = self.resolveInst(inst.lhs); const rhs = self.resolveInst(inst.rhs); - // switch (lhs) { - // // When assigning a value to a multi_value such as a struct, - // // we simply assign the local_index to the rhs one. - // // This allows us to update struct fields without having to individually - // // set each local as each field's index will be calculated off the struct's base index - // .multi_value => self.values.put(self.gpa, inst.lhs, rhs) catch unreachable, // Instruction does not dominate all uses! - // .local => |local| { - // try self.emitWValue(rhs); - // try writer.writeByte(wasm.opcode(.local_set)); - // try leb.writeULEB128(writer, lhs.local); - // }, - // else => unreachable, - // } - // return .none; - switch (lhs) { .multi_value => |multi_value| switch (rhs) { // When assigning a value to a multi_value such as a struct, @@ -889,8 +892,8 @@ pub const Context = struct { // This allows us to update struct fields without having to individually // set each local as each field's index will be calculated off the struct's base index .multi_value => self.values.put(self.gpa, inst.lhs, rhs) catch unreachable, // Instruction does not dominate all uses! - .constant => { - // emit all values onto the stack + .constant, .none => { + // emit all values onto the stack if constant try self.emitWValue(rhs); // for each local, pop the stack value into the local @@ -1037,8 +1040,14 @@ pub const Context = struct { const payload_type = ty.errorUnionChild(); if (value.getError()) |_| { // no payload, so write a '0' const - try writer.writeByte(wasm.opcode(.i32_const)); + const opcode: wasm.Opcode = buildOpcode(.{ + .op = .@"const", + .valtype1 = try self.typeToValtype(src, payload_type), + }); + try writer.writeByte(wasm.opcode(opcode)); try leb.writeULEB128(writer, @as(u32, 0)); + + // write the error value try self.emitConstant(src, data, error_type); } else { // payload first @@ -1287,4 +1296,8 @@ pub const Context = struct { // payload's local index is that of its multi_value index, so convert it to a `WValue.local` return WValue{ .local = operand.multi_value.index }; } + + fn genWrapErrUnionPayload(self: *Context, inst: *Inst.UnOp) InnerError!WValue { + return self.resolveInst(inst.operand); + } }; -- cgit v1.2.3 From 8a81dfc9997c4ae8a109071be9754d3bb52a78f0 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Fri, 28 May 2021 10:50:16 +0200 Subject: wasm: Reverse the order of error and payload This will set us up to correctly retrieve the error local index and payload index depending on that of the multi_value's index. As from now, the error will always use the multi_value's index, and the payload will use the following locals. --- src/codegen/wasm.zig | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index 5c902ea703..f2e318b9c5 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -623,7 +623,8 @@ pub const Context = struct { .Struct => { // for each struct field, generate a local const struct_data: *Module.Struct = ty.castTag(.@"struct").?.data; - try self.locals.ensureCapacity(self.gpa, self.locals.items.len + struct_data.fields.count()); + const fields_len = @intCast(u32, struct_data.fields.count()); + try self.locals.ensureCapacity(self.gpa, self.locals.items.len + fields_len); for (struct_data.fields.items()) |entry| { const val_type = try self.genValtype( .{ .node_offset = struct_data.node_offset }, @@ -634,19 +635,20 @@ pub const Context = struct { } return WValue{ .multi_value = .{ .index = initial_index, - .count = @intCast(u32, struct_data.fields.count()), + .count = fields_len, } }; }, .ErrorUnion => { - // generate a local for both the error and the payload. const payload_type = ty.errorUnionChild(); + const val_type = try self.genValtype(.{ .node_offset = 0 }, payload_type); - // we emit the payload value as the first local, and the error as the second - // The first local is also used to find the index of the error. + // we emit the error value as the first local, and the payload as the following. + // The first local is also used to find the index of the error and payload. + // + // TODO: Add support where the payload is a type that contains multiple locals such as a struct. try self.locals.ensureCapacity(self.gpa, self.locals.items.len + 2); - const val_type = try self.genValtype(.{ .node_offset = 0 }, payload_type); - self.locals.appendAssumeCapacity(val_type); self.locals.appendAssumeCapacity(wasm.valtype(.i32)); // error values are always i32 + self.locals.appendAssumeCapacity(val_type); self.local_index += 2; return WValue{ .multi_value = .{ @@ -1039,6 +1041,9 @@ pub const Context = struct { const error_type = ty.errorUnionSet(); const payload_type = ty.errorUnionChild(); if (value.getError()) |_| { + // write the error value + try self.emitConstant(src, data, error_type); + // no payload, so write a '0' const const opcode: wasm.Opcode = buildOpcode(.{ .op = .@"const", @@ -1046,15 +1051,12 @@ pub const Context = struct { }); try writer.writeByte(wasm.opcode(opcode)); try leb.writeULEB128(writer, @as(u32, 0)); - - // write the error value - try self.emitConstant(src, data, error_type); } else { - // payload first - try self.emitConstant(src, data, payload_type); // no error, so write a '0' const try writer.writeByte(wasm.opcode(.i32_const)); try leb.writeULEB128(writer, @as(u32, 0)); + // after the error code, we emit the payload + try self.emitConstant(src, data, payload_type); } }, else => |zig_type| return self.fail(src, "Wasm TODO: emitConstant for zigTypeTag {s}", .{zig_type}), @@ -1278,8 +1280,8 @@ pub const Context = struct { const offset = self.code.items.len; const writer = self.code.writer(); - // load the error value which is the payload's multi_value index + 1 - try self.emitWValue(.{ .local = operand.multi_value.index + 1 }); + // load the error value which is positioned at multi_value's index + try self.emitWValue(.{ .local = operand.multi_value.index }); // Compare the error value with '0' try writer.writeByte(wasm.opcode(.i32_const)); try leb.writeILEB128(writer, @as(i32, 0)); @@ -1293,8 +1295,11 @@ pub const Context = struct { fn genUnwrapErrUnionPayload(self: *Context, inst: *Inst.UnOp) InnerError!WValue { const operand = self.resolveInst(inst.operand); - // payload's local index is that of its multi_value index, so convert it to a `WValue.local` - return WValue{ .local = operand.multi_value.index }; + // The index of multi_value contains the error code. To get the initial index of the payload we get + // the following index. Next, convert it to a `WValue.local` + // + // TODO: Check if payload is a type that requires a multi_value as well and emit that instead. i.e. a struct. + return WValue{ .local = operand.multi_value.index + 1 }; } fn genWrapErrUnionPayload(self: *Context, inst: *Inst.UnOp) InnerError!WValue { -- cgit v1.2.3 From 5cbe930e36d9ccd6ac90d15a2354aced5d54f0dc Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Fri, 28 May 2021 11:52:08 +0200 Subject: wasm: Add stage2 tests for error unions --- src/codegen/wasm.zig | 6 +++-- test/stage2/wasm.zig | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index f2e318b9c5..528b233589 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -692,13 +692,15 @@ pub const Context = struct { .Struct => return self.fail(.{ .node_offset = 0 }, "TODO: Implement struct as return type for wasm", .{}), .Optional => return self.fail(.{ .node_offset = 0 }, "TODO: Implement optionals as return type for wasm", .{}), .ErrorUnion => { - try leb.writeULEB128(writer, @as(u32, 2)); const val_type = try self.genValtype( .{ .node_offset = 0 }, return_type.errorUnionChild(), ); - try writer.writeByte(val_type); + + // write down the amount of return values + try leb.writeULEB128(writer, @as(u32, 2)); try writer.writeByte(wasm.valtype(.i32)); // error code is always an i32 integer. + try writer.writeByte(val_type); }, else => |ret_type| { try leb.writeULEB128(writer, @as(u32, 1)); diff --git a/test/stage2/wasm.zig b/test/stage2/wasm.zig index e62fcd3bac..828292d2a4 100644 --- a/test/stage2/wasm.zig +++ b/test/stage2/wasm.zig @@ -535,4 +535,68 @@ pub fn addCases(ctx: *TestContext) !void { \\} , "2\n"); } + + { + var case = ctx.exe("wasm error unions", wasi); + + case.addCompareOutput( + \\pub export fn _start() void { + \\ var e1 = error.Foo; + \\ var e2 = error.Bar; + \\ assert(e1 != e2); + \\ assert(e1 == error.Foo); + \\ assert(e2 == error.Bar); + \\} + \\ + \\fn assert(b: bool) void { + \\ if (!b) unreachable; + \\} + , ""); + + case.addCompareOutput( + \\pub export fn _start() u32 { + \\ var e: anyerror!u32 = 5; + \\ const i = e catch 10; + \\ return i; + \\} + , "5\n"); + + case.addCompareOutput( + \\pub export fn _start() u32 { + \\ var e: anyerror!u32 = error.Foo; + \\ const i = e catch 10; + \\ return i; + \\} + , "10\n"); + + case.addCompareOutput( + \\pub export fn _start() u32 { + \\ var e = foo(); + \\ const i = e catch 69; + \\ return i; + \\} + \\ + \\fn foo() anyerror!u32 { + \\ return 5; + \\} + , "5\n"); + } + + { + // TODO implement Type equality comparison of error unions in SEMA + // before we can incrementally compile functions with an error union as return type + var case = ctx.exe("wasm error union part 2", wasi); + + case.addCompareOutput( + \\pub export fn _start() u32 { + \\ var e = foo(); + \\ const i = e catch 69; + \\ return i; + \\} + \\ + \\fn foo() anyerror!u32 { + \\ return error.Bruh; + \\} + , "69\n"); + } } -- cgit v1.2.3