zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit a5fbbb83050f2dad0a9ebc0bd995a5a8f58d0a49 (tree)
parent 93cd157da13b7c89fc809d326555cd56457b6c0b
Author: Ali Cheraghi <alichraghi@proton.me>
Date:   Tue, 16 Jun 2026 16:06:35 +0330

spirv: composite integers

composite integers (wider than usize) are represented in as `[N]u32` arrays.
however, no operations were implemented before.
following operations are implemented:

- bitwise (and, or, xor, not)
- comparison (eq/neq)
- add/sub/mul/shl/shr
- `@abs`, `@intCast`

this removes 36 SPIR-V behavior test skips.

also fixed `derivePtr` to use `OpAccessChain` instead of `OpBitcast`
for array pointer casts (e.g. `*[100]u8` to `*u8`) in logical addressing
mode, where pointer bitcasts are not valid.

Diffstat:
Msrc/codegen/spirv/CodeGen.zig | 1338++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/codegen/spirv/Module.zig | 2--
Msrc/target.zig | 1-
Mtest/behavior/abs.zig | 3---
Mtest/behavior/basic.zig | 7-------
Mtest/behavior/enum.zig | 2--
Mtest/behavior/eval.zig | 3---
Mtest/behavior/floatop.zig | 1-
Mtest/behavior/int128.zig | 3---
Mtest/behavior/math.zig | 18------------------
Mtest/behavior/muladd.zig | 2--
Mtest/behavior/while.zig | 3---
Mtest/behavior/widening.zig | 1-
Mtest/behavior/wrapping_arithmetic.zig | 19+++++++++----------
14 files changed, 1310 insertions(+), 93 deletions(-)

diff --git a/src/codegen/spirv/CodeGen.zig b/src/codegen/spirv/CodeGen.zig @@ -5,6 +5,7 @@ const Signedness = std.lang.Signedness; const assert = std.debug.assert; const log = std.log.scoped(.codegen); +const builtin = @import("builtin"); const link = @import("../../link.zig"); const codegen = @import("../../codegen.zig"); const Zcu = @import("../../Zcu.zig"); @@ -511,8 +512,8 @@ pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void { else => {}, } - if (std.meta.stringToEnum(spec.BuiltIn, nav.fqn.toSlice(ip))) |builtin| { - try cg.module.decorate(result_id, .{ .built_in = .{ .built_in = builtin } }); + if (std.meta.stringToEnum(spec.BuiltIn, nav.fqn.toSlice(ip))) |built_in| { + try cg.module.decorate(result_id, .{ .built_in = .{ .built_in = built_in } }); } try cg.module.debugName(result_id, nav.fqn.toSlice(ip)); @@ -956,6 +957,7 @@ fn constBool(cg: *CodeGen, value: bool, repr: Repr) !Id { /// This function, unlike Module.constInt, takes care to bitcast /// the value to an unsigned int first for Kernels. fn constInt(cg: *CodeGen, ty: Type, value: anytype) !Id { + const gpa = cg.module.gpa; const zcu = cg.module.zcu; const target = cg.module.zcu.getTarget(); const scalar_ty = ty.scalarType(zcu); @@ -975,11 +977,18 @@ fn constInt(cg: *CodeGen, ty: Type, value: anytype) !Id { .signed => @bitCast(@as(i64, @intCast(value))), .unsigned => @as(u64, @intCast(value)), }; - assert(backing_bits == 64); - return cg.constructComposite(result_ty_id, &.{ - try cg.constInt(.u32, @as(u32, @truncate(value64))), - try cg.constInt(.u32, @as(u32, @truncate(value64 << 32))), - }); + const n_limbs = backing_bits / Module.big_int_bits; + 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); + const constituents = try cg.id_scratch.addManyAsSlice(gpa, n_limbs); + for (constituents, 0..) |*c, i| { + c.* = try cg.constInt( + .u32, + if (i < 2) @as(u32, @truncate(value64 >> @intCast(i * 32))) else fill, + ); + } + return cg.constructComposite(result_ty_id, constituents); } const final_value: spec.LiteralContextDependentNumber = switch (target.os.tag) { @@ -1014,6 +1023,35 @@ fn constInt(cg: *CodeGen, ty: Type, value: anytype) !Id { return cg.constructCompositeSplat(ty, result_id); } +fn constIntBig(cg: *CodeGen, ty: Type, val: Value) !Id { + const gpa = cg.module.gpa; + const zcu = cg.module.zcu; + const int_info = ty.intInfo(zcu); + const backing_bits, _ = cg.module.backingIntBits(int_info.bits); + const n_limbs = backing_bits / Module.big_int_bits; + const 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); + + const bytes = std.mem.sliceAsBytes(limb_values); + bigint.writeTwosComplement(bytes, .little); + if (builtin.cpu.arch.endian() == .big) { + for (limb_values) |*limb| limb.* = @byteSwap(limb.*); + } + + 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]); + } + return cg.constructComposite(result_ty_id, constituents); +} + pub fn constructComposite(cg: *CodeGen, result_ty_id: Id, constituents: []const Id) !Id { const gpa = cg.module.gpa; const result_id = cg.module.allocId(); @@ -1106,6 +1144,11 @@ fn constant(cg: *CodeGen, ty: Type, val: Value, repr: Repr) Error!Id { .false, .true => break :cache try cg.constBool(val.toBool(), repr), }, .int => { + const int_info = ty.intInfo(zcu); + _, const is_big_int = cg.module.backingIntBits(int_info.bits); + if (is_big_int) { + break :cache try cg.constIntBig(ty, val); + } if (ty.isSignedInt(zcu)) { break :cache try cg.constInt(ty, val.toSignedInt(zcu)); } else { @@ -1385,15 +1428,32 @@ fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id { } if (oac.byte_offset == 0) { - // Allow changing the pointer type child only to restructure arrays. - // e.g. [3][2]T to T is fine, as is [2]T -> [2][1]T. - const result_ptr_id = cg.module.allocId(); - try cg.body.emit(gpa, .OpBitcast, .{ - .id_result_type = result_ty_id, - .id_result = result_ptr_id, - .operand = parent_ptr_id, - }); - return result_ptr_id; + var depth: u32 = 0; + var cur = parent_ptr_ty.childType(zcu); + const dst_child = oac.new_ptr_ty.childType(zcu); + while (cur.toIntern() != dst_child.toIntern()) { + if (cur.zigTypeTag(zcu) == .array) { + cur = cur.childType(zcu); + depth += 1; + } else break; + } + if (depth > 0 and cur.toIntern() == dst_child.toIntern()) { + const scratch_top = cg.id_scratch.items.len; + defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); + const zero = try cg.constInt(.u32, 0); + const ids = try cg.id_scratch.addManyAsSlice(gpa, depth); + @memset(ids, zero); + return cg.accessChainId(result_ty_id, parent_ptr_id, ids); + } + if (target.os.tag == .opencl) { + const result_ptr_id = cg.module.allocId(); + try cg.body.emit(gpa, .OpBitcast, .{ + .id_result_type = result_ty_id, + .id_result = result_ptr_id, + .operand = parent_ptr_id, + }); + return result_ptr_id; + } } return cg.fail("cannot perform pointer cast: '{f}' to '{f}'", .{ @@ -1684,10 +1744,6 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { return try cg.module.opaqueType("u0"); } const int_info = ty.intInfo(zcu); - const backing_bits, const big_int = cg.module.backingIntBits(int_info.bits); - if (big_int and backing_bits > 64) { - return cg.fail("integer width of {} bits is not yet supported on the SPIR-V backend", .{int_info.bits}); - } return try cg.module.intType(int_info.signedness, int_info.bits); }, .@"enum" => return try cg.resolveType(ty.intTagType(zcu), repr), @@ -2216,6 +2272,697 @@ const Temporary = struct { } }; +/// composite integers are represented as [N]u32 arrays +const CompositeInt = struct { + cg: *CodeGen, + limbs: []Id, + n_limbs: u16, + info: ArithmeticTypeInfo, + + fn init(cg: *CodeGen, composite_id: Id, info: ArithmeticTypeInfo) !CompositeInt { + const n_limbs: u16 = info.backing_bits / Module.big_int_bits; + const gpa = cg.module.gpa; + const u32_ty_id = try cg.resolveType(.u32, .direct); + const limbs = try cg.id_scratch.addManyAsSlice(gpa, n_limbs); + for (limbs, 0..) |*limb, i| { + const result_id = cg.module.allocId(); + try cg.body.emit(gpa, .OpCompositeExtract, .{ + .id_result_type = u32_ty_id, + .id_result = result_id, + .composite = composite_id, + .indexes = &.{@as(u32, @intCast(i))}, + }); + limb.* = result_id; + } + return .{ .cg = cg, .limbs = limbs, .n_limbs = n_limbs, .info = info }; + } + + fn fromLimbs(cg: *CodeGen, limbs: []Id, info: ArithmeticTypeInfo) CompositeInt { + return .{ + .cg = cg, + .limbs = limbs, + .n_limbs = @intCast(limbs.len), + .info = info, + }; + } + + fn zero(cg: *CodeGen, info: ArithmeticTypeInfo) !CompositeInt { + const n_limbs: u16 = info.backing_bits / Module.big_int_bits; + const limbs = try cg.id_scratch.addManyAsSlice(cg.module.gpa, n_limbs); + const zero_id = try cg.constInt(.u32, @as(u32, 0)); + for (limbs) |*limb| limb.* = zero_id; + return .{ .cg = cg, .limbs = limbs, .n_limbs = n_limbs, .info = info }; + } + + fn materialize(ci: CompositeInt, ty: Type) !Id { + const result_ty_id = try ci.cg.resolveType(ty, .indirect); + return ci.cg.constructComposite(result_ty_id, ci.limbs); + } + + fn limbBinOp(ci: CompositeInt, opcode: Opcode, lhs: Id, rhs: Id) !Id { + const cg = ci.cg; + const gpa = cg.module.gpa; + const u32_ty_id = try cg.resolveType(.u32, .direct); + const result_id = cg.module.allocId(); + try cg.body.emitRaw(gpa, opcode, 4); + cg.body.writeOperand(Id, u32_ty_id); + cg.body.writeOperand(Id, result_id); + cg.body.writeOperand(Id, lhs); + cg.body.writeOperand(Id, rhs); + return result_id; + } + + fn limbUnOp(ci: CompositeInt, opcode: Opcode, operand: Id) !Id { + const cg = ci.cg; + const gpa = cg.module.gpa; + const u32_ty_id = try cg.resolveType(.u32, .direct); + const result_id = cg.module.allocId(); + try cg.body.emitRaw(gpa, opcode, 3); + cg.body.writeOperand(Id, u32_ty_id); + cg.body.writeOperand(Id, result_id); + cg.body.writeOperand(Id, operand); + return result_id; + } + + fn bitwiseOp(ci: CompositeInt, other: CompositeInt, opcode: Opcode) !CompositeInt { + const cg = ci.cg; + const gpa = cg.module.gpa; + const result_limbs = try cg.id_scratch.addManyAsSlice(gpa, ci.n_limbs); + for (result_limbs, 0..) |*r, i| { + r.* = try ci.limbBinOp(opcode, ci.limbs[i], other.limbs[i]); + } + return .fromLimbs(cg, result_limbs, ci.info); + } + + fn bitwiseNot(ci: CompositeInt) !CompositeInt { + const cg = ci.cg; + const gpa = cg.module.gpa; + const result_limbs = try cg.id_scratch.addManyAsSlice(gpa, ci.n_limbs); + for (result_limbs, 0..) |*r, i| { + r.* = try ci.limbUnOp(.OpNot, ci.limbs[i]); + } + return .fromLimbs(cg, result_limbs, ci.info); + } + + fn cmp(ci: CompositeInt, other: CompositeInt, op: std.math.CompareOperator) !Id { + const cg = ci.cg; + const gpa = cg.module.gpa; + const bool_ty_id = try cg.resolveType(.bool, .direct); + + switch (op) { + .eq, .neq => { + var result = blk: { + const r = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpIEqual, 4); + cg.body.writeOperand(Id, bool_ty_id); + cg.body.writeOperand(Id, r); + cg.body.writeOperand(Id, ci.limbs[0]); + cg.body.writeOperand(Id, other.limbs[0]); + break :blk r; + }; + for (1..ci.n_limbs) |i| { + const limb_eq = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpIEqual, 4); + cg.body.writeOperand(Id, bool_ty_id); + cg.body.writeOperand(Id, limb_eq); + cg.body.writeOperand(Id, ci.limbs[i]); + cg.body.writeOperand(Id, other.limbs[i]); + const combined = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpLogicalAnd, 4); + cg.body.writeOperand(Id, bool_ty_id); + cg.body.writeOperand(Id, combined); + cg.body.writeOperand(Id, result); + cg.body.writeOperand(Id, limb_eq); + result = combined; + } + if (op == .neq) { + const negated = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpLogicalNot, 3); + cg.body.writeOperand(Id, bool_ty_id); + cg.body.writeOperand(Id, negated); + cg.body.writeOperand(Id, result); + result = negated; + } + return result; + }, + .lt, .lte, .gt, .gte => { + const is_lt = (op == .lt or op == .lte); + const is_strict = (op == .lt or op == .gt); + var result = try cg.constBool(!is_strict, .direct); + + for (0..ci.n_limbs) |i| { + const l = ci.limbs[i]; + const r = other.limbs[i]; + const limb_ne = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpINotEqual, 4); + cg.body.writeOperand(Id, bool_ty_id); + cg.body.writeOperand(Id, limb_ne); + cg.body.writeOperand(Id, l); + cg.body.writeOperand(Id, r); + + const is_top = (i == ci.n_limbs - 1); + const use_signed = is_top and ci.info.signedness == .signed; + var cmp_l = l; + var cmp_r = r; + if (use_signed) { + const i32_ty_id = try cg.resolveType(.i32, .direct); + const sl = cg.module.allocId(); + try cg.body.emit(gpa, .OpBitcast, .{ + .id_result_type = i32_ty_id, + .id_result = sl, + .operand = l, + }); + const sr = cg.module.allocId(); + try cg.body.emit(gpa, .OpBitcast, .{ + .id_result_type = i32_ty_id, + .id_result = sr, + .operand = r, + }); + cmp_l = sl; + cmp_r = sr; + } + + const cmp_opcode: Opcode = if (is_lt) + (if (use_signed) .OpSLessThan else .OpULessThan) + else + (if (use_signed) .OpSGreaterThan else .OpUGreaterThan); + + const limb_cmp = cg.module.allocId(); + try cg.body.emitRaw(gpa, cmp_opcode, 4); + cg.body.writeOperand(Id, bool_ty_id); + cg.body.writeOperand(Id, limb_cmp); + cg.body.writeOperand(Id, cmp_l); + cg.body.writeOperand(Id, cmp_r); + + const selected = cg.module.allocId(); + try cg.body.emit(gpa, .OpSelect, .{ + .id_result_type = bool_ty_id, + .id_result = selected, + .condition = limb_ne, + .object_1 = limb_cmp, + .object_2 = result, + }); + result = selected; + } + return result; + }, + } + } + + fn addSub(ci: CompositeInt, other: CompositeInt, comptime is_add: bool) !CompositeInt { + const cg = ci.cg; + const gpa = cg.module.gpa; + const pt = cg.pt; + const zcu = cg.module.zcu; + const ip = &zcu.intern_pool; + 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 carry_struct_ty: Type = .fromInterned(try ip.getTupleType(gpa, io, pt.tid, .{ + .types = &.{ u32_zig.toIntern(), u32_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)); + + const opcode: Opcode = if (is_add) .OpIAddCarry else .OpISubBorrow; + + for (0..ci.n_limbs) |i| { + const op1 = cg.module.allocId(); + try cg.body.emitRaw(gpa, opcode, 4); + cg.body.writeOperand(Id, carry_struct_ty_id); + cg.body.writeOperand(Id, op1); + cg.body.writeOperand(Id, ci.limbs[i]); + cg.body.writeOperand(Id, other.limbs[i]); + + const sum1 = cg.module.allocId(); + try cg.body.emit(gpa, .OpCompositeExtract, .{ + .id_result_type = u32_ty_id, + .id_result = sum1, + .composite = op1, + .indexes = &.{0}, + }); + const carry1 = cg.module.allocId(); + try cg.body.emit(gpa, .OpCompositeExtract, .{ + .id_result_type = u32_ty_id, + .id_result = carry1, + .composite = op1, + .indexes = &.{1}, + }); + + const op2 = cg.module.allocId(); + try cg.body.emitRaw(gpa, opcode, 4); + cg.body.writeOperand(Id, carry_struct_ty_id); + cg.body.writeOperand(Id, op2); + cg.body.writeOperand(Id, sum1); + cg.body.writeOperand(Id, carry_id); + + result_limbs[i] = cg.module.allocId(); + try cg.body.emit(gpa, .OpCompositeExtract, .{ + .id_result_type = u32_ty_id, + .id_result = result_limbs[i], + .composite = op2, + .indexes = &.{0}, + }); + const carry2 = cg.module.allocId(); + try cg.body.emit(gpa, .OpCompositeExtract, .{ + .id_result_type = u32_ty_id, + .id_result = carry2, + .composite = op2, + .indexes = &.{1}, + }); + + carry_id = try ci.limbBinOp(.OpBitwiseOr, carry1, carry2); + } + + return .fromLimbs(cg, result_limbs, ci.info); + } + + fn shl(ci: CompositeInt, shift_amt_id: Id) !CompositeInt { + const cg = ci.cg; + const gpa = cg.module.gpa; + const u32_ty_id = try cg.resolveType(.u32, .direct); + 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 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 frac_is_zero = blk: { + const r = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpIEqual, 4); + cg.body.writeOperand(Id, bool_ty_id); + cg.body.writeOperand(Id, r); + cg.body.writeOperand(Id, frac); + cg.body.writeOperand(Id, zero_id); + break :blk r; + }; + + 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))); + 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_plus_whole = try ci.limbBinOp(.OpIAdd, j_id, whole); + + const is_main = blk: { + const r = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpIEqual, 4); + cg.body.writeOperand(Id, bool_ty_id); + cg.body.writeOperand(Id, r); + cg.body.writeOperand(Id, j_plus_whole); + cg.body.writeOperand(Id, i_id); + break :blk r; + }; + const shifted = try ci.limbBinOp(.OpShiftLeftLogical, ci.limbs[j], frac); + main_val = blk: { + const r = cg.module.allocId(); + try cg.body.emit(gpa, .OpSelect, .{ + .id_result_type = u32_ty_id, + .id_result = r, + .condition = is_main, + .object_1 = shifted, + .object_2 = main_val, + }); + break :blk r; + }; + + const one_id = try cg.constInt(.u32, @as(u32, 1)); + const j_plus_whole_plus_1 = try ci.limbBinOp(.OpIAdd, j_plus_whole, one_id); + const is_carry = blk: { + const r = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpIEqual, 4); + cg.body.writeOperand(Id, bool_ty_id); + cg.body.writeOperand(Id, r); + cg.body.writeOperand(Id, j_plus_whole_plus_1); + cg.body.writeOperand(Id, i_id); + break :blk r; + }; + const carry_shifted = try ci.limbBinOp(.OpShiftRightLogical, ci.limbs[j], comp_frac); + const guarded_carry = blk: { + const r = cg.module.allocId(); + try cg.body.emit(gpa, .OpSelect, .{ + .id_result_type = u32_ty_id, + .id_result = r, + .condition = frac_is_zero, + .object_1 = zero_id, + .object_2 = carry_shifted, + }); + break :blk r; + }; + carry_val = blk: { + const r = cg.module.allocId(); + try cg.body.emit(gpa, .OpSelect, .{ + .id_result_type = u32_ty_id, + .id_result = r, + .condition = is_carry, + .object_1 = guarded_carry, + .object_2 = carry_val, + }); + break :blk r; + }; + } + + result_limbs[i] = try ci.limbBinOp(.OpBitwiseOr, main_val, carry_val); + } + + return .fromLimbs(cg, result_limbs, ci.info); + } + + fn shr(ci: CompositeInt, shift_amt_id: Id, comptime is_arithmetic: bool) !CompositeInt { + const cg = ci.cg; + const gpa = cg.module.gpa; + const u32_ty_id = try cg.resolveType(.u32, .direct); + 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 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 frac_is_zero = blk: { + const r = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpIEqual, 4); + cg.body.writeOperand(Id, bool_ty_id); + cg.body.writeOperand(Id, r); + cg.body.writeOperand(Id, frac); + cg.body.writeOperand(Id, zero_id); + break :blk r; + }; + + const fill_id = if (is_arithmetic) blk: { + const i32_ty_id = try cg.resolveType(.i32, .direct); + const msb_signed = cg.module.allocId(); + try cg.body.emit(gpa, .OpBitcast, .{ + .id_result_type = i32_ty_id, + .id_result = msb_signed, + .operand = ci.limbs[ci.n_limbs - 1], + }); + const shift31 = try cg.constInt(.i32, @as(i32, 31)); + const sign_ext = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpShiftRightArithmetic, 4); + cg.body.writeOperand(Id, i32_ty_id); + cg.body.writeOperand(Id, sign_ext); + cg.body.writeOperand(Id, msb_signed); + cg.body.writeOperand(Id, shift31); + const back = cg.module.allocId(); + try cg.body.emit(gpa, .OpBitcast, .{ + .id_result_type = u32_ty_id, + .id_result = back, + .operand = sign_ext, + }); + break :blk back; + } else zero_id; + + const result_limbs = try cg.id_scratch.addManyAsSlice(gpa, ci.n_limbs); + + const arith_carry_init = if (is_arithmetic) blk: { + const shifted_fill = try ci.limbBinOp(.OpShiftLeftLogical, fill_id, comp_frac); + const guarded = cg.module.allocId(); + try cg.body.emit(gpa, .OpSelect, .{ + .id_result_type = u32_ty_id, + .id_result = guarded, + .condition = frac_is_zero, + .object_1 = zero_id, + .object_2 = shifted_fill, + }); + break :blk guarded; + } else zero_id; + + for (0..ci.n_limbs) |i| { + const i_id = try cg.constInt(.u32, @as(u32, @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 i_plus_whole = try ci.limbBinOp(.OpIAdd, i_id, whole); + const is_main = blk: { + const r = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpIEqual, 4); + cg.body.writeOperand(Id, bool_ty_id); + cg.body.writeOperand(Id, r); + cg.body.writeOperand(Id, j_id); + cg.body.writeOperand(Id, i_plus_whole); + break :blk r; + }; + const shifted = try ci.limbBinOp(.OpShiftRightLogical, ci.limbs[j], frac); + main_val = blk: { + const r = cg.module.allocId(); + try cg.body.emit(gpa, .OpSelect, .{ + .id_result_type = u32_ty_id, + .id_result = r, + .condition = is_main, + .object_1 = shifted, + .object_2 = main_val, + }); + break :blk r; + }; + + const one_id = try cg.constInt(.u32, @as(u32, 1)); + const i_plus_whole_plus_1 = try ci.limbBinOp(.OpIAdd, i_plus_whole, one_id); + const is_carry = blk: { + const r = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpIEqual, 4); + cg.body.writeOperand(Id, bool_ty_id); + cg.body.writeOperand(Id, r); + cg.body.writeOperand(Id, j_id); + cg.body.writeOperand(Id, i_plus_whole_plus_1); + break :blk r; + }; + const carry_shifted = try ci.limbBinOp(.OpShiftLeftLogical, ci.limbs[j], comp_frac); + const guarded_carry = blk: { + const r = cg.module.allocId(); + try cg.body.emit(gpa, .OpSelect, .{ + .id_result_type = u32_ty_id, + .id_result = r, + .condition = frac_is_zero, + .object_1 = zero_id, + .object_2 = carry_shifted, + }); + break :blk r; + }; + carry_val = blk: { + const r = cg.module.allocId(); + try cg.body.emit(gpa, .OpSelect, .{ + .id_result_type = u32_ty_id, + .id_result = r, + .condition = is_carry, + .object_1 = guarded_carry, + .object_2 = carry_val, + }); + break :blk r; + }; + } + + result_limbs[i] = try ci.limbBinOp(.OpBitwiseOr, main_val, carry_val); + } + + return .fromLimbs(cg, result_limbs, ci.info); + } + + fn mul(ci: CompositeInt, other: CompositeInt, comptime wide: bool) ![]Id { + const cg = ci.cg; + const gpa = cg.module.gpa; + const pt = cg.pt; + const zcu = cg.module.zcu; + const ip = &zcu.intern_pool; + const comp = zcu.comp; + const io = comp.io; + const target = zcu.getTarget(); + + 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 pair_struct_ty: Type = .fromInterned(try ip.getTupleType(gpa, io, pt.tid, .{ + .types = &.{ u32_zig.toIntern(), u32_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)); + for (result_limbs) |*r| r.* = zero_id; + + for (0..n) |i| { + var carry_id = zero_id; + for (0..n) |j| { + const k = i + j; + if (k >= total) break; + + var lo: Id = undefined; + var hi: Id = undefined; + switch (target.os.tag) { + .opencl => { + lo = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpIMul, 4); + cg.body.writeOperand(Id, u32_ty_id); + cg.body.writeOperand(Id, lo); + cg.body.writeOperand(Id, ci.limbs[i]); + cg.body.writeOperand(Id, other.limbs[j]); + + const set = try cg.importExtendedSet(); + hi = cg.module.allocId(); + try cg.body.emit(gpa, .OpExtInst, .{ + .id_result_type = u32_ty_id, + .id_result = hi, + .set = set, + .instruction = .{ .inst = @intFromEnum(spec.OpenClOpcode.u_mul_hi) }, + .id_ref_4 = &.{ ci.limbs[i], other.limbs[j] }, + }); + }, + else => { + const mul_result = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpUMulExtended, 4); + cg.body.writeOperand(Id, pair_struct_ty_id); + cg.body.writeOperand(Id, mul_result); + cg.body.writeOperand(Id, ci.limbs[i]); + cg.body.writeOperand(Id, other.limbs[j]); + + lo = cg.module.allocId(); + try cg.body.emit(gpa, .OpCompositeExtract, .{ + .id_result_type = u32_ty_id, + .id_result = lo, + .composite = mul_result, + .indexes = &.{0}, + }); + hi = cg.module.allocId(); + try cg.body.emit(gpa, .OpCompositeExtract, .{ + .id_result_type = u32_ty_id, + .id_result = hi, + .composite = mul_result, + .indexes = &.{1}, + }); + }, + } + + const add1 = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpIAddCarry, 4); + cg.body.writeOperand(Id, pair_struct_ty_id); + cg.body.writeOperand(Id, add1); + cg.body.writeOperand(Id, result_limbs[k]); + cg.body.writeOperand(Id, lo); + + const sum1 = cg.module.allocId(); + try cg.body.emit(gpa, .OpCompositeExtract, .{ + .id_result_type = u32_ty_id, + .id_result = sum1, + .composite = add1, + .indexes = &.{0}, + }); + const c1 = cg.module.allocId(); + try cg.body.emit(gpa, .OpCompositeExtract, .{ + .id_result_type = u32_ty_id, + .id_result = c1, + .composite = add1, + .indexes = &.{1}, + }); + + const add2 = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpIAddCarry, 4); + cg.body.writeOperand(Id, pair_struct_ty_id); + cg.body.writeOperand(Id, add2); + cg.body.writeOperand(Id, sum1); + cg.body.writeOperand(Id, carry_id); + + result_limbs[k] = cg.module.allocId(); + try cg.body.emit(gpa, .OpCompositeExtract, .{ + .id_result_type = u32_ty_id, + .id_result = result_limbs[k], + .composite = add2, + .indexes = &.{0}, + }); + const c2 = cg.module.allocId(); + try cg.body.emit(gpa, .OpCompositeExtract, .{ + .id_result_type = u32_ty_id, + .id_result = c2, + .composite = add2, + .indexes = &.{1}, + }); + + const hi_plus_c1 = try ci.limbBinOp(.OpIAdd, hi, c1); + carry_id = try ci.limbBinOp(.OpIAdd, hi_plus_c1, c2); + } + if (wide and i + n < 2 * n) { + result_limbs[i + n] = try ci.limbBinOp(.OpIAdd, result_limbs[i + n], carry_id); + } + } + + return result_limbs; + } + + fn normalize(ci: CompositeInt) !CompositeInt { + if (ci.info.bits == ci.info.backing_bits) return ci; + const cg = ci.cg; + const gpa = cg.module.gpa; + const top_bits: u16 = ci.info.bits % Module.big_int_bits; + assert(top_bits != 0); + + const result_limbs = try cg.id_scratch.addManyAsSlice(gpa, ci.n_limbs); + for (0..ci.n_limbs - 1) |i| { + result_limbs[i] = ci.limbs[i]; + } + + const top_limb = ci.limbs[ci.n_limbs - 1]; + 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); + 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 as_signed = cg.module.allocId(); + try cg.body.emit(gpa, .OpBitcast, .{ + .id_result_type = i32_ty_id, + .id_result = as_signed, + .operand = top_limb, + }); + const shifted_left = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpShiftLeftLogical, 4); + cg.body.writeOperand(Id, i32_ty_id); + cg.body.writeOperand(Id, shifted_left); + cg.body.writeOperand(Id, as_signed); + cg.body.writeOperand(Id, shift_id); + const shifted_right = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpShiftRightArithmetic, 4); + cg.body.writeOperand(Id, i32_ty_id); + cg.body.writeOperand(Id, shifted_right); + cg.body.writeOperand(Id, shifted_left); + cg.body.writeOperand(Id, shift_id); + const back = cg.module.allocId(); + try cg.body.emit(gpa, .OpBitcast, .{ + .id_result_type = u32_ty_id, + .id_result = back, + .operand = shifted_right, + }); + result_limbs[ci.n_limbs - 1] = back; + }, + } + + return .fromLimbs(cg, result_limbs, ci.info); + } +}; + /// Initialize a `Temporary` from an AIR value. fn temporary(cg: *CodeGen, inst: Air.Inst.Ref) !Temporary { return .{ @@ -3234,7 +3981,21 @@ fn airBitwiseOp(cg: *CodeGen, inst: Air.Inst.Index, op: BitwiseOp) !?Id { .xor => .OpBitwiseXor, }, .float => unreachable, - .composite_integer => unreachable, // TODO + .composite_integer => { + const spv_opcode: Opcode = switch (op) { + .bit_and => .OpBitwiseAnd, + .bit_or => .OpBitwiseOr, + .xor => .OpBitwiseXor, + }; + const lhs_id = try lhs.materialize(cg); + const rhs_id = try rhs.materialize(cg); + const scratch_top = cg.id_scratch.items.len; + defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); + const ci_lhs = try CompositeInt.init(cg, lhs_id, info); + const ci_rhs = try CompositeInt.init(cg, rhs_id, info); + const ci_result = try ci_lhs.bitwiseOp(ci_rhs, spv_opcode); + return try ci_result.materialize(lhs.ty); + }, }; const result = try cg.buildBinary(opcode, lhs, rhs); @@ -3256,7 +4017,39 @@ fn airShift(cg: *CodeGen, inst: Air.Inst.Index, unsigned: Opcode, signed: Opcode const info = cg.arithmeticTypeInfo(result_ty); switch (info.class) { - .composite_integer => return cg.todo("shift ops for composite integers", .{}), + .composite_integer => { + const shift_info = cg.arithmeticTypeInfo(shift.ty); + 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 result_id = cg.module.allocId(); + try cg.body.emit(cg.module.gpa, .OpCompositeExtract, .{ + .id_result_type = u32_ty_id, + .id_result = result_id, + .composite = shift_id, + .indexes = &.{@as(u32, 0)}, + }); + break :blk result_id; + }, + else => blk: { + const converted = try cg.buildConvert(.u32, shift); + break :blk try converted.materialize(cg); + }, + }; + const base_id = try base.materialize(cg); + const scratch_top = cg.id_scratch.items.len; + defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); + const ci = try CompositeInt.init(cg, base_id, info); + const ci_result = if (unsigned == .OpShiftLeftLogical) + try ci.shl(shift_amt_id) + else switch (info.signedness) { + .unsigned => try ci.shr(shift_amt_id, false), + .signed => try ci.shr(shift_amt_id, true), + }; + const normalized = try ci_result.normalize(); + return try normalized.materialize(result_ty); + }, .integer, .strange_integer => {}, .float, .bool => unreachable, } @@ -3380,7 +4173,16 @@ fn normalize(cg: *CodeGen, value: Temporary, info: ArithmeticTypeInfo) !Temporar const zcu = cg.module.zcu; const ty = value.ty; switch (info.class) { - .composite_integer, .integer, .bool, .float => return value, + .integer, .bool, .float => return value, + .composite_integer => { + if (info.bits == info.backing_bits) return value; + const val_id = try value.materialize(cg); + const scratch_top = cg.id_scratch.items.len; + defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); + const ci = try CompositeInt.init(cg, val_id, info); + const normalized = try ci.normalize(); + return .init(ty, try normalized.materialize(ty)); + }, .strange_integer => switch (info.signedness) { .unsigned => { const mask_value = @as(u64, std.math.maxInt(u64)) >> @as(u6, @intCast(64 - info.bits)); @@ -3484,7 +4286,22 @@ fn airArithOp( const rhs = try cg.temporary(bin_op.rhs); const info = cg.arithmeticTypeInfo(lhs.ty); const result = switch (info.class) { - .composite_integer => return cg.todo("arith op for composite integers", .{}), + .composite_integer => res: { + const lhs_id = try lhs.materialize(cg); + const rhs_id = try rhs.materialize(cg); + const scratch_top = cg.id_scratch.items.len; + defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); + const ci_lhs = try CompositeInt.init(cg, lhs_id, info); + const ci_rhs = try CompositeInt.init(cg, rhs_id, info); + const ci_result = switch (uop) { + .OpIAdd => try ci_lhs.addSub(ci_rhs, true), + .OpISub => try ci_lhs.addSub(ci_rhs, false), + .OpIMul => CompositeInt.fromLimbs(cg, try ci_lhs.mul(ci_rhs, false), info), + else => return cg.todo("arith op for composite integers", .{}), + }; + const normalized = try ci_result.normalize(); + break :res Temporary.init(lhs.ty, try normalized.materialize(lhs.ty)); + }, .integer, .strange_integer => res: { const raw = switch (info.signedness) { .signed => try cg.buildBinary(sop, lhs, rhs), @@ -3514,18 +4331,50 @@ fn abs(cg: *CodeGen, result_ty: Type, value: Temporary) !Temporary { switch (operand_info.class) { .float => return try cg.buildUnary(.f_abs, value), .integer, .strange_integer => { - const abs_value = try cg.buildUnary(.i_abs, value); + var abs_value = try cg.buildUnary(.i_abs, value); switch (target.os.tag) { .vulkan, .opengl => { if (value.ty.intInfo(zcu).signedness == .signed) { - return cg.todo("perform bitcast after @abs", .{}); + const abs_id = try abs_value.materialize(cg); + const dst_ty_id = try cg.resolveType(result_ty, .direct); + const cast_id = cg.module.allocId(); + try cg.body.emit(cg.module.gpa, .OpBitcast, .{ + .id_result_type = dst_ty_id, + .id_result = cast_id, + .operand = abs_id, + }); + abs_value = .init(result_ty, cast_id); } }, else => {}, } return try cg.normalize(abs_value, cg.arithmeticTypeInfo(result_ty)); }, - .composite_integer => return cg.todo("@abs for composite integers", .{}), + .composite_integer => { + const val_id = try value.materialize(cg); + const scratch_top = cg.id_scratch.items.len; + defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); + const ci = try CompositeInt.init(cg, val_id, operand_info); + const ci_z = try CompositeInt.zero(cg, operand_info); + 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 result_limbs = try cg.id_scratch.addManyAsSlice(cg.module.gpa, ci.n_limbs); + for (0..ci.n_limbs) |i| { + result_limbs[i] = cg.module.allocId(); + try cg.body.emit(cg.module.gpa, .OpSelect, .{ + .id_result_type = u32_ty_id, + .id_result = result_limbs[i], + .condition = is_neg, + .object_1 = ci_neg.limbs[i], + .object_2 = ci.limbs[i], + }); + } + const ci_result = CompositeInt.fromLimbs(cg, result_limbs, result_info); + const normalized = try ci_result.normalize(); + return .init(result_ty, try normalized.materialize(result_ty)); + }, .bool => unreachable, } } @@ -3553,7 +4402,69 @@ fn airAddSubOverflow( const info = cg.arithmeticTypeInfo(lhs.ty); switch (info.class) { - .composite_integer => return cg.todo("add/sub-with-overflow for composite integers", .{}), + .composite_integer => { + const lhs_id = try lhs.materialize(cg); + const rhs_id = try rhs.materialize(cg); + const scratch_top = cg.id_scratch.items.len; + defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); + const ci_lhs = try CompositeInt.init(cg, lhs_id, info); + const ci_rhs = try CompositeInt.init(cg, rhs_id, info); + const ci_sum = if (add == .OpIAdd) try ci_lhs.addSub(ci_rhs, true) else try ci_lhs.addSub(ci_rhs, false); + const ci_result = try ci_sum.normalize(); + const result_val_id = try ci_result.materialize(lhs.ty); + + const ov_bool = switch (info.signedness) { + .unsigned => blk: { + const ci_res2 = try CompositeInt.init(cg, result_val_id, info); + const ci_lhs2 = try CompositeInt.init(cg, lhs_id, info); + break :blk if (add == .OpIAdd) + try ci_res2.cmp(ci_lhs2, .lt) + else + try ci_res2.cmp(ci_lhs2, .gt); + }, + .signed => blk: { + const ci_res2 = try CompositeInt.init(cg, result_val_id, info); + const ci_lhs2 = try CompositeInt.init(cg, lhs_id, info); + const ci_rhs2 = try CompositeInt.init(cg, rhs_id, info); + const ci_z = try CompositeInt.zero(cg, info); + const lhs_neg = try ci_lhs2.cmp(ci_z, .lt); + const rhs_neg = try ci_rhs2.cmp(ci_z, .lt); + const res_neg = try ci_res2.cmp(ci_z, .lt); + + const bool_ty_id = try cg.resolveType(.bool, .direct); + const signs_match = cg.module.allocId(); + try cg.body.emitRaw(cg.module.gpa, .OpLogicalEqual, 4); + cg.body.writeOperand(Id, bool_ty_id); + cg.body.writeOperand(Id, signs_match); + cg.body.writeOperand(Id, lhs_neg); + cg.body.writeOperand(Id, rhs_neg); + const res_sign_diff = cg.module.allocId(); + try cg.body.emitRaw(cg.module.gpa, .OpLogicalNotEqual, 4); + cg.body.writeOperand(Id, bool_ty_id); + cg.body.writeOperand(Id, res_sign_diff); + cg.body.writeOperand(Id, lhs_neg); + cg.body.writeOperand(Id, res_neg); + const ov_cond = if (add == .OpIAdd) signs_match else blk2: { + const not_match = cg.module.allocId(); + try cg.body.emitRaw(cg.module.gpa, .OpLogicalNot, 3); + cg.body.writeOperand(Id, bool_ty_id); + cg.body.writeOperand(Id, not_match); + cg.body.writeOperand(Id, signs_match); + break :blk2 not_match; + }; + const ov_result = cg.module.allocId(); + try cg.body.emitRaw(cg.module.gpa, .OpLogicalAnd, 4); + cg.body.writeOperand(Id, bool_ty_id); + cg.body.writeOperand(Id, ov_result); + cg.body.writeOperand(Id, ov_cond); + cg.body.writeOperand(Id, res_sign_diff); + break :blk ov_result; + }, + }; + const ov = try cg.intFromBool(.init(.bool, ov_bool), .u1); + const result_ty_id = try cg.resolveType(result_ty, .direct); + return try cg.constructComposite(result_ty_id, &.{ result_val_id, try ov.materialize(cg) }); + }, .strange_integer, .integer => {}, .float, .bool => unreachable, } @@ -3595,6 +4506,7 @@ fn airAddSubOverflow( fn airMulOverflow(cg: *CodeGen, inst: Air.Inst.Index) !?Id { const pt = cg.pt; + const gpa = cg.module.gpa; const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; const extra = cg.air.extraData(Air.Bin, ty_pl.payload).data; const lhs = try cg.temporary(extra.lhs); @@ -3603,7 +4515,158 @@ fn airMulOverflow(cg: *CodeGen, inst: Air.Inst.Index) !?Id { const info = cg.arithmeticTypeInfo(lhs.ty); switch (info.class) { - .composite_integer => return cg.todo("mul-with-overflow for composite integers", .{}), + .composite_integer => { + const lhs_id = try lhs.materialize(cg); + const rhs_id = try rhs.materialize(cg); + const scratch_top = cg.id_scratch.items.len; + defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); + const ci_lhs = try CompositeInt.init(cg, lhs_id, info); + const ci_rhs = try CompositeInt.init(cg, rhs_id, info); + + const low_limbs = try ci_lhs.mul(ci_rhs, false); + const ci_result = try CompositeInt.fromLimbs(cg, low_limbs, info).normalize(); + const result_val_id = try ci_result.materialize(lhs.ty); + + const ci_lhs2 = try CompositeInt.init(cg, lhs_id, info); + const ci_rhs2 = try CompositeInt.init(cg, rhs_id, info); + const wide_limbs = try ci_lhs2.mul(ci_rhs2, true); + 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 / Module.big_int_bits; + + const ov_bool = switch (info.signedness) { + .unsigned => blk: { + const zero_id = try cg.constInt(.u32, @as(u32, 0)); + var any_nonzero = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpINotEqual, 4); + cg.body.writeOperand(Id, bool_ty_id); + cg.body.writeOperand(Id, any_nonzero); + cg.body.writeOperand(Id, high_limbs[0]); + cg.body.writeOperand(Id, zero_id); + + for (1..n) |i| { + const limb_nz = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpINotEqual, 4); + cg.body.writeOperand(Id, bool_ty_id); + cg.body.writeOperand(Id, limb_nz); + cg.body.writeOperand(Id, high_limbs[i]); + cg.body.writeOperand(Id, zero_id); + + const combined = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpLogicalOr, 4); + cg.body.writeOperand(Id, bool_ty_id); + cg.body.writeOperand(Id, combined); + cg.body.writeOperand(Id, any_nonzero); + cg.body.writeOperand(Id, limb_nz); + any_nonzero = combined; + } + + break :blk any_nonzero; + }, + .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 top_bits: u16 = if (info.bits % Module.big_int_bits == 0) + Module.big_int_bits + else + info.bits % Module.big_int_bits; + + const shift_amt: u32 = top_bits - 1; + const shift_id = try cg.constInt(.u32, shift_amt); + + const as_signed = cg.module.allocId(); + try cg.body.emit(gpa, .OpBitcast, .{ + .id_result_type = i32_ty_id, + .id_result = as_signed, + .operand = top_limb, + }); + const sign_ext = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpShiftRightArithmetic, 4); + cg.body.writeOperand(Id, i32_ty_id); + cg.body.writeOperand(Id, sign_ext); + cg.body.writeOperand(Id, as_signed); + cg.body.writeOperand(Id, shift_id); + const expected = cg.module.allocId(); + try cg.body.emit(gpa, .OpBitcast, .{ + .id_result_type = u32_ty_id, + .id_result = expected, + .operand = sign_ext, + }); + + var any_mismatch = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpINotEqual, 4); + cg.body.writeOperand(Id, bool_ty_id); + cg.body.writeOperand(Id, any_mismatch); + cg.body.writeOperand(Id, high_limbs[0]); + cg.body.writeOperand(Id, expected); + + for (1..n) |i| { + const limb_ne = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpINotEqual, 4); + cg.body.writeOperand(Id, bool_ty_id); + cg.body.writeOperand(Id, limb_ne); + cg.body.writeOperand(Id, high_limbs[i]); + cg.body.writeOperand(Id, expected); + + const combined = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpLogicalOr, 4); + cg.body.writeOperand(Id, bool_ty_id); + cg.body.writeOperand(Id, combined); + cg.body.writeOperand(Id, any_mismatch); + cg.body.writeOperand(Id, limb_ne); + any_mismatch = combined; + } + + if (info.bits != info.backing_bits) { + const top_bits_s: u16 = info.bits % Module.big_int_bits; + const s_shift_id = try cg.constInt(.u32, top_bits_s - 1); + + const top_as_signed = cg.module.allocId(); + try cg.body.emit(gpa, .OpBitcast, .{ + .id_result_type = i32_ty_id, + .id_result = top_as_signed, + .operand = top_limb, + }); + const top_sign_ext = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpShiftRightArithmetic, 4); + cg.body.writeOperand(Id, i32_ty_id); + cg.body.writeOperand(Id, top_sign_ext); + cg.body.writeOperand(Id, top_as_signed); + cg.body.writeOperand(Id, s_shift_id); + const top_expected = cg.module.allocId(); + try cg.body.emit(gpa, .OpBitcast, .{ + .id_result_type = u32_ty_id, + .id_result = top_expected, + .operand = top_sign_ext, + }); + const top_mismatch = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpINotEqual, 4); + cg.body.writeOperand(Id, bool_ty_id); + cg.body.writeOperand(Id, top_mismatch); + cg.body.writeOperand(Id, top_limb); + cg.body.writeOperand(Id, top_expected); + + const combined = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpLogicalOr, 4); + cg.body.writeOperand(Id, bool_ty_id); + cg.body.writeOperand(Id, combined); + cg.body.writeOperand(Id, any_mismatch); + cg.body.writeOperand(Id, top_mismatch); + any_mismatch = combined; + } + + break :blk any_mismatch; + }, + }; + + const ov = try cg.intFromBool(.init(.bool, ov_bool), .u1); + const result_ty_id = try cg.resolveType(result_ty, .direct); + return try cg.constructComposite(result_ty_id, &.{ result_val_id, try ov.materialize(cg) }); + }, .strange_integer, .integer => {}, .float, .bool => unreachable, } @@ -3622,7 +4685,7 @@ fn airMulOverflow(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 1...16 => 32, 17...32 => if (largest_int_bits > 32) 64 else null, // Upcast if we can. 33...64 => null, // Always use wide multiplication. - else => unreachable, // TODO: Composite integers + else => unreachable, }; const result, const overflowed = switch (info.signedness) { @@ -4245,7 +5308,16 @@ fn cmp( const info = cg.arithmeticTypeInfo(scalar_ty); const pred: Opcode = switch (info.class) { - .composite_integer => return cg.todo("comparison for composite integers", .{}), + .composite_integer => { + const lhs_id = try lhs.materialize(cg); + const rhs_id = try rhs.materialize(cg); + const scratch_top = cg.id_scratch.items.len; + defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); + const ci_lhs = try CompositeInt.init(cg, lhs_id, info); + const ci_rhs = try CompositeInt.init(cg, rhs_id, info); + const result_id = try ci_lhs.cmp(ci_rhs, op); + return .init(.bool, result_id); + }, .float => switch (op) { .eq => .OpFOrdEqual, .neq => .OpFUnordNotEqual, @@ -4408,6 +5480,192 @@ fn airIntCast(cg: *CodeGen, inst: Air.Inst.Index) !?Id { const src_info = cg.arithmeticTypeInfo(src.ty); const dst_info = cg.arithmeticTypeInfo(dst_ty); + const src_composite = src_info.class == .composite_integer; + const dst_composite = dst_info.class == .composite_integer; + + if (src_composite or dst_composite) { + const gpa = cg.module.gpa; + const scratch_top = cg.id_scratch.items.len; + defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); + + if (src_composite and dst_composite) { + const src_id = try src.materialize(cg); + const src_n: u16 = src_info.backing_bits / Module.big_int_bits; + const dst_n: u16 = dst_info.backing_bits / Module.big_int_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.module.allocId(); + try cg.body.emit(gpa, .OpCompositeExtract, .{ + .id_result_type = u32_ty_id, + .id_result = result_limbs[i], + .composite = src_id, + .indexes = &.{@as(u32, @intCast(i))}, + }); + } + if (dst_n > src_n) { + const fill = if (src_info.signedness == .signed) blk: { + const i32_ty_id = try cg.resolveType(.i32, .direct); + const msb = result_limbs[src_n - 1]; + const msb_signed = cg.module.allocId(); + try cg.body.emit(gpa, .OpBitcast, .{ + .id_result_type = i32_ty_id, + .id_result = msb_signed, + .operand = msb, + }); + const shift31 = try cg.constInt(.i32, @as(i32, 31)); + const sign_ext = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpShiftRightArithmetic, 4); + cg.body.writeOperand(Id, i32_ty_id); + cg.body.writeOperand(Id, sign_ext); + cg.body.writeOperand(Id, msb_signed); + cg.body.writeOperand(Id, shift31); + const back = cg.module.allocId(); + try cg.body.emit(gpa, .OpBitcast, .{ + .id_result_type = u32_ty_id, + .id_result = back, + .operand = sign_ext, + }); + break :blk back; + } else try cg.constInt(.u32, @as(u32, 0)); + for (min_n..dst_n) |i| { + result_limbs[i] = fill; + } + } + const ci = CompositeInt.fromLimbs(cg, result_limbs, dst_info); + const normalized = try ci.normalize(); + 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 limb0 = cg.module.allocId(); + try cg.body.emit(gpa, .OpCompositeExtract, .{ + .id_result_type = u32_ty_id, + .id_result = limb0, + .composite = src_id, + .indexes = &.{@as(u32, 0)}, + }); + const tmp: Temporary = .init(.u32, limb0); + const converted = try cg.buildConvert(dst_ty, tmp); + const result = if (dst_info.bits < src_info.bits) + try cg.normalize(converted, dst_info) + else + converted; + return try result.materialize(cg); + } else { + const limb0 = cg.module.allocId(); + try cg.body.emit(gpa, .OpCompositeExtract, .{ + .id_result_type = u32_ty_id, + .id_result = limb0, + .composite = src_id, + .indexes = &.{@as(u32, 0)}, + }); + const limb1 = cg.module.allocId(); + try cg.body.emit(gpa, .OpCompositeExtract, .{ + .id_result_type = u32_ty_id, + .id_result = limb1, + .composite = src_id, + .indexes = &.{@as(u32, 1)}, + }); + const u64_ty_id = try cg.resolveType(.u64, .direct); + const lo = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpUConvert, 3); + cg.body.writeOperand(Id, u64_ty_id); + cg.body.writeOperand(Id, lo); + cg.body.writeOperand(Id, limb0); + const hi = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpUConvert, 3); + cg.body.writeOperand(Id, u64_ty_id); + cg.body.writeOperand(Id, hi); + cg.body.writeOperand(Id, limb1); + const shift32 = try cg.constInt(.u64, @as(u64, 32)); + const hi_shifted = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpShiftLeftLogical, 4); + cg.body.writeOperand(Id, u64_ty_id); + cg.body.writeOperand(Id, hi_shifted); + cg.body.writeOperand(Id, hi); + cg.body.writeOperand(Id, shift32); + const combined = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpBitwiseOr, 4); + cg.body.writeOperand(Id, u64_ty_id); + cg.body.writeOperand(Id, combined); + cg.body.writeOperand(Id, lo); + cg.body.writeOperand(Id, hi_shifted); + const tmp: Temporary = .init(.u64, combined); + const converted = try cg.buildConvert(dst_ty, tmp); + const result = if (dst_info.bits < src_info.bits) + try cg.normalize(converted, dst_info) + else + converted; + return try result.materialize(cg); + } + } else { + const dst_n: u16 = dst_info.backing_bits / Module.big_int_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); + 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.module.allocId(); + try cg.body.emitRaw(gpa, .OpUConvert, 3); + cg.body.writeOperand(Id, u32_ty_id); + cg.body.writeOperand(Id, result_limbs[0]); + cg.body.writeOperand(Id, src_id); + const u64_ty_id = try cg.resolveType(.u64, .direct); + const shift32 = try cg.constInt(.u64, @as(u64, 32)); + const hi = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpShiftRightLogical, 4); + cg.body.writeOperand(Id, u64_ty_id); + cg.body.writeOperand(Id, hi); + cg.body.writeOperand(Id, src_id); + cg.body.writeOperand(Id, shift32); + result_limbs[1] = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpUConvert, 3); + cg.body.writeOperand(Id, u32_ty_id); + cg.body.writeOperand(Id, result_limbs[1]); + cg.body.writeOperand(Id, hi); + } + // Sign/zero-extend remaining limbs. + const fill_start: u16 = if (src_info.backing_bits <= 32) 1 else 2; + const fill = if (src_info.signedness == .signed) blk: { + const i32_ty_id = try cg.resolveType(.i32, .direct); + const msb = result_limbs[fill_start - 1]; + const msb_signed = cg.module.allocId(); + try cg.body.emit(gpa, .OpBitcast, .{ + .id_result_type = i32_ty_id, + .id_result = msb_signed, + .operand = msb, + }); + const shift31 = try cg.constInt(.i32, @as(i32, 31)); + const sign_ext = cg.module.allocId(); + try cg.body.emitRaw(gpa, .OpShiftRightArithmetic, 4); + cg.body.writeOperand(Id, i32_ty_id); + cg.body.writeOperand(Id, sign_ext); + cg.body.writeOperand(Id, msb_signed); + cg.body.writeOperand(Id, shift31); + const back = cg.module.allocId(); + try cg.body.emit(gpa, .OpBitcast, .{ + .id_result_type = u32_ty_id, + .id_result = back, + .operand = sign_ext, + }); + break :blk back; + } else try cg.constInt(.u32, @as(u32, 0)); + for (fill_start..dst_n) |i| { + result_limbs[i] = fill; + } + const ci = CompositeInt.fromLimbs(cg, result_limbs, dst_info); + const normalized = try ci.normalize(); + return try normalized.materialize(dst_ty); + } + } + if (src_info.backing_bits == dst_info.backing_bits) { const result = if (dst_info.bits < src_info.bits) try cg.normalize(src.pun(dst_ty), dst_info) @@ -4513,7 +5771,15 @@ fn airNot(cg: *CodeGen, inst: Air.Inst.Index) !?Id { const result = switch (info.class) { .bool => try cg.buildUnary(.l_not, operand), .float => unreachable, - .composite_integer => return cg.todo("bitwise not for composite integers", .{}), + .composite_integer => blk: { + const op_id = try operand.materialize(cg); + const scratch_top = cg.id_scratch.items.len; + defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); + const ci = try CompositeInt.init(cg, op_id, info); + const notted = try ci.bitwiseNot(); + const normalized = try notted.normalize(); + break :blk Temporary.init(result_ty, try normalized.materialize(result_ty)); + }, .strange_integer, .integer => blk: { const complement = try cg.buildUnary(.bit_not, operand); break :blk try cg.normalize(complement, info); @@ -6198,9 +7464,7 @@ fn airSwitchBr(cg: *CodeGen, inst: Air.Inst.Index) !void { const value: Value = .fromInterned(item.toInterned().?); const int_val: u64 = switch (cond_ty.zigTypeTag(zcu)) { .bool, .int => if (cond_ty.isSignedInt(zcu)) @bitCast(value.toSignedInt(zcu)) else value.toUnsignedInt(zcu), - .@"enum" => blk: { - break :blk value.intFromEnum(zcu).toUnsignedInt(zcu); // TODO: composite integer constants - }, + .@"enum" => value.intFromEnum(zcu).toUnsignedInt(zcu), .error_set => value.getErrorInt(zcu), .pointer => value.toUnsignedInt(zcu), else => unreachable, @@ -6502,7 +7766,7 @@ fn airCall(cg: *CodeGen, inst: Air.Inst.Index, modifier: std.lang.CallModifier) fn builtin3D( cg: *CodeGen, result_ty: Type, - builtin: spec.BuiltIn, + built_in: spec.BuiltIn, dimension: u32, out_of_range_value: anytype, ) !Id { @@ -6511,7 +7775,7 @@ fn builtin3D( const u32_ty_id = try cg.module.intType(.unsigned, 32); const vec_ty_id = try cg.module.vectorType(3, u32_ty_id); const ptr_ty_id = try cg.module.ptrType(vec_ty_id, .input); - const spv_decl_index = try cg.module.builtin(ptr_ty_id, builtin, .input); + const spv_decl_index = try cg.module.builtin(ptr_ty_id, built_in, .input); try cg.module.decl_deps.append(gpa, spv_decl_index); const ptr_id = cg.module.declPtr(spv_decl_index).result_id; const vec_id = cg.module.allocId(); diff --git a/src/codegen/spirv/Module.zig b/src/codegen/spirv/Module.zig @@ -658,8 +658,6 @@ pub fn intType(module: *Module, signedness: std.lang.Signedness, bits: u16) !Id }; const backing_bits, const big_int = module.backingIntBits(bits); if (big_int) { - // TODO: support composite integers larger than 64 bit - assert(backing_bits <= 64); const u32_ty = try module.intType(.unsigned, 32); const len_id = try module.constant(u32_ty, .{ .uint32 = backing_bits / big_int_bits }); return module.arrayType(len_id, u32_ty); diff --git a/src/target.zig b/src/target.zig @@ -953,7 +953,6 @@ pub inline fn backendSupportsFeature(backend: std.lang.CompilerBackend, comptime // threads because they would all just be locking the same mutex to // protect Builder. .stage2_llvm => false, - .stage2_spirv => true, // Please do not make any more exceptions. Backends must support // being run in a separate thread from now on. else => true, diff --git a/test/behavior/abs.zig b/test/behavior/abs.zig @@ -7,7 +7,6 @@ test "@abs integers" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; try comptime testAbsIntegers(); try testAbsIntegers(); @@ -54,7 +53,6 @@ test "@abs signed C ABI integers" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; const S = struct { fn doTheTest() !void { @@ -146,7 +144,6 @@ test "@abs big int <= 128 bits" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO try comptime testAbsSignedBigInt(); diff --git a/test/behavior/basic.zig b/test/behavior/basic.zig @@ -24,7 +24,6 @@ fn testTruncate(x: u32) u8 { } test "truncate to non-power-of-two integers" { - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO try testTrunc(u32, u1, 0b10101, 0b1); @@ -41,7 +40,6 @@ test "truncate to non-power-of-two integers" { test "truncate to non-power-of-two integers from 128-bit" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; try testTrunc(u128, u1, 0xffffffff_ffffffff_ffffffff_01010101, 0x01); try testTrunc(u128, u1, 0xffffffff_ffffffff_ffffffff_01010110, 0x00); @@ -299,7 +297,6 @@ const global_b: *const i32 = &global_a; const global_c: *const f32 = @as(*const f32, @ptrCast(global_b)); test "compile time global reinterpret" { if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; - const d = @as(*const i32, @ptrCast(global_c)); try expect(d.* == 1234); } @@ -1217,8 +1214,6 @@ fn testUnsignedCmp(comptime T: type) !void { } test "integer compare <= 64 bits" { - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; - inline for (.{ u8, u16, u32, u64, usize, u10, u20, u30, u60 }) |T| { try testUnsignedCmp(T); try comptime testUnsignedCmp(T); @@ -1231,7 +1226,6 @@ test "integer compare <= 64 bits" { test "integer compare <= 128 bits" { if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; inline for (.{ u65, u96, u127, u128 }) |T| { try testUnsignedCmp(T); @@ -1245,7 +1239,6 @@ test "integer compare <= 128 bits" { test "integer compare > 128 bits" { if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; inline for (.{ u129, u255, u512, u800 }) |T| { try testUnsignedCmp(T); diff --git a/test/behavior/enum.zig b/test/behavior/enum.zig @@ -26,7 +26,6 @@ const EnumFromIntNumber = enum { Zero, One, Two, Three, Four }; test "int to enum" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; try testEnumFromIntEval(3); } @@ -974,7 +973,6 @@ test "@tagName" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; try expect(mem.eql(u8, testEnumTagNameBare(BareNumber.Three), "Three")); diff --git a/test/behavior/eval.zig b/test/behavior/eval.zig @@ -456,7 +456,6 @@ test "binary math operator in partially inlined function" { test "comptime shl" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; const a: u128 = 3; const b: u7 = 63; @@ -904,7 +903,6 @@ test "const local with comptime init through array init" { } test "closure capture type of runtime-known parameter" { - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO const S = struct { @@ -1185,7 +1183,6 @@ test "lazy sizeof is resolved in division" { } test "lazy sizeof union tag size in compare" { - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; const A = union(enum) { a: void, diff --git a/test/behavior/floatop.zig b/test/behavior/floatop.zig @@ -144,7 +144,6 @@ test "cmp f64" { test "cmp f128" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_c and builtin.cpu.arch.isArm()) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; try testCmp(f128); diff --git a/test/behavior/int128.zig b/test/behavior/int128.zig @@ -7,7 +7,6 @@ const builtin = @import("builtin"); test "uint128" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; var buff: u128 = maxInt(u128); @@ -45,7 +44,6 @@ test "undefined 128 bit int" { test "int128" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; var buff: i128 = -1; @@ -90,7 +88,6 @@ test "truncate int128" { test "shift int128" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const types = .{ u128, i128 }; diff --git a/test/behavior/math.zig b/test/behavior/math.zig @@ -383,7 +383,6 @@ fn not(comptime T: type, a: T) T { test "binary not" { if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; try expect(not(u0, 0) == 0); try expect(not(u1, 0) == 1); @@ -424,7 +423,6 @@ test "binary not" { test "binary not big int <= 128 bits" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; try expect(not(u65, 1) == 0x1_FFFFFFFF_FFFFFFFE); @@ -659,7 +657,6 @@ fn testSignedWrappingEval(x: i32) !void { } test "signed negation wrapping" { - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; try testSignedNegationWrappingEval(minInt(i16)); try comptime testSignedNegationWrappingEval(minInt(i16)); @@ -763,7 +760,6 @@ fn should_not_be_zero(x: f128) !void { test "umax wrapped squaring" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; { var x: u4 = maxInt(u4); @@ -820,7 +816,6 @@ test "umax wrapped squaring" { test "128-bit multiplication" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; if (builtin.zig_backend == .stage2_c and builtin.cpu.arch.isArm()) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; @@ -873,7 +868,6 @@ test "@addWithOverflow <= 128 bits" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO try testAddWithOverflow(u65, 4, 105, 109, 0); try testAddWithOverflow(u65, 1000, 100, 1100, 0); @@ -1000,7 +994,6 @@ test "extensive @mulWithOverflow" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; try testMulWithOverflow(u5, 3, 10, 30, 0); try testMulWithOverflow(u5, 3, 11, 1, 1); @@ -1207,7 +1200,6 @@ test "@subWithOverflow <= 128 bits" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO try testSubWithOverflow(u65, 4, 105, maxInt(u65) - 100, 1); try testSubWithOverflow(u65, 1000, 100, 900, 0); @@ -1371,7 +1363,6 @@ fn testAnd(comptime T: type, a: T, b: T, expected: T) !void { test "and > 128 bits" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; try testAnd(u140, (1 << 139) | (1 << 70) | 0xaa, (1 << 139) | (1 << 69) | 0xcc, (1 << 139) | 0x88); try testAnd(u140, maxInt(u140), 1 << 100, 1 << 100); @@ -1431,7 +1422,6 @@ fn testXor(comptime T: type, a: T, b: T, expected: T) !void { test "xor > 128 bits" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; try testXor(u140, 0, maxInt(u140), maxInt(u140)); try testXor(u140, 1 << 139, 1 << 139, 0); @@ -1461,7 +1451,6 @@ fn testNot(comptime T: type, a: T, expected: T) !void { test "not > 128 bits" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; try testNot(u140, 0, maxInt(u140)); try testNot(u140, maxInt(u140), 0); @@ -1491,7 +1480,6 @@ fn testShl(comptime T: type, a: T, b: std.math.Log2Int(T), expected: T) !void { test "shl > 128 bits" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; try testShl(u140, 1 << 5, 10, 1 << 15); try testShl(u140, 3, 138, (1 << 139) | (1 << 138)); @@ -1521,7 +1509,6 @@ fn testShr(comptime T: type, a: T, b: std.math.Log2Int(T), expected: T) !void { test "shr > 128 bits" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; try testShr(u140, 1 << 139, 39, 1 << 100); try testShr(u140, (1 << 70) | 8, 3, (1 << 67) | 1); @@ -2534,7 +2521,6 @@ test "partially-runtime integer vector division would be illegal if vector eleme if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch == .hexagon) return error.SkipZigTest; var lhs: @Vector(2, i8) = .{ -128, 5 }; @@ -2562,7 +2548,6 @@ test "float vector division of comptime zero by runtime nan is nan" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; const ct_zero: @Vector(1, f32) = .{0}; var rt_nan: @Vector(1, f32) = .{math.nan(f32)}; @@ -2579,7 +2564,6 @@ test "float vector multiplication of comptime zero by runtime nan is nan" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; const ct_zero: @Vector(1, f32) = .{0}; var rt_nan: @Vector(1, f32) = .{math.nan(f32)}; @@ -2594,7 +2578,6 @@ test "comptime float vector division of zero by nan is nan" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; const ct_zero: @Vector(1, f32) = .{0}; const ct_nan: @Vector(1, f32) = .{math.nan(f32)}; @@ -2608,7 +2591,6 @@ test "comptime float vector multiplication of zero by nan is nan" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; const ct_zero: @Vector(1, f32) = .{0}; const ct_nan: @Vector(1, f32) = .{math.nan(f32)}; diff --git a/test/behavior/muladd.zig b/test/behavior/muladd.zig @@ -6,7 +6,6 @@ test "@mulAdd" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; try comptime testMulAdd(); try testMulAdd(); @@ -33,7 +32,6 @@ test "@mulAdd f16" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; try comptime testMulAdd16(); try testMulAdd16(); diff --git a/test/behavior/while.zig b/test/behavior/while.zig @@ -38,7 +38,6 @@ fn staticWhileLoop2() i32 { } test "while with continue expression" { - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; var sum: i32 = 0; { @@ -158,7 +157,6 @@ test "while with optional as condition with else" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; numbers_left = 10; var sum: i32 = 0; @@ -176,7 +174,6 @@ test "while with optional as condition with else" { test "while with error union condition" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; numbers_left = 10; var sum: i32 = 0; diff --git a/test/behavior/widening.zig b/test/behavior/widening.zig @@ -6,7 +6,6 @@ const builtin = @import("builtin"); test "integer widening" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; var a: u8 = 250; diff --git a/test/behavior/wrapping_arithmetic.zig b/test/behavior/wrapping_arithmetic.zig @@ -3,7 +3,6 @@ const builtin = @import("builtin"); const minInt = std.math.minInt; const maxInt = std.math.maxInt; const expect = std.testing.expect; -const skip128 = builtin.zig_backend == .stage2_spirv; test "wrapping add" { if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; @@ -14,14 +13,14 @@ test "wrapping add" { try testWrapAdd(i8, -128, -128, 0); try testWrapAdd(i2, 1, 1, -2); try testWrapAdd(i64, maxInt(i64), 1, minInt(i64)); - if (!skip128) try testWrapAdd(i128, maxInt(i128), -maxInt(i128), 0); - if (!skip128) try testWrapAdd(i128, minInt(i128), maxInt(i128), -1); + try testWrapAdd(i128, maxInt(i128), -maxInt(i128), 0); + try testWrapAdd(i128, minInt(i128), maxInt(i128), -1); try testWrapAdd(i8, 127, 127, -2); try testWrapAdd(u8, 3, 10, 13); try testWrapAdd(u8, 255, 255, 254); try testWrapAdd(u2, 3, 2, 1); try testWrapAdd(u3, 7, 1, 0); - if (!skip128) try testWrapAdd(u128, maxInt(u128), 1, minInt(u128)); + try testWrapAdd(u128, maxInt(u128), 1, minInt(u128)); } fn testWrapAdd(comptime T: type, lhs: T, rhs: T, expected: T) !void { @@ -51,12 +50,12 @@ test "wrapping subtraction" { try testWrapSub(i8, -128, -128, 0); try testWrapSub(i8, -1, 127, -128); try testWrapSub(i64, minInt(i64), 1, maxInt(i64)); - if (!skip128) try testWrapSub(i128, maxInt(i128), -1, minInt(i128)); - if (!skip128) try testWrapSub(i128, minInt(i128), -maxInt(i128), -1); + try testWrapSub(i128, maxInt(i128), -1, minInt(i128)); + try testWrapSub(i128, minInt(i128), -maxInt(i128), -1); try testWrapSub(u8, 10, 3, 7); try testWrapSub(u8, 0, 255, 1); try testWrapSub(u5, 0, 31, 1); - if (!skip128) try testWrapSub(u128, 0, maxInt(u128), 1); + try testWrapSub(u128, 0, maxInt(u128), 1); } fn testWrapSub(comptime T: type, lhs: T, rhs: T, expected: T) !void { @@ -88,11 +87,11 @@ test "wrapping multiplication" { try testWrapMul(i8, -128, -128, 0); try testWrapMul(i8, maxInt(i8), maxInt(i8), 1); try testWrapMul(i16, maxInt(i16), -1, minInt(i16) + 1); - if (!skip128) try testWrapMul(i128, maxInt(i128), -1, minInt(i128) + 1); - if (!skip128) try testWrapMul(i128, minInt(i128), -1, minInt(i128)); + try testWrapMul(i128, maxInt(i128), -1, minInt(i128) + 1); + try testWrapMul(i128, minInt(i128), -1, minInt(i128)); try testWrapMul(u8, 10, 3, 30); try testWrapMul(u8, 2, 255, 254); - if (!skip128) try testWrapMul(u128, maxInt(u128), maxInt(u128), 1); + try testWrapMul(u128, maxInt(u128), maxInt(u128), 1); } fn testWrapMul(comptime T: type, lhs: T, rhs: T, expected: T) !void {