From 3ef5b80d2c359c94ec2fa14bde492a6c9774d536 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sat, 13 Jan 2024 18:43:50 +0100 Subject: [PATCH 01/20] std: use simple eqlBytes for spirv The SPIR-V backend doesn't support the advanced eqlBytes yet, and when it does, it likely that it will be detrimental. --- lib/std/mem.zig | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/std/mem.zig b/lib/std/mem.zig index f15773170a170af6ea11f1db8997739537f5aac2..55c51d595985cf5ea817f2468ca1d6172e3e1c7a 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -632,10 +632,16 @@ test "lessThan" { try testing.expect(lessThan(u8, "", "a")); } +const backend_can_use_eql_bytes = switch (builtin.zig_backend) { + // The SPIR-V backend does not support the optimized path yet. + .stage2_spirv64 => false, + else => true, +}; + /// Compares two slices and returns whether they are equal. pub fn eql(comptime T: type, a: []const T, b: []const T) bool { if (@sizeOf(T) == 0) return true; - if (!@inComptime() and std.meta.hasUniqueRepresentation(T)) return eqlBytes(sliceAsBytes(a), sliceAsBytes(b)); + if (!@inComptime() and std.meta.hasUniqueRepresentation(T) and backend_can_use_eql_bytes) return eqlBytes(sliceAsBytes(a), sliceAsBytes(b)); if (a.len != b.len) return false; if (a.len == 0 or a.ptr == b.ptr) return true; @@ -648,6 +654,10 @@ pub fn eql(comptime T: type, a: []const T, b: []const T) bool { /// std.mem.eql heavily optimized for slices of bytes. fn eqlBytes(a: []const u8, b: []const u8) bool { + if (!backend_can_use_eql_bytes) { + return eql(u8, a, b); + } + if (a.len != b.len) return false; if (a.len == 0 or a.ptr == b.ptr) return true; -- 2.54.0 From 747f4ae3f5efc89df0b1b76787eb90eab90fc362 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Mon, 15 Jan 2024 21:58:13 +0100 Subject: [PATCH 02/20] spirv: sh[rl](_exact)? --- src/codegen/spirv.zig | 43 ++++++++++++++++++++++++++++-------------- test/behavior/math.zig | 5 ----- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 6c058308df7c65af892c638f58033e4ca0a15b8f..580c3d959a050568d10b8c6bbb8b83e61165a316 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -2111,7 +2111,8 @@ const DeclGen = struct { .bool_and => try self.airBinOpSimple(inst, .OpLogicalAnd), .bool_or => try self.airBinOpSimple(inst, .OpLogicalOr), - .shl => try self.airShift(inst, .OpShiftLeftLogical), + .shl, .shl_exact => try self.airShift(inst, .OpShiftLeftLogical, .OpShiftLeftLogical), + .shr, .shr_exact => try self.airShift(inst, .OpShiftRightLogical, .OpShiftRightArithmetic), .min => try self.airMinMax(inst, .lt), .max => try self.airMinMax(inst, .gt), @@ -2254,28 +2255,42 @@ const DeclGen = struct { return try self.binOpSimple(ty, lhs_id, rhs_id, opcode); } - fn airShift(self: *DeclGen, inst: Air.Inst.Index, comptime opcode: Opcode) !?IdRef { + fn airShift(self: *DeclGen, inst: Air.Inst.Index, comptime unsigned: Opcode, comptime signed: Opcode) !?IdRef { if (self.liveness.isUnused(inst)) return null; + const mod = self.module; const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; const lhs_id = try self.resolve(bin_op.lhs); const rhs_id = try self.resolve(bin_op.rhs); - const result_type_id = try self.resolveTypeId(self.typeOfIndex(inst)); - - // the shift and the base must be the same type in SPIR-V, but in Zig the shift is a smaller int. - const shift_id = self.spv.allocId(); - try self.func.body.emit(self.spv.gpa, .OpUConvert, .{ - .id_result_type = result_type_id, - .id_result = shift_id, - .unsigned_value = rhs_id, - }); + const result_ty = self.typeOfIndex(inst); + const result_ty_ref = try self.resolveType(result_ty, .direct); const result_id = self.spv.allocId(); - try self.func.body.emit(self.spv.gpa, opcode, .{ - .id_result_type = result_type_id, + + // Sometimes Zig doesn't make both of the arguments the same types here. SPIR-V expects that, + // so just manually upcast it if required. + const shift_ty_ref = try self.resolveType(self.typeOf(bin_op.rhs), .direct); + const shift_id = if (shift_ty_ref != result_ty_ref) blk: { + const shift_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpUConvert, .{ + .id_result_type = self.typeId(result_ty_ref), + .id_result = shift_id, + .unsigned_value = rhs_id, + }); + break :blk shift_id; + } else rhs_id; + + const args = .{ + .id_result_type = self.typeId(result_ty_ref), .id_result = result_id, .base = lhs_id, .shift = shift_id, - }); + }; + + if (result_ty.isSignedInt(mod)) { + try self.func.body.emit(self.spv.gpa, signed, args); + } else { + try self.func.body.emit(self.spv.gpa, unsigned, args); + } return result_id; } diff --git a/test/behavior/math.zig b/test/behavior/math.zig index 5b5d62df96bbdcf90ca9fc076f0ea32fb6c08363..e96299abaa3016990cbc2fc19650319b67ac0915 100644 --- a/test/behavior/math.zig +++ b/test/behavior/math.zig @@ -12,7 +12,6 @@ const math = std.math; test "assignment operators" { 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_spirv64) return error.SkipZigTest; var i: u32 = 0; i += 5; @@ -649,8 +648,6 @@ test "bit shift a u1" { } test "truncating shift right" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - try testShrTrunc(maxInt(u16)); try comptime testShrTrunc(maxInt(u16)); } @@ -1343,8 +1340,6 @@ fn testShlExact(x: u8) !void { } test "exact shift right" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - try testShrExact(0b10110100); try comptime testShrExact(0b10110100); } -- 2.54.0 From cb9e20da00a2c33706e2c7bf2008887c6c72a896 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Mon, 15 Jan 2024 23:06:54 +0100 Subject: [PATCH 03/20] spirv: element-wise operation helper --- src/codegen/spirv.zig | 123 ++++++++++++++++++++++++++++++++--------- test/behavior/math.zig | 1 + 2 files changed, 97 insertions(+), 27 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 580c3d959a050568d10b8c6bbb8b83e61165a316..a8bc385f7a367e5aaceac47faedc62c24097ea8e 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -1760,6 +1760,92 @@ const DeclGen = struct { return union_layout; } + /// This structure is used as helper for element-wise operations. It is intended + /// to be used with both vectors and single elements. + const WipElementWise = struct { + dg: *DeclGen, + result_ty: Type, + /// Always in direct representation. + result_ty_ref: CacheRef, + scalar_ty: Type, + /// Always in direct representation. + scalar_ty_ref: CacheRef, + scalar_ty_id: IdRef, + /// True if the input is actually a vector type. + is_vector: bool, + /// The element-wise operation should fill these results before calling finalize(). + /// These should all be in **direct** representation! `finalize()` will convert + /// them to indirect if required. + results: []IdRef, + + fn deinit(wip: *WipElementWise) void { + wip.dg.gpa.free(wip.results); + } + + /// Utility function to extract the element at a particular index in an + /// input vector. This type is expected to be a vector if `wip.is_vector`, and + /// a scalar otherwise. + fn elementAt(wip: WipElementWise, ty: Type, value: IdRef, index: usize) !IdRef { + const mod = wip.dg.module; + if (wip.is_vector) { + assert(ty.isVector(mod)); + return try wip.dg.extractField(ty, value, @intCast(index)); + } else { + assert(!ty.isVector(mod)); + assert(index == 0); + return value; + } + } + + /// Turns the results of this WipElementWise into a result. This can either + /// be a vector or single element, depending on `result_ty`. + /// After calling this function, this WIP is no longer usable. + /// Results is in `direct` representation. + fn finalize(wip: *WipElementWise) !IdRef { + if (wip.is_vector) { + // Convert all the constituents to indirect, as required for the array. + for (wip.results) |*result| { + result.* = try wip.dg.convertToIndirect(wip.scalar_ty, result.*); + } + return try wip.dg.constructArray(wip.result_ty, wip.results); + } else { + return wip.results[0]; + } + } + + /// Allocate a result id at a particular index, and return it. + fn allocId(wip: *WipElementWise, index: usize) IdRef { + assert(wip.is_vector or index == 0); + wip.results[index] = wip.dg.spv.allocId(); + return wip.results[index]; + } + }; + + /// Create a new element-wise operation. + fn elementWise(self: *DeclGen, result_ty: Type) !WipElementWise { + const mod = self.module; + // For now, this operation also reasons in terms of `.direct` representation. + const result_ty_ref = try self.resolveType(result_ty, .direct); + const is_vector = result_ty.isVector(mod); + const num_results = if (is_vector) result_ty.vectorLen(mod) else 1; + const results = try self.gpa.alloc(IdRef, num_results); + for (results) |*result| result.* = undefined; + + const scalar_ty = if (is_vector) result_ty.childType(mod) else result_ty; + const scalar_ty_ref = try self.resolveType(scalar_ty, .direct); + + return .{ + .dg = self, + .result_ty = result_ty, + .result_ty_ref = result_ty_ref, + .scalar_ty = scalar_ty, + .scalar_ty_ref = scalar_ty_ref, + .scalar_ty_id = self.typeId(scalar_ty_ref), + .is_vector = is_vector, + .results = results, + }; + } + /// The SPIR-V backend is not yet advanced enough to support the std testing infrastructure. /// In order to be able to run tests, we "temporarily" lower test kernels into separate entry- /// points. The test executor will then be able to invoke these to run the tests. @@ -2214,34 +2300,17 @@ const DeclGen = struct { } fn binOpSimple(self: *DeclGen, ty: Type, lhs_id: IdRef, rhs_id: IdRef, comptime opcode: Opcode) !IdRef { - const mod = self.module; - - if (ty.isVector(mod)) { - const child_ty = ty.childType(mod); - const vector_len = ty.vectorLen(mod); - - const constituents = try self.gpa.alloc(IdRef, vector_len); - defer self.gpa.free(constituents); - - for (constituents, 0..) |*constituent, i| { - const lhs_index_id = try self.extractField(child_ty, lhs_id, @intCast(i)); - const rhs_index_id = try self.extractField(child_ty, rhs_id, @intCast(i)); - const result_id = try self.binOpSimple(child_ty, lhs_index_id, rhs_index_id, opcode); - constituent.* = try self.convertToIndirect(child_ty, result_id); - } - - return try self.constructArray(ty, constituents); + var wip = try self.elementWise(ty); + defer wip.deinit(); + for (0..wip.results.len) |i| { + try self.func.body.emit(self.spv.gpa, opcode, .{ + .id_result_type = wip.scalar_ty_id, + .id_result = wip.allocId(i), + .operand_1 = try wip.elementAt(ty, lhs_id, i), + .operand_2 = try wip.elementAt(ty, rhs_id, i), + }); } - - const result_id = self.spv.allocId(); - const result_type_id = try self.resolveTypeId(ty); - try self.func.body.emit(self.spv.gpa, opcode, .{ - .id_result_type = result_type_id, - .id_result = result_id, - .operand_1 = lhs_id, - .operand_2 = rhs_id, - }); - return result_id; + return try wip.finalize(); } fn airBinOpSimple(self: *DeclGen, inst: Air.Inst.Index, comptime opcode: Opcode) !?IdRef { diff --git a/test/behavior/math.zig b/test/behavior/math.zig index e96299abaa3016990cbc2fc19650319b67ac0915..93c467eb5d1e0f627fb4522c6733e72efb85bc79 100644 --- a/test/behavior/math.zig +++ b/test/behavior/math.zig @@ -12,6 +12,7 @@ const math = std.math; test "assignment operators" { 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_spirv64) return error.SkipZigTest; var i: u32 = 0; i += 5; -- 2.54.0 From 403c6262bb4c9087f1d0138fc83fe4dd979864ad Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Mon, 15 Jan 2024 23:38:43 +0100 Subject: [PATCH 04/20] spirv: use new vector stuff for arithOp and shift --- src/codegen/spirv.zig | 158 ++++++++++++++++++++++-------------------- 1 file changed, 83 insertions(+), 75 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index a8bc385f7a367e5aaceac47faedc62c24097ea8e..a3b8a6c8f6ec4769653a6a98c9e9426a95854ff8 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -1782,6 +1782,19 @@ const DeclGen = struct { wip.dg.gpa.free(wip.results); } + /// Return the scalar type of an input vector. This type is expected to be a vector + /// if `wip.is_vector`, and a scalar otherwise. + fn scalarType(wip: WipElementWise, ty: Type) Type { + const mod = wip.dg.module; + if (wip.is_vector) { + assert(ty.isVector(mod)); + return ty.childType(mod); + } else { + assert(!ty.isVector(mod)); + return ty; + } + } + /// Utility function to extract the element at a particular index in an /// input vector. This type is expected to be a vector if `wip.is_vector`, and /// a scalar otherwise. @@ -1789,7 +1802,7 @@ const DeclGen = struct { const mod = wip.dg.module; if (wip.is_vector) { assert(ty.isVector(mod)); - return try wip.dg.extractField(ty, value, @intCast(index)); + return try wip.dg.extractField(ty.childType(mod), value, @intCast(index)); } else { assert(!ty.isVector(mod)); assert(index == 0); @@ -2331,36 +2344,45 @@ const DeclGen = struct { const lhs_id = try self.resolve(bin_op.lhs); const rhs_id = try self.resolve(bin_op.rhs); const result_ty = self.typeOfIndex(inst); - const result_ty_ref = try self.resolveType(result_ty, .direct); - - const result_id = self.spv.allocId(); // Sometimes Zig doesn't make both of the arguments the same types here. SPIR-V expects that, // so just manually upcast it if required. - const shift_ty_ref = try self.resolveType(self.typeOf(bin_op.rhs), .direct); - const shift_id = if (shift_ty_ref != result_ty_ref) blk: { - const shift_id = self.spv.allocId(); - try self.func.body.emit(self.spv.gpa, .OpUConvert, .{ - .id_result_type = self.typeId(result_ty_ref), - .id_result = shift_id, - .unsigned_value = rhs_id, - }); - break :blk shift_id; - } else rhs_id; + // TODO(robin) - const args = .{ - .id_result_type = self.typeId(result_ty_ref), - .id_result = result_id, - .base = lhs_id, - .shift = shift_id, - }; + var wip = try self.elementWise(result_ty); + defer wip.deinit(); - if (result_ty.isSignedInt(mod)) { - try self.func.body.emit(self.spv.gpa, signed, args); - } else { - try self.func.body.emit(self.spv.gpa, unsigned, args); + const shift_ty = wip.scalarType(self.typeOf(bin_op.rhs)); + const shift_ty_ref = try self.resolveType(shift_ty, .direct); + + for (0..wip.results.len) |i| { + const lhs_elem_id = try wip.elementAt(result_ty, lhs_id, i); + const rhs_elem_id = try wip.elementAt(result_ty, rhs_id, i); + + const shift_id = if (shift_ty_ref != wip.result_ty_ref) blk: { + const shift_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpUConvert, .{ + .id_result_type = wip.scalar_ty_id, + .id_result = shift_id, + .unsigned_value = rhs_elem_id, + }); + break :blk shift_id; + } else rhs_elem_id; + + const args = .{ + .id_result_type = wip.scalar_ty_id, + .id_result = wip.allocId(i), + .base = lhs_elem_id, + .shift = shift_id, + }; + + if (result_ty.isSignedInt(mod)) { + try self.func.body.emit(self.spv.gpa, signed, args); + } else { + try self.func.body.emit(self.spv.gpa, unsigned, args); + } } - return result_id; + return try wip.finalize(); } fn airMinMax(self: *DeclGen, inst: Air.Inst.Index, op: std.math.CompareOperator) !?IdRef { @@ -2483,35 +2505,14 @@ const DeclGen = struct { fn arithOp( self: *DeclGen, ty: Type, - lhs_id_: IdRef, - rhs_id_: IdRef, + lhs_id: IdRef, + rhs_id: IdRef, comptime fop: Opcode, comptime sop: Opcode, comptime uop: Opcode, /// true if this operation holds under modular arithmetic. comptime modular: bool, ) !IdRef { - var rhs_id = rhs_id_; - var lhs_id = lhs_id_; - - const mod = self.module; - const result_ty_ref = try self.resolveType(ty, .direct); - - if (ty.isVector(mod)) { - const child_ty = ty.childType(mod); - const vector_len = ty.vectorLen(mod); - const constituents = try self.gpa.alloc(IdRef, vector_len); - defer self.gpa.free(constituents); - - for (constituents, 0..) |*constituent, i| { - const lhs_index_id = try self.extractField(child_ty, lhs_id, @intCast(i)); - const rhs_index_id = try self.extractField(child_ty, rhs_id, @intCast(i)); - constituent.* = try self.arithOp(child_ty, lhs_index_id, rhs_index_id, fop, sop, uop, modular); - } - - return self.constructArray(ty, constituents); - } - // Binary operations are generally applicable to both scalar and vector operations // in SPIR-V, but int and float versions of operations require different opcodes. const info = try self.arithmeticTypeInfo(ty); @@ -2520,17 +2521,7 @@ const DeclGen = struct { .composite_integer => { return self.todo("binary operations for composite integers", .{}); }, - .strange_integer => blk: { - if (!modular) { - lhs_id = try self.normalizeInt(result_ty_ref, lhs_id, info); - rhs_id = try self.normalizeInt(result_ty_ref, rhs_id, info); - } - break :blk switch (info.signedness) { - .signed => @as(usize, 1), - .unsigned => @as(usize, 2), - }; - }, - .integer => switch (info.signedness) { + .integer, .strange_integer => switch (info.signedness) { .signed => @as(usize, 1), .unsigned => @as(usize, 2), }, @@ -2538,24 +2529,41 @@ const DeclGen = struct { .bool => unreachable, }; - const result_id = self.spv.allocId(); - const operands = .{ - .id_result_type = self.typeId(result_ty_ref), - .id_result = result_id, - .operand_1 = lhs_id, - .operand_2 = rhs_id, - }; - - switch (opcode_index) { - 0 => try self.func.body.emit(self.spv.gpa, fop, operands), - 1 => try self.func.body.emit(self.spv.gpa, sop, operands), - 2 => try self.func.body.emit(self.spv.gpa, uop, operands), - else => unreachable, + var wip = try self.elementWise(ty); + defer wip.deinit(); + for (0..wip.results.len) |i| { + const lhs_elem_id = try wip.elementAt(ty, lhs_id, i); + const rhs_elem_id = try wip.elementAt(ty, rhs_id, i); + + const lhs_norm_id = if (modular and info.class == .strange_integer) + try self.normalizeInt(wip.scalar_ty_ref, lhs_elem_id, info) + else + lhs_elem_id; + + const rhs_norm_id = if (modular and info.class == .strange_integer) + try self.normalizeInt(wip.scalar_ty_ref, rhs_elem_id, info) + else + rhs_elem_id; + + const operands = .{ + .id_result_type = wip.scalar_ty_id, + .id_result = wip.allocId(i), + .operand_1 = lhs_norm_id, + .operand_2 = rhs_norm_id, + }; + + switch (opcode_index) { + 0 => try self.func.body.emit(self.spv.gpa, fop, operands), + 1 => try self.func.body.emit(self.spv.gpa, sop, operands), + 2 => try self.func.body.emit(self.spv.gpa, uop, operands), + else => unreachable, + } + + // TODO: Trap on overflow? Probably going to be annoying. + // TODO: Look into SPV_KHR_no_integer_wrap_decoration which provides NoSignedWrap/NoUnsignedWrap. } - // TODO: Trap on overflow? Probably going to be annoying. - // TODO: Look into SPV_KHR_no_integer_wrap_decoration which provides NoSignedWrap/NoUnsignedWrap. - return result_id; + return try wip.finalize(); } fn airAddSubOverflow( -- 2.54.0 From 15cf5f88c1bfb4b92285c515d294e1061d02672a Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Mon, 15 Jan 2024 23:50:06 +0100 Subject: [PATCH 05/20] spirv: vectors for air not --- src/codegen/spirv.zig | 45 ++++++++++++++++++++-------------------- test/behavior/vector.zig | 1 - 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index a3b8a6c8f6ec4769653a6a98c9e9426a95854ff8..7732f9eccb3427a56a4901bbfc41da628e07c388 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -3218,31 +3218,31 @@ const DeclGen = struct { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const operand_id = try self.resolve(ty_op.operand); const result_ty = self.typeOfIndex(inst); - const result_ty_id = try self.resolveTypeId(result_ty); const info = try self.arithmeticTypeInfo(result_ty); - const result_id = self.spv.allocId(); - switch (info.class) { - .bool => { - try self.func.body.emit(self.spv.gpa, .OpLogicalNot, .{ - .id_result_type = result_ty_id, - .id_result = result_id, - .operand = operand_id, - }); - }, - .float => unreachable, - .composite_integer => unreachable, // TODO - .strange_integer, .integer => { - // Note: strange integer bits will be masked before operations that do not hold under modulo. - try self.func.body.emit(self.spv.gpa, .OpNot, .{ - .id_result_type = result_ty_id, - .id_result = result_id, - .operand = operand_id, - }); - }, + var wip = try self.elementWise(result_ty); + defer wip.deinit(); + + for (0..wip.results.len) |i| { + const args = .{ + .id_result_type = wip.scalar_ty_id, + .id_result = wip.allocId(i), + .operand = try wip.elementAt(result_ty, operand_id, i), + }; + switch (info.class) { + .bool => { + try self.func.body.emit(self.spv.gpa, .OpLogicalNot, args); + }, + .float => unreachable, + .composite_integer => unreachable, // TODO + .strange_integer, .integer => { + // Note: strange integer bits will be masked before operations that do not hold under modulo. + try self.func.body.emit(self.spv.gpa, .OpNot, args); + }, + } } - return result_id; + return try wip.finalize(); } fn airArrayToSlice(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { @@ -3305,7 +3305,6 @@ const DeclGen = struct { const elements: []const Air.Inst.Ref = @ptrCast(self.air.extra[ty_pl.payload..][0..len]); switch (result_ty.zigTypeTag(mod)) { - .Vector => unreachable, // TODO .Struct => { if (mod.typeToPackedStruct(result_ty)) |struct_type| { _ = struct_type; @@ -3353,7 +3352,7 @@ const DeclGen = struct { constituents[0..index], ); }, - .Array => { + .Vector, .Array => { const array_info = result_ty.arrayInfo(mod); const n_elems: usize = @intCast(result_ty.arrayLenIncludingSentinel(mod)); const elem_ids = try self.gpa.alloc(IdRef, n_elems); diff --git a/test/behavior/vector.zig b/test/behavior/vector.zig index ab4f89a0524d76874a8a6643dc5cc888bb338210..0c28b519b35529630cf51e719a0e1d9ca5935212 100644 --- a/test/behavior/vector.zig +++ b/test/behavior/vector.zig @@ -628,7 +628,6 @@ test "vector bitwise not operator" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO 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_spirv64) return error.SkipZigTest; const S = struct { fn doTheTestNot(comptime T: type, x: @Vector(4, T)) !void { -- 2.54.0 From 2f815853dcae49bbfd109675cde1f4097b75c8cc Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Tue, 16 Jan 2024 23:06:15 +0100 Subject: [PATCH 06/20] spirv: shlWithOverflow --- src/codegen/spirv.zig | 128 +++++++++++++++++++++++++++++++-------- test/behavior/math.zig | 2 - test/behavior/vector.zig | 5 -- 3 files changed, 103 insertions(+), 32 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 7732f9eccb3427a56a4901bbfc41da628e07c388..286b45f9733b55cd3d87b9b290ffe6231978fd77 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -1782,19 +1782,6 @@ const DeclGen = struct { wip.dg.gpa.free(wip.results); } - /// Return the scalar type of an input vector. This type is expected to be a vector - /// if `wip.is_vector`, and a scalar otherwise. - fn scalarType(wip: WipElementWise, ty: Type) Type { - const mod = wip.dg.module; - if (wip.is_vector) { - assert(ty.isVector(mod)); - return ty.childType(mod); - } else { - assert(!ty.isVector(mod)); - return ty; - } - } - /// Utility function to extract the element at a particular index in an /// input vector. This type is expected to be a vector if `wip.is_vector`, and /// a scalar otherwise. @@ -1844,7 +1831,7 @@ const DeclGen = struct { const results = try self.gpa.alloc(IdRef, num_results); for (results) |*result| result.* = undefined; - const scalar_ty = if (is_vector) result_ty.childType(mod) else result_ty; + const scalar_ty = result_ty.scalarType(mod); const scalar_ty_ref = try self.resolveType(scalar_ty, .direct); return .{ @@ -2198,6 +2185,7 @@ const DeclGen = struct { .add_with_overflow => try self.airAddSubOverflow(inst, .OpIAdd, .OpULessThan, .OpSLessThan), .sub_with_overflow => try self.airAddSubOverflow(inst, .OpISub, .OpUGreaterThan, .OpSGreaterThan), + .shl_with_overflow => try self.airShlOverflow(inst), .shuffle => try self.airShuffle(inst), @@ -2343,23 +2331,30 @@ const DeclGen = struct { const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; const lhs_id = try self.resolve(bin_op.lhs); const rhs_id = try self.resolve(bin_op.rhs); + const result_ty = self.typeOfIndex(inst); + const shift_ty = self.typeOf(bin_op.rhs); + const scalar_shift_ty_ref = try self.resolveType(shift_ty.scalarType(mod), .direct); - // Sometimes Zig doesn't make both of the arguments the same types here. SPIR-V expects that, - // so just manually upcast it if required. - // TODO(robin) + const info = try self.arithmeticTypeInfo(result_ty); + switch (info.class) { + .composite_integer => return self.todo("shift ops for composite integers", .{}), + .integer, .strange_integer => {}, + .float, .bool => unreachable, + } var wip = try self.elementWise(result_ty); defer wip.deinit(); - - const shift_ty = wip.scalarType(self.typeOf(bin_op.rhs)); - const shift_ty_ref = try self.resolveType(shift_ty, .direct); - for (0..wip.results.len) |i| { const lhs_elem_id = try wip.elementAt(result_ty, lhs_id, i); - const rhs_elem_id = try wip.elementAt(result_ty, rhs_id, i); + const rhs_elem_id = try wip.elementAt(shift_ty, rhs_id, i); - const shift_id = if (shift_ty_ref != wip.result_ty_ref) blk: { + // TODO: Can we omit normalizing lhs? + const lhs_norm_id = try self.normalizeInt(wip.scalar_ty_ref, lhs_elem_id, info); + + // Sometimes Zig doesn't make both of the arguments the same types here. SPIR-V expects that, + // so just manually upcast it if required. + const shift_id = if (scalar_shift_ty_ref != wip.scalar_ty_ref) blk: { const shift_id = self.spv.allocId(); try self.func.body.emit(self.spv.gpa, .OpUConvert, .{ .id_result_type = wip.scalar_ty_id, @@ -2368,12 +2363,13 @@ const DeclGen = struct { }); break :blk shift_id; } else rhs_elem_id; + const shift_norm_id = try self.normalizeInt(wip.scalar_ty_ref, shift_id, info); const args = .{ .id_result_type = wip.scalar_ty_id, .id_result = wip.allocId(i), - .base = lhs_elem_id, - .shift = shift_id, + .base = lhs_norm_id, + .shift = shift_norm_id, }; if (result_ty.isSignedInt(mod)) { @@ -2680,6 +2676,88 @@ const DeclGen = struct { ); } + fn airShlOverflow(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { + if (self.liveness.isUnused(inst)) return null; + const mod = self.module; + const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; + const extra = self.air.extraData(Air.Bin, ty_pl.payload).data; + const lhs = try self.resolve(extra.lhs); + const rhs = try self.resolve(extra.rhs); + + const result_ty = self.typeOfIndex(inst); + const operand_ty = self.typeOf(extra.lhs); + const shift_ty = self.typeOf(extra.rhs); + const scalar_shift_ty_ref = try self.resolveType(shift_ty.scalarType(mod), .direct); + + const ov_ty = result_ty.structFieldType(1, self.module); + + const bool_ty_ref = try self.resolveType(Type.bool, .direct); + + const info = try self.arithmeticTypeInfo(operand_ty); + switch (info.class) { + .composite_integer => return self.todo("overflow shift for composite integers", .{}), + .integer, .strange_integer => {}, + .float, .bool => unreachable, + } + + var wip_result = try self.elementWise(operand_ty); + defer wip_result.deinit(); + var wip_ov = try self.elementWise(ov_ty); + defer wip_ov.deinit(); + for (0..wip_result.results.len, wip_ov.results) |i, *ov_id| { + const lhs_elem_id = try wip_result.elementAt(operand_ty, lhs, i); + const rhs_elem_id = try wip_result.elementAt(shift_ty, rhs, i); + + // Normalize both so that we can shift back and check if the result is the same. + const lhs_norm_id = try self.normalizeInt(wip_result.scalar_ty_ref, lhs_elem_id, info); + + // Sometimes Zig doesn't make both of the arguments the same types here. SPIR-V expects that, + // so just manually upcast it if required. + const shift_id = if (scalar_shift_ty_ref != wip_result.scalar_ty_ref) blk: { + const shift_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpUConvert, .{ + .id_result_type = wip_result.scalar_ty_id, + .id_result = shift_id, + .unsigned_value = rhs_elem_id, + }); + break :blk shift_id; + } else rhs_elem_id; + const shift_norm_id = try self.normalizeInt(wip_result.scalar_ty_ref, shift_id, info); + + try self.func.body.emit(self.spv.gpa, .OpShiftLeftLogical, .{ + .id_result_type = wip_result.scalar_ty_id, + .id_result = wip_result.allocId(i), + .base = lhs_norm_id, + .shift = shift_norm_id, + }); + + // To check if overflow happened, just check if the right-shifted result is the same value. + const right_shift_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpShiftRightLogical, .{ + .id_result_type = wip_result.scalar_ty_id, + .id_result = right_shift_id, + .base = try self.normalizeInt(wip_result.scalar_ty_ref, wip_result.results[i], info), + .shift = shift_norm_id, + }); + + const overflowed_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpINotEqual, .{ + .id_result_type = self.typeId(bool_ty_ref), + .id_result = overflowed_id, + .operand_1 = lhs_norm_id, + .operand_2 = right_shift_id, + }); + + ov_id.* = try self.intFromBool(wip_ov.scalar_ty_ref, overflowed_id); + } + + return try self.constructStruct( + result_ty, + &.{ operand_ty, ov_ty }, + &.{ try wip_result.finalize(), try wip_ov.finalize() }, + ); + } + fn airShuffle(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { const mod = self.module; if (self.liveness.isUnused(inst)) return null; diff --git a/test/behavior/math.zig b/test/behavior/math.zig index 93c467eb5d1e0f627fb4522c6733e72efb85bc79..3aa65dddbb7ab5a85f522a3ac675e0109100d35e 100644 --- a/test/behavior/math.zig +++ b/test/behavior/math.zig @@ -1328,8 +1328,6 @@ fn testShlTrunc(x: u16) !void { } test "exact shift left" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - try testShlExact(0b00110101); try comptime testShlExact(0b00110101); diff --git a/test/behavior/vector.zig b/test/behavior/vector.zig index 0c28b519b35529630cf51e719a0e1d9ca5935212..d02f0b65151c5ef579db14267b59f3567e54d672 100644 --- a/test/behavior/vector.zig +++ b/test/behavior/vector.zig @@ -179,7 +179,6 @@ test "array vector coercion - odd sizes" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO 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_spirv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; @@ -219,7 +218,6 @@ test "array to vector with element type coercion" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO 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_spirv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf) return error.SkipZigTest; @@ -659,7 +657,6 @@ test "vector shift operators" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO 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_spirv64) return error.SkipZigTest; const S = struct { fn doTheTestShift(x: anytype, y: anytype) !void { @@ -1168,7 +1165,6 @@ test "@shlWithOverflow" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO 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_spirv64) return error.SkipZigTest; const S = struct { fn doTheTest() !void { @@ -1453,7 +1449,6 @@ test "compare vectors with different element types" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO var a: @Vector(2, u8) = .{ 1, 2 }; var b: @Vector(2, u9) = .{ 3, 0 }; -- 2.54.0 From 761594e2260eb780ab1861568e38a7066a7513df Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Fri, 19 Jan 2024 01:12:56 +0100 Subject: [PATCH 07/20] spirv: reduce, reduce_optimized --- src/codegen/spirv.zig | 75 +++++++++++++++++++++++++++++++++++++++- test/behavior/vector.zig | 2 -- 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 286b45f9733b55cd3d87b9b290ffe6231978fd77..33e068032ca60df16e7fdb65b2de17d797f1f14d 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -2187,6 +2187,7 @@ const DeclGen = struct { .sub_with_overflow => try self.airAddSubOverflow(inst, .OpISub, .OpUGreaterThan, .OpSGreaterThan), .shl_with_overflow => try self.airShlOverflow(inst), + .reduce, .reduce_optimized => try self.airReduce(inst), .shuffle => try self.airShuffle(inst), .ptr_add => try self.airPtrAdd(inst), @@ -2388,9 +2389,14 @@ const DeclGen = struct { const lhs_id = try self.resolve(bin_op.lhs); const rhs_id = try self.resolve(bin_op.rhs); const result_ty = self.typeOfIndex(inst); + + return try self.minMax(result_ty, op, lhs_id, rhs_id); + } + + fn minMax(self: *DeclGen, result_ty: Type, op: std.math.CompareOperator, lhs_id: IdRef, rhs_id: IdRef) !IdRef { const result_ty_ref = try self.resolveType(result_ty, .direct); - const info = try self.arithmeticTypeInfo(result_ty); + // TODO: Use fmin for OpenCL const cmp_id = try self.cmp(op, Type.bool, result_ty, lhs_id, rhs_id); const selection_id = switch (info.class) { @@ -2758,6 +2764,73 @@ const DeclGen = struct { ); } + fn airReduce(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { + if (self.liveness.isUnused(inst)) return null; + const mod = self.module; + const reduce = self.air.instructions.items(.data)[@intFromEnum(inst)].reduce; + const operand = try self.resolve(reduce.operand); + const operand_ty = self.typeOf(reduce.operand); + const scalar_ty = operand_ty.scalarType(mod); + const scalar_ty_ref = try self.resolveType(scalar_ty, .direct); + const scalar_ty_id = self.typeId(scalar_ty_ref); + + const info = try self.arithmeticTypeInfo(operand_ty); + + var result_id = try self.extractField(scalar_ty, operand, 0); + const len = operand_ty.vectorLen(mod); + + switch (reduce.operation) { + .Min, .Max => |op| { + const cmp_op: std.math.CompareOperator = if (op == .Max) .gt else .lt; + for (1..len) |i| { + const lhs = result_id; + const rhs = try self.extractField(scalar_ty, operand, @intCast(i)); + result_id = try self.minMax(scalar_ty, cmp_op, lhs, rhs); + } + + return result_id; + }, + else => {}, + } + + const opcode: Opcode = switch (info.class) { + .bool => switch (reduce.operation) { + .And => .OpLogicalAnd, + .Or => .OpLogicalOr, + .Xor => .OpLogicalNotEqual, + else => unreachable, + }, + .strange_integer, .integer => switch (reduce.operation) { + .And => .OpBitwiseAnd, + .Or => .OpBitwiseOr, + .Xor => .OpBitwiseXor, + .Add => .OpIAdd, + .Mul => .OpIMul, + else => unreachable, + }, + .float => switch (reduce.operation) { + .Add => .OpFAdd, + .Mul => .OpFMul, + else => unreachable, + }, + .composite_integer => unreachable, // TODO + }; + + for (1..len) |i| { + const lhs = result_id; + const rhs = try self.extractField(scalar_ty, operand, @intCast(i)); + result_id = self.spv.allocId(); + + try self.func.body.emitRaw(self.spv.gpa, opcode, 4); + self.func.body.writeOperand(spec.IdResultType, scalar_ty_id); + self.func.body.writeOperand(spec.IdResult, result_id); + self.func.body.writeOperand(spec.IdResultType, lhs); + self.func.body.writeOperand(spec.IdResultType, rhs); + } + + return result_id; + } + fn airShuffle(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { const mod = self.module; if (self.liveness.isUnused(inst)) return null; diff --git a/test/behavior/vector.zig b/test/behavior/vector.zig index d02f0b65151c5ef579db14267b59f3567e54d672..f87f7b722d4e0bcd306e4a336763a0eb3580495f 100644 --- a/test/behavior/vector.zig +++ b/test/behavior/vector.zig @@ -1231,7 +1231,6 @@ test "byte vector initialized in inline function" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO 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_spirv64) return error.SkipZigTest; if (comptime builtin.zig_backend == .stage2_llvm and builtin.cpu.arch == .x86_64 and builtin.cpu.features.isEnabled(@intFromEnum(std.Target.x86.Feature.avx512f))) @@ -1301,7 +1300,6 @@ test "@intCast to u0" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO 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_spirv64) return error.SkipZigTest; var zeros = @Vector(2, u32){ 0, 0 }; _ = &zeros; -- 2.54.0 From b67d983abda198c69fbcde68a961e0e8b92b7939 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Fri, 19 Jan 2024 23:56:02 +0100 Subject: [PATCH 08/20] spirv: vectorize add/sub overflow --- src/codegen/spirv.zig | 161 ++++++++++++++++++++------------------- test/behavior/vector.zig | 4 +- 2 files changed, 84 insertions(+), 81 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 33e068032ca60df16e7fdb65b2de17d797f1f14d..6a67bb58ac0b276752a3b90791946e0ea2f70b3c 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -2582,103 +2582,108 @@ const DeclGen = struct { const lhs = try self.resolve(extra.lhs); const rhs = try self.resolve(extra.rhs); - const operand_ty = self.typeOf(extra.lhs); const result_ty = self.typeOfIndex(inst); + const operand_ty = self.typeOf(extra.lhs); + const ov_ty = result_ty.structFieldType(1, self.module); + + const bool_ty_ref = try self.resolveType(Type.bool, .direct); const info = try self.arithmeticTypeInfo(operand_ty); switch (info.class) { .composite_integer => return self.todo("overflow ops for composite integers", .{}), - .strange_integer => return self.todo("overflow ops for strange integers", .{}), - .integer => {}, + .strange_integer, .integer => {}, .float, .bool => unreachable, } - // The operand type must be the same as the result type in SPIR-V, which - // is the same as in Zig. - const operand_ty_ref = try self.resolveType(operand_ty, .direct); - const operand_ty_id = self.typeId(operand_ty_ref); + var wip_result = try self.elementWise(operand_ty); + defer wip_result.deinit(); + var wip_ov = try self.elementWise(ov_ty); + defer wip_ov.deinit(); + for (wip_result.results, wip_ov.results, 0..) |*value_id, *ov_id, i| { + const lhs_elem_id = try wip_result.elementAt(operand_ty, lhs, i); + const rhs_elem_id = try wip_result.elementAt(operand_ty, rhs, i); - const bool_ty_ref = try self.resolveType(Type.bool, .direct); + // Normalize both so that we can properly check for overflow + const lhs_norm_id = try self.normalizeInt(wip_result.scalar_ty_ref, lhs_elem_id, info); + const rhs_norm_id = try self.normalizeInt(wip_result.scalar_ty_ref, rhs_elem_id, info); + const op_result_id = self.spv.allocId(); - const ov_ty = result_ty.structFieldType(1, self.module); - // Note: result is stored in a struct, so indirect representation. - const ov_ty_ref = try self.resolveType(ov_ty, .indirect); + try self.func.body.emit(self.spv.gpa, add, .{ + .id_result_type = wip_result.scalar_ty_id, + .id_result = op_result_id, + .operand_1 = lhs_norm_id, + .operand_2 = rhs_norm_id, + }); - // TODO: Operations other than addition. - const value_id = self.spv.allocId(); - try self.func.body.emit(self.spv.gpa, add, .{ - .id_result_type = operand_ty_id, - .id_result = value_id, - .operand_1 = lhs, - .operand_2 = rhs, - }); + // Normalize the result so that the comparisons go well + value_id.* = try self.normalizeInt(wip_result.scalar_ty_ref, op_result_id, info); - const overflowed_id = switch (info.signedness) { - .unsigned => blk: { - // Overflow happened if the result is smaller than either of the operands. It doesn't matter which. - // For subtraction the conditions need to be swapped. - const overflowed_id = self.spv.allocId(); - try self.func.body.emit(self.spv.gpa, ucmp, .{ - .id_result_type = self.typeId(bool_ty_ref), - .id_result = overflowed_id, - .operand_1 = value_id, - .operand_2 = lhs, - }); - break :blk overflowed_id; - }, - .signed => blk: { - // lhs - rhs - // For addition, overflow happened if: - // - rhs is negative and value > lhs - // - rhs is positive and value < lhs - // This can be shortened to: - // (rhs < 0 and value > lhs) or (rhs >= 0 and value <= lhs) - // = (rhs < 0) == (value > lhs) - // = (rhs < 0) == (lhs < value) - // Note that signed overflow is also wrapping in spir-v. - // For subtraction, overflow happened if: - // - rhs is negative and value < lhs - // - rhs is positive and value > lhs - // This can be shortened to: - // (rhs < 0 and value < lhs) or (rhs >= 0 and value >= lhs) - // = (rhs < 0) == (value < lhs) - // = (rhs < 0) == (lhs > value) + const overflowed_id = switch (info.signedness) { + .unsigned => blk: { + // Overflow happened if the result is smaller than either of the operands. It doesn't matter which. + // For subtraction the conditions need to be swapped. + const overflowed_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, ucmp, .{ + .id_result_type = self.typeId(bool_ty_ref), + .id_result = overflowed_id, + .operand_1 = value_id.*, + .operand_2 = lhs_norm_id, + }); + break :blk overflowed_id; + }, + .signed => blk: { + // lhs - rhs + // For addition, overflow happened if: + // - rhs is negative and value > lhs + // - rhs is positive and value < lhs + // This can be shortened to: + // (rhs < 0 and value > lhs) or (rhs >= 0 and value <= lhs) + // = (rhs < 0) == (value > lhs) + // = (rhs < 0) == (lhs < value) + // Note that signed overflow is also wrapping in spir-v. + // For subtraction, overflow happened if: + // - rhs is negative and value < lhs + // - rhs is positive and value > lhs + // This can be shortened to: + // (rhs < 0 and value < lhs) or (rhs >= 0 and value >= lhs) + // = (rhs < 0) == (value < lhs) + // = (rhs < 0) == (lhs > value) - const rhs_lt_zero_id = self.spv.allocId(); - const zero_id = try self.constInt(operand_ty_ref, 0); - try self.func.body.emit(self.spv.gpa, .OpSLessThan, .{ - .id_result_type = self.typeId(bool_ty_ref), - .id_result = rhs_lt_zero_id, - .operand_1 = rhs, - .operand_2 = zero_id, - }); + const rhs_lt_zero_id = self.spv.allocId(); + const zero_id = try self.constInt(wip_result.scalar_ty_ref, 0); + try self.func.body.emit(self.spv.gpa, .OpSLessThan, .{ + .id_result_type = self.typeId(bool_ty_ref), + .id_result = rhs_lt_zero_id, + .operand_1 = rhs_norm_id, + .operand_2 = zero_id, + }); - const value_gt_lhs_id = self.spv.allocId(); - try self.func.body.emit(self.spv.gpa, scmp, .{ - .id_result_type = self.typeId(bool_ty_ref), - .id_result = value_gt_lhs_id, - .operand_1 = lhs, - .operand_2 = value_id, - }); + const value_gt_lhs_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, scmp, .{ + .id_result_type = self.typeId(bool_ty_ref), + .id_result = value_gt_lhs_id, + .operand_1 = lhs_norm_id, + .operand_2 = value_id.*, + }); - const overflowed_id = self.spv.allocId(); - try self.func.body.emit(self.spv.gpa, .OpLogicalEqual, .{ - .id_result_type = self.typeId(bool_ty_ref), - .id_result = overflowed_id, - .operand_1 = rhs_lt_zero_id, - .operand_2 = value_gt_lhs_id, - }); - break :blk overflowed_id; - }, - }; + const overflowed_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpLogicalEqual, .{ + .id_result_type = self.typeId(bool_ty_ref), + .id_result = overflowed_id, + .operand_1 = rhs_lt_zero_id, + .operand_2 = value_gt_lhs_id, + }); + break :blk overflowed_id; + }, + }; + + ov_id.* = try self.intFromBool(wip_ov.scalar_ty_ref, overflowed_id); + } - // Construct the struct that Zig wants as result. - // The value should already be the correct type. - const ov_id = try self.intFromBool(ov_ty_ref, overflowed_id); return try self.constructStruct( result_ty, &.{ operand_ty, ov_ty }, - &.{ value_id, ov_id }, + &.{ try wip_result.finalize(), try wip_ov.finalize() }, ); } diff --git a/test/behavior/vector.zig b/test/behavior/vector.zig index f87f7b722d4e0bcd306e4a336763a0eb3580495f..b23eac924d02c44d9567162f74f8b7a4f70b69f9 100644 --- a/test/behavior/vector.zig +++ b/test/behavior/vector.zig @@ -259,7 +259,6 @@ test "tuple to vector" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO 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_spirv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch == .aarch64) { // Regressed with LLVM 14: @@ -1063,7 +1062,7 @@ test "@addWithOverflow" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO 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_spirv64) return error.SkipZigTest; + // if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const S = struct { fn doTheTest() !void { @@ -1111,7 +1110,6 @@ test "@subWithOverflow" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO 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_spirv64) return error.SkipZigTest; const S = struct { fn doTheTest() !void { -- 2.54.0 From 54ec9365498635aa127ff13dfbdd3942890b53d0 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sat, 20 Jan 2024 23:57:19 +0100 Subject: [PATCH 09/20] spirv: wrap strange its before instead of after operation Wrapping strange integers before an operation was initially done as an attempt to minimize the amount of normalizations required: This way, there would not be a normalization necessary between two modular operations. This was a premature optimization, since the resulting logic is more complicated than naive way of wrapping the result after the operation. This commit updates handling of strange integers to do wrapping after each operation. It also seems slightly more efficient in terms of size of generated code, as it reduces the size of the behavior tests binary by about 1%. --- src/codegen/spirv.zig | 233 +++++++++++++++++++++--------------------- 1 file changed, 114 insertions(+), 119 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 6a67bb58ac0b276752a3b90791946e0ea2f70b3c..56f1832b5d1355f67d3ed9aeb3631a377738e9ad 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -2167,21 +2167,20 @@ const DeclGen = struct { const air_tags = self.air.instructions.items(.tag); const maybe_result_id: ?IdRef = switch (air_tags[@intFromEnum(inst)]) { // zig fmt: off - .add, .add_wrap => try self.airArithOp(inst, .OpFAdd, .OpIAdd, .OpIAdd, true), - .sub, .sub_wrap => try self.airArithOp(inst, .OpFSub, .OpISub, .OpISub, true), - .mul, .mul_wrap => try self.airArithOp(inst, .OpFMul, .OpIMul, .OpIMul, true), + .add, .add_wrap => try self.airArithOp(inst, .OpFAdd, .OpIAdd, .OpIAdd), + .sub, .sub_wrap => try self.airArithOp(inst, .OpFSub, .OpISub, .OpISub), + .mul, .mul_wrap => try self.airArithOp(inst, .OpFMul, .OpIMul, .OpIMul), .div_float, .div_float_optimized, // TODO: Check that this is the right operation. .div_trunc, .div_trunc_optimized, - => try self.airArithOp(inst, .OpFDiv, .OpSDiv, .OpUDiv, false), + => try self.airArithOp(inst, .OpFDiv, .OpSDiv, .OpUDiv), // TODO: Check if this is the right operation - // TODO: Make airArithOp for rem not emit a mask for the LHS. .rem, .rem_optimized, - => try self.airArithOp(inst, .OpFRem, .OpSRem, .OpSRem, false), + => try self.airArithOp(inst, .OpFRem, .OpSRem, .OpSRem), .add_with_overflow => try self.airAddSubOverflow(inst, .OpIAdd, .OpULessThan, .OpSLessThan), .sub_with_overflow => try self.airAddSubOverflow(inst, .OpISub, .OpUGreaterThan, .OpSGreaterThan), @@ -2346,13 +2345,10 @@ const DeclGen = struct { var wip = try self.elementWise(result_ty); defer wip.deinit(); - for (0..wip.results.len) |i| { + for (wip.results, 0..) |*result_id, i| { const lhs_elem_id = try wip.elementAt(result_ty, lhs_id, i); const rhs_elem_id = try wip.elementAt(shift_ty, rhs_id, i); - // TODO: Can we omit normalizing lhs? - const lhs_norm_id = try self.normalizeInt(wip.scalar_ty_ref, lhs_elem_id, info); - // Sometimes Zig doesn't make both of the arguments the same types here. SPIR-V expects that, // so just manually upcast it if required. const shift_id = if (scalar_shift_ty_ref != wip.scalar_ty_ref) blk: { @@ -2364,13 +2360,13 @@ const DeclGen = struct { }); break :blk shift_id; } else rhs_elem_id; - const shift_norm_id = try self.normalizeInt(wip.scalar_ty_ref, shift_id, info); + const value_id = self.spv.allocId(); const args = .{ .id_result_type = wip.scalar_ty_id, - .id_result = wip.allocId(i), - .base = lhs_norm_id, - .shift = shift_norm_id, + .id_result = value_id, + .base = lhs_elem_id, + .shift = shift_id, }; if (result_ty.isSignedInt(mod)) { @@ -2378,6 +2374,8 @@ const DeclGen = struct { } else { try self.func.body.emit(self.spv.gpa, unsigned, args); } + + result_id.* = try self.normalize(wip.scalar_ty_ref, value_id, info); } return try wip.finalize(); } @@ -2435,47 +2433,52 @@ const DeclGen = struct { return result_id; } - /// This function canonicalizes a "strange" integer value: - /// For unsigned integers, the value is masked so that only the relevant bits can contain - /// non-zeros. - /// For signed integers, the value is also sign extended. - fn normalizeInt(self: *DeclGen, ty_ref: CacheRef, value_id: IdRef, info: ArithmeticTypeInfo) !IdRef { - assert(info.class != .composite_integer); // TODO - if (info.bits == info.backing_bits) { - return value_id; - } - - switch (info.signedness) { - .unsigned => { - const mask_value = if (info.bits == 64) 0xFFFF_FFFF_FFFF_FFFF else (@as(u64, 1) << @as(u6, @intCast(info.bits))) - 1; - const result_id = self.spv.allocId(); - const mask_id = try self.constInt(ty_ref, mask_value); - try self.func.body.emit(self.spv.gpa, .OpBitwiseAnd, .{ - .id_result_type = self.typeId(ty_ref), - .id_result = result_id, - .operand_1 = value_id, - .operand_2 = mask_id, - }); - return result_id; - }, - .signed => { - // Shift left and right so that we can copy the sight bit that way. - const shift_amt_id = try self.constInt(ty_ref, info.backing_bits - info.bits); - const left_id = self.spv.allocId(); - try self.func.body.emit(self.spv.gpa, .OpShiftLeftLogical, .{ - .id_result_type = self.typeId(ty_ref), - .id_result = left_id, - .base = value_id, - .shift = shift_amt_id, - }); - const right_id = self.spv.allocId(); - try self.func.body.emit(self.spv.gpa, .OpShiftRightArithmetic, .{ - .id_result_type = self.typeId(ty_ref), - .id_result = right_id, - .base = left_id, - .shift = shift_amt_id, - }); - return right_id; + /// This function normalizes values to a canonical representation + /// after some arithmetic operation. This mostly consists of wrapping + /// behavior for strange integers: + /// - Unsigned integers are bitwise masked with a mask that only passes + /// the valid bits through. + /// - Signed integers are also sign extended if they are negative. + /// All other values are returned unmodified (this makes strange integer + /// wrapping easier to use in generic operations). + fn normalize(self: *DeclGen, ty_ref: CacheRef, value_id: IdRef, info: ArithmeticTypeInfo) !IdRef { + switch (info.class) { + .integer, .bool, .float => return value_id, + .composite_integer => unreachable, // TODO + .strange_integer => { + switch (info.signedness) { + .unsigned => { + const mask_value = if (info.bits == 64) 0xFFFF_FFFF_FFFF_FFFF else (@as(u64, 1) << @as(u6, @intCast(info.bits))) - 1; + const result_id = self.spv.allocId(); + const mask_id = try self.constInt(ty_ref, mask_value); + try self.func.body.emit(self.spv.gpa, .OpBitwiseAnd, .{ + .id_result_type = self.typeId(ty_ref), + .id_result = result_id, + .operand_1 = value_id, + .operand_2 = mask_id, + }); + return result_id; + }, + .signed => { + // Shift left and right so that we can copy the sight bit that way. + const shift_amt_id = try self.constInt(ty_ref, info.backing_bits - info.bits); + const left_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpShiftLeftLogical, .{ + .id_result_type = self.typeId(ty_ref), + .id_result = left_id, + .base = value_id, + .shift = shift_amt_id, + }); + const right_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpShiftRightArithmetic, .{ + .id_result_type = self.typeId(ty_ref), + .id_result = right_id, + .base = left_id, + .shift = shift_amt_id, + }); + return right_id; + }, + } }, } } @@ -2486,8 +2489,6 @@ const DeclGen = struct { comptime fop: Opcode, comptime sop: Opcode, comptime uop: Opcode, - /// true if this operation holds under modular arithmetic. - comptime modular: bool, ) !?IdRef { if (self.liveness.isUnused(inst)) return null; @@ -2501,7 +2502,7 @@ const DeclGen = struct { assert(self.typeOf(bin_op.lhs).eql(ty, self.module)); assert(self.typeOf(bin_op.rhs).eql(ty, self.module)); - return try self.arithOp(ty, lhs_id, rhs_id, fop, sop, uop, modular); + return try self.arithOp(ty, lhs_id, rhs_id, fop, sop, uop); } fn arithOp( @@ -2512,8 +2513,6 @@ const DeclGen = struct { comptime fop: Opcode, comptime sop: Opcode, comptime uop: Opcode, - /// true if this operation holds under modular arithmetic. - comptime modular: bool, ) !IdRef { // Binary operations are generally applicable to both scalar and vector operations // in SPIR-V, but int and float versions of operations require different opcodes. @@ -2533,25 +2532,16 @@ const DeclGen = struct { var wip = try self.elementWise(ty); defer wip.deinit(); - for (0..wip.results.len) |i| { + for (wip.results, 0..) |*result_id, i| { const lhs_elem_id = try wip.elementAt(ty, lhs_id, i); const rhs_elem_id = try wip.elementAt(ty, rhs_id, i); - const lhs_norm_id = if (modular and info.class == .strange_integer) - try self.normalizeInt(wip.scalar_ty_ref, lhs_elem_id, info) - else - lhs_elem_id; - - const rhs_norm_id = if (modular and info.class == .strange_integer) - try self.normalizeInt(wip.scalar_ty_ref, rhs_elem_id, info) - else - rhs_elem_id; - + const value_id = self.spv.allocId(); const operands = .{ .id_result_type = wip.scalar_ty_id, - .id_result = wip.allocId(i), - .operand_1 = lhs_norm_id, - .operand_2 = rhs_norm_id, + .id_result = value_id, + .operand_1 = lhs_elem_id, + .operand_2 = rhs_elem_id, }; switch (opcode_index) { @@ -2563,6 +2553,7 @@ const DeclGen = struct { // TODO: Trap on overflow? Probably going to be annoying. // TODO: Look into SPV_KHR_no_integer_wrap_decoration which provides NoSignedWrap/NoUnsignedWrap. + result_id.* = try self.normalize(wip.scalar_ty_ref, value_id, info); } return try wip.finalize(); @@ -2599,24 +2590,22 @@ const DeclGen = struct { defer wip_result.deinit(); var wip_ov = try self.elementWise(ov_ty); defer wip_ov.deinit(); - for (wip_result.results, wip_ov.results, 0..) |*value_id, *ov_id, i| { + for (wip_result.results, wip_ov.results, 0..) |*result_id, *ov_id, i| { const lhs_elem_id = try wip_result.elementAt(operand_ty, lhs, i); const rhs_elem_id = try wip_result.elementAt(operand_ty, rhs, i); // Normalize both so that we can properly check for overflow - const lhs_norm_id = try self.normalizeInt(wip_result.scalar_ty_ref, lhs_elem_id, info); - const rhs_norm_id = try self.normalizeInt(wip_result.scalar_ty_ref, rhs_elem_id, info); - const op_result_id = self.spv.allocId(); + const value_id = self.spv.allocId(); try self.func.body.emit(self.spv.gpa, add, .{ .id_result_type = wip_result.scalar_ty_id, - .id_result = op_result_id, - .operand_1 = lhs_norm_id, - .operand_2 = rhs_norm_id, + .id_result = value_id, + .operand_1 = lhs_elem_id, + .operand_2 = rhs_elem_id, }); // Normalize the result so that the comparisons go well - value_id.* = try self.normalizeInt(wip_result.scalar_ty_ref, op_result_id, info); + result_id.* = try self.normalize(wip_result.scalar_ty_ref, value_id, info); const overflowed_id = switch (info.signedness) { .unsigned => blk: { @@ -2626,8 +2615,8 @@ const DeclGen = struct { try self.func.body.emit(self.spv.gpa, ucmp, .{ .id_result_type = self.typeId(bool_ty_ref), .id_result = overflowed_id, - .operand_1 = value_id.*, - .operand_2 = lhs_norm_id, + .operand_1 = result_id.*, + .operand_2 = lhs_elem_id, }); break :blk overflowed_id; }, @@ -2654,7 +2643,7 @@ const DeclGen = struct { try self.func.body.emit(self.spv.gpa, .OpSLessThan, .{ .id_result_type = self.typeId(bool_ty_ref), .id_result = rhs_lt_zero_id, - .operand_1 = rhs_norm_id, + .operand_1 = rhs_elem_id, .operand_2 = zero_id, }); @@ -2662,8 +2651,8 @@ const DeclGen = struct { try self.func.body.emit(self.spv.gpa, scmp, .{ .id_result_type = self.typeId(bool_ty_ref), .id_result = value_gt_lhs_id, - .operand_1 = lhs_norm_id, - .operand_2 = value_id.*, + .operand_1 = lhs_elem_id, + .operand_2 = result_id.*, }); const overflowed_id = self.spv.allocId(); @@ -2715,13 +2704,10 @@ const DeclGen = struct { defer wip_result.deinit(); var wip_ov = try self.elementWise(ov_ty); defer wip_ov.deinit(); - for (0..wip_result.results.len, wip_ov.results) |i, *ov_id| { + for (wip_result.results, wip_ov.results, 0..) |*result_id, *ov_id, i| { const lhs_elem_id = try wip_result.elementAt(operand_ty, lhs, i); const rhs_elem_id = try wip_result.elementAt(shift_ty, rhs, i); - // Normalize both so that we can shift back and check if the result is the same. - const lhs_norm_id = try self.normalizeInt(wip_result.scalar_ty_ref, lhs_elem_id, info); - // Sometimes Zig doesn't make both of the arguments the same types here. SPIR-V expects that, // so just manually upcast it if required. const shift_id = if (scalar_shift_ty_ref != wip_result.scalar_ty_ref) blk: { @@ -2733,29 +2719,41 @@ const DeclGen = struct { }); break :blk shift_id; } else rhs_elem_id; - const shift_norm_id = try self.normalizeInt(wip_result.scalar_ty_ref, shift_id, info); + const value_id = self.spv.allocId(); try self.func.body.emit(self.spv.gpa, .OpShiftLeftLogical, .{ .id_result_type = wip_result.scalar_ty_id, - .id_result = wip_result.allocId(i), - .base = lhs_norm_id, - .shift = shift_norm_id, + .id_result = value_id, + .base = lhs_elem_id, + .shift = shift_id, }); + result_id.* = try self.normalize(wip_result.scalar_ty_ref, value_id, info); - // To check if overflow happened, just check if the right-shifted result is the same value. const right_shift_id = self.spv.allocId(); - try self.func.body.emit(self.spv.gpa, .OpShiftRightLogical, .{ - .id_result_type = wip_result.scalar_ty_id, - .id_result = right_shift_id, - .base = try self.normalizeInt(wip_result.scalar_ty_ref, wip_result.results[i], info), - .shift = shift_norm_id, - }); + switch (info.signedness) { + .signed => { + try self.func.body.emit(self.spv.gpa, .OpShiftRightArithmetic, .{ + .id_result_type = wip_result.scalar_ty_id, + .id_result = right_shift_id, + .base = result_id.*, + .shift = shift_id, + }); + }, + .unsigned => { + try self.func.body.emit(self.spv.gpa, .OpShiftRightLogical, .{ + .id_result_type = wip_result.scalar_ty_id, + .id_result = right_shift_id, + .base = result_id.*, + .shift = shift_id, + }); + }, + } const overflowed_id = self.spv.allocId(); try self.func.body.emit(self.spv.gpa, .OpINotEqual, .{ .id_result_type = self.typeId(bool_ty_ref), .id_result = overflowed_id, - .operand_1 = lhs_norm_id, + .operand_1 = lhs_elem_id, .operand_2 = right_shift_id, }); @@ -3113,14 +3111,7 @@ const DeclGen = struct { .neq => .OpLogicalNotEqual, else => unreachable, }, - .strange_integer => sign: { - const op_ty_ref = try self.resolveType(op_ty, .direct); - // Mask operands before performing comparison. - cmp_lhs_id = try self.normalizeInt(op_ty_ref, cmp_lhs_id, info); - cmp_rhs_id = try self.normalizeInt(op_ty_ref, cmp_rhs_id, info); - break :sign info.signedness; - }, - .integer => info.signedness, + .integer, .strange_integer => info.signedness, }; break :opcode switch (signedness) { @@ -3252,18 +3243,13 @@ const DeclGen = struct { const operand_id = try self.resolve(ty_op.operand); const src_ty = self.typeOf(ty_op.operand); const dst_ty = self.typeOfIndex(inst); - const src_ty_ref = try self.resolveType(src_ty, .direct); const dst_ty_ref = try self.resolveType(dst_ty, .direct); const src_info = try self.arithmeticTypeInfo(src_ty); const dst_info = try self.arithmeticTypeInfo(dst_ty); - // While intcast promises that the value already fits, the upper bits of a - // strange integer may contain garbage. Therefore, mask/sign extend it before. - const src_id = try self.normalizeInt(src_ty_ref, operand_id, src_info); - if (src_info.backing_bits == dst_info.backing_bits) { - return src_id; + return operand_id; } const result_id = self.spv.allocId(); @@ -3271,14 +3257,23 @@ const DeclGen = struct { .signed => try self.func.body.emit(self.spv.gpa, .OpSConvert, .{ .id_result_type = self.typeId(dst_ty_ref), .id_result = result_id, - .signed_value = src_id, + .signed_value = operand_id, }), .unsigned => try self.func.body.emit(self.spv.gpa, .OpUConvert, .{ .id_result_type = self.typeId(dst_ty_ref), .id_result = result_id, - .unsigned_value = src_id, + .unsigned_value = operand_id, }), } + + // Make sure to normalize the result if shrinking. + // Because strange ints are sign extended in their backing + // type, we don't need to normalize when growing the type. The + // representation is already the same. + if (dst_info.bits < src_info.bits) { + return try self.normalize(dst_ty_ref, result_id, dst_info); + } + return result_id; } -- 2.54.0 From 77ef78a0ef00392c4e157ebc170d6c4d98f586fb Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 21 Jan 2024 01:39:20 +0100 Subject: [PATCH 10/20] spirv: clean up arithmeticTypeInfo a bit - No longer returns an error - Returns more useful vector info --- src/codegen/spirv.zig | 69 +++++++++++++++++++------------------------ 1 file changed, 31 insertions(+), 38 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 56f1832b5d1355f67d3ed9aeb3631a377738e9ad..cb3d1be8f077cd64cddce3157dbfb988ac83fd0a 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -373,8 +373,9 @@ const DeclGen = struct { /// For `composite_integer` this is 0 (TODO) backing_bits: u16, - /// Whether the type is a vector. - is_vector: bool, + /// Null if this type is a scalar, or the length + /// of the vector otherwise. + vector_len: ?u32, /// Whether the inner type is signed. Only relevant for integers. signedness: std.builtin.Signedness, @@ -597,32 +598,37 @@ const DeclGen = struct { return self.backingIntBits(ty) == null; } - fn arithmeticTypeInfo(self: *DeclGen, ty: Type) !ArithmeticTypeInfo { + fn arithmeticTypeInfo(self: *DeclGen, ty: Type) ArithmeticTypeInfo { const mod = self.module; const target = self.getTarget(); - return switch (ty.zigTypeTag(mod)) { + var scalar_ty = ty.scalarType(mod); + if (scalar_ty.zigTypeTag(mod) == .Enum) { + scalar_ty = scalar_ty.intTagType(mod); + } + const vector_len = if (ty.isVector(mod)) ty.vectorLen(mod) else null; + return switch (scalar_ty.zigTypeTag(mod)) { .Bool => ArithmeticTypeInfo{ .bits = 1, // Doesn't matter for this class. .backing_bits = self.backingIntBits(1).?, - .is_vector = false, + .vector_len = vector_len, .signedness = .unsigned, // Technically, but doesn't matter for this class. .class = .bool, }, .Float => ArithmeticTypeInfo{ - .bits = ty.floatBits(target), - .backing_bits = ty.floatBits(target), // TODO: F80? - .is_vector = false, + .bits = scalar_ty.floatBits(target), + .backing_bits = scalar_ty.floatBits(target), // TODO: F80? + .vector_len = vector_len, .signedness = .signed, // Technically, but doesn't matter for this class. .class = .float, }, .Int => blk: { - const int_info = ty.intInfo(mod); + const int_info = scalar_ty.intInfo(mod); // TODO: Maybe it's useful to also return this value. const maybe_backing_bits = self.backingIntBits(int_info.bits); break :blk ArithmeticTypeInfo{ .bits = int_info.bits, .backing_bits = maybe_backing_bits orelse 0, - .is_vector = false, + .vector_len = vector_len, .signedness = int_info.signedness, .class = if (maybe_backing_bits) |backing_bits| if (backing_bits == int_info.bits) @@ -633,22 +639,9 @@ const DeclGen = struct { .composite_integer, }; }, - .Enum => return self.arithmeticTypeInfo(ty.intTagType(mod)), - // As of yet, there is no vector support in the self-hosted compiler. - .Vector => blk: { - const child_type = ty.childType(mod); - const child_ty_info = try self.arithmeticTypeInfo(child_type); - break :blk ArithmeticTypeInfo{ - .bits = child_ty_info.bits, - .backing_bits = child_ty_info.backing_bits, - .is_vector = true, - .signedness = child_ty_info.signedness, - .class = child_ty_info.class, - }; - }, - // TODO: For which types is this the case? - // else => self.todo("implement arithmeticTypeInfo for {}", .{ty.fmt(self.module)}), - else => unreachable, + .Enum => unreachable, + .Vector => unreachable, + else => unreachable, // Unhandled arithmetic type }; } @@ -2336,7 +2329,7 @@ const DeclGen = struct { const shift_ty = self.typeOf(bin_op.rhs); const scalar_shift_ty_ref = try self.resolveType(shift_ty.scalarType(mod), .direct); - const info = try self.arithmeticTypeInfo(result_ty); + const info = self.arithmeticTypeInfo(result_ty); switch (info.class) { .composite_integer => return self.todo("shift ops for composite integers", .{}), .integer, .strange_integer => {}, @@ -2393,7 +2386,7 @@ const DeclGen = struct { fn minMax(self: *DeclGen, result_ty: Type, op: std.math.CompareOperator, lhs_id: IdRef, rhs_id: IdRef) !IdRef { const result_ty_ref = try self.resolveType(result_ty, .direct); - const info = try self.arithmeticTypeInfo(result_ty); + const info = self.arithmeticTypeInfo(result_ty); // TODO: Use fmin for OpenCL const cmp_id = try self.cmp(op, Type.bool, result_ty, lhs_id, rhs_id); @@ -2516,7 +2509,7 @@ const DeclGen = struct { ) !IdRef { // Binary operations are generally applicable to both scalar and vector operations // in SPIR-V, but int and float versions of operations require different opcodes. - const info = try self.arithmeticTypeInfo(ty); + const info = self.arithmeticTypeInfo(ty); const opcode_index: usize = switch (info.class) { .composite_integer => { @@ -2579,7 +2572,7 @@ const DeclGen = struct { const bool_ty_ref = try self.resolveType(Type.bool, .direct); - const info = try self.arithmeticTypeInfo(operand_ty); + const info = self.arithmeticTypeInfo(operand_ty); switch (info.class) { .composite_integer => return self.todo("overflow ops for composite integers", .{}), .strange_integer, .integer => {}, @@ -2693,7 +2686,7 @@ const DeclGen = struct { const bool_ty_ref = try self.resolveType(Type.bool, .direct); - const info = try self.arithmeticTypeInfo(operand_ty); + const info = self.arithmeticTypeInfo(operand_ty); switch (info.class) { .composite_integer => return self.todo("overflow shift for composite integers", .{}), .integer, .strange_integer => {}, @@ -2777,7 +2770,7 @@ const DeclGen = struct { const scalar_ty_ref = try self.resolveType(scalar_ty, .direct); const scalar_ty_id = self.typeId(scalar_ty_ref); - const info = try self.arithmeticTypeInfo(operand_ty); + const info = self.arithmeticTypeInfo(operand_ty); var result_id = try self.extractField(scalar_ty, operand, 0); const len = operand_ty.vectorLen(mod); @@ -3093,7 +3086,7 @@ const DeclGen = struct { }; const opcode: Opcode = opcode: { - const info = try self.arithmeticTypeInfo(op_ty); + const info = self.arithmeticTypeInfo(op_ty); const signedness = switch (info.class) { .composite_integer => { return self.todo("binary operations for composite integers", .{}); @@ -3245,8 +3238,8 @@ const DeclGen = struct { const dst_ty = self.typeOfIndex(inst); const dst_ty_ref = try self.resolveType(dst_ty, .direct); - const src_info = try self.arithmeticTypeInfo(src_ty); - const dst_info = try self.arithmeticTypeInfo(dst_ty); + const src_info = self.arithmeticTypeInfo(src_ty); + const dst_info = self.arithmeticTypeInfo(dst_ty); if (src_info.backing_bits == dst_info.backing_bits) { return operand_id; @@ -3302,7 +3295,7 @@ const DeclGen = struct { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const operand_ty = self.typeOf(ty_op.operand); const operand_id = try self.resolve(ty_op.operand); - const operand_info = try self.arithmeticTypeInfo(operand_ty); + const operand_info = self.arithmeticTypeInfo(operand_ty); const dest_ty = self.typeOfIndex(inst); const dest_ty_id = try self.resolveTypeId(dest_ty); @@ -3328,7 +3321,7 @@ const DeclGen = struct { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const operand_id = try self.resolve(ty_op.operand); const dest_ty = self.typeOfIndex(inst); - const dest_info = try self.arithmeticTypeInfo(dest_ty); + const dest_info = self.arithmeticTypeInfo(dest_ty); const dest_ty_id = try self.resolveTypeId(dest_ty); const result_id = self.spv.allocId(); @@ -3369,7 +3362,7 @@ const DeclGen = struct { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const operand_id = try self.resolve(ty_op.operand); const result_ty = self.typeOfIndex(inst); - const info = try self.arithmeticTypeInfo(result_ty); + const info = self.arithmeticTypeInfo(result_ty); var wip = try self.elementWise(result_ty); defer wip.deinit(); -- 2.54.0 From 345d6e280de1566000bf58ccd6683541cf601459 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 21 Jan 2024 01:41:41 +0100 Subject: [PATCH 11/20] spirv: air int_from_bool --- src/codegen/spirv.zig | 105 ++++++++++++++++++++++++++--------------- test/behavior/bool.zig | 2 - test/behavior/cast.zig | 1 - 3 files changed, 68 insertions(+), 40 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index cb3d1be8f077cd64cddce3157dbfb988ac83fd0a..d47233fc6bb1a6be7798b8bb87784fa01db44cf5 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -2202,6 +2202,7 @@ const DeclGen = struct { .int_from_ptr => try self.airIntFromPtr(inst), .float_from_int => try self.airFloatFromInt(inst), .int_from_float => try self.airIntFromFloat(inst), + .int_from_bool => try self.airIntFromBool(inst), .fpext, .fptrunc => try self.airFloatCast(inst), .not => try self.airNot(inst), @@ -3174,50 +3175,64 @@ const DeclGen = struct { const mod = self.module; const src_ty_ref = try self.resolveType(src_ty, .direct); const dst_ty_ref = try self.resolveType(dst_ty, .direct); - if (src_ty_ref == dst_ty_ref) { - return src_id; - } - - // TODO: Some more cases are missing here - // See fn bitCast in llvm.zig - - if (src_ty.zigTypeTag(mod) == .Int and dst_ty.isPtrAtRuntime(mod)) { - const result_id = self.spv.allocId(); - try self.func.body.emit(self.spv.gpa, .OpConvertUToPtr, .{ - .id_result_type = self.typeId(dst_ty_ref), - .id_result = result_id, - .integer_value = src_id, - }); - return result_id; - } - - // We can only use OpBitcast for specific conversions: between numerical types, and - // between pointers. If the resolved spir-v types fall into this category then emit OpBitcast, - // otherwise use a temporary and perform a pointer cast. const src_key = self.spv.cache.lookup(src_ty_ref); const dst_key = self.spv.cache.lookup(dst_ty_ref); - if ((src_key.isNumericalType() and dst_key.isNumericalType()) or (src_key == .ptr_type and dst_key == .ptr_type)) { - const result_id = self.spv.allocId(); + const result_id = blk: { + if (src_ty_ref == dst_ty_ref) { + break :blk src_id; + } + + // TODO: Some more cases are missing here + // See fn bitCast in llvm.zig + + if (src_ty.zigTypeTag(mod) == .Int and dst_ty.isPtrAtRuntime(mod)) { + const result_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpConvertUToPtr, .{ + .id_result_type = self.typeId(dst_ty_ref), + .id_result = result_id, + .integer_value = src_id, + }); + break :blk result_id; + } + + // We can only use OpBitcast for specific conversions: between numerical types, and + // between pointers. If the resolved spir-v types fall into this category then emit OpBitcast, + // otherwise use a temporary and perform a pointer cast. + if ((src_key.isNumericalType() and dst_key.isNumericalType()) or (src_key == .ptr_type and dst_key == .ptr_type)) { + const result_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpBitcast, .{ + .id_result_type = self.typeId(dst_ty_ref), + .id_result = result_id, + .operand = src_id, + }); + + break :blk result_id; + } + + const dst_ptr_ty_ref = try self.ptrType(dst_ty, .Function); + + const tmp_id = try self.alloc(src_ty, .{ .storage_class = .Function }); + try self.store(src_ty, tmp_id, src_id, .{}); + const casted_ptr_id = self.spv.allocId(); try self.func.body.emit(self.spv.gpa, .OpBitcast, .{ - .id_result_type = self.typeId(dst_ty_ref), - .id_result = result_id, - .operand = src_id, + .id_result_type = self.typeId(dst_ptr_ty_ref), + .id_result = casted_ptr_id, + .operand = tmp_id, }); - return result_id; + break :blk try self.load(dst_ty, casted_ptr_id, .{}); + }; + + // Because strange integers use sign-extended representation, we may need to normalize + // the result here. + // TODO: This detail could cause stuff like @as(*const i1, @ptrCast(&@as(u1, 1))) to break + // should we change the representation of strange integers? + if (dst_ty.zigTypeTag(mod) == .Int) { + const info = self.arithmeticTypeInfo(dst_ty); + return try self.normalize(dst_ty_ref, result_id, info); } - const dst_ptr_ty_ref = try self.ptrType(dst_ty, .Function); - - const tmp_id = try self.alloc(src_ty, .{ .storage_class = .Function }); - try self.store(src_ty, tmp_id, src_id, .{}); - const casted_ptr_id = self.spv.allocId(); - try self.func.body.emit(self.spv.gpa, .OpBitcast, .{ - .id_result_type = self.typeId(dst_ptr_ty_ref), - .id_result = casted_ptr_id, - .operand = tmp_id, - }); - return try self.load(dst_ty, casted_ptr_id, .{}); + return result_id; } fn airBitCast(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { @@ -3340,6 +3355,22 @@ const DeclGen = struct { return result_id; } + fn airIntFromBool(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { + if (self.liveness.isUnused(inst)) return null; + + const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op; + const operand_id = try self.resolve(un_op); + const result_ty = self.typeOfIndex(inst); + + var wip = try self.elementWise(result_ty); + defer wip.deinit(); + for (wip.results, 0..) |*result_id, i| { + const elem_id = try wip.elementAt(Type.bool, operand_id, i); + result_id.* = try self.intFromBool(wip.scalar_ty_ref, elem_id); + } + return try wip.finalize(); + } + fn airFloatCast(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { if (self.liveness.isUnused(inst)) return null; diff --git a/test/behavior/bool.zig b/test/behavior/bool.zig index 4b72022d4028e259ca1d5c916f522abc2ffd9159..608fb20ca7c451c87faa29c8703871eca6aa4f77 100644 --- a/test/behavior/bool.zig +++ b/test/behavior/bool.zig @@ -9,8 +9,6 @@ test "bool literals" { } test "cast bool to int" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - const t = true; const f = false; try expectEqual(@as(u32, 1), @intFromBool(t)); diff --git a/test/behavior/cast.zig b/test/behavior/cast.zig index 64f3c1d376d5953f4517b8e971f6168f1e2e2926..9a52d3218aec7b49ca8054e7bd90266fd5753b91 100644 --- a/test/behavior/cast.zig +++ b/test/behavior/cast.zig @@ -2430,7 +2430,6 @@ test "@intFromBool on vector" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO 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_spirv64) return error.SkipZigTest; const S = struct { fn doTheTest() !void { -- 2.54.0 From 7dfd403da1cd0f25e500ed67b2dfd21c669491fa Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 21 Jan 2024 12:17:19 +0100 Subject: [PATCH 12/20] spirv: air mul_add --- src/codegen/spirv.zig | 110 ++++++++++++++++++++++++++------------- test/behavior/muladd.zig | 5 -- 2 files changed, 73 insertions(+), 42 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index d47233fc6bb1a6be7798b8bb87784fa01db44cf5..3b231da5e0a137a56704df485f29108bf4080a3f 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -2160,9 +2160,9 @@ const DeclGen = struct { const air_tags = self.air.instructions.items(.tag); const maybe_result_id: ?IdRef = switch (air_tags[@intFromEnum(inst)]) { // zig fmt: off - .add, .add_wrap => try self.airArithOp(inst, .OpFAdd, .OpIAdd, .OpIAdd), - .sub, .sub_wrap => try self.airArithOp(inst, .OpFSub, .OpISub, .OpISub), - .mul, .mul_wrap => try self.airArithOp(inst, .OpFMul, .OpIMul, .OpIMul), + .add, .add_wrap, .add_optimized => try self.airArithOp(inst, .OpFAdd, .OpIAdd, .OpIAdd), + .sub, .sub_wrap, .sub_optimized => try self.airArithOp(inst, .OpFSub, .OpISub, .OpISub), + .mul, .mul_wrap, .mul_optimized => try self.airArithOp(inst, .OpFMul, .OpIMul, .OpIMul), .div_float, .div_float_optimized, @@ -2179,6 +2179,8 @@ const DeclGen = struct { .sub_with_overflow => try self.airAddSubOverflow(inst, .OpISub, .OpUGreaterThan, .OpSGreaterThan), .shl_with_overflow => try self.airShlOverflow(inst), + .mul_add => try self.airMulAdd(inst), + .reduce, .reduce_optimized => try self.airReduce(inst), .shuffle => try self.airShuffle(inst), @@ -2439,40 +2441,38 @@ const DeclGen = struct { switch (info.class) { .integer, .bool, .float => return value_id, .composite_integer => unreachable, // TODO - .strange_integer => { - switch (info.signedness) { - .unsigned => { - const mask_value = if (info.bits == 64) 0xFFFF_FFFF_FFFF_FFFF else (@as(u64, 1) << @as(u6, @intCast(info.bits))) - 1; - const result_id = self.spv.allocId(); - const mask_id = try self.constInt(ty_ref, mask_value); - try self.func.body.emit(self.spv.gpa, .OpBitwiseAnd, .{ - .id_result_type = self.typeId(ty_ref), - .id_result = result_id, - .operand_1 = value_id, - .operand_2 = mask_id, - }); - return result_id; - }, - .signed => { - // Shift left and right so that we can copy the sight bit that way. - const shift_amt_id = try self.constInt(ty_ref, info.backing_bits - info.bits); - const left_id = self.spv.allocId(); - try self.func.body.emit(self.spv.gpa, .OpShiftLeftLogical, .{ - .id_result_type = self.typeId(ty_ref), - .id_result = left_id, - .base = value_id, - .shift = shift_amt_id, - }); - const right_id = self.spv.allocId(); - try self.func.body.emit(self.spv.gpa, .OpShiftRightArithmetic, .{ - .id_result_type = self.typeId(ty_ref), - .id_result = right_id, - .base = left_id, - .shift = shift_amt_id, - }); - return right_id; - }, - } + .strange_integer => switch (info.signedness) { + .unsigned => { + const mask_value = if (info.bits == 64) 0xFFFF_FFFF_FFFF_FFFF else (@as(u64, 1) << @as(u6, @intCast(info.bits))) - 1; + const result_id = self.spv.allocId(); + const mask_id = try self.constInt(ty_ref, mask_value); + try self.func.body.emit(self.spv.gpa, .OpBitwiseAnd, .{ + .id_result_type = self.typeId(ty_ref), + .id_result = result_id, + .operand_1 = value_id, + .operand_2 = mask_id, + }); + return result_id; + }, + .signed => { + // Shift left and right so that we can copy the sight bit that way. + const shift_amt_id = try self.constInt(ty_ref, info.backing_bits - info.bits); + const left_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpShiftLeftLogical, .{ + .id_result_type = self.typeId(ty_ref), + .id_result = left_id, + .base = value_id, + .shift = shift_amt_id, + }); + const right_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpShiftRightArithmetic, .{ + .id_result_type = self.typeId(ty_ref), + .id_result = right_id, + .base = left_id, + .shift = shift_amt_id, + }); + return right_id; + }, }, } } @@ -2761,6 +2761,42 @@ const DeclGen = struct { ); } + fn airMulAdd(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { + if (self.liveness.isUnused(inst)) return null; + + const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; + const extra = self.air.extraData(Air.Bin, pl_op.payload).data; + + const mulend1 = try self.resolve(extra.lhs); + const mulend2 = try self.resolve(extra.rhs); + const addend = try self.resolve(pl_op.operand); + + const ty = self.typeOfIndex(inst); + + const info = self.arithmeticTypeInfo(ty); + assert(info.class == .float); // .mul_add is only emitted for floats + + var wip = try self.elementWise(ty); + defer wip.deinit(); + for (0..wip.results.len) |i| { + const mul_result = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpFMul, .{ + .id_result_type = wip.scalar_ty_id, + .id_result = mul_result, + .operand_1 = try wip.elementAt(ty, mulend1, i), + .operand_2 = try wip.elementAt(ty, mulend2, i), + }); + + try self.func.body.emit(self.spv.gpa, .OpFAdd, .{ + .id_result_type = wip.scalar_ty_id, + .id_result = wip.allocId(i), + .operand_1 = mul_result, + .operand_2 = try wip.elementAt(ty, addend, i), + }); + } + return try wip.finalize(); + } + fn airReduce(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { if (self.liveness.isUnused(inst)) return null; const mod = self.module; diff --git a/test/behavior/muladd.zig b/test/behavior/muladd.zig index 3bdba835f96cd72313b3bffbd261bb3b90dcde45..0c0b961097f40c0e95f31f2fa6aa2dc33b3b2848 100644 --- a/test/behavior/muladd.zig +++ b/test/behavior/muladd.zig @@ -10,7 +10,6 @@ test "@mulAdd" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; try comptime testMulAdd(); try testMulAdd(); @@ -37,7 +36,6 @@ test "@mulAdd f16" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf) return error.SkipZigTest; try comptime testMulAdd16(); @@ -111,7 +109,6 @@ test "vector f16" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; try comptime vector16(); try vector16(); @@ -136,7 +133,6 @@ test "vector f32" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; try comptime vector32(); try vector32(); @@ -161,7 +157,6 @@ test "vector f64" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; try comptime vector64(); try vector64(); -- 2.54.0 From 408c1172463429c1dcf675c41225100ebc750a78 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 21 Jan 2024 15:54:27 +0100 Subject: [PATCH 13/20] spirv: air is_(non_)null_ptr, optional_payload_ptr --- src/codegen/spirv.zig | 82 +++++++++++++++++++++++++++++--------- test/behavior/cast.zig | 1 - test/behavior/null.zig | 2 - test/behavior/optional.zig | 3 -- 4 files changed, 64 insertions(+), 24 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 3b231da5e0a137a56704df485f29108bf4080a3f..c2508633381a926596a4d33ef58f9d7134f3dc8f 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -2273,13 +2273,16 @@ const DeclGen = struct { .wrap_errunion_err => try self.airWrapErrUnionErr(inst), .wrap_errunion_payload => try self.airWrapErrUnionPayload(inst), - .is_null => try self.airIsNull(inst, .is_null), - .is_non_null => try self.airIsNull(inst, .is_non_null), - .is_err => try self.airIsErr(inst, .is_err), - .is_non_err => try self.airIsErr(inst, .is_non_err), + .is_null => try self.airIsNull(inst, false, .is_null), + .is_non_null => try self.airIsNull(inst, false, .is_non_null), + .is_null_ptr => try self.airIsNull(inst, true, .is_null), + .is_non_null_ptr => try self.airIsNull(inst, true, .is_non_null), + .is_err => try self.airIsErr(inst, .is_err), + .is_non_err => try self.airIsErr(inst, .is_non_err), - .optional_payload => try self.airUnwrapOptional(inst), - .wrap_optional => try self.airWrapOptional(inst), + .optional_payload => try self.airUnwrapOptional(inst), + .optional_payload_ptr => try self.airUnwrapOptionalPtr(inst), + .wrap_optional => try self.airWrapOptional(inst), .assembly => try self.airAssembly(inst), @@ -4726,20 +4729,24 @@ const DeclGen = struct { return try self.constructStruct(err_union_ty, &types, &members); } - fn airIsNull(self: *DeclGen, inst: Air.Inst.Index, pred: enum { is_null, is_non_null }) !?IdRef { + fn airIsNull(self: *DeclGen, inst: Air.Inst.Index, is_pointer: bool, pred: enum { is_null, is_non_null }) !?IdRef { if (self.liveness.isUnused(inst)) return null; const mod = self.module; const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op; const operand_id = try self.resolve(un_op); - const optional_ty = self.typeOf(un_op); - + const operand_ty = self.typeOf(un_op); + const optional_ty = if (is_pointer) operand_ty.childType(mod) else operand_ty; const payload_ty = optional_ty.optionalChild(mod); const bool_ty_ref = try self.resolveType(Type.bool, .direct); if (optional_ty.optionalReprIsPayload(mod)) { // Pointer payload represents nullability: pointer or slice. + const loaded_id = if (is_pointer) + try self.load(optional_ty, operand_id, .{}) + else + operand_id; const ptr_ty = if (payload_ty.isSlice(mod)) payload_ty.slicePtrFieldType(mod) @@ -4747,9 +4754,9 @@ const DeclGen = struct { payload_ty; const ptr_id = if (payload_ty.isSlice(mod)) - try self.extractField(ptr_ty, operand_id, 0) + try self.extractField(ptr_ty, loaded_id, 0) else - operand_id; + loaded_id; const payload_ty_ref = try self.resolveType(ptr_ty, .direct); const null_id = try self.spv.constNull(payload_ty_ref); @@ -4760,13 +4767,26 @@ const DeclGen = struct { return try self.cmp(op, Type.bool, ptr_ty, ptr_id, null_id); } - const is_non_null_id = if (payload_ty.hasRuntimeBitsIgnoreComptime(mod)) - try self.extractField(Type.bool, operand_id, 1) - else - // Optional representation is bool indicating whether the optional is set - // Optionals with no payload are represented as an (indirect) bool, so convert - // it back to the direct bool here. - try self.convertToDirect(Type.bool, operand_id); + const is_non_null_id = blk: { + if (is_pointer) { + if (payload_ty.hasRuntimeBitsIgnoreComptime(mod)) { + const storage_class = spvStorageClass(operand_ty.ptrAddressSpace(mod)); + const bool_ptr_ty = try self.ptrType(Type.bool, storage_class); + const tag_ptr_id = try self.accessChain(bool_ptr_ty, operand_id, &.{1}); + break :blk try self.load(Type.bool, tag_ptr_id, .{}); + } + + break :blk try self.load(Type.bool, operand_id, .{}); + } + + break :blk if (payload_ty.hasRuntimeBitsIgnoreComptime(mod)) + try self.extractField(Type.bool, operand_id, 1) + else + // Optional representation is bool indicating whether the optional is set + // Optionals with no payload are represented as an (indirect) bool, so convert + // it back to the direct bool here. + try self.convertToDirect(Type.bool, operand_id); + }; return switch (pred) { .is_null => blk: { @@ -4837,6 +4857,32 @@ const DeclGen = struct { return try self.extractField(payload_ty, operand_id, 0); } + fn airUnwrapOptionalPtr(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { + if (self.liveness.isUnused(inst)) return null; + + const mod = self.module; + const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; + const operand_id = try self.resolve(ty_op.operand); + const operand_ty = self.typeOf(ty_op.operand); + const optional_ty = operand_ty.childType(mod); + const payload_ty = optional_ty.optionalChild(mod); + const result_ty = self.typeOfIndex(inst); + const result_ty_ref = try self.resolveType(result_ty, .direct); + + if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) { + // There is no payload, but we still need to return a valid pointer. + // We can just return anything here, so just return a pointer to the operand. + return try self.bitCast(result_ty, operand_ty, operand_id); + } + + if (optional_ty.optionalReprIsPayload(mod)) { + // They are the same value. + return try self.bitCast(result_ty, operand_ty, operand_id); + } + + return try self.accessChain(result_ty_ref, operand_id, &.{0}); + } + fn airWrapOptional(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { if (self.liveness.isUnused(inst)) return null; diff --git a/test/behavior/cast.zig b/test/behavior/cast.zig index 9a52d3218aec7b49ca8054e7bd90266fd5753b91..be25bde6933f3cf661a55f5fa11ed17adc261d62 100644 --- a/test/behavior/cast.zig +++ b/test/behavior/cast.zig @@ -1247,7 +1247,6 @@ test "implicit cast from *[N]T to ?[*]T" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; 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_spirv64) return error.SkipZigTest; var x: ?[*]u16 = null; var y: [4]u16 = [4]u16{ 0, 1, 2, 3 }; diff --git a/test/behavior/null.zig b/test/behavior/null.zig index 20afa21cb894d35327d1b1ea1e814ba2f685ca42..ffebff6d83669b6cd4d8325aa5033932e20211ea 100644 --- a/test/behavior/null.zig +++ b/test/behavior/null.zig @@ -32,7 +32,6 @@ test "test maybe object and get a pointer to the inner value" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; var maybe_bool: ?bool = true; @@ -142,7 +141,6 @@ test "if var maybe pointer" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; try expect(shouldBeAPlus1(Particle{ .a = 14, diff --git a/test/behavior/optional.zig b/test/behavior/optional.zig index 5a5460bfd2f10e550b070d17c13b3ee20446dbc6..3da78aea0a744e7882e5a83fb278190a0bd22cb6 100644 --- a/test/behavior/optional.zig +++ b/test/behavior/optional.zig @@ -72,7 +72,6 @@ test "address of unwrap optional" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; 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_spirv64) return error.SkipZigTest; const S = struct { const Foo = struct { @@ -341,7 +340,6 @@ test "optional pointer to zero bit optional payload" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const B = struct { fn foo(_: *@This()) void {} @@ -518,7 +516,6 @@ test "copied optional doesn't alias source" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; var opt_x: ?[3]f32 = [_]f32{0.0} ** 3; -- 2.54.0 From 9f0227a326d84208e23e90c2a84ff95f734bd2ae Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 21 Jan 2024 16:05:39 +0100 Subject: [PATCH 14/20] spirv: vectorize int_cast, trunc --- src/codegen/spirv.zig | 49 +++++++++++++++++++++----------------- test/behavior/truncate.zig | 1 - 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index c2508633381a926596a4d33ef58f9d7134f3dc8f..eda8d88cdb7820b59e14daa168214fdb5d0d004b 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -3290,7 +3290,6 @@ const DeclGen = struct { const operand_id = try self.resolve(ty_op.operand); const src_ty = self.typeOf(ty_op.operand); const dst_ty = self.typeOfIndex(inst); - const dst_ty_ref = try self.resolveType(dst_ty, .direct); const src_info = self.arithmeticTypeInfo(src_ty); const dst_info = self.arithmeticTypeInfo(dst_ty); @@ -3299,29 +3298,35 @@ const DeclGen = struct { return operand_id; } - const result_id = self.spv.allocId(); - switch (dst_info.signedness) { - .signed => try self.func.body.emit(self.spv.gpa, .OpSConvert, .{ - .id_result_type = self.typeId(dst_ty_ref), - .id_result = result_id, - .signed_value = operand_id, - }), - .unsigned => try self.func.body.emit(self.spv.gpa, .OpUConvert, .{ - .id_result_type = self.typeId(dst_ty_ref), - .id_result = result_id, - .unsigned_value = operand_id, - }), - } + var wip = try self.elementWise(dst_ty); + defer wip.deinit(); + for (wip.results, 0..) |*result_id, i| { + const elem_id = try wip.elementAt(src_ty, operand_id, i); + const value_id = self.spv.allocId(); + switch (dst_info.signedness) { + .signed => try self.func.body.emit(self.spv.gpa, .OpSConvert, .{ + .id_result_type = wip.scalar_ty_id, + .id_result = value_id, + .signed_value = elem_id, + }), + .unsigned => try self.func.body.emit(self.spv.gpa, .OpUConvert, .{ + .id_result_type = wip.scalar_ty_id, + .id_result = value_id, + .unsigned_value = elem_id, + }), + } - // Make sure to normalize the result if shrinking. - // Because strange ints are sign extended in their backing - // type, we don't need to normalize when growing the type. The - // representation is already the same. - if (dst_info.bits < src_info.bits) { - return try self.normalize(dst_ty_ref, result_id, dst_info); + // Make sure to normalize the result if shrinking. + // Because strange ints are sign extended in their backing + // type, we don't need to normalize when growing the type. The + // representation is already the same. + if (dst_info.bits < src_info.bits) { + result_id.* = try self.normalize(wip.scalar_ty_ref, value_id, dst_info); + } else { + result_id.* = value_id; + } } - - return result_id; + return try wip.finalize(); } fn intFromPtr(self: *DeclGen, operand_id: IdRef) !IdRef { diff --git a/test/behavior/truncate.zig b/test/behavior/truncate.zig index 81a916f80c67f93671bade32ec8b3f33de0dbf28..267d291d4895782af45b4896b9ee40c6744529c4 100644 --- a/test/behavior/truncate.zig +++ b/test/behavior/truncate.zig @@ -69,7 +69,6 @@ test "truncate on vectors" { 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_spirv64) return error.SkipZigTest; const S = struct { fn doTheTest() !void { -- 2.54.0 From 9641d2ebdb74926a56ff3b916082534052dc637f Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 21 Jan 2024 20:12:25 +0100 Subject: [PATCH 15/20] spirv: vectorize max, min --- src/codegen/spirv.zig | 99 ++++++++++++++++--------------- test/behavior/maximum_minimum.zig | 5 -- 2 files changed, 50 insertions(+), 54 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index eda8d88cdb7820b59e14daa168214fdb5d0d004b..be3c9957e1536d914d52e2254b24afc2cd7506cc 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -2391,45 +2391,51 @@ const DeclGen = struct { } fn minMax(self: *DeclGen, result_ty: Type, op: std.math.CompareOperator, lhs_id: IdRef, rhs_id: IdRef) !IdRef { - const result_ty_ref = try self.resolveType(result_ty, .direct); const info = self.arithmeticTypeInfo(result_ty); - // TODO: Use fmin for OpenCL - const cmp_id = try self.cmp(op, Type.bool, result_ty, lhs_id, rhs_id); - const selection_id = switch (info.class) { - .float => blk: { - // cmp uses OpFOrd. When we have 0 [<>] nan this returns false, - // but we want it to pick lhs. Therefore we also have to check if - // rhs is nan. We don't need to care about the result when both - // are nan. - const rhs_is_nan_id = self.spv.allocId(); - const bool_ty_ref = try self.resolveType(Type.bool, .direct); - try self.func.body.emit(self.spv.gpa, .OpIsNan, .{ - .id_result_type = self.typeId(bool_ty_ref), - .id_result = rhs_is_nan_id, - .x = rhs_id, - }); - const float_cmp_id = self.spv.allocId(); - try self.func.body.emit(self.spv.gpa, .OpLogicalOr, .{ - .id_result_type = self.typeId(bool_ty_ref), - .id_result = float_cmp_id, - .operand_1 = cmp_id, - .operand_2 = rhs_is_nan_id, - }); - break :blk float_cmp_id; - }, - else => cmp_id, - }; + var wip = try self.elementWise(result_ty); + defer wip.deinit(); + for (wip.results, 0..) |*result_id, i| { + const lhs_elem_id = try wip.elementAt(result_ty, lhs_id, i); + const rhs_elem_id = try wip.elementAt(result_ty, rhs_id, i); - const result_id = self.spv.allocId(); - try self.func.body.emit(self.spv.gpa, .OpSelect, .{ - .id_result_type = self.typeId(result_ty_ref), - .id_result = result_id, - .condition = selection_id, - .object_1 = lhs_id, - .object_2 = rhs_id, - }); - return result_id; + // TODO: Use fmin for OpenCL + const cmp_id = try self.cmp(op, Type.bool, wip.scalar_ty, lhs_elem_id, rhs_elem_id); + const selection_id = switch (info.class) { + .float => blk: { + // cmp uses OpFOrd. When we have 0 [<>] nan this returns false, + // but we want it to pick lhs. Therefore we also have to check if + // rhs is nan. We don't need to care about the result when both + // are nan. + const rhs_is_nan_id = self.spv.allocId(); + const bool_ty_ref = try self.resolveType(Type.bool, .direct); + try self.func.body.emit(self.spv.gpa, .OpIsNan, .{ + .id_result_type = self.typeId(bool_ty_ref), + .id_result = rhs_is_nan_id, + .x = rhs_elem_id, + }); + const float_cmp_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpLogicalOr, .{ + .id_result_type = self.typeId(bool_ty_ref), + .id_result = float_cmp_id, + .operand_1 = cmp_id, + .operand_2 = rhs_is_nan_id, + }); + break :blk float_cmp_id; + }, + else => cmp_id, + }; + + result_id.* = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpSelect, .{ + .id_result_type = wip.scalar_ty_id, + .id_result = result_id.*, + .condition = selection_id, + .object_1 = lhs_elem_id, + .object_2 = rhs_elem_id, + }); + } + return wip.finalize(); } /// This function normalizes values to a canonical representation @@ -3107,20 +3113,15 @@ const DeclGen = struct { return result_id; }, .Vector => { - const child_ty = ty.childType(mod); - const vector_len = ty.vectorLen(mod); - - const constituents = try self.gpa.alloc(IdRef, vector_len); - defer self.gpa.free(constituents); - - for (constituents, 0..) |*constituent, i| { - const lhs_index_id = try self.extractField(child_ty, cmp_lhs_id, @intCast(i)); - const rhs_index_id = try self.extractField(child_ty, cmp_rhs_id, @intCast(i)); - const result_id = try self.cmp(op, Type.bool, child_ty, lhs_index_id, rhs_index_id); - constituent.* = try self.convertToIndirect(Type.bool, result_id); + var wip = try self.elementWise(result_ty); + defer wip.deinit(); + const scalar_ty = ty.scalarType(mod); + for (wip.results, 0..) |*result_id, i| { + const lhs_elem_id = try wip.elementAt(ty, lhs_id, i); + const rhs_elem_id = try wip.elementAt(ty, rhs_id, i); + result_id.* = try self.cmp(op, Type.bool, scalar_ty, lhs_elem_id, rhs_elem_id); } - - return try self.constructArray(result_ty, constituents); + return wip.finalize(); }, else => unreachable, }; diff --git a/test/behavior/maximum_minimum.zig b/test/behavior/maximum_minimum.zig index f7cb1ee51319898f6dc6d9bdb51ac3e5bfb04d2f..a6a2e3b8e8fcc1d8d7a1efa8f8c035c666de3d08 100644 --- a/test/behavior/maximum_minimum.zig +++ b/test/behavior/maximum_minimum.zig @@ -31,7 +31,6 @@ test "@max on vectors" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64 and !comptime std.Target.x86.featureSetHas(builtin.cpu.features, .sse4_1)) return error.SkipZigTest; @@ -86,7 +85,6 @@ test "@min for vectors" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO 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_spirv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64 and !comptime std.Target.x86.featureSetHas(builtin.cpu.features, .sse4_1)) return error.SkipZigTest; @@ -199,7 +197,6 @@ test "@min/@max notices vector bounds" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO 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_spirv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; var x: @Vector(2, u16) = .{ 140, 40 }; @@ -253,7 +250,6 @@ test "@min/@max notices bounds from vector types" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO 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_spirv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; var x: @Vector(2, u16) = .{ 30, 67 }; @@ -295,7 +291,6 @@ test "@min/@max notices bounds from vector types when element of comptime-known if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO 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_spirv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64 and !comptime std.Target.x86.featureSetHas(builtin.cpu.features, .avx)) return error.SkipZigTest; -- 2.54.0 From 631d1b63a8027c49073995e28aab489534f01efa Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 21 Jan 2024 20:38:56 +0100 Subject: [PATCH 16/20] spirv: fix shuffle properly --- src/codegen/spirv.zig | 32 +++++++++++++------------------- test/behavior/abs.zig | 1 - test/behavior/cast.zig | 2 -- test/behavior/shuffle.zig | 3 --- test/behavior/vector.zig | 2 -- 5 files changed, 13 insertions(+), 27 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index be3c9957e1536d914d52e2254b24afc2cd7506cc..28f2c1677cd834ff50cf453b91fac250670718fd 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -2876,37 +2876,31 @@ const DeclGen = struct { fn airShuffle(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { const mod = self.module; if (self.liveness.isUnused(inst)) return null; - const ty = self.typeOfIndex(inst); const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; const extra = self.air.extraData(Air.Shuffle, ty_pl.payload).data; const a = try self.resolve(extra.a); const b = try self.resolve(extra.b); const mask = Value.fromInterned(extra.mask); - const mask_len = extra.mask_len; - const a_len = self.typeOf(extra.a).vectorLen(mod); - const result_id = self.spv.allocId(); - const result_type_id = try self.resolveTypeId(ty); - // Similar to LLVM, SPIR-V uses indices larger than the length of the first vector - // to index into the second vector. - try self.func.body.emitRaw(self.spv.gpa, .OpVectorShuffle, 4 + mask_len); - self.func.body.writeOperand(spec.IdResultType, result_type_id); - self.func.body.writeOperand(spec.IdResult, result_id); - self.func.body.writeOperand(spec.IdRef, a); - self.func.body.writeOperand(spec.IdRef, b); + const ty = self.typeOfIndex(inst); - var i: usize = 0; - while (i < mask_len) : (i += 1) { + var wip = try self.elementWise(ty); + defer wip.deinit(); + for (wip.results, 0..) |*result_id, i| { const elem = try mask.elemValue(mod, i); if (elem.isUndef(mod)) { - self.func.body.writeOperand(spec.LiteralInteger, 0xFFFF_FFFF); + result_id.* = try self.spv.constUndef(wip.scalar_ty_ref); + continue; + } + + const index = elem.toSignedInt(mod); + if (index >= 0) { + result_id.* = try self.extractField(wip.scalar_ty, a, @intCast(index)); } else { - const int = elem.toSignedInt(mod); - const unsigned = if (int >= 0) @as(u32, @intCast(int)) else @as(u32, @intCast(~int + a_len)); - self.func.body.writeOperand(spec.LiteralInteger, unsigned); + result_id.* = try self.extractField(wip.scalar_ty, b, @intCast(~index)); } } - return result_id; + return try wip.finalize(); } fn indicesToIds(self: *DeclGen, indices: []const u32) ![]IdRef { diff --git a/test/behavior/abs.zig b/test/behavior/abs.zig index fad29a1a586888bce0163f4e846ea0b2092e6ad3..d8666405a0b09188863573a22635dd48c584dc69 100644 --- a/test/behavior/abs.zig +++ b/test/behavior/abs.zig @@ -224,7 +224,6 @@ test "@abs unsigned int vectors" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; try comptime testAbsUnsignedIntVectors(1); try testAbsUnsignedIntVectors(1); diff --git a/test/behavior/cast.zig b/test/behavior/cast.zig index be25bde6933f3cf661a55f5fa11ed17adc261d62..48feb86ef1a4ee42b2621f399a80ea48d9868fd2 100644 --- a/test/behavior/cast.zig +++ b/test/behavior/cast.zig @@ -605,7 +605,6 @@ test "@intCast on vector" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO 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_spirv64) return error.SkipZigTest; const S = struct { fn doTheTest() !void { @@ -2508,7 +2507,6 @@ test "@intCast vector of signed integer" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO diff --git a/test/behavior/shuffle.zig b/test/behavior/shuffle.zig index e9d7706ff4a9090d84783e2fbf286857b7401f0e..95913be3af23e277a51d556ff02d65bac114f737 100644 --- a/test/behavior/shuffle.zig +++ b/test/behavior/shuffle.zig @@ -8,7 +8,6 @@ test "@shuffle int" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO 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_spirv64) return error.SkipZigTest; const S = struct { fn doTheTest() !void { @@ -54,7 +53,6 @@ test "@shuffle bool 1" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO 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_spirv64) return error.SkipZigTest; const S = struct { fn doTheTest() !void { @@ -77,7 +75,6 @@ test "@shuffle bool 2" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO 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_spirv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_llvm) { // https://github.com/ziglang/zig/issues/3246 diff --git a/test/behavior/vector.zig b/test/behavior/vector.zig index b23eac924d02c44d9567162f74f8b7a4f70b69f9..26d60c337adf9391f5e7c247feb5c46fdea48b24 100644 --- a/test/behavior/vector.zig +++ b/test/behavior/vector.zig @@ -910,7 +910,6 @@ test "mask parameter of @shuffle is comptime scope" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO 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_spirv64) return error.SkipZigTest; const __v4hi = @Vector(4, i16); var v4_a = __v4hi{ 0, 0, 0, 0 }; @@ -1322,7 +1321,6 @@ test "array operands to shuffle are coerced to vectors" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO 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_spirv64) return error.SkipZigTest; const mask = [5]i32{ -1, 0, 1, 2, 3 }; -- 2.54.0 From 76d5696434095e39d9aaae92c1533b2d016c1a31 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 21 Jan 2024 22:24:53 +0100 Subject: [PATCH 17/20] spirv: air abs --- src/codegen/spirv.zig | 71 +++++++++++++++++++++++++++++++++++++++ test/behavior/abs.zig | 7 ++-- test/behavior/cast.zig | 1 - test/behavior/floatop.zig | 3 -- test/behavior/math.zig | 1 - 5 files changed, 73 insertions(+), 10 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 28f2c1677cd834ff50cf453b91fac250670718fd..9e7c49d1a5e59b7b8be43e778d1a6a0ed248d918 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -678,6 +678,18 @@ const DeclGen = struct { } } + /// Emits a float constant + fn constFloat(self: *DeclGen, ty_ref: CacheRef, value: f128) !IdRef { + const ty = self.spv.cache.lookup(ty_ref).float_type; + return switch (ty.bits) { + 16 => try self.spv.resolveId(.{ .float = .{ .ty = ty_ref, .value = .{ .float16 = @floatCast(value) } } }), + 32 => try self.spv.resolveId(.{ .float = .{ .ty = ty_ref, .value = .{ .float32 = @floatCast(value) } } }), + 64 => try self.spv.resolveId(.{ .float = .{ .ty = ty_ref, .value = .{ .float64 = @floatCast(value) } } }), + 80, 128 => unreachable, // TODO + else => unreachable, + }; + } + /// Construct a struct at runtime. /// ty must be a struct type. /// Constituents should be in `indirect` representation (as the elements of a struct should be). @@ -2164,6 +2176,8 @@ const DeclGen = struct { .sub, .sub_wrap, .sub_optimized => try self.airArithOp(inst, .OpFSub, .OpISub, .OpISub), .mul, .mul_wrap, .mul_optimized => try self.airArithOp(inst, .OpFMul, .OpIMul, .OpIMul), + .abs => try self.airAbs(inst), + .div_float, .div_float_optimized, // TODO: Check that this is the right operation. @@ -2562,6 +2576,63 @@ const DeclGen = struct { return try wip.finalize(); } + fn airAbs(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { + if (self.liveness.isUnused(inst)) return null; + + const mod = self.module; + const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; + const operand_id = try self.resolve(ty_op.operand); + // Note: operand_ty may be signed, while ty is always unsigned! + const operand_ty = self.typeOf(ty_op.operand); + const ty = self.typeOfIndex(inst); + const info = self.arithmeticTypeInfo(ty); + const operand_scalar_ty = operand_ty.scalarType(mod); + const operand_scalar_ty_ref = try self.resolveType(operand_scalar_ty, .direct); + + var wip = try self.elementWise(ty); + defer wip.deinit(); + + const zero_id = switch (info.class) { + .float => try self.constFloat(operand_scalar_ty_ref, 0), + .integer, .strange_integer => try self.constInt(operand_scalar_ty_ref, 0), + .composite_integer => unreachable, // TODO + .bool => unreachable, + }; + for (wip.results, 0..) |*result_id, i| { + const elem_id = try wip.elementAt(operand_ty, operand_id, i); + // Idk why spir-v doesn't have a dedicated abs() instruction in the base + // instruction set. For now we're just going to negate and check to avoid + // importing the extinst. + const neg_id = self.spv.allocId(); + const args = .{ + .id_result_type = self.typeId(operand_scalar_ty_ref), + .id_result = neg_id, + .operand_1 = zero_id, + .operand_2 = elem_id, + }; + switch (info.class) { + .float => try self.func.body.emit(self.spv.gpa, .OpFSub, args), + .integer, .strange_integer => try self.func.body.emit(self.spv.gpa, .OpISub, args), + .composite_integer => unreachable, // TODO + .bool => unreachable, + } + const neg_norm_id = try self.normalize(wip.scalar_ty_ref, neg_id, info); + + const gt_zero_id = try self.cmp(.gt, Type.bool, operand_scalar_ty, elem_id, zero_id); + const abs_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpSelect, .{ + .id_result_type = self.typeId(operand_scalar_ty_ref), + .id_result = abs_id, + .condition = gt_zero_id, + .object_1 = elem_id, + .object_2 = neg_norm_id, + }); + // For Shader, we may need to cast from signed to unsigned here. + result_id.* = try self.bitCast(wip.scalar_ty, operand_scalar_ty, abs_id); + } + return try wip.finalize(); + } + fn airAddSubOverflow( self: *DeclGen, inst: Air.Inst.Index, diff --git a/test/behavior/abs.zig b/test/behavior/abs.zig index d8666405a0b09188863573a22635dd48c584dc69..abea715ea056d9bdded12b896dbd8570f264ab12 100644 --- 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_riscv64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; try comptime testAbsIntegers(); try testAbsIntegers(); @@ -95,7 +94,6 @@ test "@abs floats" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf) return error.SkipZigTest; try comptime testAbsFloats(f16); @@ -105,9 +103,9 @@ test "@abs floats" { try comptime testAbsFloats(f64); try testAbsFloats(f64); try comptime testAbsFloats(f80); - if (builtin.zig_backend != .stage2_wasm) try testAbsFloats(f80); + if (builtin.zig_backend != .stage2_wasm and builtin.zig_backend != .stage2_spirv64) try testAbsFloats(f80); try comptime testAbsFloats(f128); - if (builtin.zig_backend != .stage2_wasm) try testAbsFloats(f128); + if (builtin.zig_backend != .stage2_wasm and builtin.zig_backend != .stage2_spirv64) try testAbsFloats(f128); } fn testAbsFloats(comptime T: type) !void { @@ -155,7 +153,6 @@ test "@abs int vectors" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; try comptime testAbsIntVectors(1); try testAbsIntVectors(1); diff --git a/test/behavior/cast.zig b/test/behavior/cast.zig index 48feb86ef1a4ee42b2621f399a80ea48d9868fd2..c59a9803c092a69cb95128ca41d2405ffad1c1e8 100644 --- a/test/behavior/cast.zig +++ b/test/behavior/cast.zig @@ -2465,7 +2465,6 @@ test "@as does not corrupt values with incompatible representations" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO 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_spirv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf) return error.SkipZigTest; const x: f32 = @as(f16, blk: { diff --git a/test/behavior/floatop.zig b/test/behavior/floatop.zig index 568fe6deefa4a18589de2793560b889602d9f81d..43654bc2b9bbad4b0d2c1d8e436a763502578176 100644 --- a/test/behavior/floatop.zig +++ b/test/behavior/floatop.zig @@ -969,7 +969,6 @@ test "@abs f16" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; try testFabs(f16); try comptime testFabs(f16); @@ -979,7 +978,6 @@ test "@abs f32/f64" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; try testFabs(f32); try comptime testFabs(f32); @@ -1070,7 +1068,6 @@ fn testFabs(comptime T: type) !void { test "@abs with vectors" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO try testFabsWithVectors(); diff --git a/test/behavior/math.zig b/test/behavior/math.zig index 3aa65dddbb7ab5a85f522a3ac675e0109100d35e..dc4d5f894a764485275a2c82459b4e13b67361f5 100644 --- a/test/behavior/math.zig +++ b/test/behavior/math.zig @@ -1687,7 +1687,6 @@ test "absFloat" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; try testAbsFloat(); try comptime testAbsFloat(); -- 2.54.0 From 1d548aa2aab472f14013e67e29c6d898a7b31998 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 21 Jan 2024 22:48:31 +0100 Subject: [PATCH 18/20] spirv: air splat --- src/codegen/spirv.zig | 15 +++++++++++++++ test/behavior/vector.zig | 1 - 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 9e7c49d1a5e59b7b8be43e778d1a6a0ed248d918..80a3e7b07fae432925b21c76dfdd776b77bfede9 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -2195,6 +2195,7 @@ const DeclGen = struct { .mul_add => try self.airMulAdd(inst), + .splat => try self.airSplat(inst), .reduce, .reduce_optimized => try self.airReduce(inst), .shuffle => try self.airShuffle(inst), @@ -2603,6 +2604,7 @@ const DeclGen = struct { // Idk why spir-v doesn't have a dedicated abs() instruction in the base // instruction set. For now we're just going to negate and check to avoid // importing the extinst. + // TODO: Make this a call to compiler rt / ext inst const neg_id = self.spv.allocId(); const args = .{ .id_result_type = self.typeId(operand_scalar_ty_ref), @@ -2877,6 +2879,19 @@ const DeclGen = struct { return try wip.finalize(); } + fn airSplat(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { + if (self.liveness.isUnused(inst)) return null; + const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; + const operand_id = try self.resolve(ty_op.operand); + const result_ty = self.typeOfIndex(inst); + var wip = try self.elementWise(result_ty); + defer wip.deinit(); + for (wip.results) |*result_id| { + result_id.* = operand_id; + } + return try wip.finalize(); + } + fn airReduce(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { if (self.liveness.isUnused(inst)) return null; const mod = self.module; diff --git a/test/behavior/vector.zig b/test/behavior/vector.zig index 26d60c337adf9391f5e7c247feb5c46fdea48b24..9aedac66e5c5fa92db1a8e1f10a4e802256ae5ce 100644 --- a/test/behavior/vector.zig +++ b/test/behavior/vector.zig @@ -326,7 +326,6 @@ test "vector @splat" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO 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_spirv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_llvm and builtin.os.tag == .macos) -- 2.54.0 From 9fbba0e01a5e89ce4b4be940539b6b05a7fb3931 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sat, 13 Jan 2024 18:44:44 +0100 Subject: [PATCH 19/20] spirv: update tests --- test/behavior/align.zig | 7 +++---- test/behavior/array.zig | 2 -- test/behavior/basic.zig | 1 + .../builtin_functions_returning_void_or_noreturn.zig | 1 + test/behavior/cast.zig | 7 +------ test/behavior/destructure.zig | 1 + test/behavior/duplicated_test_names.zig | 2 ++ test/behavior/eval.zig | 1 - test/behavior/export_builtin.zig | 3 --- test/behavior/export_keyword.zig | 1 + test/behavior/extern.zig | 2 ++ test/behavior/for.zig | 1 + test/behavior/globals.zig | 2 -- test/behavior/hasdecl.zig | 5 +++++ test/behavior/import.zig | 9 +++++++++ test/behavior/int_div.zig | 2 ++ test/behavior/math.zig | 3 --- test/behavior/namespace_depends_on_compile_var.zig | 2 ++ test/behavior/optional.zig | 1 + test/behavior/pub_enum.zig | 4 ++++ test/behavior/slice_sentinel_comptime.zig | 2 ++ test/behavior/struct.zig | 4 ++-- test/behavior/switch_on_captured_error.zig | 4 ++++ test/behavior/tuple.zig | 1 - test/behavior/union.zig | 3 ++- test/behavior/vector.zig | 1 - test/behavior/wrapping_arithmetic.zig | 6 ++++++ 27 files changed, 52 insertions(+), 26 deletions(-) diff --git a/test/behavior/align.zig b/test/behavior/align.zig index 03109b1e8d7beffd6fac5e021997ecab3980da44..e00fbe67443f0bf13281cc15d06f69653beaead3 100644 --- a/test/behavior/align.zig +++ b/test/behavior/align.zig @@ -18,7 +18,6 @@ test "global variable alignment" { test "large alignment of local constant" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // flaky const x: f32 align(128) = 12.34; try std.testing.expect(@intFromPtr(&x) % 128 == 0); @@ -27,7 +26,7 @@ test "large alignment of local constant" { test "slicing array of length 1 can not assume runtime index is always zero" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // flaky var runtime_index: usize = 1; _ = &runtime_index; @@ -512,7 +511,7 @@ test "struct field explicit alignment" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // flaky const S = struct { const Node = struct { @@ -581,7 +580,7 @@ test "comptime alloc alignment" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // flaky if (builtin.zig_backend == .stage2_llvm and builtin.target.cpu.arch == .x86) { // https://github.com/ziglang/zig/issues/18034 return error.SkipZigTest; diff --git a/test/behavior/array.zig b/test/behavior/array.zig index 75141c6bd13aca1b6101dee568fa8ff51be9de43..5e3a9fb52f995c9311a2d4adf3cf14b53de381b6 100644 --- a/test/behavior/array.zig +++ b/test/behavior/array.zig @@ -768,8 +768,6 @@ test "array init with no result pointer sets field result types" { } test "runtime side-effects in comptime-known array init" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - var side_effects: u4 = 0; const init = [4]u4{ blk: { diff --git a/test/behavior/basic.zig b/test/behavior/basic.zig index 42792f4acad5c309f3670edc664e94928a9b17c2..9747206a760cc509fc381459a8fe2c451e70fdd8 100644 --- a/test/behavior/basic.zig +++ b/test/behavior/basic.zig @@ -1222,6 +1222,7 @@ test "integer compare" { test "reference to inferred local variable works as expected" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const Crasher = struct { lets_crash: u64 = 0, diff --git a/test/behavior/builtin_functions_returning_void_or_noreturn.zig b/test/behavior/builtin_functions_returning_void_or_noreturn.zig index 1eb2ef3049ffb8ee649908098a48157fcc39e658..48cf0bc5c6e3de4ba9fe8aeb3c80acbeb92e9a15 100644 --- a/test/behavior/builtin_functions_returning_void_or_noreturn.zig +++ b/test/behavior/builtin_functions_returning_void_or_noreturn.zig @@ -11,6 +11,7 @@ test { 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_spirv64) return error.SkipZigTest; var val: u8 = undefined; try testing.expectEqual({}, @atomicStore(u8, &val, 0, .Unordered)); diff --git a/test/behavior/cast.zig b/test/behavior/cast.zig index c59a9803c092a69cb95128ca41d2405ffad1c1e8..d691949bae3f78301b6d271aa3a845f22541d54e 100644 --- a/test/behavior/cast.zig +++ b/test/behavior/cast.zig @@ -759,6 +759,7 @@ test "peer type resolution: error union and error set" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const a: error{Three} = undefined; const b: error{ One, Two }!u32 = undefined; @@ -1730,7 +1731,6 @@ test "peer type resolution: array with smaller child type and vector with larger if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO var arr: [2]u8 = .{ 0, 1 }; var vec: @Vector(2, u64) = .{ 2, 3 }; @@ -2318,7 +2318,6 @@ test "@floatCast on vector" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO 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_spirv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf) return error.SkipZigTest; const S = struct { @@ -2339,7 +2338,6 @@ test "@ptrFromInt on vector" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO 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_spirv64) return error.SkipZigTest; const S = struct { fn doTheTest() !void { @@ -2363,7 +2361,6 @@ test "@intFromPtr on vector" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO 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_spirv64) return error.SkipZigTest; const S = struct { fn doTheTest() !void { @@ -2387,7 +2384,6 @@ test "@floatFromInt on vector" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO 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_spirv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf) return error.SkipZigTest; const S = struct { @@ -2408,7 +2404,6 @@ test "@intFromFloat on vector" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO 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_spirv64) return error.SkipZigTest; const S = struct { fn doTheTest() !void { diff --git a/test/behavior/destructure.zig b/test/behavior/destructure.zig index c2a5ca329a5513145e294ad5194a4fedded39524..1c249ed8d505d68c981ec1b2fa802c62c9804a8e 100644 --- a/test/behavior/destructure.zig +++ b/test/behavior/destructure.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const builtin = @import("builtin"); const assert = std.debug.assert; const expect = std.testing.expect; diff --git a/test/behavior/duplicated_test_names.zig b/test/behavior/duplicated_test_names.zig index 52930ea31819c042a6e3077fc7fd0bacaed5bf92..81b9ebdf50ceaa21b7f8c9953bb9257bd8fa079c 100644 --- a/test/behavior/duplicated_test_names.zig +++ b/test/behavior/duplicated_test_names.zig @@ -15,5 +15,7 @@ comptime { test "thingy" {} test thingy { + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + if (thingy(1, 2) != 3) unreachable; } diff --git a/test/behavior/eval.zig b/test/behavior/eval.zig index 078fae519af48a0d6bc0266e0c69eda14e5fa9ab..5fdfacf2dbb694d5c9a9fc7b4dff304790f4a2b7 100644 --- a/test/behavior/eval.zig +++ b/test/behavior/eval.zig @@ -489,7 +489,6 @@ test "comptime bitwise operators" { test "comptime shlWithOverflow" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const ct_shifted = @shlWithOverflow(~@as(u64, 0), 16)[0]; var a = ~@as(u64, 0); diff --git a/test/behavior/export_builtin.zig b/test/behavior/export_builtin.zig index e3ea1f7e0be94857ddbe78850cd825db29bc0c64..6755a127f5c807b4a526e57d9d3dc60b0689b139 100644 --- a/test/behavior/export_builtin.zig +++ b/test/behavior/export_builtin.zig @@ -6,7 +6,6 @@ test "exporting enum type and value" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const S = struct { const E = enum(c_int) { one, two }; @@ -22,7 +21,6 @@ test "exporting with internal linkage" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const S = struct { fn foo() callconv(.C) void {} @@ -37,7 +35,6 @@ test "exporting using field access" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const S = struct { const Inner = struct { diff --git a/test/behavior/export_keyword.zig b/test/behavior/export_keyword.zig index 34228c8e7fa71fb6d327c4e34c27972885cc974e..a6baf6d1a598ee9e50901a1b088e3ea8ffbf1801 100644 --- a/test/behavior/export_keyword.zig +++ b/test/behavior/export_keyword.zig @@ -23,6 +23,7 @@ const PackedUnion = packed union { test "packed struct, enum, union parameters in extern function" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; testPackedStuff(&(PackedStruct{ .a = 1, diff --git a/test/behavior/extern.zig b/test/behavior/extern.zig index af9c86e3beba8a8a40883b811c5b4cd40d59afc7..65bc517210afc4df688cae4605e8b8201e4ab8d1 100644 --- a/test/behavior/extern.zig +++ b/test/behavior/extern.zig @@ -5,6 +5,7 @@ const expect = std.testing.expect; test "anyopaque extern symbol" { if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const a = @extern(*anyopaque, .{ .name = "a_mystery_symbol" }); const b: *i32 = @alignCast(@ptrCast(a)); @@ -17,6 +18,7 @@ test "function extern symbol" { if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const a = @extern(*const fn () callconv(.C) i32, .{ .name = "a_mystery_function" }); try expect(a() == 4567); diff --git a/test/behavior/for.zig b/test/behavior/for.zig index 53e21f5ce518004ece277a1e8f6a93d7c819eb00..4fd0b577503e8788803ebfaf0b414d4296d502ce 100644 --- a/test/behavior/for.zig +++ b/test/behavior/for.zig @@ -456,6 +456,7 @@ test "inline for on tuple pointer" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const S = struct { u32, u32, u32 }; var s: S = .{ 100, 200, 300 }; diff --git a/test/behavior/globals.zig b/test/behavior/globals.zig index 25968ba4242d7a695528208e16b940eebd4d740c..bf4651218340783ccfec58aa30d8deaa430d6863 100644 --- a/test/behavior/globals.zig +++ b/test/behavior/globals.zig @@ -8,7 +8,6 @@ test "store to global array" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; try expect(pos[1] == 0.0); pos = [2]f32{ 0.0, 1.0 }; @@ -21,7 +20,6 @@ test "store to global vector" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; try expect(vpos[1] == 0.0); vpos = @Vector(2, f32){ 0.0, 1.0 }; diff --git a/test/behavior/hasdecl.zig b/test/behavior/hasdecl.zig index e297979ba2a0d19ca8e0cb18588541df8174a76b..4ab521ceef5583efd367e6c23d6442a00fce95d4 100644 --- a/test/behavior/hasdecl.zig +++ b/test/behavior/hasdecl.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const builtin = @import("builtin"); const expect = std.testing.expect; const Foo = @import("hasdecl/foo.zig"); @@ -11,6 +12,8 @@ const Bar = struct { }; test "@hasDecl" { + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + try expect(@hasDecl(Foo, "public_thing")); try expect(!@hasDecl(Foo, "private_thing")); try expect(!@hasDecl(Foo, "no_thing")); @@ -21,6 +24,8 @@ test "@hasDecl" { } test "@hasDecl using a sliced string literal" { + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + try expect(@hasDecl(@This(), "std") == true); try expect(@hasDecl(@This(), "std"[0..0]) == false); try expect(@hasDecl(@This(), "std"[0..1]) == false); diff --git a/test/behavior/import.zig b/test/behavior/import.zig index c36aea7d2a9526c4070bd8b7e58a4c1ba4841d97..c2bb39983dfd52389870ab8938d563608eb8b4aa 100644 --- a/test/behavior/import.zig +++ b/test/behavior/import.zig @@ -1,17 +1,24 @@ const std = @import("std"); +const builtin = @import("builtin"); const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; const a_namespace = @import("import/a_namespace.zig"); test "call fn via namespace lookup" { + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + try expect(@as(i32, 1234) == a_namespace.foo()); } test "importing the same thing gives the same import" { + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + try expect(@import("std") == @import("std")); } test "import in non-toplevel scope" { + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + const S = struct { usingnamespace @import("import/a_namespace.zig"); }; @@ -19,5 +26,7 @@ test "import in non-toplevel scope" { } test "import empty file" { + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + _ = @import("import/empty.zig"); } diff --git a/test/behavior/int_div.zig b/test/behavior/int_div.zig index bc570434cec90a9488359aa8231cb4472d3dc505..19329f1176c840ea1caca7140f0b014c632c02d4 100644 --- a/test/behavior/int_div.zig +++ b/test/behavior/int_div.zig @@ -6,6 +6,7 @@ test "integer division" { 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_spirv64) return error.SkipZigTest; try testDivision(); try comptime testDivision(); @@ -96,6 +97,7 @@ test "large integer division" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; { var numerator: u256 = 99999999999999999997315645440; diff --git a/test/behavior/math.zig b/test/behavior/math.zig index dc4d5f894a764485275a2c82459b4e13b67361f5..e637bc8ac1cac2148d45e32fd4d24cc6cabc2e39 100644 --- a/test/behavior/math.zig +++ b/test/behavior/math.zig @@ -602,7 +602,6 @@ fn testUnsignedNegationWrappingEval(x: u16) !void { test "negation wrapping" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; try expectEqual(@as(u1, 1), negateWrap(u1, 1)); } @@ -770,7 +769,6 @@ test "@addWithOverflow" { test "small int addition" { 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_spirv64) return error.SkipZigTest; var x: u2 = 0; try expect(x == 0); @@ -1564,7 +1562,6 @@ test "vector integer addition" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const S = struct { fn doTheTest() !void { diff --git a/test/behavior/namespace_depends_on_compile_var.zig b/test/behavior/namespace_depends_on_compile_var.zig index 84b7e21d384c6728a1b0318b11eab91fad58f460..a115f557ab46b5fa8b34b20e2e79337356da998a 100644 --- a/test/behavior/namespace_depends_on_compile_var.zig +++ b/test/behavior/namespace_depends_on_compile_var.zig @@ -3,6 +3,8 @@ const builtin = @import("builtin"); const expect = std.testing.expect; test "namespace depends on compile var" { + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + if (some_namespace.a_bool) { try expect(some_namespace.a_bool); } else { diff --git a/test/behavior/optional.zig b/test/behavior/optional.zig index 3da78aea0a744e7882e5a83fb278190a0bd22cb6..32ba8ff8d3e199fee3abe00e816f7ce9f40922c9 100644 --- a/test/behavior/optional.zig +++ b/test/behavior/optional.zig @@ -451,6 +451,7 @@ test "Optional slice passed to function" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO 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_spirv64) return error.SkipZigTest; const S = struct { fn foo(a: ?[]const u8) !void { diff --git a/test/behavior/pub_enum.zig b/test/behavior/pub_enum.zig index e17546157db49e1eea6e586f0e098b4f5fda1c88..c0935b78be2d65afb20e8626019e7e9a1a9b3d0a 100644 --- a/test/behavior/pub_enum.zig +++ b/test/behavior/pub_enum.zig @@ -3,6 +3,8 @@ const other = @import("pub_enum/other.zig"); const expect = @import("std").testing.expect; test "pub enum" { + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + try pubEnumTest(other.APubEnum.Two); } fn pubEnumTest(foo: other.APubEnum) !void { @@ -10,5 +12,7 @@ fn pubEnumTest(foo: other.APubEnum) !void { } test "cast with imported symbol" { + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + try expect(@as(other.size_t, 42) == 42); } diff --git a/test/behavior/slice_sentinel_comptime.zig b/test/behavior/slice_sentinel_comptime.zig index 31b7e2349eeb45f1098833f3c58f44d7b8cac8ca..51cf7d428bee8e0ebd51e8e2efa202ebaeff64fb 100644 --- a/test/behavior/slice_sentinel_comptime.zig +++ b/test/behavior/slice_sentinel_comptime.zig @@ -1,3 +1,5 @@ +const builtin = @import("builtin"); + test "comptime slice-sentinel in bounds (unterminated)" { // array comptime { diff --git a/test/behavior/struct.zig b/test/behavior/struct.zig index c74d6344ddb2bb349639826b3952c052ad3b24e5..37d201288ae2e160a6d47f1f2acbf3a1905d2dd8 100644 --- a/test/behavior/struct.zig +++ b/test/behavior/struct.zig @@ -1744,8 +1744,6 @@ test "struct init with no result pointer sets field result types" { } test "runtime side-effects in comptime-known struct init" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - var side_effects: u4 = 0; const S = struct { a: u4, b: u4, c: u4, d: u4 }; const init = S{ @@ -2056,6 +2054,8 @@ test "struct field default value is a call" { } test "aggregate initializers should allow initializing comptime fields, verifying equality" { + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + var x: u32 = 15; _ = &x; const T = @TypeOf(.{ @as(i32, -1234), @as(u32, 5678), x }); diff --git a/test/behavior/switch_on_captured_error.zig b/test/behavior/switch_on_captured_error.zig index b6b422ba93cd12c996aa2c5297138e9850014605..f5ba762559fade8e2d445814eb218a6e66e0dfe0 100644 --- a/test/behavior/switch_on_captured_error.zig +++ b/test/behavior/switch_on_captured_error.zig @@ -5,6 +5,8 @@ const expectError = std.testing.expectError; const expectEqual = std.testing.expectEqual; test "switch on error union catch capture" { + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + const S = struct { const Error = error{ A, B, C }; fn doTheTest() !void { @@ -257,6 +259,8 @@ test "switch on error union catch capture" { } test "switch on error union if else capture" { + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + const S = struct { const Error = error{ A, B, C }; fn doTheTest() !void { diff --git a/test/behavior/tuple.zig b/test/behavior/tuple.zig index dd0f7f1007ea3c6e28cc19bf9cb5b7e01150ae6a..0e382b3a9d82326d474a135df748e95cdedf38ec 100644 --- a/test/behavior/tuple.zig +++ b/test/behavior/tuple.zig @@ -483,7 +483,6 @@ test "empty tuple type" { test "tuple with comptime fields with non empty initializer" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const a: struct { comptime comptime_int = 0 } = .{0}; _ = a; diff --git a/test/behavior/union.zig b/test/behavior/union.zig index bff21c1b6f2156222cdb1a8b2be4768c6e2d2fd0..fa050d36be24f2fe638c62e3f7ba4aae70075209 100644 --- a/test/behavior/union.zig +++ b/test/behavior/union.zig @@ -1119,6 +1119,7 @@ test "@unionInit on union with tag but no fields" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const S = struct { const Type = enum(u8) { no_op = 105 }; @@ -2059,7 +2060,6 @@ test "store of comptime reinterpreted memory to packed union" { test "union field is a pointer to an aligned version of itself" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const E = union { next: *align(1) @This(), @@ -2181,6 +2181,7 @@ test "create union(enum) from other union(enum)" { if (builtin.zig_backend == .stage2_x86) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const string = "hello world"; const TempRef = struct { diff --git a/test/behavior/vector.zig b/test/behavior/vector.zig index 9aedac66e5c5fa92db1a8e1f10a4e802256ae5ce..07a695d61d87a48e1ec051a720a16757f491f155 100644 --- a/test/behavior/vector.zig +++ b/test/behavior/vector.zig @@ -1060,7 +1060,6 @@ test "@addWithOverflow" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO 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_spirv64) return error.SkipZigTest; const S = struct { fn doTheTest() !void { diff --git a/test/behavior/wrapping_arithmetic.zig b/test/behavior/wrapping_arithmetic.zig index 23ebcfaf61ca47aeedf9494b4f142fe5435fb110..2733edad7178c4b54e4cd4f06e87c22cd5145d20 100644 --- a/test/behavior/wrapping_arithmetic.zig +++ b/test/behavior/wrapping_arithmetic.zig @@ -5,6 +5,8 @@ const maxInt = std.math.maxInt; const expect = std.testing.expect; test "wrapping add" { + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + const S = struct { fn doTheTest() !void { try testWrapAdd(i8, -3, 10, 7); @@ -40,6 +42,8 @@ test "wrapping add" { } test "wrapping subtraction" { + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + const S = struct { fn doTheTest() !void { try testWrapSub(i8, -3, 10, -13); @@ -73,6 +77,8 @@ test "wrapping subtraction" { } test "wrapping multiplication" { + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + // TODO: once #9660 has been solved, remove this line if (builtin.cpu.arch == .wasm32) return error.SkipZigTest; -- 2.54.0 From 25111061504a652bfed45b26252349f363b109af Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Tue, 23 Jan 2024 22:46:06 +0100 Subject: [PATCH 20/20] spirv: air vector_store_element --- src/codegen/spirv.zig | 24 ++++++++++++++++++++++++ test/behavior/vector.zig | 1 - 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 80a3e7b07fae432925b21c76dfdd776b77bfede9..a499f3d8ed2872d2b225ede84bba2ac4dbc7bde4 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -2236,6 +2236,8 @@ const DeclGen = struct { .ptr_elem_val => try self.airPtrElemVal(inst), .array_elem_val => try self.airArrayElemVal(inst), + .vector_store_elem => return self.airVectorStoreElem(inst), + .set_union_tag => return self.airSetUnionTag(inst), .get_union_tag => try self.airGetUnionTag(inst), .union_init => try self.airUnionInit(inst), @@ -3824,6 +3826,28 @@ const DeclGen = struct { return try self.load(elem_ty, elem_ptr_id, .{ .is_volatile = ptr_ty.isVolatilePtr(mod) }); } + fn airVectorStoreElem(self: *DeclGen, inst: Air.Inst.Index) !void { + const mod = self.module; + const data = self.air.instructions.items(.data)[@intFromEnum(inst)].vector_store_elem; + const extra = self.air.extraData(Air.Bin, data.payload).data; + + const vector_ptr_ty = self.typeOf(data.vector_ptr); + const vector_ty = vector_ptr_ty.childType(mod); + const scalar_ty = vector_ty.scalarType(mod); + + const storage_class = spvStorageClass(vector_ptr_ty.ptrAddressSpace(mod)); + const scalar_ptr_ty_ref = try self.ptrType(scalar_ty, storage_class); + + const vector_ptr = try self.resolve(data.vector_ptr); + const index = try self.resolve(extra.lhs); + const operand = try self.resolve(extra.rhs); + + const elem_ptr_id = try self.accessChainId(scalar_ptr_ty_ref, vector_ptr, &.{index}); + try self.store(scalar_ty, elem_ptr_id, operand, .{ + .is_volatile = vector_ptr_ty.isVolatilePtr(mod), + }); + } + fn airSetUnionTag(self: *DeclGen, inst: Air.Inst.Index) !void { const mod = self.module; const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; diff --git a/test/behavior/vector.zig b/test/behavior/vector.zig index 07a695d61d87a48e1ec051a720a16757f491f155..99cfe90317fb432f068b9bfa833d08306c0987a3 100644 --- a/test/behavior/vector.zig +++ b/test/behavior/vector.zig @@ -1344,7 +1344,6 @@ test "store packed vector element" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO 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_spirv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO -- 2.54.0