Commit 1f16b07d6f

Andrew Kelley <andrew@ziglang.org>
2022-05-25 02:53:04
stage2: treat `error{}!void` as a zero-bit type
1 parent c711c78
Changed files (4)
src
test
behavior
src/codegen/llvm.zig
@@ -1470,16 +1470,25 @@ pub const Object = struct {
                 return full_di_ty;
             },
             .ErrorUnion => {
-                const err_set_ty = ty.errorUnionSet();
                 const payload_ty = ty.errorUnionPayload();
-                if (err_set_ty.errorSetCardinality() == .zero) {
-                    const payload_di_ty = try o.lowerDebugType(payload_ty, .full);
-                    // The recursive call to `lowerDebugType` means we can't use `gop` anymore.
-                    try o.di_type_map.putContext(gpa, ty, AnnotatedDITypePtr.initFull(payload_di_ty), .{ .mod = o.module });
-                    return payload_di_ty;
+                switch (ty.errorUnionSet().errorSetCardinality()) {
+                    .zero => {
+                        const payload_di_ty = try o.lowerDebugType(payload_ty, .full);
+                        // The recursive call to `lowerDebugType` means we can't use `gop` anymore.
+                        try o.di_type_map.putContext(gpa, ty, AnnotatedDITypePtr.initFull(payload_di_ty), .{ .mod = o.module });
+                        return payload_di_ty;
+                    },
+                    .one => {
+                        if (payload_ty.isNoReturn()) {
+                            const di_type = dib.createBasicType("void", 0, DW.ATE.signed);
+                            gop.value_ptr.* = AnnotatedDITypePtr.initFull(di_type);
+                            return di_type;
+                        }
+                    },
+                    .many => {},
                 }
                 if (!payload_ty.hasRuntimeBitsIgnoreComptime()) {
-                    const err_set_di_ty = try o.lowerDebugType(err_set_ty, .full);
+                    const err_set_di_ty = try o.lowerDebugType(Type.anyerror, .full);
                     // The recursive call to `lowerDebugType` means we can't use `gop` anymore.
                     try o.di_type_map.putContext(gpa, ty, AnnotatedDITypePtr.initFull(err_set_di_ty), .{ .mod = o.module });
                     return err_set_di_ty;
@@ -1502,56 +1511,51 @@ pub const Object = struct {
                     break :blk fwd_decl;
                 };
 
-                const err_set_size = err_set_ty.abiSize(target);
-                const err_set_align = err_set_ty.abiAlignment(target);
+                const error_size = Type.anyerror.abiSize(target);
+                const error_align = Type.anyerror.abiAlignment(target);
                 const payload_size = payload_ty.abiSize(target);
                 const payload_align = payload_ty.abiAlignment(target);
 
-                var offset: u64 = 0;
-                offset += err_set_size;
-                offset = std.mem.alignForwardGeneric(u64, offset, payload_align);
-                const payload_offset = offset;
-
-                var len: u8 = 2;
-                var fields: [3]*llvm.DIType = .{
-                    dib.createMemberType(
-                        fwd_decl.toScope(),
-                        "tag",
-                        di_file,
-                        line,
-                        err_set_size * 8, // size in bits
-                        err_set_align * 8, // align in bits
-                        0, // offset in bits
-                        0, // flags
-                        try o.lowerDebugType(err_set_ty, .full),
-                    ),
-                    dib.createMemberType(
-                        fwd_decl.toScope(),
-                        "value",
-                        di_file,
-                        line,
-                        payload_size * 8, // size in bits
-                        payload_align * 8, // align in bits
-                        payload_offset * 8, // offset in bits
-                        0, // flags
-                        try o.lowerDebugType(payload_ty, .full),
-                    ),
-                    undefined,
-                };
-
-                const error_size = Type.anyerror.abiSize(target);
-                if (payload_align > error_size) {
-                    fields[2] = fields[1];
-                    const pad_len = @intCast(u32, payload_align - error_size);
-                    fields[1] = dib.createArrayType(
-                        pad_len * 8,
-                        8,
-                        try o.lowerDebugType(Type.u8, .full),
-                        @intCast(c_int, pad_len),
-                    );
-                    len += 1;
+                var error_index: u32 = undefined;
+                var payload_index: u32 = undefined;
+                var error_offset: u64 = undefined;
+                var payload_offset: u64 = undefined;
+                if (error_align > payload_align) {
+                    error_index = 0;
+                    payload_index = 1;
+                    error_offset = 0;
+                    payload_offset = std.mem.alignForwardGeneric(u64, error_size, payload_align);
+                } else {
+                    payload_index = 0;
+                    error_index = 1;
+                    payload_offset = 0;
+                    error_offset = std.mem.alignForwardGeneric(u64, payload_size, error_align);
                 }
 
+                var fields: [2]*llvm.DIType = undefined;
+                fields[error_index] = dib.createMemberType(
+                    fwd_decl.toScope(),
+                    "tag",
+                    di_file,
+                    line,
+                    error_size * 8, // size in bits
+                    error_align * 8, // align in bits
+                    error_offset * 8, // offset in bits
+                    0, // flags
+                    try o.lowerDebugType(Type.anyerror, .full),
+                );
+                fields[payload_index] = dib.createMemberType(
+                    fwd_decl.toScope(),
+                    "value",
+                    di_file,
+                    line,
+                    payload_size * 8, // size in bits
+                    payload_align * 8, // align in bits
+                    payload_offset * 8, // offset in bits
+                    0, // flags
+                    try o.lowerDebugType(payload_ty, .full),
+                );
+
                 const full_di_ty = dib.createStructType(
                     compile_unit_scope,
                     name.ptr,
@@ -1562,7 +1566,7 @@ pub const Object = struct {
                     0, // flags
                     null, // derived from
                     &fields,
-                    len,
+                    fields.len,
                     0, // run time lang
                     null, // vtable holder
                     "", // unique id
@@ -2455,18 +2459,23 @@ pub const DeclGen = struct {
                 return dg.context.structType(&fields, fields.len, .False);
             },
             .ErrorUnion => {
-                const error_type = t.errorUnionSet();
-                const payload_type = t.errorUnionPayload();
-                if (error_type.errorSetCardinality() == .zero) {
-                    return dg.lowerType(payload_type);
+                const payload_ty = t.errorUnionPayload();
+                switch (t.errorUnionSet().errorSetCardinality()) {
+                    .zero => return dg.lowerType(payload_ty),
+                    .one => {
+                        if (payload_ty.isNoReturn()) {
+                            return dg.context.voidType();
+                        }
+                    },
+                    .many => {},
                 }
-                if (!payload_type.hasRuntimeBitsIgnoreComptime()) {
+                if (!payload_ty.hasRuntimeBitsIgnoreComptime()) {
                     return try dg.lowerType(Type.anyerror);
                 }
-                const llvm_error_type = try dg.lowerType(error_type);
-                const llvm_payload_type = try dg.lowerType(payload_type);
+                const llvm_error_type = try dg.lowerType(Type.anyerror);
+                const llvm_payload_type = try dg.lowerType(payload_ty);
 
-                const payload_align = payload_type.abiAlignment(target);
+                const payload_align = payload_ty.abiAlignment(target);
                 const error_align = Type.anyerror.abiAlignment(target);
                 if (error_align > payload_align) {
                     const fields: [2]*const llvm.Type = .{ llvm_error_type, llvm_payload_type };
@@ -2476,9 +2485,7 @@ pub const DeclGen = struct {
                     return dg.context.structType(&fields, fields.len, .False);
                 }
             },
-            .ErrorSet => {
-                return dg.context.intType(16);
-            },
+            .ErrorSet => return dg.context.intType(16),
             .Struct => {
                 const gop = try dg.object.type_map.getOrPutContext(gpa, t, .{ .mod = dg.module });
                 if (gop.found_existing) return gop.value_ptr.*;
@@ -3095,7 +3102,7 @@ pub const DeclGen = struct {
                 return dg.resolveLlvmFunction(fn_decl_index);
             },
             .ErrorSet => {
-                const llvm_ty = try dg.lowerType(tv.ty);
+                const llvm_ty = try dg.lowerType(Type.anyerror);
                 switch (tv.val.tag()) {
                     .@"error" => {
                         const err_name = tv.val.castTag(.@"error").?.data.name;
@@ -3109,9 +3116,8 @@ pub const DeclGen = struct {
                 }
             },
             .ErrorUnion => {
-                const error_type = tv.ty.errorUnionSet();
                 const payload_type = tv.ty.errorUnionPayload();
-                if (error_type.errorSetCardinality() == .zero) {
+                if (tv.ty.errorUnionSet().errorSetCardinality() == .zero) {
                     const payload_val = tv.val.castTag(.eu_payload).?.data;
                     return dg.lowerValue(.{ .ty = payload_type, .val = payload_val });
                 }
@@ -3120,13 +3126,13 @@ pub const DeclGen = struct {
                 if (!payload_type.hasRuntimeBitsIgnoreComptime()) {
                     // We use the error type directly as the type.
                     const err_val = if (!is_pl) tv.val else Value.initTag(.zero);
-                    return dg.lowerValue(.{ .ty = error_type, .val = err_val });
+                    return dg.lowerValue(.{ .ty = Type.anyerror, .val = err_val });
                 }
 
                 const payload_align = payload_type.abiAlignment(target);
                 const error_align = Type.anyerror.abiAlignment(target);
                 const llvm_error_value = try dg.lowerValue(.{
-                    .ty = error_type,
+                    .ty = Type.anyerror,
                     .val = if (is_pl) Value.initTag(.zero) else tv.val,
                 });
                 const llvm_payload_value = try dg.lowerValue(.{
@@ -5656,13 +5662,12 @@ pub const FuncGen = struct {
         const operand = try self.resolveInst(ty_op.operand);
         const error_union_ty = self.air.typeOf(ty_op.operand).childType();
 
-        const error_ty = error_union_ty.errorUnionSet();
-        if (error_ty.errorSetCardinality() == .zero) {
+        if (error_union_ty.errorUnionSet().errorSetCardinality() == .zero) {
             // TODO: write undefined bytes through the pointer here
             return operand;
         }
         const payload_ty = error_union_ty.errorUnionPayload();
-        const non_error_val = try self.dg.lowerValue(.{ .ty = error_ty, .val = Value.zero });
+        const non_error_val = try self.dg.lowerValue(.{ .ty = Type.anyerror, .val = Value.zero });
         if (!payload_ty.hasRuntimeBitsIgnoreComptime()) {
             _ = self.builder.buildStore(non_error_val, operand);
             return operand;
@@ -6715,9 +6720,9 @@ pub const FuncGen = struct {
         if (self.liveness.isUnused(inst)) return null;
 
         const ty_op = self.air.instructions.items(.data)[inst].ty_op;
-        const operand = try self.resolveInst(ty_op.operand);
         const operand_ty = self.air.typeOf(ty_op.operand);
         const inst_ty = self.air.typeOfIndex(inst);
+        const operand = try self.resolveInst(ty_op.operand);
         const operand_is_ref = isByRef(operand_ty);
         const result_is_ref = isByRef(inst_ty);
         const llvm_dest_ty = try self.dg.lowerType(inst_ty);
src/Sema.zig
@@ -23320,7 +23320,6 @@ pub fn typeHasOnePossibleValue(
         .optional_single_const_pointer,
         .enum_literal,
         .anyerror_void_error_union,
-        .error_union,
         .error_set_inferred,
         .@"opaque",
         .var_args_param,
@@ -23360,6 +23359,29 @@ pub fn typeHasOnePossibleValue(
             }
         },
 
+        .error_union => {
+            const error_ty = ty.errorUnionSet();
+            switch (error_ty.errorSetCardinality()) {
+                .zero => {
+                    const payload_ty = ty.errorUnionPayload();
+                    if (try typeHasOnePossibleValue(sema, block, src, payload_ty)) |payload_val| {
+                        return try Value.Tag.eu_payload.create(sema.arena, payload_val);
+                    } else {
+                        return null;
+                    }
+                },
+                .one => {
+                    if (ty.errorUnionPayload().isNoReturn()) {
+                        const error_val = (try typeHasOnePossibleValue(sema, block, src, error_ty)).?;
+                        return error_val;
+                    } else {
+                        return null;
+                    }
+                },
+                .many => return null,
+            }
+        },
+
         .error_set_single => {
             const name = ty.castTag(.error_set_single).?.data;
             return try Value.Tag.@"error".create(sema.arena, .{ .name = name });
src/type.zig
@@ -2416,14 +2416,18 @@ pub const Type = extern union {
                 // This code needs to be kept in sync with the equivalent switch prong
                 // in abiSizeAdvanced.
                 const data = ty.castTag(.error_union).?.data;
-                if (data.error_set.errorSetCardinality() == .zero) {
-                    return hasRuntimeBitsAdvanced(data.payload, ignore_comptime_only, sema_kit);
-                } else if (ignore_comptime_only) {
-                    return true;
-                } else if (sema_kit) |sk| {
-                    return !(try sk.sema.typeRequiresComptime(sk.block, sk.src, ty));
-                } else {
-                    return !comptimeOnly(ty);
+                switch (data.error_set.errorSetCardinality()) {
+                    .zero => return hasRuntimeBitsAdvanced(data.payload, ignore_comptime_only, sema_kit),
+                    .one => return !data.payload.isNoReturn(),
+                    .many => {
+                        if (ignore_comptime_only) {
+                            return true;
+                        } else if (sema_kit) |sk| {
+                            return !(try sk.sema.typeRequiresComptime(sk.block, sk.src, ty));
+                        } else {
+                            return !comptimeOnly(ty);
+                        }
+                    },
                 }
             },
 
@@ -2970,8 +2974,14 @@ pub const Type = extern union {
                 // This code needs to be kept in sync with the equivalent switch prong
                 // in abiSizeAdvanced.
                 const data = ty.castTag(.error_union).?.data;
-                if (data.error_set.errorSetCardinality() == .zero) {
-                    return abiAlignmentAdvanced(data.payload, target, strat);
+                switch (data.error_set.errorSetCardinality()) {
+                    .zero => return abiAlignmentAdvanced(data.payload, target, strat),
+                    .one => {
+                        if (data.payload.isNoReturn()) {
+                            return AbiAlignmentAdvanced{ .scalar = 0 };
+                        }
+                    },
+                    .many => {},
                 }
                 const code_align = abiAlignment(Type.anyerror, target);
                 switch (strat) {
@@ -3440,8 +3450,14 @@ pub const Type = extern union {
                 // 1 bit of data which is whether or not the value is an error.
                 // Zig still uses the error code encoding at runtime, even when only 1 bit
                 // would suffice. This prevents coercions from needing to branch.
-                if (data.error_set.errorSetCardinality() == .zero) {
-                    return abiSizeAdvanced(data.payload, target, strat);
+                switch (data.error_set.errorSetCardinality()) {
+                    .zero => return abiSizeAdvanced(data.payload, target, strat),
+                    .one => {
+                        if (data.payload.isNoReturn()) {
+                            return AbiSizeAdvanced{ .scalar = 0 };
+                        }
+                    },
+                    .many => {},
                 }
                 const code_size = abiSize(Type.anyerror, target);
                 if (!data.payload.hasRuntimeBits()) {
@@ -4843,7 +4859,6 @@ pub const Type = extern union {
             .optional_single_const_pointer,
             .enum_literal,
             .anyerror_void_error_union,
-            .error_union,
             .error_set_inferred,
             .@"opaque",
             .var_args_param,
@@ -4883,6 +4898,30 @@ pub const Type = extern union {
                 }
             },
 
+            .error_union => {
+                const error_ty = ty.errorUnionSet();
+                switch (error_ty.errorSetCardinality()) {
+                    .zero => {
+                        const payload_ty = ty.errorUnionPayload();
+                        if (onePossibleValue(payload_ty)) |payload_val| {
+                            _ = payload_val;
+                            return Value.initTag(.the_only_possible_value);
+                        } else {
+                            return null;
+                        }
+                    },
+                    .one => {
+                        if (ty.errorUnionPayload().isNoReturn()) {
+                            const error_val = onePossibleValue(error_ty).?;
+                            return error_val;
+                        } else {
+                            return null;
+                        }
+                    },
+                    .many => return null,
+                }
+            },
+
             .error_set_single => return Value.initTag(.the_only_possible_value),
             .error_set => {
                 const err_set_obj = ty.castTag(.error_set).?.data;
test/behavior/error.zig
@@ -479,11 +479,38 @@ test "optional error set with only one error is the same size as bool" {
 test "optional empty error set" {
     if (builtin.zig_backend == .stage1) return error.SkipZigTest;
 
-    const T = ?error{};
-    var t: T = undefined;
-    if (t != null) {
+    comptime try expect(@sizeOf(error{}!void) == @sizeOf(void));
+    comptime try expect(@alignOf(error{}!void) == @alignOf(void));
+
+    var x: ?error{} = undefined;
+    if (x != null) {
+        @compileError("test failed");
+    }
+}
+
+test "empty error set plus zero-bit payload" {
+    if (builtin.zig_backend == .stage1) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
+    comptime try expect(@sizeOf(error{}!void) == @sizeOf(void));
+    comptime try expect(@alignOf(error{}!void) == @alignOf(void));
+
+    var x: error{}!void = undefined;
+    if (x) |payload| {
+        if (payload != {}) {
+            @compileError("test failed");
+        }
+    } else |_| {
         @compileError("test failed");
     }
+    const S = struct {
+        fn empty() error{}!void {}
+        fn inferred() !void {
+            return empty();
+        }
+    };
+    try S.inferred();
 }
 
 test "nested catch" {