From 587bdfc8e5fdc63911070243c308d182f63a8555 Mon Sep 17 00:00:00 2001 From: Ali Cheraghi Date: Wed, 8 Jul 2026 07:49:12 +0330 Subject: [PATCH 1/6] spirv: decorate physical_storage_buffer types --- src/codegen/spirv/CodeGen.zig | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/codegen/spirv/CodeGen.zig b/src/codegen/spirv/CodeGen.zig index 0ea7567b5eed6859069b031835c57d1b539afbed..0b9e8b547e68185f889ed52f607e1df57d4cf952 100644 --- a/src/codegen/spirv/CodeGen.zig +++ b/src/codegen/spirv/CodeGen.zig @@ -2288,7 +2288,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { }), }, }; - const child_ty_id = try cg.resolveType(child_ty, .indirect); + const child_ty_id = try cg.pointeeType(ptr_info.flags.address_space, child_ty, false); const storage_class = cg.storageClass(ptr_info.flags.address_space); const ptr_ty_id = try cg.ptrType(child_ty_id, storage_class); @@ -4218,13 +4218,16 @@ const MemoryOptions = struct { fn needsLayout(cg: *CodeGen, as: std.lang.AddressSpace, pointee_ty: Type) bool { const target = cg.zcu.getTarget(); if (target.os.tag != .vulkan and target.os.tag != .opengl) return false; - switch (as) { - .uniform, .push_constant, .storage_buffer => {}, - else => return false, - } - return switch (pointee_ty.zigTypeTag(cg.zcu)) { - .@"struct", .@"union", .array => true, - .spirv => pointee_ty.isSpirvRuntimeArray(cg.zcu), + return switch (as) { + .uniform, + .push_constant, + .storage_buffer, + .physical_storage_buffer, + => switch (pointee_ty.zigTypeTag(cg.zcu)) { + .@"struct", .@"union", .array => true, + .spirv => pointee_ty.isSpirvRuntimeArray(cg.zcu), + else => false, + }, else => false, }; } -- 2.54.0 From f02d21c8c888b07ead4034562beb1872faee11d6 Mon Sep 17 00:00:00 2001 From: Ali Cheraghi Date: Wed, 8 Jul 2026 09:10:54 +0330 Subject: [PATCH 2/6] Sema: reject more unrepresentable pointer casts for SPIR-V --- src/Sema.zig | 28 +++++++++++++++++++ src/codegen/spirv/CodeGen.zig | 22 ++++++++++++++- test/behavior/spirv.zig | 9 ++++++ ...pirv_pointer_cast_requires_offset_zero.zig | 20 +++++++++++++ 4 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 test/cases/compile_errors/spirv_pointer_cast_requires_offset_zero.zig diff --git a/src/Sema.zig b/src/Sema.zig index 0e58f0c819bf00d512bd58aa7f346e634bdb0368..48303df878684068303d127882203272cba3ea84 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -21849,6 +21849,34 @@ fn ptrCastFull( try sema.validateRuntimeValue(block, operand_src, operand); + if (zcu.getTarget().cpu.arch.isSpirV() and + src_info.flags.address_space != .physical_storage_buffer and + src_info.flags.address_space == dest_info.flags.address_space and + src_info.child != dest_info.child and + Type.fromInterned(dest_info.child).hasRuntimeBits(zcu)) + { + var cur: Type = .fromInterned(src_info.child); + while (cur.toIntern() != dest_info.child) { + cur = switch (cur.zigTypeTag(zcu)) { + .array, .vector => cur.childType(zcu), + .@"struct" => if (cur.structFieldOffset(0, zcu) == 0) cur.fieldType(0, zcu) else null, + else => null, + } orelse return sema.failWithOwnedErrorMsg(block, msg: { + const msg = try sema.errMsg(src, "cannot cast pointer '{f}' to '{f}'", .{ + operand_ty.fmt(pt), dest_ty.fmt(pt), + }); + errdefer msg.destroy(sema.gpa); + try sema.errNote(src, msg, "'{f}' must appear at offset 0 inside '{f}'", .{ + Type.fromInterned(dest_info.child).fmt(pt), Type.fromInterned(src_info.child).fmt(pt), + }); + try sema.errNote(src, msg, "'{s}' pointers can only reach nested types through a first struct field or an array element", .{ + @tagName(src_info.flags.address_space), + }); + break :msg msg; + }); + } + } + const can_cast_to_int = !target_util.shouldBlockPointerOps(zcu.getTarget(), operand_ty.ptrAddressSpace(zcu)); const need_null_check = can_cast_to_int and block.wantSafety() and operand_ty.ptrAllowsZero(zcu) and !dest_ty.ptrAllowsZero(zcu); const need_align_check = can_cast_to_int and block.wantSafety() and dest_align.compare(.gt, src_align); diff --git a/src/codegen/spirv/CodeGen.zig b/src/codegen/spirv/CodeGen.zig index 0b9e8b547e68185f889ed52f607e1df57d4cf952..e2a42ce78fe6a2907f0437525d269921ca4dd629 100644 --- a/src/codegen/spirv/CodeGen.zig +++ b/src/codegen/spirv/CodeGen.zig @@ -5966,7 +5966,27 @@ fn bitCast( if (src_ty.toIntern() == dst_ty.toIntern()) return src_id; if (src_ty.isPtrAtRuntime(zcu) and dst_ty.isPtrAtRuntime(zcu)) switch (target.os.tag) { - .vulkan, .opengl => if (src_ty.ptrAddressSpace(zcu) != .physical_storage_buffer) return src_id, + .vulkan, .opengl => if (src_ty.ptrAddressSpace(zcu) != .physical_storage_buffer) { + const src_child = src_ty.childType(zcu); + const dst_child = dst_ty.childType(zcu); + if (!dst_child.hasRuntimeBits(zcu)) return src_id; + if (src_child.toIntern() == dst_child.toIntern()) return src_id; + if (src_ty.ptrInfo(zcu).packed_offset.host_size != 0 or + dst_ty.ptrInfo(zcu).packed_offset.host_size != 0) return src_id; + + var indices: std.ArrayList(u32) = .empty; + defer indices.deinit(gpa); + var cur = src_child; + while (cur.toIntern() != dst_child.toIntern()) : (try indices.append(gpa, 0)) { + cur = switch (cur.zigTypeTag(zcu)) { + .array, .vector => cur.childType(zcu), + .@"struct" => cur.fieldType(0, zcu), + else => unreachable, + }; + } + const dst_ty_id = try cg.resolveType(dst_ty, .direct); + return try cg.accessChain(dst_ty_id, src_id, indices.items); + }, else => {}, }; diff --git a/test/behavior/spirv.zig b/test/behavior/spirv.zig index 7bb632b483297fa1e058d6e1940b09cc883a107b..ed07aa33c5455bbf5b5a08093dfc7fe561cb36ab 100644 --- a/test/behavior/spirv.zig +++ b/test/behavior/spirv.zig @@ -49,6 +49,15 @@ test "@SpirvType" { _ = runtime_array; } +const InnerStruct = extern struct { x: u32 }; +const OuterStruct = extern struct { inner: InnerStruct, y: u32 }; +const outer_pc = @extern(*addrspace(.push_constant) const OuterStruct, .{ .name = "outer_pc" }); + +test "@ptrCast to first field type" { + const pc_inner: *addrspace(.push_constant) const InnerStruct = @ptrCast(outer_pc); + _ = pc_inner; +} + test "@SpirvType equality" { try expect(@SpirvType(.sampler) == Sampler); try expect(@SpirvType(.{ .runtime_array = u32 }) == RuntimeArray); diff --git a/test/cases/compile_errors/spirv_pointer_cast_requires_offset_zero.zig b/test/cases/compile_errors/spirv_pointer_cast_requires_offset_zero.zig new file mode 100644 index 0000000000000000000000000000000000000000..da77d7cd169be3f6bb7274f7424bec87101e01dc --- /dev/null +++ b/test/cases/compile_errors/spirv_pointer_cast_requires_offset_zero.zig @@ -0,0 +1,20 @@ +const A = extern struct { x: u32, y: u32 }; +const B = extern struct { a: u64 }; + +const a = @extern(*addrspace(.uniform) const A, .{ + .name = "a", + .decoration = .{ .descriptor = .{ .set = 0, .binding = 0 } }, +}); + +export fn main() callconv(.{ .spirv_kernel = .{ .x = 1, .y = 1, .z = 1 } }) void { + const b: *addrspace(.uniform) const B = @ptrCast(a); + _ = &b; +} + +// error +// backend=selfhosted +// target=spirv32-vulkan +// +// :10:44: error: cannot cast pointer '*addrspace(.uniform) const A' to '*addrspace(.uniform) const B' +// :10:44: note: 'B' must appear at offset 0 inside 'A' +// :10:44: note: 'uniform' pointers can only reach nested types through a first struct field or an array element -- 2.54.0 From 332c73ccd2246af417afaa42597055c9ba6255c1 Mon Sep 17 00:00:00 2001 From: Ali Cheraghi Date: Wed, 8 Jul 2026 10:31:16 +0330 Subject: [PATCH 3/6] spirv: cache composite integer limbs and use 64bit ints when possible --- src/codegen/spirv/CodeGen.zig | 355 ++++++++++++++++++++-------------- 1 file changed, 208 insertions(+), 147 deletions(-) diff --git a/src/codegen/spirv/CodeGen.zig b/src/codegen/spirv/CodeGen.zig index e2a42ce78fe6a2907f0437525d269921ca4dd629..c077896d90d07183ce57378b99eece19af1b95f8 100644 --- a/src/codegen/spirv/CodeGen.zig +++ b/src/codegen/spirv/CodeGen.zig @@ -37,6 +37,10 @@ prologue: Section = .{}, body: Section = .{}, args: std.ArrayList(Id) = .empty, next_arg_index: u32 = 0, +/// Caches the limb extractions for composite integer values so repeated +/// arithmetic on the same operand doesn't re-emit `OpCompositeExtract` per +/// limb per use. Slices are owned by `cg.arena`. +composite_limbs: std.AutoHashMapUnmanaged(Id, []const Id) = .empty, block_stack: std.ArrayList(*Block) = .empty, block_label: Id = .none, /// Whether the current block has been terminated by a terminator @@ -49,7 +53,18 @@ tracked_allocas: std.AutoHashMapUnmanaged(Id, ?Id) = .empty, loop_switches: std.AutoHashMapUnmanaged(Air.Inst.Index, LoopSwitch) = .empty, id_scratch: std.ArrayList(Id) = .empty, -const big_int_bits = @bitSizeOf(u32); +fn bigIntBits(cg: *const CodeGen) u16 { + const target = cg.zcu.getTarget(); + return if (target.cpu.has(.spirv, .int64)) 64 else 32; +} + +fn limbType(cg: *const CodeGen) Type { + return if (cg.bigIntBits() == 64) .u64 else .u32; +} + +fn limbTypeId(cg: *CodeGen) !Id { + return cg.resolveType(cg.limbType(), .direct); +} /// Data can be lowered into in two basic representations: indirect, which is when /// a type is stored in memory, and direct, which is how a type is stored when its @@ -163,6 +178,7 @@ pub fn deinit(cg: *CodeGen) void { cg.block_stack.deinit(gpa); cg.block_results.deinit(gpa); cg.args.deinit(gpa); + cg.composite_limbs.deinit(gpa); cg.tracked_allocas.deinit(gpa); cg.inst_results.deinit(gpa); cg.loop_switches.deinit(gpa); @@ -478,7 +494,7 @@ pub fn backingIntBits(cg: *const CodeGen, bits: u16) struct { u16, bool } { if (bits <= int.bits and int.enabled) return .{ int.bits, false }; } - return .{ std.mem.alignForward(u16, bits, big_int_bits), true }; + return .{ std.mem.alignForward(u16, bits, cg.bigIntBits()), true }; } pub fn intType(cg: *CodeGen, signedness: std.lang.Signedness, bits: u16) !Id { @@ -492,14 +508,16 @@ pub fn intType(cg: *CodeGen, signedness: std.lang.Signedness, bits: u16) !Id { }; const backing_bits, const big_int = cg.backingIntBits(bits); if (big_int) { - const u32_ty = try cg.intType(.unsigned, 32); + const limb_bits = cg.bigIntBits(); + const limb_ty = try cg.intType(.unsigned, limb_bits); + const len_ty = try cg.intType(.unsigned, 32); const len_id = cg.allocId(); try cg.sections.globals.emit(cg.gpa, .OpConstant, .{ - .id_result_type = u32_ty, + .id_result_type = len_ty, .id_result = len_id, - .value = .{ .uint32 = backing_bits / big_int_bits }, + .value = .{ .uint32 = backing_bits / limb_bits }, }); - return cg.arrayType(len_id, u32_ty); + return cg.arrayType(len_id, limb_ty); } const result_id = cg.allocId(); @@ -1443,7 +1461,7 @@ fn constInt(cg: *CodeGen, ty: Type, value: anytype) !Id { .signed => @bitCast(@as(i64, @intCast(value))), .unsigned => @as(u64, @intCast(value)), }; - const n_limbs = backing_bits / big_int_bits; + const n_limbs = backing_bits / cg.bigIntBits(); const fill: u32 = if (signedness == .signed and value < 0) 0xFFFFFFFF else 0; const scratch_top = cg.id_scratch.items.len; defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); @@ -1616,21 +1634,33 @@ fn constant(cg: *CodeGen, ty: Type, val: Value, repr: Repr) Error!Id { const int_info = ty.intInfo(zcu); const backing_bits, const is_big_int = cg.backingIntBits(int_info.bits); if (is_big_int) { - const n_limbs = backing_bits / big_int_bits; + const limb_bits = cg.bigIntBits(); + const n_limbs = backing_bits / limb_bits; const big_result_ty_id = try cg.resolveType(ty, .indirect); var bigint_space: Value.BigIntSpace = undefined; const bigint = val.toBigInt(&bigint_space, zcu); - const limb_values = try gpa.alloc(u32, n_limbs); - defer gpa.free(limb_values); - bigint.writeTwosComplement(std.mem.sliceAsBytes(limb_values), .little); - if (builtin.cpu.arch.endian() == .big) { - for (limb_values) |*limb| limb.* = @byteSwap(limb.*); - } + const limb_bytes = try gpa.alloc(u8, backing_bits / 8); + defer gpa.free(limb_bytes); + bigint.writeTwosComplement(limb_bytes, .little); const scratch_top = cg.id_scratch.items.len; defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); const constituents = try cg.id_scratch.addManyAsSlice(gpa, n_limbs); - for (constituents, 0..) |*c, i| { - c.* = try cg.constInt(.u32, limb_values[i]); + switch (limb_bits) { + 32 => { + const limbs_u32: []u32 = @ptrCast(@alignCast(limb_bytes)); + for (constituents, limbs_u32) |*c, v| { + const host_v = if (builtin.cpu.arch.endian() == .big) @byteSwap(v) else v; + c.* = try cg.constInt(.u32, host_v); + } + }, + 64 => { + const limbs_u64: []u64 = @ptrCast(@alignCast(limb_bytes)); + for (constituents, limbs_u64) |*c, v| { + const host_v = if (builtin.cpu.arch.endian() == .big) @byteSwap(v) else v; + c.* = try cg.constInt(.u64, host_v); + } + }, + else => unreachable, } break :cache try cg.constructComposite(big_result_ty_id, constituents); } @@ -2766,20 +2796,28 @@ const CompositeInt = struct { info: ArithmeticTypeInfo, fn init(cg: *CodeGen, composite_id: Id, info: ArithmeticTypeInfo) !CompositeInt { - const n_limbs: u16 = info.backing_bits / big_int_bits; + const n_limbs: u16 = info.backing_bits / cg.bigIntBits(); const gpa = cg.gpa; - const u32_ty_id = try cg.resolveType(.u32, .direct); + if (cg.composite_limbs.get(composite_id)) |cached| { + assert(cached.len == n_limbs); + const limbs = try cg.id_scratch.addManyAsSlice(gpa, n_limbs); + @memcpy(limbs, cached); + return .{ .cg = cg, .limbs = limbs, .n_limbs = n_limbs, .info = info }; + } + const limb_ty_id = try cg.limbTypeId(); const limbs = try cg.id_scratch.addManyAsSlice(gpa, n_limbs); for (limbs, 0..) |*limb, i| { const result_id = cg.allocId(); try cg.body.emit(gpa, .OpCompositeExtract, .{ - .id_result_type = u32_ty_id, + .id_result_type = limb_ty_id, .id_result = result_id, .composite = composite_id, .indexes = &.{@as(u32, @intCast(i))}, }); limb.* = result_id; } + const cached = try cg.arena.dupe(Id, limbs); + try cg.composite_limbs.put(gpa, composite_id, cached); return .{ .cg = cg, .limbs = limbs, .n_limbs = n_limbs, .info = info }; } @@ -2793,9 +2831,9 @@ const CompositeInt = struct { } fn zero(cg: *CodeGen, info: ArithmeticTypeInfo) !CompositeInt { - const n_limbs: u16 = info.backing_bits / big_int_bits; + const n_limbs: u16 = info.backing_bits / cg.bigIntBits(); const limbs = try cg.id_scratch.addManyAsSlice(cg.gpa, n_limbs); - const zero_id = try cg.constInt(.u32, @as(u32, 0)); + const zero_id = try cg.constInt(cg.limbType(), @as(u64, 0)); for (limbs) |*limb| limb.* = zero_id; return .{ .cg = cg, .limbs = limbs, .n_limbs = n_limbs, .info = info }; } @@ -2808,10 +2846,10 @@ const CompositeInt = struct { fn limbBinOp(ci: CompositeInt, opcode: Opcode, lhs: Id, rhs: Id) !Id { const cg = ci.cg; const gpa = cg.gpa; - const u32_ty_id = try cg.resolveType(.u32, .direct); + const limb_ty_id = try cg.limbTypeId(); const result_id = cg.allocId(); try cg.body.emitRaw(gpa, opcode, 4); - cg.body.writeOperand(Id, u32_ty_id); + cg.body.writeOperand(Id, limb_ty_id); cg.body.writeOperand(Id, result_id); cg.body.writeOperand(Id, lhs); cg.body.writeOperand(Id, rhs); @@ -2821,10 +2859,10 @@ const CompositeInt = struct { fn limbUnOp(ci: CompositeInt, opcode: Opcode, operand: Id) !Id { const cg = ci.cg; const gpa = cg.gpa; - const u32_ty_id = try cg.resolveType(.u32, .direct); + const limb_ty_id = try cg.limbTypeId(); const result_id = cg.allocId(); try cg.body.emitRaw(gpa, opcode, 3); - cg.body.writeOperand(Id, u32_ty_id); + cg.body.writeOperand(Id, limb_ty_id); cg.body.writeOperand(Id, result_id); cg.body.writeOperand(Id, operand); return result_id; @@ -2916,16 +2954,17 @@ const CompositeInt = struct { var cmp_l = l; var cmp_r = r; if (use_signed) { - const i32_ty_id = try cg.resolveType(.i32, .direct); + const signed_limb_ty: Type = if (cg.bigIntBits() == 64) .i64 else .i32; + const signed_limb_ty_id = try cg.resolveType(signed_limb_ty, .direct); const sl = cg.allocId(); try cg.body.emit(gpa, .OpBitcast, .{ - .id_result_type = i32_ty_id, + .id_result_type = signed_limb_ty_id, .id_result = sl, .operand = l, }); const sr = cg.allocId(); try cg.body.emit(gpa, .OpBitcast, .{ - .id_result_type = i32_ty_id, + .id_result_type = signed_limb_ty_id, .id_result = sr, .operand = r, }); @@ -2969,16 +3008,17 @@ const CompositeInt = struct { const comp = zcu.comp; const io = comp.io; - const u32_zig = try pt.intType(.unsigned, 32); - const u32_ty_id = try cg.resolveType(.u32, .direct); + const limb_bits = cg.bigIntBits(); + const limb_zig = try pt.intType(.unsigned, limb_bits); + const limb_ty_id = try cg.limbTypeId(); const carry_struct_ty: Type = .fromInterned(try ip.getTupleType(gpa, io, pt.tid, .{ - .types = &.{ u32_zig.toIntern(), u32_zig.toIntern() }, + .types = &.{ limb_zig.toIntern(), limb_zig.toIntern() }, .values = &.{ .none, .none }, })); const carry_struct_ty_id = try cg.resolveType(carry_struct_ty, .direct); const result_limbs = try cg.id_scratch.addManyAsSlice(gpa, ci.n_limbs); - var carry_id = try cg.constInt(.u32, @as(u32, 0)); + var carry_id = try cg.constInt(cg.limbType(), @as(u64, 0)); const opcode: Opcode = if (is_add) .OpIAddCarry else .OpISubBorrow; @@ -2992,14 +3032,14 @@ const CompositeInt = struct { const sum1 = cg.allocId(); try cg.body.emit(gpa, .OpCompositeExtract, .{ - .id_result_type = u32_ty_id, + .id_result_type = limb_ty_id, .id_result = sum1, .composite = op1, .indexes = &.{0}, }); const carry1 = cg.allocId(); try cg.body.emit(gpa, .OpCompositeExtract, .{ - .id_result_type = u32_ty_id, + .id_result_type = limb_ty_id, .id_result = carry1, .composite = op1, .indexes = &.{1}, @@ -3014,14 +3054,14 @@ const CompositeInt = struct { result_limbs[i] = cg.allocId(); try cg.body.emit(gpa, .OpCompositeExtract, .{ - .id_result_type = u32_ty_id, + .id_result_type = limb_ty_id, .id_result = result_limbs[i], .composite = op2, .indexes = &.{0}, }); const carry2 = cg.allocId(); try cg.body.emit(gpa, .OpCompositeExtract, .{ - .id_result_type = u32_ty_id, + .id_result_type = limb_ty_id, .id_result = carry2, .composite = op2, .indexes = &.{1}, @@ -3036,16 +3076,18 @@ const CompositeInt = struct { fn shl(ci: CompositeInt, shift_amt_id: Id) !CompositeInt { const cg = ci.cg; const gpa = cg.gpa; - const u32_ty_id = try cg.resolveType(.u32, .direct); + const limb_bits = cg.bigIntBits(); + const limb_ty = cg.limbType(); + const limb_ty_id = try cg.limbTypeId(); const bool_ty_id = try cg.resolveType(.bool, .direct); - const zero_id = try cg.constInt(.u32, @as(u32, 0)); - const five_id = try cg.constInt(.u32, @as(u32, 5)); - const thirty_one_id = try cg.constInt(.u32, @as(u32, 31)); - const thirty_two_id = try cg.constInt(.u32, @as(u32, 32)); + const zero_id = try cg.constInt(limb_ty, @as(u64, 0)); + const log2_bits_id = try cg.constInt(limb_ty, @as(u64, std.math.log2_int(u16, limb_bits))); + const bits_minus_1_id = try cg.constInt(limb_ty, @as(u64, limb_bits - 1)); + const bits_id = try cg.constInt(limb_ty, @as(u64, limb_bits)); - const whole = try ci.limbBinOp(.OpShiftRightLogical, shift_amt_id, five_id); - const frac = try ci.limbBinOp(.OpBitwiseAnd, shift_amt_id, thirty_one_id); - const comp_frac = try ci.limbBinOp(.OpISub, thirty_two_id, frac); + const whole = try ci.limbBinOp(.OpShiftRightLogical, shift_amt_id, log2_bits_id); + const frac = try ci.limbBinOp(.OpBitwiseAnd, shift_amt_id, bits_minus_1_id); + const comp_frac = try ci.limbBinOp(.OpISub, bits_id, frac); const frac_is_zero = blk: { const r = cg.allocId(); try cg.body.emit(gpa, .OpIEqual, .{ @@ -3060,12 +3102,12 @@ const CompositeInt = struct { const result_limbs = try cg.id_scratch.addManyAsSlice(gpa, ci.n_limbs); for (0..ci.n_limbs) |i| { - const i_id = try cg.constInt(.u32, @as(u32, @intCast(i))); + const i_id = try cg.constInt(limb_ty, @as(u64, @intCast(i))); var main_val = zero_id; var carry_val = zero_id; for (0..ci.n_limbs) |j| { - const j_id = try cg.constInt(.u32, @as(u32, @intCast(j))); + const j_id = try cg.constInt(limb_ty, @as(u64, @intCast(j))); const j_plus_whole = try ci.limbBinOp(.OpIAdd, j_id, whole); const is_main = blk: { @@ -3082,7 +3124,7 @@ const CompositeInt = struct { main_val = blk: { const r = cg.allocId(); try cg.body.emit(gpa, .OpSelect, .{ - .id_result_type = u32_ty_id, + .id_result_type = limb_ty_id, .id_result = r, .condition = is_main, .object_1 = shifted, @@ -3091,7 +3133,7 @@ const CompositeInt = struct { break :blk r; }; - const one_id = try cg.constInt(.u32, @as(u32, 1)); + const one_id = try cg.constInt(limb_ty, @as(u64, 1)); const j_plus_whole_plus_1 = try ci.limbBinOp(.OpIAdd, j_plus_whole, one_id); const is_carry = blk: { const r = cg.allocId(); @@ -3107,7 +3149,7 @@ const CompositeInt = struct { const guarded_carry = blk: { const r = cg.allocId(); try cg.body.emit(gpa, .OpSelect, .{ - .id_result_type = u32_ty_id, + .id_result_type = limb_ty_id, .id_result = r, .condition = frac_is_zero, .object_1 = zero_id, @@ -3118,7 +3160,7 @@ const CompositeInt = struct { carry_val = blk: { const r = cg.allocId(); try cg.body.emit(gpa, .OpSelect, .{ - .id_result_type = u32_ty_id, + .id_result_type = limb_ty_id, .id_result = r, .condition = is_carry, .object_1 = guarded_carry, @@ -3137,16 +3179,18 @@ const CompositeInt = struct { fn shr(ci: CompositeInt, shift_amt_id: Id, comptime is_arithmetic: bool) !CompositeInt { const cg = ci.cg; const gpa = cg.gpa; - const u32_ty_id = try cg.resolveType(.u32, .direct); + const limb_bits = cg.bigIntBits(); + const limb_ty = cg.limbType(); + const limb_ty_id = try cg.limbTypeId(); const bool_ty_id = try cg.resolveType(.bool, .direct); - const zero_id = try cg.constInt(.u32, @as(u32, 0)); - const five_id = try cg.constInt(.u32, @as(u32, 5)); - const thirty_one_id = try cg.constInt(.u32, @as(u32, 31)); - const thirty_two_id = try cg.constInt(.u32, @as(u32, 32)); + const zero_id = try cg.constInt(limb_ty, @as(u64, 0)); + const log2_bits_id = try cg.constInt(limb_ty, @as(u64, std.math.log2_int(u16, limb_bits))); + const bits_minus_1_id = try cg.constInt(limb_ty, @as(u64, limb_bits - 1)); + const bits_id = try cg.constInt(limb_ty, @as(u64, limb_bits)); - const whole = try ci.limbBinOp(.OpShiftRightLogical, shift_amt_id, five_id); - const frac = try ci.limbBinOp(.OpBitwiseAnd, shift_amt_id, thirty_one_id); - const comp_frac = try ci.limbBinOp(.OpISub, thirty_two_id, frac); + const whole = try ci.limbBinOp(.OpShiftRightLogical, shift_amt_id, log2_bits_id); + const frac = try ci.limbBinOp(.OpBitwiseAnd, shift_amt_id, bits_minus_1_id); + const comp_frac = try ci.limbBinOp(.OpISub, bits_id, frac); const frac_is_zero = blk: { const r = cg.allocId(); try cg.body.emit(gpa, .OpIEqual, .{ @@ -3159,24 +3203,25 @@ const CompositeInt = struct { }; const fill_id = if (is_arithmetic) blk: { - const i32_ty_id = try cg.resolveType(.i32, .direct); + const signed_limb_ty: Type = if (limb_bits == 64) .i64 else .i32; + const signed_limb_ty_id = try cg.resolveType(signed_limb_ty, .direct); const msb_signed = cg.allocId(); try cg.body.emit(gpa, .OpBitcast, .{ - .id_result_type = i32_ty_id, + .id_result_type = signed_limb_ty_id, .id_result = msb_signed, .operand = ci.limbs[ci.n_limbs - 1], }); - const shift31 = try cg.constInt(.i32, @as(i32, 31)); + const shift_amt = try cg.constInt(signed_limb_ty, @as(u64, limb_bits - 1)); const sign_ext = cg.allocId(); try cg.body.emit(gpa, .OpShiftRightArithmetic, .{ - .id_result_type = i32_ty_id, + .id_result_type = signed_limb_ty_id, .id_result = sign_ext, .base = msb_signed, - .shift = shift31, + .shift = shift_amt, }); const back = cg.allocId(); try cg.body.emit(gpa, .OpBitcast, .{ - .id_result_type = u32_ty_id, + .id_result_type = limb_ty_id, .id_result = back, .operand = sign_ext, }); @@ -3189,7 +3234,7 @@ const CompositeInt = struct { const shifted_fill = try ci.limbBinOp(.OpShiftLeftLogical, fill_id, comp_frac); const guarded = cg.allocId(); try cg.body.emit(gpa, .OpSelect, .{ - .id_result_type = u32_ty_id, + .id_result_type = limb_ty_id, .id_result = guarded, .condition = frac_is_zero, .object_1 = zero_id, @@ -3199,12 +3244,12 @@ const CompositeInt = struct { } else zero_id; for (0..ci.n_limbs) |i| { - const i_id = try cg.constInt(.u32, @as(u32, @intCast(i))); + const i_id = try cg.constInt(limb_ty, @as(u64, @intCast(i))); var main_val = fill_id; var carry_val = arith_carry_init; for (0..ci.n_limbs) |j| { - const j_id = try cg.constInt(.u32, @as(u32, @intCast(j))); + const j_id = try cg.constInt(limb_ty, @as(u64, @intCast(j))); const i_plus_whole = try ci.limbBinOp(.OpIAdd, i_id, whole); const is_main = blk: { const r = cg.allocId(); @@ -3220,7 +3265,7 @@ const CompositeInt = struct { main_val = blk: { const r = cg.allocId(); try cg.body.emit(gpa, .OpSelect, .{ - .id_result_type = u32_ty_id, + .id_result_type = limb_ty_id, .id_result = r, .condition = is_main, .object_1 = shifted, @@ -3229,7 +3274,7 @@ const CompositeInt = struct { break :blk r; }; - const one_id = try cg.constInt(.u32, @as(u32, 1)); + const one_id = try cg.constInt(limb_ty, @as(u64, 1)); const i_plus_whole_plus_1 = try ci.limbBinOp(.OpIAdd, i_plus_whole, one_id); const is_carry = blk: { const r = cg.allocId(); @@ -3245,7 +3290,7 @@ const CompositeInt = struct { const guarded_carry = blk: { const r = cg.allocId(); try cg.body.emit(gpa, .OpSelect, .{ - .id_result_type = u32_ty_id, + .id_result_type = limb_ty_id, .id_result = r, .condition = frac_is_zero, .object_1 = zero_id, @@ -3256,7 +3301,7 @@ const CompositeInt = struct { carry_val = blk: { const r = cg.allocId(); try cg.body.emit(gpa, .OpSelect, .{ - .id_result_type = u32_ty_id, + .id_result_type = limb_ty_id, .id_result = r, .condition = is_carry, .object_1 = guarded_carry, @@ -3284,17 +3329,18 @@ const CompositeInt = struct { const n: usize = ci.n_limbs; const total: usize = if (wide) 2 * n else n; - const u32_zig = try pt.intType(.unsigned, 32); - const u32_ty_id = try cg.resolveType(.u32, .direct); + const limb_bits = cg.bigIntBits(); + const limb_zig = try pt.intType(.unsigned, limb_bits); + const limb_ty_id = try cg.limbTypeId(); const pair_struct_ty: Type = .fromInterned(try ip.getTupleType(gpa, io, pt.tid, .{ - .types = &.{ u32_zig.toIntern(), u32_zig.toIntern() }, + .types = &.{ limb_zig.toIntern(), limb_zig.toIntern() }, .values = &.{ .none, .none }, })); const pair_struct_ty_id = try cg.resolveType(pair_struct_ty, .direct); const result_limbs = try cg.id_scratch.addManyAsSlice(gpa, total); - const zero_id = try cg.constInt(.u32, @as(u32, 0)); + const zero_id = try cg.constInt(cg.limbType(), @as(u64, 0)); for (result_limbs) |*r| r.* = zero_id; for (0..n) |i| { @@ -3309,7 +3355,7 @@ const CompositeInt = struct { .opencl => { lo = cg.allocId(); try cg.body.emit(gpa, .OpIMul, .{ - .id_result_type = u32_ty_id, + .id_result_type = limb_ty_id, .id_result = lo, .operand_1 = ci.limbs[i], .operand_2 = other.limbs[j], @@ -3318,7 +3364,7 @@ const CompositeInt = struct { const set = try cg.importExtendedSet(); hi = cg.allocId(); try cg.body.emit(gpa, .OpExtInst, .{ - .id_result_type = u32_ty_id, + .id_result_type = limb_ty_id, .id_result = hi, .set = set, .instruction = .{ .inst = @intFromEnum(spec.OpenClOpcode.u_mul_hi) }, @@ -3336,14 +3382,14 @@ const CompositeInt = struct { lo = cg.allocId(); try cg.body.emit(gpa, .OpCompositeExtract, .{ - .id_result_type = u32_ty_id, + .id_result_type = limb_ty_id, .id_result = lo, .composite = mul_result, .indexes = &.{0}, }); hi = cg.allocId(); try cg.body.emit(gpa, .OpCompositeExtract, .{ - .id_result_type = u32_ty_id, + .id_result_type = limb_ty_id, .id_result = hi, .composite = mul_result, .indexes = &.{1}, @@ -3361,14 +3407,14 @@ const CompositeInt = struct { const sum1 = cg.allocId(); try cg.body.emit(gpa, .OpCompositeExtract, .{ - .id_result_type = u32_ty_id, + .id_result_type = limb_ty_id, .id_result = sum1, .composite = add1, .indexes = &.{0}, }); const c1 = cg.allocId(); try cg.body.emit(gpa, .OpCompositeExtract, .{ - .id_result_type = u32_ty_id, + .id_result_type = limb_ty_id, .id_result = c1, .composite = add1, .indexes = &.{1}, @@ -3384,14 +3430,14 @@ const CompositeInt = struct { result_limbs[k] = cg.allocId(); try cg.body.emit(gpa, .OpCompositeExtract, .{ - .id_result_type = u32_ty_id, + .id_result_type = limb_ty_id, .id_result = result_limbs[k], .composite = add2, .indexes = &.{0}, }); const c2 = cg.allocId(); try cg.body.emit(gpa, .OpCompositeExtract, .{ - .id_result_type = u32_ty_id, + .id_result_type = limb_ty_id, .id_result = c2, .composite = add2, .indexes = &.{1}, @@ -3412,7 +3458,8 @@ const CompositeInt = struct { if (ci.info.bits == ci.info.backing_bits) return ci; const cg = ci.cg; const gpa = cg.gpa; - const top_bits: u16 = ci.info.bits % big_int_bits; + const limb_bits = cg.bigIntBits(); + const top_bits: u16 = ci.info.bits % limb_bits; assert(top_bits != 0); const result_limbs = try cg.id_scratch.addManyAsSlice(gpa, ci.n_limbs); @@ -3421,41 +3468,43 @@ const CompositeInt = struct { } const top_limb = ci.limbs[ci.n_limbs - 1]; + const limb_ty = cg.limbType(); + const limb_signed_ty: Type = if (limb_bits == 64) .i64 else .i32; switch (ci.info.signedness) { .unsigned => { - const mask_val: u32 = (@as(u32, 1) << @as(u5, @intCast(top_bits))) - 1; - const mask_id = try cg.constInt(.u32, mask_val); + const mask_val: u64 = (@as(u64, 1) << @as(u6, @intCast(top_bits))) - 1; + const mask_id = try cg.constInt(limb_ty, mask_val); result_limbs[ci.n_limbs - 1] = try ci.limbBinOp(.OpBitwiseAnd, top_limb, mask_id); }, .signed => { - const u32_ty_id = try cg.resolveType(.u32, .direct); - const i32_ty_id = try cg.resolveType(.i32, .direct); - const shift_amt: u32 = 32 - top_bits; - const shift_id = try cg.constInt(.u32, shift_amt); + const limb_ty_id = try cg.limbTypeId(); + const signed_ty_id = try cg.resolveType(limb_signed_ty, .direct); + const shift_amt: u32 = @intCast(limb_bits - top_bits); + const shift_id = try cg.constInt(limb_ty, shift_amt); const as_signed = cg.allocId(); try cg.body.emit(gpa, .OpBitcast, .{ - .id_result_type = i32_ty_id, + .id_result_type = signed_ty_id, .id_result = as_signed, .operand = top_limb, }); const shifted_left = cg.allocId(); try cg.body.emit(gpa, .OpShiftLeftLogical, .{ - .id_result_type = i32_ty_id, + .id_result_type = signed_ty_id, .id_result = shifted_left, .base = as_signed, .shift = shift_id, }); const shifted_right = cg.allocId(); try cg.body.emit(gpa, .OpShiftRightArithmetic, .{ - .id_result_type = i32_ty_id, + .id_result_type = signed_ty_id, .id_result = shifted_right, .base = shifted_left, .shift = shift_id, }); const back = cg.allocId(); try cg.body.emit(gpa, .OpBitcast, .{ - .id_result_type = u32_ty_id, + .id_result_type = limb_ty_id, .id_result = back, .operand = shifted_right, }); @@ -4552,13 +4601,14 @@ fn airShift(cg: *CodeGen, inst: Air.Inst.Index, unsigned: Opcode, signed: Opcode switch (info.class) { .composite_integer => { const shift_info = cg.arithmeticTypeInfo(shift.ty); + const limb_ty = cg.limbType(); const shift_amt_id = switch (shift_info.class) { .composite_integer => blk: { const shift_id = try shift.materialize(cg); - const u32_ty_id = try cg.resolveType(.u32, .direct); + const limb_ty_id = try cg.limbTypeId(); const result_id = cg.allocId(); try cg.body.emit(cg.gpa, .OpCompositeExtract, .{ - .id_result_type = u32_ty_id, + .id_result_type = limb_ty_id, .id_result = result_id, .composite = shift_id, .indexes = &.{@as(u32, 0)}, @@ -4566,7 +4616,7 @@ fn airShift(cg: *CodeGen, inst: Air.Inst.Index, unsigned: Opcode, signed: Opcode break :blk result_id; }, else => blk: { - const converted = try cg.buildConvert(.u32, shift); + const converted = try cg.buildConvert(limb_ty, shift); break :blk try converted.materialize(cg); }, }; @@ -4887,12 +4937,12 @@ fn airAbs(cg: *CodeGen, inst: Air.Inst.Index) !?Id { const is_neg = try ci.cmp(ci_z, .lt); const ci_neg = try ci_z.addSub(ci, false); const result_info = cg.arithmeticTypeInfo(result_ty); - const u32_ty_id = try cg.resolveType(.u32, .direct); + const limb_ty_id = try cg.limbTypeId(); const result_limbs = try cg.id_scratch.addManyAsSlice(cg.gpa, ci.n_limbs); for (0..ci.n_limbs) |i| { result_limbs[i] = cg.allocId(); try cg.body.emit(cg.gpa, .OpSelect, .{ - .id_result_type = u32_ty_id, + .id_result_type = limb_ty_id, .id_result = result_limbs[i], .condition = is_neg, .object_1 = ci_neg.limbs[i], @@ -5066,12 +5116,13 @@ fn airMulOverflow(cg: *CodeGen, inst: Air.Inst.Index) !?Id { const high_limbs = wide_limbs[ci_lhs2.n_limbs..]; const bool_ty_id = try cg.resolveType(.bool, .direct); - const u32_ty_id = try cg.resolveType(.u32, .direct); - const n: usize = info.backing_bits / big_int_bits; + const limb_ty_id = try cg.limbTypeId(); + const limb_ty = cg.limbType(); + const n: usize = info.backing_bits / cg.bigIntBits(); const ov_bool = switch (info.signedness) { .unsigned => blk: { - const zero_id = try cg.constInt(.u32, @as(u32, 0)); + const zero_id = try cg.constInt(limb_ty, @as(u64, 0)); var any_nonzero = cg.allocId(); try cg.body.emit(gpa, .OpINotEqual, .{ .id_result_type = bool_ty_id, @@ -5104,32 +5155,33 @@ fn airMulOverflow(cg: *CodeGen, inst: Air.Inst.Index) !?Id { .signed => blk: { const ci_res = try CompositeInt.init(cg, result_val_id, info); const top_limb = ci_res.limbs[n - 1]; - const i32_ty_id = try cg.resolveType(.i32, .direct); + const signed_limb_ty: Type = if (cg.bigIntBits() == 64) .i64 else .i32; + const signed_limb_ty_id = try cg.resolveType(signed_limb_ty, .direct); - const top_bits: u16 = if (info.bits % big_int_bits == 0) - big_int_bits + const top_bits: u16 = if (info.bits % cg.bigIntBits() == 0) + cg.bigIntBits() else - info.bits % big_int_bits; + info.bits % cg.bigIntBits(); - const shift_amt: u32 = top_bits - 1; - const shift_id = try cg.constInt(.u32, shift_amt); + const shift_amt: u64 = top_bits - 1; + const shift_id = try cg.constInt(limb_ty, shift_amt); const as_signed = cg.allocId(); try cg.body.emit(gpa, .OpBitcast, .{ - .id_result_type = i32_ty_id, + .id_result_type = signed_limb_ty_id, .id_result = as_signed, .operand = top_limb, }); const sign_ext = cg.allocId(); try cg.body.emit(gpa, .OpShiftRightArithmetic, .{ - .id_result_type = i32_ty_id, + .id_result_type = signed_limb_ty_id, .id_result = sign_ext, .base = as_signed, .shift = shift_id, }); const expected = cg.allocId(); try cg.body.emit(gpa, .OpBitcast, .{ - .id_result_type = u32_ty_id, + .id_result_type = limb_ty_id, .id_result = expected, .operand = sign_ext, }); @@ -5162,25 +5214,25 @@ fn airMulOverflow(cg: *CodeGen, inst: Air.Inst.Index) !?Id { } if (info.bits != info.backing_bits) { - const top_bits_s: u16 = info.bits % big_int_bits; - const s_shift_id = try cg.constInt(.u32, top_bits_s - 1); + const top_bits_s: u16 = info.bits % cg.bigIntBits(); + const s_shift_id = try cg.constInt(limb_ty, @as(u64, top_bits_s - 1)); const top_as_signed = cg.allocId(); try cg.body.emit(gpa, .OpBitcast, .{ - .id_result_type = i32_ty_id, + .id_result_type = signed_limb_ty_id, .id_result = top_as_signed, .operand = top_limb, }); const top_sign_ext = cg.allocId(); try cg.body.emit(gpa, .OpShiftRightArithmetic, .{ - .id_result_type = i32_ty_id, + .id_result_type = signed_limb_ty_id, .id_result = top_sign_ext, .base = top_as_signed, .shift = s_shift_id, }); const top_expected = cg.allocId(); try cg.body.emit(gpa, .OpBitcast, .{ - .id_result_type = u32_ty_id, + .id_result_type = limb_ty_id, .id_result = top_expected, .operand = top_sign_ext, }); @@ -6117,15 +6169,17 @@ fn airIntCast(cg: *CodeGen, inst: Air.Inst.Index) !?Id { if (src_composite and dst_composite) { const src_id = try src.materialize(cg); - const src_n: u16 = src_info.backing_bits / big_int_bits; - const dst_n: u16 = dst_info.backing_bits / big_int_bits; + const limb_bits = cg.bigIntBits(); + const limb_ty = cg.limbType(); + const limb_ty_id = try cg.limbTypeId(); + const src_n: u16 = src_info.backing_bits / limb_bits; + const dst_n: u16 = dst_info.backing_bits / limb_bits; const result_limbs = try cg.id_scratch.addManyAsSlice(gpa, dst_n); const min_n = @min(src_n, dst_n); - const u32_ty_id = try cg.resolveType(.u32, .direct); for (0..min_n) |i| { result_limbs[i] = cg.allocId(); try cg.body.emit(gpa, .OpCompositeExtract, .{ - .id_result_type = u32_ty_id, + .id_result_type = limb_ty_id, .id_result = result_limbs[i], .composite = src_id, .indexes = &.{@as(u32, @intCast(i))}, @@ -6133,30 +6187,31 @@ fn airIntCast(cg: *CodeGen, inst: Air.Inst.Index) !?Id { } if (dst_n > src_n) { const fill = if (src_info.signedness == .signed) blk: { - const i32_ty_id = try cg.resolveType(.i32, .direct); + const signed_limb_ty: Type = if (limb_bits == 64) .i64 else .i32; + const signed_limb_ty_id = try cg.resolveType(signed_limb_ty, .direct); const msb = result_limbs[src_n - 1]; const msb_signed = cg.allocId(); try cg.body.emit(gpa, .OpBitcast, .{ - .id_result_type = i32_ty_id, + .id_result_type = signed_limb_ty_id, .id_result = msb_signed, .operand = msb, }); - const shift31 = try cg.constInt(.i32, @as(i32, 31)); + const shift_amt = try cg.constInt(signed_limb_ty, @as(u64, limb_bits - 1)); const sign_ext = cg.allocId(); try cg.body.emit(gpa, .OpShiftRightArithmetic, .{ - .id_result_type = i32_ty_id, + .id_result_type = signed_limb_ty_id, .id_result = sign_ext, .base = msb_signed, - .shift = shift31, + .shift = shift_amt, }); const back = cg.allocId(); try cg.body.emit(gpa, .OpBitcast, .{ - .id_result_type = u32_ty_id, + .id_result_type = limb_ty_id, .id_result = back, .operand = sign_ext, }); break :blk back; - } else try cg.constInt(.u32, @as(u32, 0)); + } else try cg.constInt(limb_ty, @as(u64, 0)); for (min_n..dst_n) |i| { result_limbs[i] = fill; } @@ -6166,16 +6221,18 @@ fn airIntCast(cg: *CodeGen, inst: Air.Inst.Index) !?Id { return try normalized.materialize(dst_ty); } else if (src_composite and !dst_composite) { const src_id = try src.materialize(cg); - const u32_ty_id = try cg.resolveType(.u32, .direct); - if (dst_info.backing_bits <= 32) { + const limb_bits = cg.bigIntBits(); + const limb_ty = cg.limbType(); + const limb_ty_id = try cg.limbTypeId(); + if (dst_info.backing_bits <= limb_bits) { const limb0 = cg.allocId(); try cg.body.emit(gpa, .OpCompositeExtract, .{ - .id_result_type = u32_ty_id, + .id_result_type = limb_ty_id, .id_result = limb0, .composite = src_id, .indexes = &.{@as(u32, 0)}, }); - const tmp: Temporary = .init(.u32, limb0); + const tmp: Temporary = .init(limb_ty, limb0); const converted = try cg.buildConvert(dst_ty, tmp); const result = if (dst_info.bits < src_info.bits) try cg.normalize(converted, dst_info) @@ -6183,16 +6240,17 @@ fn airIntCast(cg: *CodeGen, inst: Air.Inst.Index) !?Id { converted; return try result.materialize(cg); } else { + assert(limb_bits == 32); // dst > 64 while limbs are 64 shouldn't happen — dst fits in one 64-bit limb. const limb0 = cg.allocId(); try cg.body.emit(gpa, .OpCompositeExtract, .{ - .id_result_type = u32_ty_id, + .id_result_type = limb_ty_id, .id_result = limb0, .composite = src_id, .indexes = &.{@as(u32, 0)}, }); const limb1 = cg.allocId(); try cg.body.emit(gpa, .OpCompositeExtract, .{ - .id_result_type = u32_ty_id, + .id_result_type = limb_ty_id, .id_result = limb1, .composite = src_id, .indexes = &.{@as(u32, 1)}, @@ -6234,19 +6292,21 @@ fn airIntCast(cg: *CodeGen, inst: Air.Inst.Index) !?Id { return try result.materialize(cg); } } else { - const dst_n: u16 = dst_info.backing_bits / big_int_bits; + const limb_bits = cg.bigIntBits(); + const limb_ty = cg.limbType(); + const limb_ty_id = try cg.limbTypeId(); + const dst_n: u16 = dst_info.backing_bits / limb_bits; const result_limbs = try cg.id_scratch.addManyAsSlice(gpa, dst_n); - const u32_ty_id = try cg.resolveType(.u32, .direct); - if (src_info.backing_bits <= 32) { - const converted = try cg.buildConvert(.u32, src); + if (src_info.backing_bits <= limb_bits) { + const converted = try cg.buildConvert(limb_ty, src); result_limbs[0] = try converted.materialize(cg); } else { const src_as_u64 = try cg.buildConvert(.u64, src); const src_id = try src_as_u64.materialize(cg); result_limbs[0] = cg.allocId(); try cg.body.emit(gpa, .OpUConvert, .{ - .id_result_type = u32_ty_id, + .id_result_type = limb_ty_id, .id_result = result_limbs[0], .unsigned_value = src_id, }); @@ -6261,38 +6321,39 @@ fn airIntCast(cg: *CodeGen, inst: Air.Inst.Index) !?Id { }); result_limbs[1] = cg.allocId(); try cg.body.emit(gpa, .OpUConvert, .{ - .id_result_type = u32_ty_id, + .id_result_type = limb_ty_id, .id_result = result_limbs[1], .unsigned_value = hi, }); } // Sign/zero-extend remaining limbs. - const fill_start: u16 = if (src_info.backing_bits <= 32) 1 else 2; + const fill_start: u16 = if (src_info.backing_bits <= limb_bits) 1 else 2; const fill = if (src_info.signedness == .signed) blk: { - const i32_ty_id = try cg.resolveType(.i32, .direct); + const signed_limb_ty: Type = if (limb_bits == 64) .i64 else .i32; + const signed_limb_ty_id = try cg.resolveType(signed_limb_ty, .direct); const msb = result_limbs[fill_start - 1]; const msb_signed = cg.allocId(); try cg.body.emit(gpa, .OpBitcast, .{ - .id_result_type = i32_ty_id, + .id_result_type = signed_limb_ty_id, .id_result = msb_signed, .operand = msb, }); - const shift31 = try cg.constInt(.i32, @as(i32, 31)); + const shift_amt = try cg.constInt(signed_limb_ty, @as(u64, limb_bits - 1)); const sign_ext = cg.allocId(); try cg.body.emit(gpa, .OpShiftRightArithmetic, .{ - .id_result_type = i32_ty_id, + .id_result_type = signed_limb_ty_id, .id_result = sign_ext, .base = msb_signed, - .shift = shift31, + .shift = shift_amt, }); const back = cg.allocId(); try cg.body.emit(gpa, .OpBitcast, .{ - .id_result_type = u32_ty_id, + .id_result_type = limb_ty_id, .id_result = back, .operand = sign_ext, }); break :blk back; - } else try cg.constInt(.u32, @as(u32, 0)); + } else try cg.constInt(limb_ty, @as(u64, 0)); for (fill_start..dst_n) |i| { result_limbs[i] = fill; } -- 2.54.0 From c498b66434412616a97328a7f7a05014aa5183dc Mon Sep 17 00:00:00 2001 From: Ali Cheraghi Date: Wed, 8 Jul 2026 16:44:29 +0330 Subject: [PATCH 4/6] spirv: spec constants add `std.spirv.specConst()` and support lowering literal values as string in inline assembly --- lib/std/spirv.zig | 30 ++++++ src/codegen/spirv/Assembler.zig | 160 ++++++++++++++++++++++++++++++-- src/codegen/spirv/CodeGen.zig | 118 +++++++++++++++-------- src/link/SpirV/BinaryModule.zig | 2 +- 4 files changed, 262 insertions(+), 48 deletions(-) diff --git a/lib/std/spirv.zig b/lib/std/spirv.zig index 6f9fd74967053ec295ad14882d4abda7eedccc18..513f73c53bf7b203ab553f4318dbd68f37e68cf0 100644 --- a/lib/std/spirv.zig +++ b/lib/std/spirv.zig @@ -82,3 +82,33 @@ pub fn workgroupBarrier() void { .{ .acquire_release = true, .workgroup_memory = true }, ); } + +pub fn specConst(T: type, comptime default_value: T, comptime spec_id: u32) T { + switch (@typeInfo(T)) { + .bool => { + const op = if (default_value) "OpSpecConstantTrue" else "OpSpecConstantFalse"; + return asm ("%ret = " ++ op ++ " %ty\n" ++ + "OpDecorate %ret SpecId $spec_id" + : [ret] "" (-> T), + : [ty] "t" (T), + [spec_id] "c" (spec_id), + ); + }, + .int, .float => return asm ( + \\%ret = OpSpecConstant %ty $default_value + \\OpDecorate %ret SpecId $spec_id" + : [ret] "" (-> T), + : [ty] "t" (T), + [default_value] "c" (default_value), + [spec_id] "c" (spec_id), + ), + .vector => return asm ( + \\%ret = OpSpecConstantComposite %ty %default_value %spec_id + : [ret] "" (-> T), + : [ty] "t" (T), + [default_value] "c" (default_value), + [spec_id] "c" (spec_id), + ), + else => @compileError("unsupported spec constant type"), + } +} diff --git a/src/codegen/spirv/Assembler.zig b/src/codegen/spirv/Assembler.zig index f18f9028212b4916ed70a89d080e28a3bad75f3f..04549eb900aae5604849fbcdb261dc6083d608ee 100644 --- a/src/codegen/spirv/Assembler.zig +++ b/src/codegen/spirv/Assembler.zig @@ -58,6 +58,10 @@ const Operand = union(enum) { pub fn deinit(ass: *Assembler) void { const gpa = ass.cg.gpa; for (ass.errors.items) |err| gpa.free(err.msg); + for (ass.value_map.values()) |v| switch (v) { + .constant_composite => |cc| gpa.free(cc.values), + else => {}, + }; ass.tokens.deinit(gpa); ass.errors.deinit(gpa); ass.inst.operands.deinit(gpa); @@ -132,8 +136,18 @@ const AsmValue = union(enum) { value: Id, /// A type registered into the module's type system. ty: Id, - /// A pre-supplied constant integer value. - constant: u32, + /// A pre-supplied constant value, holding the raw bit pattern of the input. + /// For integers the value is sign-extended (for signed) or zero-extended + /// (for unsigned) to 64 bits. For floats, the value is the bit pattern + /// zero-extended from the float's width to 64 bits. + constant: u64, + /// A vector "c" input expanded by `processSpecConstVector`. + constant_composite: struct { + child: Id, + child_kind: std.lang.TypeId, + child_bit_width: u16, + values: []u64, + }, string: []const u8, /// Retrieve the result-id of this AsmValue. Asserts that this AsmValue @@ -145,6 +159,7 @@ const AsmValue = union(enum) { .unresolved_forward_reference, // TODO: Lower this value as constant? .constant, + .constant_composite, .string, => unreachable, .value => |result| result, @@ -178,6 +193,12 @@ fn processInstruction(ass: *Assembler) !void { }; break :blk .{ .value = try cg.importInstructionSet(set_tag) }; }, + .OpSpecConstantComposite => blk: { + if (try ass.processSpecConstVector()) |result| { + break :blk result; + } + break :blk (try ass.processGenericInstruction()) orelse return; + }, else => switch (ass.inst.opcode.class()) { .type_declaration => try ass.processTypeInstruction(), else => (try ass.processGenericInstruction()) orelse return, @@ -398,6 +419,87 @@ fn processGenericInstruction(ass: *Assembler) !?AsmValue { return null; } +/// Handles `%ret = OpSpecConstantComposite %ty %vec %spec_id` where `%vec` is a +/// vector `"c"` input and `%spec_id` is a base SpecId `"c"` input. +/// returns null to fall back to normal processing. +fn processSpecConstVector(ass: *Assembler) !?AsmValue { + if (ass.inst.operands.items.len != 4) return null; + const vec_ref = switch (ass.inst.operands.items[2]) { + .ref_id => |i| i, + else => return null, + }; + const sid_ref = switch (ass.inst.operands.items[3]) { + .ref_id => |i| i, + else => return null, + }; + const cc = switch (try ass.resolveRef(vec_ref)) { + .constant_composite => |cc| cc, + else => return null, + }; + const spec_id_base = switch (try ass.resolveRef(sid_ref)) { + .constant => |v| v, + else => return null, + }; + + const cg = ass.cg; + const gpa = cg.gpa; + const ty_ref = switch (ass.inst.operands.items[0]) { + .ref_id => |i| i, + else => return ass.fail(0, "missing result type", .{}), + }; + const composite_ty_id = switch (try ass.resolveRef(ty_ref)) { + .ty => |id| id, + else => return ass.fail(0, "%ty must be a type", .{}), + }; + + const globals = &cg.sections.globals; + const annotations = &cg.sections.annotations; + const literal_words: usize = if (cc.child_bit_width <= @bitSizeOf(Word)) 1 else 2; + + const elem_ids = try gpa.alloc(Id, cc.values.len); + defer gpa.free(elem_ids); + for (cc.values, elem_ids, 0..) |value, *elem_id_out, i| { + const elem_id = cg.allocId(); + elem_id_out.* = elem_id; + + switch (cc.child_kind) { + .bool => { + const opcode: Opcode = if (value & 1 != 0) .OpSpecConstantTrue else .OpSpecConstantFalse; + try globals.emitRaw(gpa, opcode, 2); + globals.writeOperand(Id, cc.child); + globals.writeOperand(Id, elem_id); + }, + .int, .float => { + try globals.emitRaw(gpa, .OpSpecConstant, 2 + literal_words); + globals.writeOperand(Id, cc.child); + globals.writeOperand(Id, elem_id); + if (literal_words == 1) { + globals.writeWord(@truncate(value)); + } else { + globals.writeDoubleWord(value); + } + }, + else => unreachable, + } + + const spec_id_word = std.math.cast(u32, spec_id_base + i) orelse { + return ass.fail(0, "SpecId {} does not fit in 32 bits", .{spec_id_base + i}); + }; + try annotations.emitRaw(gpa, .OpDecorate, 3); + annotations.writeOperand(Id, elem_id); + annotations.writeWord(@intFromEnum(spec.Decoration.spec_id)); + annotations.writeWord(spec_id_word); + } + + const result_id = cg.allocId(); + try globals.emitRaw(gpa, .OpSpecConstantComposite, 2 + cc.values.len); + globals.writeOperand(Id, composite_ty_id); + globals.writeOperand(Id, result_id); + for (elem_ids) |id| globals.writeOperand(Id, id); + + return .{ .value = result_id }; +} + fn resolveMaybeForwardRef(ass: *Assembler, ref: AsmValue.Ref) !AsmValue { const value = ass.value_map.values()[ref]; switch (value) { @@ -579,7 +681,14 @@ fn parseValueEnum(ass: *Assembler, kind: spec.OperandKind) !void { return ass.fail(tok.start, "invalid placeholder '${s}'", .{name}); }; switch (value) { - .constant => |literal32| { + .constant => |literal| { + const literal32 = std.math.cast(u32, literal) orelse { + return ass.fail( + tok.start, + "placeholder value {} does not fit in 32 bits", + .{literal}, + ); + }; try ass.inst.operands.append(gpa, .{ .value = literal32 }); }, .string => |str| { @@ -646,7 +755,14 @@ fn parseLiteralInteger(ass: *Assembler) !void { return ass.fail(tok.start, "invalid placeholder '${s}'", .{name}); }; switch (value) { - .constant => |literal32| { + .constant => |literal| { + const literal32 = std.math.cast(u32, literal) orelse { + return ass.fail( + tok.start, + "placeholder value {} does not fit in 32 bits", + .{literal}, + ); + }; try ass.inst.operands.append(gpa, .{ .literal32 = literal32 }); }, else => { @@ -679,7 +795,14 @@ fn parseLiteralExtInstInteger(ass: *Assembler) !void { return ass.fail(tok.start, "invalid placeholder '${s}'", .{name}); }; switch (value) { - .constant => |literal32| { + .constant => |literal| { + const literal32 = std.math.cast(u32, literal) orelse { + return ass.fail( + tok.start, + "placeholder value {} does not fit in 32 bits", + .{literal}, + ); + }; try ass.inst.operands.append(gpa, .{ .literal32 = literal32 }); }, else => { @@ -767,8 +890,12 @@ fn parseContextDependentInt(ass: *Assembler, signedness: std.lang.Signedness, wi return ass.fail(tok.start, "invalid placeholder '${s}'", .{name}); }; switch (value) { - .constant => |literal32| { - try ass.inst.operands.append(gpa, .{ .literal32 = literal32 }); + .constant => |literal| { + if (width <= @bitSizeOf(spec.Word)) { + try ass.inst.operands.append(gpa, .{ .literal32 = @truncate(literal) }); + } else { + try ass.inst.operands.append(gpa, .{ .literal64 = literal }); + } }, else => { return ass.fail(tok.start, "value '{s}' cannot be used as placeholder", .{name}); @@ -815,6 +942,25 @@ fn parseContextDependentFloat(ass: *Assembler, comptime width: u16) !void { const Int = @Int(.unsigned, width); const tok = ass.currentToken(); + if (ass.eatToken(.placeholder)) { + const name = ass.tokenText(tok)[1..]; + const value = ass.value_map.get(name) orelse { + return ass.fail(tok.start, "invalid placeholder '${s}'", .{name}); + }; + switch (value) { + .constant => |literal| { + if (width <= @bitSizeOf(spec.Word)) { + try ass.inst.operands.append(gpa, .{ .literal32 = @truncate(literal) }); + } else { + try ass.inst.operands.append(gpa, .{ .literal64 = literal }); + } + }, + else => { + return ass.fail(tok.start, "value '{s}' cannot be used as placeholder", .{name}); + }, + } + return; + } try ass.expectToken(.value); const text = ass.tokenText(tok); diff --git a/src/codegen/spirv/CodeGen.zig b/src/codegen/spirv/CodeGen.zig index c077896d90d07183ce57378b99eece19af1b95f8..ea67f6d0c86dd6b9464f0ba47f8b4233d32d8e73 100644 --- a/src/codegen/spirv/CodeGen.zig +++ b/src/codegen/spirv/CodeGen.zig @@ -423,7 +423,7 @@ pub fn addEntryPointDeps( cg: *CodeGen, decl_index: Decl.Index, seen: *std.bit_set.Dynamic, - interface: *std.array_list.Managed(Id), + interface: *std.ArrayList(Id), ) !void { const decl = cg.declPtr(decl_index); const deps = cg.decl_deps.items[decl.begin_dep..decl.end_dep]; @@ -435,7 +435,7 @@ pub fn addEntryPointDeps( seen.set(@intFromEnum(decl_index)); if (decl.kind == .global) { - try interface.append(decl.result_id); + try interface.append(cg.gpa, decl.result_id); } for (deps) |dep| { @@ -1806,11 +1806,11 @@ fn constant(cg: *CodeGen, ty: Type, val: Value, repr: Repr) Error!Id { const struct_type = zcu.typeToStruct(ty).?; assert(struct_type.layout != .@"packed"); // packed structs use `bitpack` - var types = std.array_list.Managed(Type).init(gpa); - defer types.deinit(); + var types: std.ArrayList(Type) = .empty; + defer types.deinit(gpa); - var constituents = std.array_list.Managed(Id).init(gpa); - defer constituents.deinit(); + var constituents: std.ArrayList(Id) = .empty; + defer constituents.deinit(gpa); var it = struct_type.iterateRuntimeOrder(ip); while (it.next()) |field_index| { @@ -1824,8 +1824,8 @@ fn constant(cg: *CodeGen, ty: Type, val: Value, repr: Repr) Error!Id { const field_val = try val.fieldValue(pt, field_index); const field_id = try cg.constant(field_ty, field_val, .indirect); - try types.append(field_ty); - try constituents.append(field_id); + try types.append(gpa, field_ty); + try constituents.append(gpa, field_id); } const comp_ty_id = try cg.resolveType(ty, .direct); @@ -2366,11 +2366,11 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { return try cg.resolveType(.fromInterned(struct_type.packed_backing_int_type), .direct); } - var member_types = std.array_list.Managed(Id).init(gpa); - defer member_types.deinit(); + var member_types: std.ArrayList(Id) = .empty; + defer member_types.deinit(gpa); - var member_names = std.array_list.Managed([]const u8).init(gpa); - defer member_names.deinit(); + var member_names: std.ArrayList([]const u8) = .empty; + defer member_names.deinit(gpa); var it = struct_type.iterateRuntimeOrder(ip); while (it.next()) |field_index| { @@ -2378,8 +2378,8 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { if (!field_ty.hasRuntimeBits(zcu)) continue; const field_name = struct_type.field_names.get(ip)[field_index]; - try member_types.append(try cg.resolveType(field_ty, .indirect)); - try member_names.append(field_name.toSlice(ip)); + try member_types.append(gpa, try cg.resolveType(field_ty, .indirect)); + try member_names.append(gpa, field_name.toSlice(ip)); } const result_id = try cg.structType( @@ -8688,31 +8688,69 @@ fn airAssembly(cg: *CodeGen, inst: Air.Inst.Index) !?Id { }); const ip = &zcu.intern_pool; - switch (ip.indexToKey(val.toIntern())) { - .int_type, - .ptr_type, - .array_type, - .vector_type, - .opt_type, - .anyframe_type, - .error_union_type, - .simple_type, - .struct_type, - .union_type, - .opaque_type, - .spirv_type, - .enum_type, - .func_type, - .error_set_type, - .inferred_error_set_type, - => unreachable, // types, not values - - .undef => return cg.fail("assembly input with 'c' constraint cannot be undefined", .{}), - - .int => try ass.value_map.put(gpa, in.name, .{ .constant = @intCast(val.toUnsignedInt(zcu)) }), - .enum_literal => |str| try ass.value_map.put(gpa, in.name, .{ .string = str.toSlice(ip) }), - - else => unreachable, // TODO + const target = cg.pt.zcu.getTarget(); + if (ip.indexToKey(val.toIntern()) == .undef) { + return cg.fail("assembly input with 'c' constraint cannot be undefined", .{}); + } + switch (input_ty.zigTypeTag(zcu)) { + .int => { + const bits: u64 = switch (input_ty.intInfo(zcu).signedness) { + .unsigned => val.toUnsignedInt(zcu), + .signed => @bitCast(val.toSignedInt(zcu)), + }; + try ass.value_map.put(gpa, in.name, .{ .constant = bits }); + }, + .float => { + const bits: u64 = switch (input_ty.floatBits(target)) { + 16 => @as(u16, @bitCast(val.toFloat(f16, zcu))), + 32 => @as(u32, @bitCast(val.toFloat(f32, zcu))), + 64 => @bitCast(val.toFloat(f64, zcu)), + else => return cg.fail("unsupported float width for 'c' constraint", .{}), + }; + try ass.value_map.put(gpa, in.name, .{ .constant = bits }); + }, + .vector => { + const child_ty = input_ty.childType(zcu); + const child_kind = child_ty.zigTypeTag(zcu); + const child_bit_width: u16 = switch (child_kind) { + .bool => 0, + .int => @intCast(child_ty.intInfo(zcu).bits), + .float => child_ty.floatBits(target), + else => return cg.fail("'c' constraint vector element must be bool, int, or float", .{}), + }; + const vec_len: usize = @intCast(input_ty.vectorLen(zcu)); + const values = try gpa.alloc(u64, vec_len); + errdefer gpa.free(values); + for (values, 0..) |*out, i| { + const elem: Value = try val.elemValue(cg.pt, i); + out.* = switch (child_kind) { + .bool => @intFromBool(elem.toBool()), + .int => switch (child_ty.intInfo(zcu).signedness) { + .unsigned => elem.toUnsignedInt(zcu), + .signed => @bitCast(elem.toSignedInt(zcu)), + }, + .float => switch (child_bit_width) { + 16 => @as(u16, @bitCast(elem.toFloat(f16, zcu))), + 32 => @as(u32, @bitCast(elem.toFloat(f32, zcu))), + 64 => @bitCast(elem.toFloat(f64, zcu)), + else => unreachable, + }, + else => unreachable, + }; + } + const child_ty_id = try cg.resolveType(child_ty, .direct); + try ass.value_map.put(gpa, in.name, .{ .constant_composite = .{ + .child = child_ty_id, + .child_kind = child_kind, + .child_bit_width = child_bit_width, + .values = values, + } }); + }, + .@"enum" => switch (ip.indexToKey(val.toIntern())) { + .enum_literal => |str| try ass.value_map.put(gpa, in.name, .{ .string = str.toSlice(ip) }), + else => unreachable, + }, + else => return cg.fail("unsupported type for 'c' constraint", .{}), } } else if (std.mem.eql(u8, in.constraint, "t")) { // type @@ -8779,7 +8817,7 @@ fn airAssembly(cg: *CodeGen, inst: Air.Inst.Index) !?Id { .just_declared, .unresolved_forward_reference => unreachable, .ty => return cg.fail("cannot return spir-v type as value from assembly", .{}), .value => |ref| return ref, - .constant, .string => return cg.fail("cannot return constant from assembly", .{}), + .constant, .constant_composite, .string => return cg.fail("cannot return constant from assembly", .{}), } // TODO: Multiple results // TODO: Check that the output type from assembly is the same as the type actually expected by Zig. diff --git a/src/link/SpirV/BinaryModule.zig b/src/link/SpirV/BinaryModule.zig index 55604fc194d6e70df9fbd54eb417b6e55426eea5..53172685dc5c55a36fa0c57ccd7226a5f1b54fd4 100644 --- a/src/link/SpirV/BinaryModule.zig +++ b/src/link/SpirV/BinaryModule.zig @@ -303,7 +303,7 @@ pub const Parser = struct { } }, .literal_context_dependent_number => { - assert(inst.opcode == .OpConstant or inst.opcode == .OpSpecConstantOp); + assert(inst.opcode == .OpConstant or inst.opcode == .OpSpecConstant); const bit_width = binary.arith_type_width.get(@enumFromInt(inst.operands[0])) orelse { log.err("invalid LiteralContextDependentNumber type {}", .{inst.operands[0]}); return error.InvalidId; -- 2.54.0 From 48821ea886e29c5568cd2036cd46ca435550aaa3 Mon Sep 17 00:00:00 2001 From: Ali Cheraghi Date: Wed, 8 Jul 2026 17:06:51 +0330 Subject: [PATCH 5/6] spirv: always emit int64 cap for spirv64 --- lib/std/Target.zig | 3 ++- src/codegen/spirv/CodeGen.zig | 11 +++++++---- src/link/SpirV.zig | 9 ++++++--- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index 387f96d04b75c647832e946bfe1d2cacc5c78e15..fdd462e5d684af3c9f5d6e3eeb9ac6d139db3480 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -2334,7 +2334,8 @@ pub fn supportsAddressSpace( .constant => (is_gpu and (context == null or context == .constant)) or (is_spirv and (context == null or context == .constant or context == .pointer)), .param => is_nvptx, - .input, .output, .uniform, .push_constant, .storage_buffer, .physical_storage_buffer => is_spirv, + .input, .output, .uniform, .push_constant, .storage_buffer => is_spirv, + .physical_storage_buffer => arch == .spirv64, .externref, .funcref => target.cpu.has(.wasm, .reference_types), }; } diff --git a/src/codegen/spirv/CodeGen.zig b/src/codegen/spirv/CodeGen.zig index ea67f6d0c86dd6b9464f0ba47f8b4233d32d8e73..b90be9c093e0e63d3ad32aaec53c4b695bf0d527 100644 --- a/src/codegen/spirv/CodeGen.zig +++ b/src/codegen/spirv/CodeGen.zig @@ -53,9 +53,12 @@ tracked_allocas: std.AutoHashMapUnmanaged(Id, ?Id) = .empty, loop_switches: std.AutoHashMapUnmanaged(Air.Inst.Index, LoopSwitch) = .empty, id_scratch: std.ArrayList(Id) = .empty, +fn hasInt64(target: *const std.Target) bool { + return target.cpu.arch == .spirv64 or target.cpu.has(.spirv, .int64); +} + fn bigIntBits(cg: *const CodeGen) u16 { - const target = cg.zcu.getTarget(); - return if (target.cpu.has(.spirv, .int64)) 64 else 32; + return if (hasInt64(cg.zcu.getTarget())) 64 else 32; } fn limbType(cg: *const CodeGen) Type { @@ -487,7 +490,7 @@ pub fn backingIntBits(cg: *const CodeGen, bits: u16) struct { u16, bool } { .{ .bits = 8, .enabled = target.cpu.has(.spirv, .int8) }, .{ .bits = 16, .enabled = target.cpu.has(.spirv, .int16) }, .{ .bits = 32, .enabled = true }, - .{ .bits = 64, .enabled = target.cpu.has(.spirv, .int64) or target.cpu.arch == .spirv64 }, + .{ .bits = 64, .enabled = hasInt64(target) }, }; for (ints) |int| { @@ -5273,7 +5276,7 @@ fn airMulOverflow(cg: *CodeGen, inst: Air.Inst.Index) !?Id { // of the result too. const target = cg.zcu.getTarget(); - const largest_int_bits: u16 = if (target.cpu.has(.spirv, .int64) or target.cpu.arch == .spirv64) 64 else 32; + const largest_int_bits: u16 = if (hasInt64(target)) 64 else 32; // If non-null, the number of bits that the multiplication should be performed in. If // null, we have to use wide multiplication. const maybe_op_ty_bits: ?u16 = switch (info.bits) { diff --git a/src/link/SpirV.zig b/src/link/SpirV.zig index be2e2e47ec30e6fb699cf175d17994d392f34da5..aeb6217f5dcc5c83681bb9aea5cbaa8dc4821801 100644 --- a/src/link/SpirV.zig +++ b/src/link/SpirV.zig @@ -621,9 +621,12 @@ fn emitPreamble( }, else => unreachable, } - if (target.os.tag == .vulkan and target.cpu.arch == .spirv64) { - caps.insert(.physical_storage_buffer_addresses); - try exts.put(gpa, "SPV_KHR_physical_storage_buffer", {}); + if (target.cpu.arch == .spirv64) { + caps.insert(.int64); + if (target.os.tag == .vulkan) { + caps.insert(.physical_storage_buffer_addresses); + try exts.put(gpa, "SPV_KHR_physical_storage_buffer", {}); + } } if (has_linkage) caps.insert(.linkage); -- 2.54.0 From 11d8a359d6447d29d8fda37ee99e0e22e67d352d Mon Sep 17 00:00:00 2001 From: Ali Cheraghi Date: Wed, 8 Jul 2026 18:42:58 +0330 Subject: [PATCH 6/6] spirv: move some compile errors into frontend --- src/Sema.zig | 43 ++++++++++++++++--- src/codegen/spirv/CodeGen.zig | 38 ++++------------ .../loading_spirv_runtime_array_value.zig | 4 +- .../spirv_c_constraint_errors.zig | 33 ++++++++++++++ .../spirv_cannot_call_function_pointer.zig | 13 ++++++ .../spirv_extern_var_addrspace.zig | 11 +++++ ...pirv_pointer_cast_requires_offset_zero.zig | 2 +- .../spirv_unsupported_float_width.zig | 16 +++++++ 8 files changed, 123 insertions(+), 37 deletions(-) create mode 100644 test/cases/compile_errors/spirv_c_constraint_errors.zig create mode 100644 test/cases/compile_errors/spirv_cannot_call_function_pointer.zig create mode 100644 test/cases/compile_errors/spirv_extern_var_addrspace.zig create mode 100644 test/cases/compile_errors/spirv_unsupported_float_width.zig diff --git a/src/Sema.zig b/src/Sema.zig index 48303df878684068303d127882203272cba3ea84..9158aeeecd936aa7ea22e87c565e27b25eae0bd0 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -6916,6 +6916,9 @@ fn analyzeCall( const is_inline_call = block.isComptime() or inline_requested; if (!is_inline_call) { + if (func_val == null and !func_is_extern and !block.is_typeof and zcu.getTarget().cpu.arch.isSpirV()) { + return sema.fail(block, func_src, "SPIR-V does not support calling function pointers", .{}); + } if (sema.func_is_naked) return sema.failWithOwnedErrorMsg(block, msg: { const msg = try sema.errMsg(call_src, "runtime {s} not allowed in naked function", .{@tagName(operation)}); errdefer msg.destroy(gpa); @@ -15184,6 +15187,23 @@ fn zirAsm( } const constraint = sema.code.nullTerminatedString(input.data.constraint); + if (zcu.getTarget().cpu.arch.isSpirV() and std.mem.eql(u8, constraint, "c")) { + const val = sema.resolveValue(arg.*) orelse { + return sema.fail(block, input_src, "assembly input with 'c' constraint must be compile-time known", .{}); + }; + if (val.isUndef(zcu)) { + return sema.fail(block, input_src, "assembly input with 'c' constraint cannot be undefined", .{}); + } + const bad_type: bool = switch (uncasted_arg_ty.zigTypeTag(zcu)) { + .bool, .int, .float, .comptime_int, .comptime_float, .enum_literal => false, + .vector => switch (uncasted_arg_ty.childType(zcu).zigTypeTag(zcu)) { + .bool, .int, .float => false, + else => true, + }, + else => true, + }; + if (bad_type) return sema.fail(block, input_src, "unsupported type '{f}' for 'c' constraint", .{uncasted_arg_ty.fmt(pt)}); + } needed_capacity += (constraint.len + name.len + (2 + 3)) / 4; inputs[arg_i] = .{ .c = constraint, .n = name }; } @@ -34415,11 +34435,24 @@ pub fn resolveNavPtrModifiers( }, }; const target = zcu.getTarget(); - const addrspace_body = zir_decl.addrspace_body orelse break :as switch (addrspace_ctx) { - .function => target_util.defaultAddressSpace(target, .function), - .variable => target_util.defaultAddressSpace(target, .global_mutable), - .constant => target_util.defaultAddressSpace(target, .global_constant), - else => unreachable, + const addrspace_body = zir_decl.addrspace_body orelse { + if (zir_decl.linkage == .@"extern" and + target.cpu.arch.isSpirV() and + nav_ty.zigTypeTag(zcu) != .@"fn") + { + return sema.fail( + block, + block.src(.{ .node_offset_var_decl_ty = .zero }), + "SPIR-V extern variables require an explicit address space", + .{}, + ); + } + break :as switch (addrspace_ctx) { + .function => target_util.defaultAddressSpace(target, .function), + .variable => target_util.defaultAddressSpace(target, .global_mutable), + .constant => target_util.defaultAddressSpace(target, .global_constant), + else => unreachable, + }; }; const addrspace_ref = try sema.resolveInlineBody(block, addrspace_body, decl_inst); break :as try sema.analyzeAsAddressSpace(block, addrspace_src, addrspace_ref, addrspace_ctx); diff --git a/src/codegen/spirv/CodeGen.zig b/src/codegen/spirv/CodeGen.zig index b90be9c093e0e63d3ad32aaec53c4b695bf0d527..edf9a3628d9540abacdbaa713843bace8431e248 100644 --- a/src/codegen/spirv/CodeGen.zig +++ b/src/codegen/spirv/CodeGen.zig @@ -2222,14 +2222,10 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { 64 => target.cpu.has(.spirv, .float64), else => false, }; - - if (!supported) { - return cg.fail( - "floating point width of {} bits is not supported for the current SPIR-V feature set", - .{bits}, - ); - } - + if (!supported) return cg.fail( + "'{f}' is not supported on the current SPIR-V feature set", + .{ty.fmt(cg.pt)}, + ); return try cg.floatType(bits); }, .array => { @@ -4591,10 +4587,6 @@ fn airShift(cg: *CodeGen, inst: Air.Inst.Index, unsigned: Opcode, signed: Opcode const zcu = cg.zcu; const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; - if (cg.typeOf(bin_op.lhs).isVector(zcu) and !cg.typeOf(bin_op.rhs).isVector(zcu)) { - return cg.fail("vector shift with scalar rhs", .{}); - } - const base = try cg.temporary(bin_op.lhs); const shift = try cg.temporary(bin_op.rhs); @@ -5425,10 +5417,6 @@ fn airShlOverflow(cg: *CodeGen, inst: Air.Inst.Index) !?Id { const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; const extra = cg.air.extraData(Air.Bin, ty_pl.payload).data; - if (cg.typeOf(extra.lhs).isVector(zcu) and !cg.typeOf(extra.rhs).isVector(zcu)) { - return cg.fail("vector shift with scalar rhs", .{}); - } - const base = try cg.temporary(extra.lhs); const shift = try cg.temporary(extra.rhs); @@ -8685,16 +8673,9 @@ fn airAssembly(cg: *CodeGen, inst: Air.Inst.Index) !?Id { const input_ty = cg.typeOf(in.operand); if (std.mem.eql(u8, in.constraint, "c")) { - // constant - const val: Value = .fromInterned(in.operand.toInterned() orelse { - return cg.fail("assembly inputs with 'c' constraint have to be compile-time known", .{}); - }); - + const val: Value = .fromInterned(in.operand.toInterned().?); const ip = &zcu.intern_pool; const target = cg.pt.zcu.getTarget(); - if (ip.indexToKey(val.toIntern()) == .undef) { - return cg.fail("assembly input with 'c' constraint cannot be undefined", .{}); - } switch (input_ty.zigTypeTag(zcu)) { .int => { const bits: u64 = switch (input_ty.intInfo(zcu).signedness) { @@ -8708,7 +8689,7 @@ fn airAssembly(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 16 => @as(u16, @bitCast(val.toFloat(f16, zcu))), 32 => @as(u32, @bitCast(val.toFloat(f32, zcu))), 64 => @bitCast(val.toFloat(f64, zcu)), - else => return cg.fail("unsupported float width for 'c' constraint", .{}), + else => unreachable, // Sema rejects unsupported float widths. }; try ass.value_map.put(gpa, in.name, .{ .constant = bits }); }, @@ -8719,7 +8700,7 @@ fn airAssembly(cg: *CodeGen, inst: Air.Inst.Index) !?Id { .bool => 0, .int => @intCast(child_ty.intInfo(zcu).bits), .float => child_ty.floatBits(target), - else => return cg.fail("'c' constraint vector element must be bool, int, or float", .{}), + else => unreachable, // Sema rejects unsupported vector element types. }; const vec_len: usize = @intCast(input_ty.vectorLen(zcu)); const values = try gpa.alloc(u64, vec_len); @@ -8753,7 +8734,7 @@ fn airAssembly(cg: *CodeGen, inst: Air.Inst.Index) !?Id { .enum_literal => |str| try ass.value_map.put(gpa, in.name, .{ .string = str.toSlice(ip) }), else => unreachable, }, - else => return cg.fail("unsupported type for 'c' constraint", .{}), + else => unreachable, // Sema rejects unsupported types. } } else if (std.mem.eql(u8, in.constraint, "t")) { // type @@ -8840,8 +8821,7 @@ fn airCall(cg: *CodeGen, inst: Air.Inst.Index, modifier: std.lang.CallModifier) const callee_ty = cg.typeOf(air_call.callee); const zig_fn_ty = switch (callee_ty.zigTypeTag(zcu)) { .@"fn" => callee_ty, - .pointer => return cg.fail("cannot call function pointers", .{}), - else => unreachable, + else => unreachable, // rejected by Sema for SPIR-V }; const fn_info = zcu.typeToFunc(zig_fn_ty).?; const return_type = fn_info.return_type; diff --git a/test/cases/compile_errors/loading_spirv_runtime_array_value.zig b/test/cases/compile_errors/loading_spirv_runtime_array_value.zig index b3473fa406eb8883f7fbf372c22e075386b10f71..39cd38b36f0e3bce36884911de9b9e2f4f03c55f 100644 --- a/test/cases/compile_errors/loading_spirv_runtime_array_value.zig +++ b/test/cases/compile_errors/loading_spirv_runtime_array_value.zig @@ -6,11 +6,11 @@ const buf = @extern(*addrspace(.storage_buffer) Buffer, .{ .name = "buf", .decoration = .{ .descriptor = .{ .set = 0, .binding = 0 } }, }); -export fn main() callconv(.{ .spirv_kernel = .{ .x = 1, .y = 1, .z = 1 } }) void { +export fn main() callconv(.kernel) void { const a = buf.data; _ = a; } -export fn main2() callconv(.{ .spirv_kernel = .{ .x = 1, .y = 1, .z = 1 } }) void { +export fn main2() callconv(.kernel) void { const p: *addrspace(.storage_buffer) const RuntimeArray = &buf.data; _ = p.*; } diff --git a/test/cases/compile_errors/spirv_c_constraint_errors.zig b/test/cases/compile_errors/spirv_c_constraint_errors.zig new file mode 100644 index 0000000000000000000000000000000000000000..e7d2a5601b47337c860e27b350e25b0444a2c8ed --- /dev/null +++ b/test/cases/compile_errors/spirv_c_constraint_errors.zig @@ -0,0 +1,33 @@ +export fn not_comptime() callconv(.kernel) void { + var runtime: u32 = 42; + _ = &runtime; + _ = asm ("%ret = OpSpecConstant %ty $default" + : [ret] "" (-> u32), + : [ty] "t" (u32), + [default] "c" (runtime), + ); +} + +export fn undef_input() callconv(.kernel) void { + const x: u32 = undefined; + _ = asm ("%ret = OpDummy $x" + : [ret] "" (-> u32), + : [x] "c" (x), + ); +} + +export fn unsupported_type() callconv(.kernel) void { + const s = "hi"; + _ = asm ("%ret = OpDummy $x" + : [ret] "" (-> u32), + : [x] "c" (s), + ); +} + +// error +// backend=selfhosted +// target=spirv32-vulkan +// +// :7:26: error: assembly input with 'c' constraint must be compile-time known +// :15:20: error: assembly input with 'c' constraint cannot be undefined +// :23:20: error: unsupported type '*const [2:0]u8' for 'c' constraint diff --git a/test/cases/compile_errors/spirv_cannot_call_function_pointer.zig b/test/cases/compile_errors/spirv_cannot_call_function_pointer.zig new file mode 100644 index 0000000000000000000000000000000000000000..1eadba5473c6182163faa8537c3d2dda366e4305 --- /dev/null +++ b/test/cases/compile_errors/spirv_cannot_call_function_pointer.zig @@ -0,0 +1,13 @@ +fn foo() void {} + +export fn main() callconv(.kernel) void { + var fp = &foo; + fp = &foo; + fp(); +} + +// error +// backend=selfhosted +// target=spirv32-vulkan +// +// :6:5: error: SPIR-V does not support calling function pointers diff --git a/test/cases/compile_errors/spirv_extern_var_addrspace.zig b/test/cases/compile_errors/spirv_extern_var_addrspace.zig new file mode 100644 index 0000000000000000000000000000000000000000..b750eda17fda0f48874a05697ca37b0570d66995 --- /dev/null +++ b/test/cases/compile_errors/spirv_extern_var_addrspace.zig @@ -0,0 +1,11 @@ +extern var x: u32; + +export fn main() callconv(.kernel) void { + _ = x; +} + +// error +// backend=selfhosted +// target=spirv64-vulkan +// +// :1:15: error: SPIR-V extern variables require an explicit address space diff --git a/test/cases/compile_errors/spirv_pointer_cast_requires_offset_zero.zig b/test/cases/compile_errors/spirv_pointer_cast_requires_offset_zero.zig index da77d7cd169be3f6bb7274f7424bec87101e01dc..8eda14440d21815df25b8e9e509a46f9b1eb1db6 100644 --- a/test/cases/compile_errors/spirv_pointer_cast_requires_offset_zero.zig +++ b/test/cases/compile_errors/spirv_pointer_cast_requires_offset_zero.zig @@ -6,7 +6,7 @@ const a = @extern(*addrspace(.uniform) const A, .{ .decoration = .{ .descriptor = .{ .set = 0, .binding = 0 } }, }); -export fn main() callconv(.{ .spirv_kernel = .{ .x = 1, .y = 1, .z = 1 } }) void { +export fn main() callconv(.kernel) void { const b: *addrspace(.uniform) const B = @ptrCast(a); _ = &b; } diff --git a/test/cases/compile_errors/spirv_unsupported_float_width.zig b/test/cases/compile_errors/spirv_unsupported_float_width.zig new file mode 100644 index 0000000000000000000000000000000000000000..364e9f8385f607af4f766bdd742c261a782635a8 --- /dev/null +++ b/test/cases/compile_errors/spirv_unsupported_float_width.zig @@ -0,0 +1,16 @@ +export fn use_f80() callconv(.kernel) void { + var x: f80 = 1.5; + _ = &x; +} + +export fn use_f16() callconv(.kernel) void { + var x: f16 = 1.5; + _ = &x; +} + +// error +// backend=selfhosted +// target=spirv32-vulkan +// +// :2:5: error: 'f80' is not supported on the current SPIR-V feature set +// :7:5: error: 'f16' is not supported on the current SPIR-V feature set -- 2.54.0