From 747f4ae3f5efc89df0b1b76787eb90eab90fc362 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Mon, 15 Jan 2024 21:58:13 +0100 Subject: spirv: sh[rl](_exact)? --- src/codegen/spirv.zig | 43 +++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 14 deletions(-) (limited to 'src/codegen/spirv.zig') diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 6c058308df..580c3d959a 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; } -- cgit v1.2.3 From cb9e20da00a2c33706e2c7bf2008887c6c72a896 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Mon, 15 Jan 2024 23:06:54 +0100 Subject: spirv: element-wise operation helper --- src/codegen/spirv.zig | 123 ++++++++++++++++++++++++++++++++++++++----------- test/behavior/math.zig | 1 + 2 files changed, 97 insertions(+), 27 deletions(-) (limited to 'src/codegen/spirv.zig') diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 580c3d959a..a8bc385f7a 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 e96299abaa..93c467eb5d 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; -- cgit v1.2.3 From 403c6262bb4c9087f1d0138fc83fe4dd979864ad Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Mon, 15 Jan 2024 23:38:43 +0100 Subject: spirv: use new vector stuff for arithOp and shift --- src/codegen/spirv.zig | 156 ++++++++++++++++++++++++++------------------------ 1 file changed, 82 insertions(+), 74 deletions(-) (limited to 'src/codegen/spirv.zig') diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index a8bc385f7a..a3b8a6c8f6 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, - }; + 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); - 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, + 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( -- cgit v1.2.3 From 15cf5f88c1bfb4b92285c515d294e1061d02672a Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Mon, 15 Jan 2024 23:50:06 +0100 Subject: spirv: vectors for air not --- src/codegen/spirv.zig | 45 ++++++++++++++++++++++----------------------- test/behavior/vector.zig | 1 - 2 files changed, 22 insertions(+), 24 deletions(-) (limited to 'src/codegen/spirv.zig') diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index a3b8a6c8f6..7732f9eccb 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 ab4f89a052..0c28b519b3 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 { -- cgit v1.2.3 From 2f815853dcae49bbfd109675cde1f4097b75c8cc Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Tue, 16 Jan 2024 23:06:15 +0100 Subject: spirv: shlWithOverflow --- src/codegen/spirv.zig | 128 ++++++++++++++++++++++++++++++++++++++--------- test/behavior/math.zig | 2 - test/behavior/vector.zig | 5 -- 3 files changed, 103 insertions(+), 32 deletions(-) (limited to 'src/codegen/spirv.zig') diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 7732f9eccb..286b45f973 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); + + // TODO: Can we omit normalizing lhs? + const lhs_norm_id = try self.normalizeInt(wip.scalar_ty_ref, lhs_elem_id, info); - const shift_id = if (shift_ty_ref != wip.result_ty_ref) blk: { + // 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 93c467eb5d..3aa65dddbb 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 0c28b519b3..d02f0b6515 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 }; -- cgit v1.2.3 From 761594e2260eb780ab1861568e38a7066a7513df Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Fri, 19 Jan 2024 01:12:56 +0100 Subject: spirv: reduce, reduce_optimized --- src/codegen/spirv.zig | 75 +++++++++++++++++++++++++++++++++++++++++++++++- test/behavior/vector.zig | 2 -- 2 files changed, 74 insertions(+), 3 deletions(-) (limited to 'src/codegen/spirv.zig') diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 286b45f973..33e068032c 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); - const result_ty_ref = try self.resolveType(result_ty, .direct); + 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 d02f0b6515..f87f7b722d 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; -- cgit v1.2.3 From b67d983abda198c69fbcde68a961e0e8b92b7939 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Fri, 19 Jan 2024 23:56:02 +0100 Subject: spirv: vectorize add/sub overflow --- src/codegen/spirv.zig | 165 ++++++++++++++++++++++++----------------------- test/behavior/vector.zig | 4 +- 2 files changed, 86 insertions(+), 83 deletions(-) (limited to 'src/codegen/spirv.zig') diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 33e068032c..6a67bb58ac 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); - - // 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, - }); + 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, + }); - 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 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, - }); + // 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_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(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 f87f7b722d..b23eac924d 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 { -- cgit v1.2.3 From 54ec9365498635aa127ff13dfbdd3942890b53d0 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sat, 20 Jan 2024 23:57:19 +0100 Subject: 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(-) (limited to 'src/codegen/spirv.zig') diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 6a67bb58ac..56f1832b5d 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; } -- cgit v1.2.3 From 77ef78a0ef00392c4e157ebc170d6c4d98f586fb Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 21 Jan 2024 01:39:20 +0100 Subject: 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(-) (limited to 'src/codegen/spirv.zig') diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 56f1832b5d..cb3d1be8f0 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(); -- cgit v1.2.3 From 345d6e280de1566000bf58ccd6683541cf601459 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 21 Jan 2024 01:41:41 +0100 Subject: spirv: air int_from_bool --- src/codegen/spirv.zig | 103 ++++++++++++++++++++++++++++++++----------------- test/behavior/bool.zig | 2 - test/behavior/cast.zig | 1 - 3 files changed, 67 insertions(+), 39 deletions(-) (limited to 'src/codegen/spirv.zig') diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index cb3d1be8f0..d47233fc6b 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; - } + const src_key = self.spv.cache.lookup(src_ty_ref); + const dst_key = self.spv.cache.lookup(dst_ty_ref); - // TODO: Some more cases are missing here - // See fn bitCast in llvm.zig + const result_id = blk: { + if (src_ty_ref == dst_ty_ref) { + break :blk src_id; + } - 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; - } + // TODO: Some more cases are missing here + // See fn bitCast in llvm.zig - // 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_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; + } - 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 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, .{}); + }; - const dst_ptr_ty_ref = try self.ptrType(dst_ty, .Function); + // 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 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 4b72022d40..608fb20ca7 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 64f3c1d376..9a52d3218a 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 { -- cgit v1.2.3 From 7dfd403da1cd0f25e500ed67b2dfd21c669491fa Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 21 Jan 2024 12:17:19 +0100 Subject: spirv: air mul_add --- src/codegen/spirv.zig | 110 +++++++++++++++++++++++++++++++---------------- test/behavior/muladd.zig | 5 --- 2 files changed, 73 insertions(+), 42 deletions(-) (limited to 'src/codegen/spirv.zig') diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index d47233fc6b..3b231da5e0 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 3bdba835f9..0c0b961097 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(); -- cgit v1.2.3 From 408c1172463429c1dcf675c41225100ebc750a78 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 21 Jan 2024 15:54:27 +0100 Subject: 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(-) (limited to 'src/codegen/spirv.zig') diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 3b231da5e0..c250863338 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 9a52d3218a..be25bde693 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 20afa21cb8..ffebff6d83 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 5a5460bfd2..3da78aea0a 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; -- cgit v1.2.3 From 9f0227a326d84208e23e90c2a84ff95f734bd2ae Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 21 Jan 2024 16:05:39 +0100 Subject: spirv: vectorize int_cast, trunc --- src/codegen/spirv.zig | 49 +++++++++++++++++++++++++--------------------- test/behavior/truncate.zig | 1 - 2 files changed, 27 insertions(+), 23 deletions(-) (limited to 'src/codegen/spirv.zig') diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index c250863338..eda8d88cdb 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 81a916f80c..267d291d48 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 { -- cgit v1.2.3 From 9641d2ebdb74926a56ff3b916082534052dc637f Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 21 Jan 2024 20:12:25 +0100 Subject: spirv: vectorize max, min --- src/codegen/spirv.zig | 99 ++++++++++++++++++++------------------- test/behavior/maximum_minimum.zig | 5 -- 2 files changed, 50 insertions(+), 54 deletions(-) (limited to 'src/codegen/spirv.zig') diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index eda8d88cdb..be3c9957e1 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); + + // 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, + }; - 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; + 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 f7cb1ee513..a6a2e3b8e8 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; -- cgit v1.2.3 From 631d1b63a8027c49073995e28aab489534f01efa Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 21 Jan 2024 20:38:56 +0100 Subject: 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(-) (limited to 'src/codegen/spirv.zig') diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index be3c9957e1..28f2c1677c 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 fad29a1a58..d8666405a0 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 be25bde693..48feb86ef1 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 e9d7706ff4..95913be3af 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 b23eac924d..26d60c337a 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 }; -- cgit v1.2.3 From 76d5696434095e39d9aaae92c1533b2d016c1a31 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 21 Jan 2024 22:24:53 +0100 Subject: 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(-) (limited to 'src/codegen/spirv.zig') diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 28f2c1677c..9e7c49d1a5 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 d8666405a0..abea715ea0 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 48feb86ef1..c59a9803c0 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 568fe6deef..43654bc2b9 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 3aa65dddbb..dc4d5f894a 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(); -- cgit v1.2.3 From 1d548aa2aab472f14013e67e29c6d898a7b31998 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 21 Jan 2024 22:48:31 +0100 Subject: spirv: air splat --- src/codegen/spirv.zig | 15 +++++++++++++++ test/behavior/vector.zig | 1 - 2 files changed, 15 insertions(+), 1 deletion(-) (limited to 'src/codegen/spirv.zig') diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 9e7c49d1a5..80a3e7b07f 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 26d60c337a..9aedac66e5 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) -- cgit v1.2.3 From 25111061504a652bfed45b26252349f363b109af Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Tue, 23 Jan 2024 22:46:06 +0100 Subject: spirv: air vector_store_element --- src/codegen/spirv.zig | 24 ++++++++++++++++++++++++ test/behavior/vector.zig | 1 - 2 files changed, 24 insertions(+), 1 deletion(-) (limited to 'src/codegen/spirv.zig') diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 80a3e7b07f..a499f3d8ed 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 07a695d61d..99cfe90317 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 -- cgit v1.2.3