diff options
Diffstat (limited to 'src/codegen')
| -rw-r--r-- | src/codegen/spirv.zig | 233 |
1 files changed, 114 insertions, 119 deletions
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; } |
