Skip to content

Add emulation of (s/u)_(add/sub)_sat intrinsics if needed #2138

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions include/LLVMSPIRVOpts.h
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,14 @@ class TranslatorOpts {
ReplaceLLVMFmulAddWithOpenCLMad = Value;
}

void setUseOpenCLExtInstructionsForLLVMMathIntrinsic(bool Value) noexcept {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit regarding noexcept:
LLVM itself prohibits C++ exceptions and they are turned off in CMakeLists by compiler flag -fno-exceptions.
If we use exceptions in translator, then it would be better to stop doing it and start using the compiler flag -fno-exceptions as well.

UseOpenCLExtInstructionsForLLVMMathIntrinsic = Value;
}

bool shouldUseOpenCLExtInstructionsForLLVMMathIntrinsic() const noexcept {
return UseOpenCLExtInstructionsForLLVMMathIntrinsic;
}

bool shouldPreserveOCLKernelArgTypeMetadataThroughString() const noexcept {
return PreserveOCLKernelArgTypeMetadataThroughString;
}
Expand Down Expand Up @@ -243,6 +251,11 @@ class TranslatorOpts {
// extended instruction set or with a simple fmul + fadd
bool ReplaceLLVMFmulAddWithOpenCLMad = true;

// Controls whether llvm math intrinsics should be replaced with instructions
// from OpenCL extended instruction set or emulated by native SPIR-V
// instructions
bool UseOpenCLExtInstructionsForLLVMMathIntrinsic = true;

// Add a workaround to preserve OpenCL kernel_arg_type and
// kernel_arg_type_qual metadata through OpString
bool PreserveOCLKernelArgTypeMetadataThroughString = false;
Expand Down
137 changes: 123 additions & 14 deletions lib/SPIRV/SPIRVWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3860,21 +3860,130 @@ SPIRVValue *LLVMToSPIRVBase::transIntrinsicInst(IntrinsicInst *II,
case Intrinsic::usub_sat:
case Intrinsic::sadd_sat:
case Intrinsic::ssub_sat: {
SPIRVWord ExtOp;
if (IID == Intrinsic::uadd_sat)
ExtOp = OpenCLLIB::UAdd_sat;
else if (IID == Intrinsic::usub_sat)
ExtOp = OpenCLLIB::USub_sat;
else if (IID == Intrinsic::sadd_sat)
ExtOp = OpenCLLIB::SAdd_sat;
else
ExtOp = OpenCLLIB::SSub_sat;
if (BM->shouldUseOpenCLExtInstructionsForLLVMMathIntrinsic()) {
SPIRVWord ExtOp;
if (IID == Intrinsic::uadd_sat)
ExtOp = OpenCLLIB::UAdd_sat;
else if (IID == Intrinsic::usub_sat)
ExtOp = OpenCLLIB::USub_sat;
else if (IID == Intrinsic::sadd_sat)
ExtOp = OpenCLLIB::SAdd_sat;
else
ExtOp = OpenCLLIB::SSub_sat;

SPIRVType *Ty = transType(II->getType());
std::vector<SPIRVValue *> Operands = {transValue(II->getArgOperand(0), BB),
transValue(II->getArgOperand(1), BB)};
return BM->addExtInst(Ty, BM->getExtInstSetId(SPIRVEIS_OpenCL), ExtOp,
std::move(Operands), BB);
SPIRVType *Ty = transType(II->getType());
std::vector<SPIRVValue *> Operands = {
transValue(II->getArgOperand(0), BB),
transValue(II->getArgOperand(1), BB)};
return BM->addExtInst(Ty, BM->getExtInstSetId(SPIRVEIS_OpenCL), ExtOp,
std::move(Operands), BB);
}

Type *LLVMTy = II->getType();
SPIRVType *Ty = transType(LLVMTy);
Type *BoolTy = IntegerType::getInt1Ty(M->getContext());
if (auto *VecTy = dyn_cast<VectorType>(LLVMTy))
BoolTy = VectorType::get(BoolTy, VecTy->getElementCount());
SPIRVType *SPVBoolTy = transType(BoolTy);
SPIRVValue *FirstArgVal = transValue(II->getArgOperand(0), BB);
SPIRVValue *SecondArgVal = transValue(II->getArgOperand(1), BB);

if (IID == Intrinsic::uadd_sat) {
// uadd.sat(a, b) -> res = a + b, (res >= a) ? res : MAX
SPIRVValue *Max =
transValue(Constant::getAllOnesValue(II->getType()), BB);
SPIRVValue *Add =
BM->addBinaryInst(OpIAdd, Ty, FirstArgVal, SecondArgVal, BB);
SPIRVValue *Cmp =
BM->addCmpInst(OpUGreaterThanEqual, SPVBoolTy, Add, FirstArgVal, BB);
return BM->addSelectInst(Cmp, Add, Max, BB);
}
if (IID == Intrinsic::usub_sat) {
// usub.sat(a, b) -> (a > b) ? a - b : 0
SPIRVValue *Sub =
BM->addBinaryInst(OpISub, Ty, FirstArgVal, SecondArgVal, BB);
SPIRVValue *Cmp = BM->addCmpInst(OpUGreaterThan, SPVBoolTy, FirstArgVal,
SecondArgVal, BB);
SPIRVValue *Zero = transValue(Constant::getNullValue(II->getType()), BB);
return BM->addSelectInst(Cmp, Sub, Zero, BB);
}

uint64_t NumBits = LLVMTy->getScalarSizeInBits();
SPIRVValue *Zero = transValue(Constant::getNullValue(II->getType()), BB);
SPIRVValue *Max = transValue(
Constant::getIntegerValue(LLVMTy, APInt::getSignedMaxValue(NumBits)),
BB);
SPIRVValue *Min = transValue(
Constant::getIntegerValue(LLVMTy, APInt::getSignedMinValue(NumBits)),
BB);
SPIRVValue *IsPositive =
BM->addCmpInst(OpSGreaterThan, SPVBoolTy, SecondArgVal, Zero, BB);
SPIRVValue *IsNegative =
BM->addCmpInst(OpSLessThan, SPVBoolTy, SecondArgVal, Zero, BB);

if (IID == Intrinsic::sadd_sat) {
// sadd.sat(a, b) -> if (b > 0) && a > MAX - b => overflow -> MAX
Copy link
Contributor

@LU-JOHN LU-JOHN Sep 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use this sequence:

res = a+b
if (b>0 && a>res) res = MAX
if (b<0 && a<res) res = MIN

This makes it unnecessary to calculate "MAX - b" and "MIN - b"

Copy link
Contributor

@LU-JOHN LU-JOHN Sep 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another implementation with a single comparison:

sum = a+b;
if ( ( ~(a^b)   &   // addends have same sign
       (sum^a)      // result has different sign )
    
     // test MSB
     < 0)
    sum = (0x7fffffff^(a>>31));  // saturated result

// -> else if (b < 0) && a < MIN - b => overflow -> MIN
// -> else a + b
// There two ways to represent such sequence in IR:
// 1. Via 2 branch instructions plus 'phi' instruction;
// 2. Via set of select instructions.
// The later was chosed because of the following consideration:
// speculative branch prediction will most likely consider 'if' statements
// here as always false falling through to 'a + b', and reversing it will
// cause performance degradation.
SPIRVValue *Add =
BM->addBinaryInst(OpIAdd, Ty, FirstArgVal, SecondArgVal, BB);

// check if (b > 0) && a > MAX - b condition
SPIRVValue *MaxSubB =
BM->addBinaryInst(OpISub, Ty, Max, SecondArgVal, BB);
SPIRVValue *CanPosOverflow =
BM->addCmpInst(OpSGreaterThan, SPVBoolTy, FirstArgVal, MaxSubB, BB);
SPIRVValue *PosOverflow = BM->addInstTemplate(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if the order of the operand could impact performance here. Again just a request for quick check. Not a PR blocker.

Thanks

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH I'm not expecting this implementation be performant at all. Ideally we should generate such code that is collapsing to sadd/sub_sat intrinsic after we run InstCombine , but it's not happening. That is because InstCombine checks overflow by extending integer to a larger one and comparing the result with MAX/MIN of the previous type. We can't do this in general case, because we can't cast integer to a wider one in SPIR-V without extensions.

OpLogicalAnd, {CanPosOverflow->getId(), IsPositive->getId()}, BB,
SPVBoolTy);

// check if (b < 0) && MIN - b > a condition
SPIRVValue *MinSubB =
BM->addBinaryInst(OpISub, Ty, Min, SecondArgVal, BB);
SPIRVValue *CanNegOverflow =
BM->addCmpInst(OpSGreaterThan, SPVBoolTy, MinSubB, FirstArgVal, BB);
SPIRVValue *NegOverflow = BM->addInstTemplate(
OpLogicalAnd, {CanNegOverflow->getId(), IsNegative->getId()}, BB,
SPVBoolTy);

// do selects
SPIRVValue *FirstSelect = BM->addSelectInst(PosOverflow, Max, Add, BB);
return BM->addSelectInst(NegOverflow, Min, FirstSelect, BB);
}
// ssub.sat(a, b) -> if (b > 0) && a < MIN + b => overflow -> MIN
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be:

res = a-b
if (b<0 && a>res) res = MAX
if (b>0 && a<res) res = MIN

This makes it unnecessary to calculate "MAX + b" and "MIN + b"

// -> else if (b < 0) && a > MAX + b => overflow -> MAX
// -> else a - b
// still represent via 2 selects
assert(IID == Intrinsic::ssub_sat && "Expected llvm.ssub_sat");
SPIRVValue *Sub =
BM->addBinaryInst(OpISub, Ty, FirstArgVal, SecondArgVal, BB);

// check if (b > 0) && MIN + b > a
SPIRVValue *MinAddB = BM->addBinaryInst(OpIAdd, Ty, Min, SecondArgVal, BB);
SPIRVValue *CanNegOverflow =
BM->addCmpInst(OpSGreaterThan, SPVBoolTy, MinAddB, FirstArgVal, BB);
SPIRVValue *NegOverflow = BM->addInstTemplate(
OpLogicalAnd, {CanNegOverflow->getId(), IsPositive->getId()}, BB,
SPVBoolTy);

// check if (b < 0) && a > MAX + b
SPIRVValue *MaxAddB = BM->addBinaryInst(OpIAdd, Ty, Max, SecondArgVal, BB);
SPIRVValue *CanPosOverflow =
BM->addCmpInst(OpSGreaterThan, SPVBoolTy, FirstArgVal, MaxAddB, BB);
SPIRVValue *PosOverflow = BM->addInstTemplate(
OpLogicalAnd, {CanPosOverflow->getId(), IsNegative->getId()}, BB,
SPVBoolTy);

// do selects
SPIRVValue *FirstSelect = BM->addSelectInst(PosOverflow, Max, Sub, BB);
return BM->addSelectInst(NegOverflow, Min, FirstSelect, BB);
}
case Intrinsic::memset: {
// Generally there is no direct mapping of memset to SPIR-V. But it turns
Expand Down
4 changes: 4 additions & 0 deletions lib/SPIRV/libSPIRV/SPIRVModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,10 @@ class SPIRVModule {
return TranslationOpts.shouldReplaceLLVMFmulAddWithOpenCLMad();
}

bool shouldUseOpenCLExtInstructionsForLLVMMathIntrinsic() const noexcept {
return TranslationOpts.shouldUseOpenCLExtInstructionsForLLVMMathIntrinsic();
}

bool shouldPreserveOCLKernelArgTypeMetadataThroughString() const noexcept {
return TranslationOpts
.shouldPreserveOCLKernelArgTypeMetadataThroughString();
Expand Down
139 changes: 93 additions & 46 deletions test/llvm-intrinsics/add_sub.sat.ll
Original file line number Diff line number Diff line change
@@ -1,95 +1,142 @@
; RUN: llvm-as < %s -o %t.bc
; RUN: llvm-spirv %t.bc -o %t.spv
; RUN: llvm-spirv %t.spv -to-text -o - | FileCheck %s
; RUN: llvm-spirv %t.spv -to-text -o - | FileCheck %s --check-prefixes=COMMON,OPENCL
; RUN: spirv-val %t.spv

; Test checks that saturation addition and substraction llvm intrinsics
; RUN: llvm-spirv %t.bc --spirv-use-ocl-for-llvm-math-intrinsic=true -o %t.spv
; RUN: llvm-spirv %t.spv -to-text -o - | FileCheck %s --check-prefixes=COMMON,OPENCL
; RUN: spirv-val %t.spv

; RUN: llvm-spirv %t.bc --spirv-use-ocl-for-llvm-math-intrinsic=false -o %t.spv
; RUN: llvm-spirv %t.spv -to-text -o - | FileCheck %s --check-prefixes=COMMON,EMULATION
; RUN: spirv-val %t.spv

; Test checks that saturation addition and subtraction llvm intrinsics
; are translated into instruction from OpenCL Extended Instruction Set.

target datalayout = "e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024"
target triple = "spir64-unknown-unknown"

; CHECK: ExtInstImport [[ext:[0-9]+]] "OpenCL.std"
; COMMON: ExtInstImport [[ext:[0-9]+]] "OpenCL.std"

; COMMON: Name [[test_uadd:[0-9]+]] "test_uadd"
; COMMON: Name [[test_usub:[0-9]+]] "test_usub"
; COMMON: Name [[test_sadd:[0-9]+]] "test_sadd"
; COMMON: Name [[test_ssub:[0-9]+]] "test_ssub"
; COMMON: Name [[test_vectors:[0-9]+]] "test_vectors"

; CHECK: Name [[test_uadd:[0-9]+]] "test_uadd"
; CHECK: Name [[test_usub:[0-9]+]] "test_usub"
; CHECK: Name [[test_sadd:[0-9]+]] "test_sadd"
; CHECK: Name [[test_ssub:[0-9]+]] "test_ssub"
; CHECK: Name [[test_vectors:[0-9]+]] "test_vectors"
; COMMON-DAG: TypeInt [[int:[0-9]+]] 32 0
; COMMON-DAG: TypeVoid [[void:[0-9]+]]
; COMMON-DAG: TypeVector [[vector:[0-9]+]] [[int]] 4

; CHECK-DAG: TypeInt [[int:[0-9]+]] 32 0
; CHECK-DAG: TypeVoid [[void:[0-9]+]]
; CHECK: TypeVector [[vector:[0-9]+]] [[int]] 4
; EMULATION-DAG: TypeBool [[bool:[0-9]+]]
; EMULATION-DAG: TypeVector [[vector_bool:[0-9]+]] [[bool]] 4
; EMULATION-DAG: Constant [[int]] [[uint_max:[0-9]+]] 4294967295
; EMULATION-DAG: Constant [[int]] [[zero:[0-9]+]] 0
; EMULATION-DAG: Constant [[int]] [[int_max:[0-9]+]] 2147483647
; EMULATION-DAG: Constant [[int]] [[int_min:[0-9]+]] 2147483648
; EMULATION-DAG: ConstantComposite [[vector]] [[vector_uint_max:[0-9]+]] [[uint_max]] [[uint_max]] [[uint_max]] [[uint_max]]

define spir_func void @test_uadd(i32 %a, i32 %b) {
entry:
%0 = call i32 @llvm.uadd.sat.i32(i32 %a, i32 %b)
ret void
}

; CHECK: Function [[void]] [[test_uadd]]
; CHECK-NEXT: FunctionParameter [[int]] [[lhs:[0-9]+]]
; CHECK-NEXT: FunctionParameter [[int]] [[rhs:[0-9]+]]
; CHECK-EMPTY:
; CHECK-NEXT: Label
; CHECK-NEXT: ExtInst [[int]] {{[0-9]+}} [[ext]] u_add_sat [[lhs]] [[rhs]]
; CHECK-NEXT: Return
; COMMON: Function [[void]] [[test_uadd]]
; COMMON-NEXT: FunctionParameter [[int]] [[lhs:[0-9]+]]
; COMMON-NEXT: FunctionParameter [[int]] [[rhs:[0-9]+]]
; COMMON-EMPTY:
; COMMON-NEXT: Label
; OPENCL-NEXT: ExtInst [[int]] {{[0-9]+}} [[ext]] u_add_sat [[lhs]] [[rhs]]
; EMULATION-NEXT: IAdd [[int]] [[add:[0-9]+]] [[lhs]] [[rhs]]
; EMULATION-NEXT: UGreaterThanEqual [[bool]] [[greater:[0-9]+]] [[add]] [[lhs]]
; EMULATION-NEXT: Select [[int]] {{[0-9]+}} [[greater]] [[add]] [[uint_max]]
; COMMON-NEXT: Return

define spir_func void @test_usub(i32 %a, i32 %b) {
entry:
%0 = call i32 @llvm.usub.sat.i32(i32 %a, i32 %b)
ret void
}

; CHECK: Function [[void]] [[test_usub]]
; CHECK-NEXT: FunctionParameter [[int]] [[lhs:[0-9]+]]
; CHECK-NEXT: FunctionParameter [[int]] [[rhs:[0-9]+]]
; CHECK-EMPTY:
; CHECK-NEXT: Label
; CHECK-NEXT: ExtInst [[int]] {{[0-9]+}} [[ext]] u_sub_sat [[lhs]] [[rhs]]
; CHECK-NEXT: Return
; COMMON: Function [[void]] [[test_usub]]
; COMMON-NEXT: FunctionParameter [[int]] [[lhs:[0-9]+]]
; COMMON-NEXT: FunctionParameter [[int]] [[rhs:[0-9]+]]
; COMMON-EMPTY:
; COMMON-NEXT: Label
; OPENCL-NEXT: ExtInst [[int]] {{[0-9]+}} [[ext]] u_sub_sat [[lhs]] [[rhs]]
; EMULATION-NEXT: ISub [[int]] [[sub:[0-9]+]] [[lhs]] [[rhs]]
; EMULATION-NEXT: UGreaterThan [[bool]] [[greater:[0-9]+]] [[lhs]] [[rhs]]
; EMULATION-NEXT: Select [[int]] {{[0-9]+}} [[greater]] [[sub]] [[zero]]
; COMMON-NEXT: Return

define spir_func void @test_sadd(i32 %a, i32 %b) {
entry:
%0 = call i32 @llvm.sadd.sat.i32(i32 %a, i32 %b)
ret void
}

; CHECK: Function [[void]] [[test_sadd]]
; CHECK-NEXT: FunctionParameter [[int]] [[lhs:[0-9]+]]
; CHECK-NEXT: FunctionParameter [[int]] [[rhs:[0-9]+]]
; CHECK-EMPTY:
; CHECK-NEXT: Label
; CHECK-NEXT: ExtInst [[int]] {{[0-9]+}} [[ext]] s_add_sat [[lhs]] [[rhs]]
; CHECK-NEXT: Return
; COMMON: Function [[void]] [[test_sadd]]
; COMMON-NEXT: FunctionParameter [[int]] [[lhs:[0-9]+]]
; COMMON-NEXT: FunctionParameter [[int]] [[rhs:[0-9]+]]
; COMMON-EMPTY:
; COMMON-NEXT: Label
; OPENCL-NEXT: ExtInst [[int]] {{[0-9]+}} [[ext]] s_add_sat [[lhs]] [[rhs]]
; EMULATION-NEXT: SGreaterThan [[bool]] [[greater1:[0-9]+]] [[rhs]] [[zero]]
; EMULATION-NEXT: SLessThan [[bool]] [[less:[0-9]+]] [[rhs]] [[zero]]
; EMULATION-NEXT: IAdd [[int]] [[add:[0-9]+]] [[lhs]] [[rhs]]
; EMULATION-NEXT: ISub [[int]] [[sub1:[0-9]+]] [[int_max]] [[rhs]]
; EMULATION-NEXT: SGreaterThan [[bool]] [[greater2:[0-9]+]] [[lhs]] [[sub1]]
; EMULATION-NEXT: LogicalAnd [[bool]] [[and1:[0-9]+]] [[greater2]] [[greater1]]
; EMULATION-NEXT: ISub [[int]] [[sub2:[0-9]+]] [[int_min]] [[rhs]]
; EMULATION-NEXT: SGreaterThan [[bool]] [[greater3:[0-9]+]] [[sub2]] [[lhs]]
; EMULATION-NEXT: LogicalAnd [[bool]] [[and2:[0-9]+]] [[greater3]] [[less]]
; EMULATION-NEXT: Select [[int]] [[select:[0-9]+]] [[and1]] [[int_max]] [[add]]
; EMULATION-NEXT: Select [[int]] {{[0-9]+}} [[and2]] [[int_min]] [[select]]
; COMMON-NEXT: Return

define spir_func void @test_ssub(i32 %a, i32 %b) {
entry:
%0 = call i32 @llvm.ssub.sat.i32(i32 %a, i32 %b)
ret void
}

; CHECK: Function [[void]] [[test_ssub]]
; CHECK-NEXT: FunctionParameter [[int]] [[lhs:[0-9]+]]
; CHECK-NEXT: FunctionParameter [[int]] [[rhs:[0-9]+]]
; CHECK-EMPTY:
; CHECK-NEXT: Label
; CHECK-NEXT: ExtInst [[int]] {{[0-9]+}} [[ext]] s_sub_sat [[lhs]] [[rhs]]
; CHECK-NEXT: Return
; COMMON: Function [[void]] [[test_ssub]]
; COMMON-NEXT: FunctionParameter [[int]] [[lhs:[0-9]+]]
; COMMON-NEXT: FunctionParameter [[int]] [[rhs:[0-9]+]]
; COMMON-EMPTY:
; COMMON-NEXT: Label
; OPENCL-NEXT: ExtInst [[int]] {{[0-9]+}} [[ext]] s_sub_sat [[lhs]] [[rhs]]
; EMULATION-NEXT: SGreaterThan [[bool]] [[greater1:[0-9]+]] [[rhs]] [[zero]]
; EMULATION-NEXT: SLessThan [[bool]] [[less:[0-9]+]] [[rhs]] [[zero]]
; EMULATION-NEXT: ISub [[int]] [[sub1:[0-9]+]] [[lhs]] [[rhs]]
; EMULATION-NEXT: IAdd [[int]] [[add1:[0-9]+]] [[int_min]] [[rhs]]
; EMULATION-NEXT: SGreaterThan [[bool]] [[greater2:[0-9]+]] [[add1]] [[lhs]]
; EMULATION-NEXT: LogicalAnd [[bool]] [[and1:[0-9]+]] [[greater2]] [[greater1]]
; EMULATION-NEXT: IAdd [[int]] [[add2:[0-9]+]] [[int_max]] [[rhs]]
; EMULATION-NEXT: SGreaterThan [[bool]] [[greater3:[0-9]+]] [[lhs]] [[add2]]
; EMULATION-NEXT: LogicalAnd [[bool]] [[and2:[0-9]+]] [[greater3]] [[less]]
; EMULATION-NEXT: Select [[int]] [[select:[0-9]+]] [[and2]] [[int_max]] [[sub1]]
; EMULATION-NEXT: Select [[int]] {{[0-9]+}} [[and1]] [[int_min]] [[select]]
; COMMON-NEXT: Return

define spir_func void @test_vectors(<4 x i32> %a, <4 x i32> %b) {
entry:
%0 = call <4 x i32> @llvm.uadd.sat.v4i32(<4 x i32> %a, <4 x i32> %b)
ret void
}

; CHECK: Function [[void]] [[test_vectors]]
; CHECK-NEXT: FunctionParameter [[vector]] [[lhs:[0-9]+]]
; CHECK-NEXT: FunctionParameter [[vector]] [[rhs:[0-9]+]]
; CHECK-EMPTY:
; CHECK-NEXT: Label
; CHECK-NEXT: ExtInst [[vector]] {{[0-9]+}} [[ext]] u_add_sat [[lhs]] [[rhs]]
; CHECK-NEXT: Return
; COMMON: Function [[void]] [[test_vectors]]
; COMMON-NEXT: FunctionParameter [[vector]] [[lhs:[0-9]+]]
; COMMON-NEXT: FunctionParameter [[vector]] [[rhs:[0-9]+]]
; COMMON-EMPTY:
; COMMON-NEXT: Label
; OPENCL-NEXT: ExtInst [[vector]] {{[0-9]+}} [[ext]] u_add_sat [[lhs]] [[rhs]]
; EMULATION-NEXT: IAdd [[vector]] [[add:[0-9]+]] [[lhs]] [[rhs]]
; EMULATION-NEXT: UGreaterThanEqual [[vector_bool]] [[greater:[0-9]+]] [[add]] [[lhs]]
; EMULATION-NEXT: Select [[vector]] {{[0-9]+}} [[greater]] [[add]] [[vector_uint_max]]
; COMMON-NEXT: Return

declare i32 @llvm.uadd.sat.i32(i32, i32);
declare i32 @llvm.usub.sat.i32(i32, i32);
Expand Down
Loading