[mlir][StandardToSPIRV] Fix conversion for signed remainder

Per the Vulkan's SPIR-V environment spec, "for the OpSRem and OpSMod
instructions, if either operand is negative the result is undefined."
So we cannot directly use spv.SRem/spv.SMod if either operand can be
negative. Emulate it via spv.UMod.

Because the emulation uses spv.SNegate, this commit also defines
spv.SNegate.

Differential Revision: https://reviews.llvm.org/D83679
This commit is contained in:
Lei Zhang 2020-07-12 13:03:23 -04:00
parent 9908a3b9f5
commit 4ba45a778a
8 changed files with 204 additions and 54 deletions

View file

@ -452,6 +452,31 @@ def SPV_SModOp : SPV_ArithmeticBinaryOp<"SMod", SPV_Integer, []> {
// -----
def SPV_SNegateOp : SPV_ArithmeticUnaryOp<"SNegate", SPV_Integer, []> {
let summary = "Signed-integer subtract of Operand from zero.";
let description = [{
Result Type must be a scalar or vector of integer type.
Operands type must be a scalar or vector of integer type. It must
have the same number of components as Result Type. The component width
must equal the component width in Result Type.
Results are computed per component.
<!-- End of AutoGen section -->
#### Example:
```mlir
%1 = spv.SNegate %0 : i32
%3 = spv.SNegate %2 : vector<4xi32>
```
}];
}
// -----
def SPV_SRemOp : SPV_ArithmeticBinaryOp<"SRem", SPV_Integer, []> {
let summary = [{
Signed remainder operation for the remainder whose sign matches the sign

View file

@ -3150,6 +3150,7 @@ def SPV_OC_OpUConvert : I32EnumAttrCase<"OpUConvert", 113>;
def SPV_OC_OpSConvert : I32EnumAttrCase<"OpSConvert", 114>;
def SPV_OC_OpFConvert : I32EnumAttrCase<"OpFConvert", 115>;
def SPV_OC_OpBitcast : I32EnumAttrCase<"OpBitcast", 124>;
def SPV_OC_OpSNegate : I32EnumAttrCase<"OpSNegate", 126>;
def SPV_OC_OpFNegate : I32EnumAttrCase<"OpFNegate", 127>;
def SPV_OC_OpIAdd : I32EnumAttrCase<"OpIAdd", 128>;
def SPV_OC_OpFAdd : I32EnumAttrCase<"OpFAdd", 129>;
@ -3271,41 +3272,42 @@ def SPV_OpcodeAttr :
SPV_OC_OpCompositeInsert, SPV_OC_OpTranspose, SPV_OC_OpConvertFToU,
SPV_OC_OpConvertFToS, SPV_OC_OpConvertSToF, SPV_OC_OpConvertUToF,
SPV_OC_OpUConvert, SPV_OC_OpSConvert, SPV_OC_OpFConvert, SPV_OC_OpBitcast,
SPV_OC_OpFNegate, SPV_OC_OpIAdd, SPV_OC_OpFAdd, SPV_OC_OpISub, SPV_OC_OpFSub,
SPV_OC_OpIMul, SPV_OC_OpFMul, SPV_OC_OpUDiv, SPV_OC_OpSDiv, SPV_OC_OpFDiv,
SPV_OC_OpUMod, SPV_OC_OpSRem, SPV_OC_OpSMod, SPV_OC_OpFRem, SPV_OC_OpFMod,
SPV_OC_OpMatrixTimesScalar, SPV_OC_OpMatrixTimesMatrix, SPV_OC_OpLogicalEqual,
SPV_OC_OpLogicalNotEqual, SPV_OC_OpLogicalOr, SPV_OC_OpLogicalAnd,
SPV_OC_OpLogicalNot, SPV_OC_OpSelect, SPV_OC_OpIEqual, SPV_OC_OpINotEqual,
SPV_OC_OpUGreaterThan, SPV_OC_OpSGreaterThan, SPV_OC_OpUGreaterThanEqual,
SPV_OC_OpSGreaterThanEqual, SPV_OC_OpULessThan, SPV_OC_OpSLessThan,
SPV_OC_OpULessThanEqual, SPV_OC_OpSLessThanEqual, SPV_OC_OpFOrdEqual,
SPV_OC_OpFUnordEqual, SPV_OC_OpFOrdNotEqual, SPV_OC_OpFUnordNotEqual,
SPV_OC_OpFOrdLessThan, SPV_OC_OpFUnordLessThan, SPV_OC_OpFOrdGreaterThan,
SPV_OC_OpFUnordGreaterThan, SPV_OC_OpFOrdLessThanEqual,
SPV_OC_OpFUnordLessThanEqual, SPV_OC_OpFOrdGreaterThanEqual,
SPV_OC_OpFUnordGreaterThanEqual, SPV_OC_OpShiftRightLogical,
SPV_OC_OpShiftRightArithmetic, SPV_OC_OpShiftLeftLogical, SPV_OC_OpBitwiseOr,
SPV_OC_OpBitwiseXor, SPV_OC_OpBitwiseAnd, SPV_OC_OpNot,
SPV_OC_OpBitFieldInsert, SPV_OC_OpBitFieldSExtract, SPV_OC_OpBitFieldUExtract,
SPV_OC_OpBitReverse, SPV_OC_OpBitCount, SPV_OC_OpControlBarrier,
SPV_OC_OpMemoryBarrier, SPV_OC_OpAtomicCompareExchangeWeak,
SPV_OC_OpAtomicIIncrement, SPV_OC_OpAtomicIDecrement, SPV_OC_OpAtomicIAdd,
SPV_OC_OpAtomicISub, SPV_OC_OpAtomicSMin, SPV_OC_OpAtomicUMin,
SPV_OC_OpAtomicSMax, SPV_OC_OpAtomicUMax, SPV_OC_OpAtomicAnd,
SPV_OC_OpAtomicOr, SPV_OC_OpAtomicXor, SPV_OC_OpPhi, SPV_OC_OpLoopMerge,
SPV_OC_OpSelectionMerge, SPV_OC_OpLabel, SPV_OC_OpBranch,
SPV_OC_OpBranchConditional, SPV_OC_OpReturn, SPV_OC_OpReturnValue,
SPV_OC_OpUnreachable, SPV_OC_OpNoLine, SPV_OC_OpModuleProcessed,
SPV_OC_OpGroupNonUniformElect, SPV_OC_OpGroupNonUniformBallot,
SPV_OC_OpGroupNonUniformIAdd, SPV_OC_OpGroupNonUniformFAdd,
SPV_OC_OpGroupNonUniformIMul, SPV_OC_OpGroupNonUniformFMul,
SPV_OC_OpGroupNonUniformSMin, SPV_OC_OpGroupNonUniformUMin,
SPV_OC_OpGroupNonUniformFMin, SPV_OC_OpGroupNonUniformSMax,
SPV_OC_OpGroupNonUniformUMax, SPV_OC_OpGroupNonUniformFMax,
SPV_OC_OpSubgroupBallotKHR, SPV_OC_OpTypeCooperativeMatrixNV,
SPV_OC_OpCooperativeMatrixLoadNV, SPV_OC_OpCooperativeMatrixStoreNV,
SPV_OC_OpCooperativeMatrixMulAddNV, SPV_OC_OpCooperativeMatrixLengthNV
SPV_OC_OpSNegate, SPV_OC_OpFNegate, SPV_OC_OpIAdd, SPV_OC_OpFAdd,
SPV_OC_OpISub, SPV_OC_OpFSub, SPV_OC_OpIMul, SPV_OC_OpFMul, SPV_OC_OpUDiv,
SPV_OC_OpSDiv, SPV_OC_OpFDiv, SPV_OC_OpUMod, SPV_OC_OpSRem, SPV_OC_OpSMod,
SPV_OC_OpFRem, SPV_OC_OpFMod, SPV_OC_OpMatrixTimesScalar,
SPV_OC_OpMatrixTimesMatrix, SPV_OC_OpLogicalEqual, SPV_OC_OpLogicalNotEqual,
SPV_OC_OpLogicalOr, SPV_OC_OpLogicalAnd, SPV_OC_OpLogicalNot, SPV_OC_OpSelect,
SPV_OC_OpIEqual, SPV_OC_OpINotEqual, SPV_OC_OpUGreaterThan,
SPV_OC_OpSGreaterThan, SPV_OC_OpUGreaterThanEqual, SPV_OC_OpSGreaterThanEqual,
SPV_OC_OpULessThan, SPV_OC_OpSLessThan, SPV_OC_OpULessThanEqual,
SPV_OC_OpSLessThanEqual, SPV_OC_OpFOrdEqual, SPV_OC_OpFUnordEqual,
SPV_OC_OpFOrdNotEqual, SPV_OC_OpFUnordNotEqual, SPV_OC_OpFOrdLessThan,
SPV_OC_OpFUnordLessThan, SPV_OC_OpFOrdGreaterThan, SPV_OC_OpFUnordGreaterThan,
SPV_OC_OpFOrdLessThanEqual, SPV_OC_OpFUnordLessThanEqual,
SPV_OC_OpFOrdGreaterThanEqual, SPV_OC_OpFUnordGreaterThanEqual,
SPV_OC_OpShiftRightLogical, SPV_OC_OpShiftRightArithmetic,
SPV_OC_OpShiftLeftLogical, SPV_OC_OpBitwiseOr, SPV_OC_OpBitwiseXor,
SPV_OC_OpBitwiseAnd, SPV_OC_OpNot, SPV_OC_OpBitFieldInsert,
SPV_OC_OpBitFieldSExtract, SPV_OC_OpBitFieldUExtract, SPV_OC_OpBitReverse,
SPV_OC_OpBitCount, SPV_OC_OpControlBarrier, SPV_OC_OpMemoryBarrier,
SPV_OC_OpAtomicCompareExchangeWeak, SPV_OC_OpAtomicIIncrement,
SPV_OC_OpAtomicIDecrement, SPV_OC_OpAtomicIAdd, SPV_OC_OpAtomicISub,
SPV_OC_OpAtomicSMin, SPV_OC_OpAtomicUMin, SPV_OC_OpAtomicSMax,
SPV_OC_OpAtomicUMax, SPV_OC_OpAtomicAnd, SPV_OC_OpAtomicOr, SPV_OC_OpAtomicXor,
SPV_OC_OpPhi, SPV_OC_OpLoopMerge, SPV_OC_OpSelectionMerge, SPV_OC_OpLabel,
SPV_OC_OpBranch, SPV_OC_OpBranchConditional, SPV_OC_OpReturn,
SPV_OC_OpReturnValue, SPV_OC_OpUnreachable, SPV_OC_OpNoLine,
SPV_OC_OpModuleProcessed, SPV_OC_OpGroupNonUniformElect,
SPV_OC_OpGroupNonUniformBallot, SPV_OC_OpGroupNonUniformIAdd,
SPV_OC_OpGroupNonUniformFAdd, SPV_OC_OpGroupNonUniformIMul,
SPV_OC_OpGroupNonUniformFMul, SPV_OC_OpGroupNonUniformSMin,
SPV_OC_OpGroupNonUniformUMin, SPV_OC_OpGroupNonUniformFMin,
SPV_OC_OpGroupNonUniformSMax, SPV_OC_OpGroupNonUniformUMax,
SPV_OC_OpGroupNonUniformFMax, SPV_OC_OpSubgroupBallotKHR,
SPV_OC_OpTypeCooperativeMatrixNV, SPV_OC_OpCooperativeMatrixLoadNV,
SPV_OC_OpCooperativeMatrixStoreNV, SPV_OC_OpCooperativeMatrixMulAddNV,
SPV_OC_OpCooperativeMatrixLengthNV
]>;
// End opcode section. Generated from SPIR-V spec; DO NOT MODIFY!

View file

@ -26,6 +26,12 @@ class SPV_LogicalBinaryOp<string mnemonic, Type operandsType,
SameOperandsAndResultShape])> {
let parser = [{ return ::parseLogicalBinaryOp(parser, result); }];
let printer = [{ return ::printLogicalOp(getOperation(), p); }];
let builders = [
OpBuilder<
"OpBuilder &builder, OperationState &state, Value lhs, Value rhs",
"::buildLogicalBinaryOp(builder, state, lhs, rhs);">
];
}
class SPV_LogicalUnaryOp<string mnemonic, Type operandType,

View file

@ -97,6 +97,35 @@ static FloatAttr convertFloatAttr(FloatAttr srcAttr, FloatType dstType,
return builder.getF32FloatAttr(dstVal.convertToFloat());
}
/// Returns signed remainder for `lhs` and `rhs` and lets the result follow
/// the sign of `signOperand`.
///
/// Note that this is needed for Vulkan. Per the Vulkan's SPIR-V environment
/// spec, "for the OpSRem and OpSMod instructions, if either operand is negative
/// the result is undefined." So we cannot directly use spv.SRem/spv.SMod
/// if either operand can be negative. Emulate it via spv.UMod.
static Value emulateSignedRemainder(Location loc, Value lhs, Value rhs,
Value signOperand, OpBuilder &builder) {
assert(lhs.getType() == rhs.getType());
assert(lhs == signOperand || rhs == signOperand);
Type type = lhs.getType();
// Calculate the remainder with spv.UMod.
Value lhsAbs = builder.create<spirv::GLSLSAbsOp>(loc, type, lhs);
Value rhsAbs = builder.create<spirv::GLSLSAbsOp>(loc, type, rhs);
Value abs = builder.create<spirv::UModOp>(loc, lhsAbs, rhsAbs);
// Fix the sign.
Value isPositive;
if (lhs == signOperand)
isPositive = builder.create<spirv::IEqualOp>(loc, lhs, lhsAbs);
else
isPositive = builder.create<spirv::IEqualOp>(loc, rhs, rhsAbs);
Value absNegate = builder.create<spirv::SNegateOp>(loc, type, abs);
return builder.create<spirv::SelectOp>(loc, type, isPositive, abs, absNegate);
}
/// Returns the offset of the value in `targetBits` representation. `srcIdx` is
/// an index into a 1-D array with each element having `sourceBits`. When
/// accessing an element in the array treating as having elements of
@ -308,6 +337,19 @@ public:
}
};
/// Converts std.remi_signed to SPIR-V ops.
///
/// This cannot be merged into the template unary/binary pattern due to
/// Vulkan restrictions over spv.SRem and spv.SMod.
class SignedRemIOpPattern final : public SPIRVOpLowering<SignedRemIOp> {
public:
using SPIRVOpLowering<SignedRemIOp>::SPIRVOpLowering;
LogicalResult
matchAndRewrite(SignedRemIOp remOp, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const override;
};
/// Converts bitwise standard operations to SPIR-V operations. This is a special
/// pattern other than the BinaryOpPatternPattern because if the operands are
/// boolean values, SPIR-V uses different operations (`SPIRVLogicalOp`). For
@ -506,6 +548,20 @@ public:
} // namespace
//===----------------------------------------------------------------------===//
// SignedRemIOpPattern
//===----------------------------------------------------------------------===//
LogicalResult SignedRemIOpPattern::matchAndRewrite(
SignedRemIOp remOp, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const {
Value result = emulateSignedRemainder(remOp.getLoc(), operands[0],
operands[1], operands[0], rewriter);
rewriter.replaceOp(remOp, result);
return success();
}
//===----------------------------------------------------------------------===//
// ConstantOp with composite type.
//===----------------------------------------------------------------------===//
@ -1005,6 +1061,9 @@ void populateStandardToSPIRVPatterns(MLIRContext *context,
SPIRVTypeConverter &typeConverter,
OwningRewritePatternList &patterns) {
patterns.insert<
// Unary and binary patterns
BitwiseOpPattern<AndOp, spirv::LogicalAndOp, spirv::BitwiseAndOp>,
BitwiseOpPattern<OrOp, spirv::LogicalOrOp, spirv::BitwiseOrOp>,
UnaryAndBinaryOpPattern<AbsFOp, spirv::GLSLFAbsOp>,
UnaryAndBinaryOpPattern<AddFOp, spirv::FAddOp>,
UnaryAndBinaryOpPattern<AddIOp, spirv::IAddOp>,
@ -1020,7 +1079,6 @@ void populateStandardToSPIRVPatterns(MLIRContext *context,
UnaryAndBinaryOpPattern<RsqrtOp, spirv::GLSLInverseSqrtOp>,
UnaryAndBinaryOpPattern<ShiftLeftOp, spirv::ShiftLeftLogicalOp>,
UnaryAndBinaryOpPattern<SignedDivIOp, spirv::SDivOp>,
UnaryAndBinaryOpPattern<SignedRemIOp, spirv::SRemOp>,
UnaryAndBinaryOpPattern<SignedShiftRightOp,
spirv::ShiftRightArithmeticOp>,
UnaryAndBinaryOpPattern<SinOp, spirv::GLSLSinOp>,
@ -1031,19 +1089,28 @@ void populateStandardToSPIRVPatterns(MLIRContext *context,
UnaryAndBinaryOpPattern<UnsignedDivIOp, spirv::UDivOp>,
UnaryAndBinaryOpPattern<UnsignedRemIOp, spirv::UModOp>,
UnaryAndBinaryOpPattern<UnsignedShiftRightOp, spirv::ShiftRightLogicalOp>,
AllocOpPattern, DeallocOpPattern,
BitwiseOpPattern<AndOp, spirv::LogicalAndOp, spirv::BitwiseAndOp>,
BitwiseOpPattern<OrOp, spirv::LogicalOrOp, spirv::BitwiseOrOp>,
BoolCmpIOpPattern, ConstantCompositeOpPattern, ConstantScalarOpPattern,
CmpFOpPattern, CmpIOpPattern, IntLoadOpPattern, LoadOpPattern,
ReturnOpPattern, SelectOpPattern, IntStoreOpPattern, StoreOpPattern,
SignedRemIOpPattern, XOrOpPattern,
// Comparison patterns
BoolCmpIOpPattern, CmpFOpPattern, CmpIOpPattern,
// Constant patterns
ConstantCompositeOpPattern, ConstantScalarOpPattern,
// Memory patterns
AllocOpPattern, DeallocOpPattern, IntLoadOpPattern, IntStoreOpPattern,
LoadOpPattern, StoreOpPattern,
ReturnOpPattern, SelectOpPattern,
// Type cast patterns
ZeroExtendI1Pattern, TypeCastingOpPattern<IndexCastOp, spirv::SConvertOp>,
TypeCastingOpPattern<SIToFPOp, spirv::ConvertSToFOp>,
TypeCastingOpPattern<ZeroExtendIOp, spirv::UConvertOp>,
TypeCastingOpPattern<TruncateIOp, spirv::SConvertOp>,
TypeCastingOpPattern<FPToSIOp, spirv::ConvertFToSOp>,
TypeCastingOpPattern<FPExtOp, spirv::FConvertOp>,
TypeCastingOpPattern<FPTruncOp, spirv::FConvertOp>, XOrOpPattern>(
context, typeConverter);
TypeCastingOpPattern<FPTruncOp, spirv::FConvertOp>>(context,
typeConverter);
}
} // namespace mlir

View file

@ -844,6 +844,18 @@ static LogicalResult verifyShiftOp(Operation *op) {
return success();
}
static void buildLogicalBinaryOp(OpBuilder &builder, OperationState &state,
Value lhs, Value rhs) {
assert(lhs.getType() == rhs.getType());
Type boolType = builder.getI1Type();
if (auto vecType = lhs.getType().dyn_cast<VectorType>())
boolType = VectorType::get(vecType.getShape(), boolType);
state.addTypes(boolType);
state.addOperands({lhs, rhs});
}
//===----------------------------------------------------------------------===//
// spv.AccessChainOp
//===----------------------------------------------------------------------===//

View file

@ -22,12 +22,23 @@ func @int32_scalar(%lhs: i32, %rhs: i32) {
%2 = muli %lhs, %rhs: i32
// CHECK: spv.SDiv %{{.*}}, %{{.*}}: i32
%3 = divi_signed %lhs, %rhs: i32
// CHECK: spv.SRem %{{.*}}, %{{.*}}: i32
%4 = remi_signed %lhs, %rhs: i32
// CHECK: spv.UDiv %{{.*}}, %{{.*}}: i32
%5 = divi_unsigned %lhs, %rhs: i32
%4 = divi_unsigned %lhs, %rhs: i32
// CHECK: spv.UMod %{{.*}}, %{{.*}}: i32
%6 = remi_unsigned %lhs, %rhs: i32
%5 = remi_unsigned %lhs, %rhs: i32
return
}
// CHECK-LABEL: @scalar_srem
// CHECK-SAME: (%[[LHS:.+]]: i32, %[[RHS:.+]]: i32)
func @scalar_srem(%lhs: i32, %rhs: i32) {
// CHECK: %[[LABS:.+]] = spv.GLSL.SAbs %[[LHS]] : i32
// CHECK: %[[RABS:.+]] = spv.GLSL.SAbs %[[RHS]] : i32
// CHECK: %[[ABS:.+]] = spv.UMod %[[LABS]], %[[RABS]] : i32
// CHECK: %[[POS:.+]] = spv.IEqual %[[LHS]], %[[LABS]] : i32
// CHECK: %[[NEG:.+]] = spv.SNegate %[[ABS]] : i32
// CHECK: %{{.+}} = spv.Select %[[POS]], %[[ABS]], %[[NEG]] : i1, i32
%0 = remi_signed %lhs, %rhs: i32
return
}
@ -75,13 +86,24 @@ func @float32_binary_scalar(%lhs: f32, %rhs: f32) {
// Check int vector types.
// CHECK-LABEL: @int_vector234
func @int_vector234(%arg0: vector<2xi8>, %arg1: vector<3xi16>, %arg2: vector<4xi64>) {
func @int_vector234(%arg0: vector<2xi8>, %arg1: vector<4xi64>) {
// CHECK: spv.SDiv %{{.*}}, %{{.*}}: vector<2xi8>
%0 = divi_signed %arg0, %arg0: vector<2xi8>
// CHECK: spv.SRem %{{.*}}, %{{.*}}: vector<3xi16>
%1 = remi_signed %arg1, %arg1: vector<3xi16>
// CHECK: spv.UDiv %{{.*}}, %{{.*}}: vector<4xi64>
%2 = divi_unsigned %arg2, %arg2: vector<4xi64>
%1 = divi_unsigned %arg1, %arg1: vector<4xi64>
return
}
// CHECK-LABEL: @vector_srem
// CHECK-SAME: (%[[LHS:.+]]: vector<3xi16>, %[[RHS:.+]]: vector<3xi16>)
func @vector_srem(%arg0: vector<3xi16>, %arg1: vector<3xi16>) {
// CHECK: %[[LABS:.+]] = spv.GLSL.SAbs %[[LHS]] : vector<3xi16>
// CHECK: %[[RABS:.+]] = spv.GLSL.SAbs %[[RHS]] : vector<3xi16>
// CHECK: %[[ABS:.+]] = spv.UMod %[[LABS]], %[[RABS]] : vector<3xi16>
// CHECK: %[[POS:.+]] = spv.IEqual %[[LHS]], %[[LABS]] : vector<3xi16>
// CHECK: %[[NEG:.+]] = spv.SNegate %[[ABS]] : vector<3xi16>
// CHECK: %{{.+}} = spv.Select %[[POS]], %[[ABS]], %[[NEG]] : vector<3xi1>, vector<3xi16>
%0 = remi_signed %arg0, %arg1: vector<3xi16>
return
}
@ -132,8 +154,8 @@ module attributes {
func @int_vector23(%arg0: vector<2xi8>, %arg1: vector<3xi16>) {
// CHECK: spv.SDiv %{{.*}}, %{{.*}}: vector<2xi32>
%0 = divi_signed %arg0, %arg0: vector<2xi8>
// CHECK: spv.SRem %{{.*}}, %{{.*}}: vector<3xi32>
%1 = remi_signed %arg1, %arg1: vector<3xi16>
// CHECK: spv.SDiv %{{.*}}, %{{.*}}: vector<3xi32>
%1 = divi_signed %arg1, %arg1: vector<3xi16>
return
}

View file

@ -71,6 +71,11 @@ spv.module Logical GLSL450 requires #spv.vce<v1.0, [Shader], []> {
%0 = spv.SMod %arg0, %arg1 : vector<4xi32>
spv.Return
}
spv.func @snegate(%arg0 : vector<4xi32>) "None" {
// CHECK: {{%.*}} = spv.SNegate {{%.*}} : vector<4xi32>
%0 = spv.SNegate %arg0 : vector<4xi32>
spv.Return
}
spv.func @srem(%arg0 : vector<4xi32>, %arg1 : vector<4xi32>) "None" {
// CHECK: {{%.*}} = spv.SRem {{%.*}}, {{%.*}} : vector<4xi32>
%0 = spv.SRem %arg0, %arg1 : vector<4xi32>

View file

@ -174,6 +174,17 @@ func @smod_scalar(%arg: i32) -> i32 {
// -----
//===----------------------------------------------------------------------===//
// spv.SNegate
//===----------------------------------------------------------------------===//
func @snegate_scalar(%arg: i32) -> i32 {
// CHECK: spv.SNegate
%0 = spv.SNegate %arg : i32
return %0 : i32
}
// -----
//===----------------------------------------------------------------------===//
// spv.SRem
//===----------------------------------------------------------------------===//