From 57634b7809d07c8a07a015bec55829937d5795e1 Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Wed, 29 Apr 2026 12:35:38 +0100 Subject: [PATCH] compiler: remove `i0` from the language Resolves: https://github.com/ziglang/zig/issues/1593 --- lib/std/zig/AstGen.zig | 108 ++++++++++++++++++---------------- lib/std/zig/Zir.zig | 1 - src/Air.zig | 1 - src/InternPool.zig | 9 +-- src/Sema.zig | 7 ++- src/Sema/LowerZon.zig | 14 ++--- src/Sema/arith.zig | 14 +++-- src/Type.zig | 2 +- src/Value.zig | 2 +- src/codegen/llvm.zig | 2 +- src/codegen/llvm/FuncGen.zig | 2 +- src/codegen/spirv/CodeGen.zig | 4 +- 12 files changed, 84 insertions(+), 82 deletions(-) diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig index 3d8dc31769..931980edbe 100644 --- a/lib/std/zig/AstGen.zig +++ b/lib/std/zig/AstGen.zig @@ -8073,39 +8073,42 @@ fn identifier( return rvalue(gz, ri, zir_const_ref, ident); } - if (ident_name_raw.len >= 2) integer: { - // Keep in sync with logic in `comptimeExpr2`. - const first_c = ident_name_raw[0]; - if (first_c == 'i' or first_c == 'u') { - const signedness: std.builtin.Signedness = switch (first_c == 'i') { - true => .signed, - false => .unsigned, - }; - if (ident_name_raw.len >= 3 and ident_name_raw[1] == '0') { - return astgen.failNode( - ident, - "primitive integer type '{s}' has leading zero", - .{ident_name_raw}, - ); - } - const bit_count = parseBitCount(ident_name_raw[1..]) catch |err| switch (err) { - error.Overflow => return astgen.failNode( - ident, - "primitive integer type '{s}' exceeds maximum bit width of 65535", - .{ident_name_raw}, - ), - error.InvalidCharacter => break :integer, - }; - const result = try gz.add(.{ - .tag = .int_type, - .data = .{ .int_type = .{ - .src_node = gz.nodeIndexToRelative(ident), - .signedness = signedness, - .bit_count = bit_count, - } }, - }); - return rvalue(gz, ri, result, ident); + int_type: { + if (ident_name_raw.len < 2) break :int_type; + const signedness: std.builtin.Signedness = switch (ident_name_raw[0]) { + 'u' => .unsigned, + 'i' => .signed, + else => break :int_type, + }; + // `u0` already handled by `primitive_instrs` + if (std.mem.eql(u8, ident_name_raw, "i0")) { + return astgen.failNode(ident, "signed integer cannot have bit width 0", .{}); } + if (ident_name_raw[1] == '0') { + assert(ident_name_raw.len >= 3); // `u0` and `i0` handled + return astgen.failNode( + ident, + "primitive integer type '{s}' has leading zero", + .{ident_name_raw}, + ); + } + const bit_count = parseBitCount(ident_name_raw[1..]) catch |err| switch (err) { + error.Overflow => return astgen.failNode( + ident, + "primitive integer type '{s}' exceeds maximum bit width of 65535", + .{ident_name_raw}, + ), + error.InvalidCharacter => break :int_type, + }; + const result = try gz.add(.{ + .tag = .int_type, + .data = .{ .int_type = .{ + .src_node = gz.nodeIndexToRelative(ident), + .signedness = signedness, + .bit_count = bit_count, + } }, + }); + return rvalue(gz, ri, result, ident); } } @@ -10122,32 +10125,37 @@ const primitive_instrs = std.StaticStringMap(Zir.Inst.Ref).initComptime(.{ .{ "c_ushort", .c_ushort_type }, .{ "comptime_float", .comptime_float_type }, .{ "comptime_int", .comptime_int_type }, - .{ "f128", .f128_type }, - .{ "f16", .f16_type }, - .{ "f32", .f32_type }, - .{ "f64", .f64_type }, - .{ "f80", .f80_type }, .{ "false", .bool_false }, - .{ "i16", .i16_type }, - .{ "i32", .i32_type }, - .{ "i64", .i64_type }, - .{ "i128", .i128_type }, - .{ "i8", .i8_type }, - .{ "isize", .isize_type }, .{ "noreturn", .noreturn_type }, .{ "null", .null_value }, .{ "true", .bool_true }, .{ "type", .type_type }, - .{ "u16", .u16_type }, - .{ "u29", .u29_type }, - .{ "u32", .u32_type }, - .{ "u64", .u64_type }, - .{ "u128", .u128_type }, + .{ "undefined", .undef }, + .{ "void", .void_type }, + + .{ "f16", .f16_type }, + .{ "f32", .f32_type }, + .{ "f64", .f64_type }, + .{ "f80", .f80_type }, + .{ "f128", .f128_type }, + + .{ "u0", .u0_type }, .{ "u1", .u1_type }, .{ "u8", .u8_type }, - .{ "undefined", .undef }, + .{ "i8", .i8_type }, + .{ "u16", .u16_type }, + .{ "i16", .i16_type }, + .{ "u29", .u29_type }, + .{ "u32", .u32_type }, + .{ "i32", .i32_type }, + .{ "u64", .u64_type }, + .{ "i64", .i64_type }, + .{ "u80", .u80_type }, + .{ "u128", .u128_type }, + .{ "i128", .i128_type }, + .{ "u256", .u256_type }, .{ "usize", .usize_type }, - .{ "void", .void_type }, + .{ "isize", .isize_type }, }); comptime { diff --git a/lib/std/zig/Zir.zig b/lib/std/zig/Zir.zig index 7029bcc1eb..21a9de21d6 100644 --- a/lib/std/zig/Zir.zig +++ b/lib/std/zig/Zir.zig @@ -2197,7 +2197,6 @@ pub const Inst = struct { /// and `[]Ref`. pub const Ref = enum(u32) { u0_type, - i0_type, u1_type, u8_type, i8_type, diff --git a/src/Air.zig b/src/Air.zig index e20dc0c9ad..ec53a6175e 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -1032,7 +1032,6 @@ pub const Inst = struct { /// The ref `none` is an exception: it has the tag bit set but refers to the InternPool. pub const Ref = enum(u32) { u0_type = @intFromEnum(InternPool.Index.u0_type), - i0_type = @intFromEnum(InternPool.Index.i0_type), u1_type = @intFromEnum(InternPool.Index.u1_type), u8_type = @intFromEnum(InternPool.Index.u8_type), i8_type = @intFromEnum(InternPool.Index.i8_type), diff --git a/src/InternPool.zig b/src/InternPool.zig index 4ab59b3a32..7471fa78f6 100644 --- a/src/InternPool.zig +++ b/src/InternPool.zig @@ -3902,7 +3902,6 @@ pub const Index = enum(u32) { pub const last_value: Index = .empty_tuple; u0_type, - i0_type, u1_type, u8_type, i8_type, @@ -4350,11 +4349,6 @@ pub const static_keys: [static_len]Key = .{ .bits = 0, } }, - .{ .int_type = .{ - .signedness = .signed, - .bits = 0, - } }, - .{ .int_type = .{ .signedness = .unsigned, .bits = 1, @@ -7207,6 +7201,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, io: Io, tid: Zcu.PerThread.Id, key: try items.ensureUnusedCapacity(1); switch (key) { .int_type => |int_type| { + if (int_type.signedness == .signed) assert(int_type.bits > 0); const t: Tag = switch (int_type.signedness) { .signed => .type_int_signed, .unsigned => .type_int_unsigned, @@ -11447,7 +11442,6 @@ pub fn typeOf(ip: *const InternPool, index: Index) Index { // mean that the range of type indices would not be dense. return switch (index) { .u0_type, - .i0_type, .u1_type, .u8_type, .i8_type, @@ -11772,7 +11766,6 @@ pub fn getBackingAddrTag(ip: *const InternPool, val: Index) ?Key.Ptr.BaseAddr.Ta pub fn zigTypeTag(ip: *const InternPool, index: Index) std.builtin.TypeId { return switch (index) { .u0_type, - .i0_type, .u1_type, .u8_type, .i8_type, diff --git a/src/Sema.zig b/src/Sema.zig index 6ba07edab0..425bb77b77 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -19617,6 +19617,9 @@ fn zirReifyInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; const signedness = try sema.resolveBuiltinEnum(block, signedness_src, extra.lhs, .Signedness, .{ .simple = .int_signedness }); const bits: u16 = @intCast(try sema.resolveInt(block, bits_src, extra.rhs, .u16, .{ .simple = .int_bit_width })); + if (bits == 0 and signedness == .signed) { + return sema.fail(block, bits_src, "signed integer cannot have bit width 0", .{}); + } return .fromType(try sema.pt.intType(signedness, bits)); } @@ -20708,7 +20711,7 @@ fn zirIntFromFloat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro } try sema.requireRuntimeBlock(block, src, operand_src); - if (dest_scalar_ty.intInfo(zcu).bits == 0) { + if (dest_scalar_ty.toIntern() == .u0_type) { if (block.wantSafety()) { // Emit an explicit safety check. We can do this one like `abs(x) < 1`. const abs_ref = try block.addTyOp(.abs, operand_ty, operand); @@ -20824,7 +20827,7 @@ fn zirRoundCast( try sema.requireRuntimeBlock(block, src, operand_src); - if (dest_scalar_ty.intInfo(zcu).bits == 0) { + if (dest_scalar_ty.toIntern() == .u0_type) { if (block.wantSafety()) { const abs_ref = try block.addTyOp(.abs, operand_ty, operand); const is_vector = dest_ty.zigTypeTag(zcu) == .vector; diff --git a/src/Sema/LowerZon.zig b/src/Sema/LowerZon.zig index 2012d5167c..96b854885f 100644 --- a/src/Sema/LowerZon.zig +++ b/src/Sema/LowerZon.zig @@ -448,16 +448,12 @@ fn lowerInt( // If lhs has less than the 32 bits rhs can hold, we need to check the max and // min values if (std.math.cast(u5, lhs_info.bits)) |bits| { - const min_int: i32 = if (lhs_info.signedness == .unsigned or bits == 0) b: { - break :b 0; - } else b: { - break :b -(@as(i32, 1) << (bits - 1)); - }; - const max_int: i32 = if (bits == 0) b: { - break :b 0; - } else b: { - break :b (@as(i32, 1) << (bits - @intFromBool(lhs_info.signedness == .signed))) - 1; + const unsigned_bits = bits - @intFromBool(lhs_info.signedness == .signed); + const min_int: i32 = switch (lhs_info.signedness) { + .unsigned => 0, + .signed => -(@as(i32, 1) << unsigned_bits), }; + const max_int: i32 = (@as(i32, 1) << unsigned_bits) - 1; if (rhs < min_int or rhs > max_int) { return self.fail( node, diff --git a/src/Sema/arith.zig b/src/Sema/arith.zig index 024fc5da62..709bc8a4fc 100644 --- a/src/Sema/arith.zig +++ b/src/Sema/arith.zig @@ -20,7 +20,7 @@ pub fn incrementDefinedInt( const zcu = pt.zcu; assert(prev_val.typeOf(zcu).toIntern() == ty.toIntern()); assert(!prev_val.isUndef(zcu)); - if (ty.intInfo(zcu).bits == 0) { + if (ty.toIntern() == .u0_type) { return .{ .overflow = true, .val = try comptimeIntAdd(sema, prev_val, .one_comptime_int) }; } const res = try intAdd(sema, prev_val, try pt.intValue(ty, 1), ty); @@ -1313,7 +1313,7 @@ fn bitwiseBinScalar( 0b11 => return pt.undefValue(ty), }; }; - if (ty.toIntern() == .u0_type or ty.toIntern() == .i0_type) return pt.intValue(ty, 0); + if (ty.toIntern() == .u0_type) return pt.intValue(ty, 0); // zig fmt: off switch (op) { .@"and" => return intBitwiseAnd(sema, def_lhs, def_rhs, ty), @@ -2209,9 +2209,13 @@ fn intBitwiseNot(sema: *Sema, val: Value, ty: Type) !Value { const zcu = pt.zcu; if (val.isUndef(zcu)) return pt.undefValue(ty); - if (ty.toIntern() == .bool_type) return .makeBool(!val.toBool()); + switch (ty.toIntern()) { + .bool_type => return .makeBool(!val.toBool()), + .u0_type => return val, + else => {}, + } + const info = ty.intInfo(zcu); - if (info.bits == 0) return val; var val_space: Value.BigIntSpace = undefined; const val_bigint = val.toBigInt(&val_space, zcu); @@ -2231,7 +2235,7 @@ fn intValueAa(sema: *Sema, ty: Type) !Value { const zcu = pt.zcu; if (ty.toIntern() == .bool_type) return .true; - if (ty.toIntern() == .u0_type or ty.toIntern() == .i0_type) return pt.intValue(ty, 0); + if (ty.toIntern() == .u0_type) return pt.intValue(ty, 0); const info = ty.intInfo(zcu); const buf = try sema.arena.alloc(u8, (info.bits + 7) / 8); diff --git a/src/Type.zig b/src/Type.zig index 1c33640b1e..c16f1c0025 100644 --- a/src/Type.zig +++ b/src/Type.zig @@ -2272,7 +2272,7 @@ pub fn minInt(ty: Type, pt: Zcu.PerThread, dest_ty: Type) !Value { pub fn minIntScalar(ty: Type, pt: Zcu.PerThread, dest_ty: Type) !Value { const zcu = pt.zcu; const info = ty.intInfo(zcu); - if (info.signedness == .unsigned or info.bits == 0) return pt.intValue(dest_ty, 0); + if (info.signedness == .unsigned) return pt.intValue(dest_ty, 0); if (std.math.cast(u6, info.bits - 1)) |shift| { const n = @as(i64, std.math.minInt(i64)) >> (63 - shift); diff --git a/src/Value.zig b/src/Value.zig index 04761539fc..6e7d6e1395 100644 --- a/src/Value.zig +++ b/src/Value.zig @@ -518,9 +518,9 @@ pub fn readFromPackedMemory( }, .int => { if (buffer.len == 0) return pt.intValue(ty, 0); + if (ty.toIntern() == .u0_type) return pt.intValue(ty, 0); const int_info = ty.intInfo(zcu); const bits = int_info.bits; - if (bits == 0) return pt.intValue(ty, 0); // Fast path for integers <= u64 if (bits <= 64) switch (int_info.signedness) { diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 4175293a2c..92ab92cb82 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -2948,7 +2948,7 @@ pub const Object = struct { const target = zcu.getTarget(); const ip = &zcu.intern_pool; return switch (t.toIntern()) { - .u0_type, .i0_type => unreachable, // no runtime bits + .u0_type => unreachable, // no runtime bits inline .u1_type, .u8_type, .i8_type, diff --git a/src/codegen/llvm/FuncGen.zig b/src/codegen/llvm/FuncGen.zig index 40fc1f56a8..8ddfa9c657 100644 --- a/src/codegen/llvm/FuncGen.zig +++ b/src/codegen/llvm/FuncGen.zig @@ -7410,7 +7410,7 @@ fn toLlvmAtomicRmwBinOp( fn minIntConst(b: *Builder, min_ty: Type, as_ty: Builder.Type, zcu: *const Zcu) Allocator.Error!Builder.Constant { const info = min_ty.intInfo(zcu); - if (info.signedness == .unsigned or info.bits == 0) { + if (info.signedness == .unsigned) { return b.intConst(as_ty, 0); } if (std.math.cast(u6, info.bits - 1)) |shift| { diff --git a/src/codegen/spirv/CodeGen.zig b/src/codegen/spirv/CodeGen.zig index aa0b43bada..e8f74b2422 100644 --- a/src/codegen/spirv/CodeGen.zig +++ b/src/codegen/spirv/CodeGen.zig @@ -1327,12 +1327,12 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { .indirect => return try cg.resolveType(.u1, .indirect), }, .int => { - const int_info = ty.intInfo(zcu); - if (int_info.bits == 0) { + if (ty.toIntern() == .u0_type) { assert(repr == .indirect); if (target.os.tag != .opencl) return cg.fail("cannot generate opaque type", .{}); return try cg.module.opaqueType("u0"); } + const int_info = ty.intInfo(zcu); return try cg.module.intType(int_info.signedness, int_info.bits); }, .@"enum" => return try cg.resolveType(ty.intTagType(zcu), repr),