authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2022-11-27 14:15:08+01:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2023-04-09 01:51:50+02:00
log8608d6e23573a6d7d4d1ce92bf461b035970c592
treee0586808156fc6340a63d354c76a1b2952eddf5a
parent23e210c38f9c35e26ac43f6f585c6d8a4751e318
signaturelock-open Commit is signed but in an unrecognized format.

spirv: div, rem, intcast, some strange integer masking

Implements the div-family and intcast AIR instructions, and starts implementing a mechanism for masking the value of 'strange' integers before they are used in an operation that does not hold under modulo.

1 files changed, 102 insertions(+), 18 deletions(-)

src/codegen/spirv.zig+102-18
......@@ -824,9 +824,21 @@ pub const DeclGen = struct {
824824 const air_tags = self.air.instructions.items(.tag);
825825 const maybe_result_id: ?IdRef = switch (air_tags[inst]) {
826826 // zig fmt: off
827 .add, .addwrap => try self.airArithOp(inst, .OpFAdd, .OpIAdd, .OpIAdd),
828 .sub, .subwrap => try self.airArithOp(inst, .OpFSub, .OpISub, .OpISub),
829 .mul, .mulwrap => try self.airArithOp(inst, .OpFMul, .OpIMul, .OpIMul),
827 .add, .addwrap => try self.airArithOp(inst, .OpFAdd, .OpIAdd, .OpIAdd, true),
828 .sub, .subwrap => try self.airArithOp(inst, .OpFSub, .OpISub, .OpISub, true),
829 .mul, .mulwrap => try self.airArithOp(inst, .OpFMul, .OpIMul, .OpIMul, true),
830
831 .div_float,
832 .div_float_optimized,
833 // TODO: Check that this is the right operation.
834 .div_trunc,
835 .div_trunc_optimized,
836 => try self.airArithOp(inst, .OpFDiv, .OpSDiv, .OpUDiv, false),
837 // TODO: Check if this is the right operation
838 // TODO: Make airArithOp for rem not emit a mask for the LHS.
839 .rem,
840 .rem_optimized,
841 => try self.airArithOp(inst, .OpFRem, .OpSRem, .OpSRem, false),
830842
831843 .add_with_overflow => try self.airOverflowArithOp(inst),
832844
......@@ -838,8 +850,9 @@ pub const DeclGen = struct {
838850 .bool_and => try self.airBinOpSimple(inst, .OpLogicalAnd),
839851 .bool_or => try self.airBinOpSimple(inst, .OpLogicalOr),
840852
841 .bitcast => try self.airBitcast(inst),
842 .not => try self.airNot(inst),
853 .bitcast => try self.airBitcast(inst),
854 .intcast => try self.airIntcast(inst),
855 .not => try self.airNot(inst),
843856
844857 .slice_ptr => try self.airSliceField(inst, 0),
845858 .slice_len => try self.airSliceField(inst, 1),
......@@ -909,20 +922,47 @@ pub const DeclGen = struct {
909922 return result_id.toRef();
910923 }
911924
925 fn maskStrangeInt(self: *DeclGen, ty_id: IdResultType, int_id: IdRef, bits: u16) !IdRef {
926 const backing_bits = self.backingIntBits(bits).?;
927 const mask_value = if (bits == 64) 0xFFFF_FFFF_FFFF_FFFF else (@as(u64, 1) << @intCast(u6, bits)) - 1;
928 const mask_lit: spec.LiteralContextDependentNumber = switch (backing_bits) {
929 1...32 => .{ .uint32 = @truncate(u32, mask_value) },
930 33...64 => .{ .uint64 = mask_value },
931 else => unreachable,
932 };
933 // TODO: We should probably optimize these constants a bit.
934 const mask_id = self.spv.allocId();
935 try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpConstant, .{
936 .id_result_type = ty_id,
937 .id_result = mask_id,
938 .value = mask_lit,
939 });
940 const result_id = self.spv.allocId();
941 try self.func.body.emit(self.spv.gpa, .OpBitwiseAnd, .{
942 .id_result_type = ty_id,
943 .id_result = result_id,
944 .operand_1 = int_id,
945 .operand_2 = mask_id.toRef(),
946 });
947 return result_id.toRef();
948 }
949
912950 fn airArithOp(
913951 self: *DeclGen,
914952 inst: Air.Inst.Index,
915953 comptime fop: Opcode,
916954 comptime sop: Opcode,
917955 comptime uop: Opcode,
956 /// true if this operation holds under modular arithmetic.
957 comptime modular: bool,
918958 ) !?IdRef {
919959 if (self.liveness.isUnused(inst)) return null;
920960 // LHS and RHS are guaranteed to have the same type, and AIR guarantees
921961 // the result to be the same as the LHS and RHS, which matches SPIR-V.
922962 const ty = self.air.typeOfIndex(inst);
923963 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
924 const lhs_id = try self.resolve(bin_op.lhs);
925 const rhs_id = try self.resolve(bin_op.rhs);
964 var lhs_id = try self.resolve(bin_op.lhs);
965 var rhs_id = try self.resolve(bin_op.rhs);
926966
927967 const result_id = self.spv.allocId();
928968 const result_type_id = try self.resolveTypeId(ty);
......@@ -938,15 +978,22 @@ pub const DeclGen = struct {
938978 .composite_integer => {
939979 return self.todo("binary operations for composite integers", .{});
940980 },
941 .strange_integer => {
942 return self.todo("binary operations for strange integers", .{});
981 .strange_integer => blk: {
982 if (!modular) {
983 lhs_id = try self.maskStrangeInt(result_type_id, lhs_id, info.bits);
984 rhs_id = try self.maskStrangeInt(result_type_id, rhs_id, info.bits);
985 }
986 break :blk switch (info.signedness) {
987 .signed => @as(usize, 1),
988 .unsigned => @as(usize, 2),
989 };
943990 },
944991 .integer => switch (info.signedness) {
945992 .signed => @as(usize, 1),
946993 .unsigned => @as(usize, 2),
947994 },
948995 .float => 0,
949 else => unreachable,
996 .bool => unreachable,
950997 };
951998
952999 const operands = .{
......@@ -981,11 +1028,18 @@ pub const DeclGen = struct {
9811028 const operand_ty = self.air.typeOf(extra.lhs);
9821029 const result_ty = self.air.typeOfIndex(inst);
9831030
1031 const info = try self.arithmeticTypeInfo(operand_ty);
1032 switch (info.class) {
1033 .composite_integer => return self.todo("overflow ops for composite integers", .{}),
1034 .strange_integer => return self.todo("overflow ops for strange integers", .{}),
1035 .integer => {},
1036 .float, .bool => unreachable,
1037 }
1038
9841039 const operand_ty_id = try self.resolveTypeId(operand_ty);
9851040 const result_type_id = try self.resolveTypeId(result_ty);
9861041
987 const operand_bits = operand_ty.intInfo(target).bits;
988 const overflow_member_ty = try self.intType(.unsigned, operand_bits);
1042 const overflow_member_ty = try self.intType(.unsigned, info.bits);
9891043 const overflow_member_ty_id = self.spv.typeResultId(overflow_member_ty);
9901044
9911045 const op_result_id = blk: {
......@@ -1083,8 +1137,8 @@ pub const DeclGen = struct {
10831137 fn airCmp(self: *DeclGen, inst: Air.Inst.Index, comptime fop: Opcode, comptime sop: Opcode, comptime uop: Opcode) !?IdRef {
10841138 if (self.liveness.isUnused(inst)) return null;
10851139 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
1086 const lhs_id = try self.resolve(bin_op.lhs);
1087 const rhs_id = try self.resolve(bin_op.rhs);
1140 var lhs_id = try self.resolve(bin_op.lhs);
1141 var rhs_id = try self.resolve(bin_op.rhs);
10881142 const result_id = self.spv.allocId();
10891143 const result_type_id = try self.resolveTypeId(Type.initTag(.bool));
10901144 const op_ty = self.air.typeOf(bin_op.lhs);
......@@ -1100,10 +1154,15 @@ pub const DeclGen = struct {
11001154 },
11011155 .float => 0,
11021156 .bool => 1,
1103 // TODO: Should strange integers be masked before comparison?
1104 .strange_integer,
1105 .integer,
1106 => switch (info.signedness) {
1157 .strange_integer => blk: {
1158 lhs_id = try self.maskStrangeInt(result_type_id, lhs_id, info.bits);
1159 rhs_id = try self.maskStrangeInt(result_type_id, rhs_id, info.bits);
1160 break :blk switch (info.signedness) {
1161 .signed => @as(usize, 1),
1162 .unsigned => @as(usize, 2),
1163 };
1164 },
1165 .integer => switch (info.signedness) {
11071166 .signed => @as(usize, 1),
11081167 .unsigned => @as(usize, 2),
11091168 },
......@@ -1144,6 +1203,31 @@ pub const DeclGen = struct {
11441203 return try self.bitcast(result_type_id, operand_id);
11451204 }
11461205
1206 fn airIntcast(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
1207 if (self.liveness.isUnused(inst)) return null;
1208
1209 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1210 const operand_id = try self.resolve(ty_op.operand);
1211 const dest_ty = self.air.typeOfIndex(inst);
1212 const dest_info = try self.arithmeticTypeInfo(dest_ty);
1213 const dest_ty_id = try self.resolveTypeId(dest_ty);
1214
1215 const result_id = self.spv.allocId();
1216 switch (dest_info.signedness) {
1217 .signed => try self.func.body.emit(self.spv.gpa, .OpSConvert, .{
1218 .id_result_type = dest_ty_id,
1219 .id_result = result_id,
1220 .signed_value = operand_id,
1221 }),
1222 .unsigned => try self.func.body.emit(self.spv.gpa, .OpUConvert, .{
1223 .id_result_type = dest_ty_id,
1224 .id_result = result_id,
1225 .unsigned_value = operand_id,
1226 }),
1227 }
1228 return result_id.toRef();
1229 }
1230
11471231 fn airNot(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
11481232 if (self.liveness.isUnused(inst)) return null;
11491233 const ty_op = self.air.instructions.items(.data)[inst].ty_op;