Skip to content

Commit 14301c2

Browse files
authored
Regularize LLVM code to remove use of non standard integer types in fptoui and fptosi intrinsics (#2500)
* Regularize LLVM code to remove use of non standard integer types in fptoui and fptosi intrinsics Signed-off-by: Sudarsanam, Arvind <[email protected]>
1 parent 89b19c4 commit 14301c2

File tree

3 files changed

+162
-0
lines changed

3 files changed

+162
-0
lines changed

lib/SPIRV/SPIRVRegularizeLLVM.cpp

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,104 @@ void SPIRVRegularizeLLVMBase::expandSYCLTypeUsing(Module *M) {
322322
expandVIDWithSYCLTypeByValComp(F);
323323
}
324324

325+
// In this function, we handle two conversion operations
326+
// 1. fptoui.sat.iX.fY (X is not 8,16,32,64; Y is 32 or 64)
327+
// 2. fptosi.sat.iX.fY (X is not 8,16,32,64; Y is 32 or 64)
328+
// Such non-standard integer types cannot be handled in SPIR-V. Hence, they
329+
// will be promoted to
330+
// 1. fptoui.sat.i64.fY (Y is 32 or 64)
331+
// 2. fptosi.sat.i64.fY (Y is 32 or 64)
332+
// However, LLVM documentation requires the following rules to be obeyed.
333+
// Rule 1: If the argument is any NaN, zero is returned.
334+
// Rule 2: If the argument is smaller than the smallest representable
335+
// (un)signed integer of the result type, the smallest representable
336+
// (un)signed integer is returned.
337+
// Rule 3: If the argument is larger than the largest representable (un)signed
338+
// integer of the result type, the largest representable (un)signed integer is
339+
// returned.
340+
// Rule 4: Otherwise, the result of rounding the argument towards zero is
341+
// returned.
342+
// Rules 1 & 4 are preserved when promoting iX to i64. For preserving Rule 2
343+
// and Rule 3, we saturate the result of the promoted instruction based on
344+
// original integer type (iX)
345+
// Example:
346+
// Input:
347+
// %0 = call i2 @llvm.fptosi.sat.i2.f32(float %input)
348+
// %1 = sext i32 %0
349+
// Output:
350+
// %0 = call i32 @_Z17convert_long_satf(float %input)
351+
// %1 = icmp sge i32 %0, 1 <Largest 2-bit signed integer>
352+
// %2 = icmp sle i32 %0, -2 <Smallest 2-bit signed integer>
353+
// %3 = select i1 %1, i32 1, i32 %0
354+
// %4 = select i1 %2, i32 -2, i32 %3
355+
// Replace uses of %1 in Input with %4 in Output
356+
void SPIRVRegularizeLLVMBase::cleanupConversionToNonStdIntegers(Module *M) {
357+
for (auto FI = M->begin(), FE = M->end(); FI != FE;) {
358+
Function *F = &(*FI++);
359+
std::vector<Instruction *> ToErase;
360+
auto IID = F->getIntrinsicID();
361+
if (IID != Intrinsic::fptosi_sat && IID != Intrinsic::fptoui_sat)
362+
continue;
363+
for (auto *I : F->users()) {
364+
if (IntrinsicInst *II = dyn_cast<IntrinsicInst>(I)) {
365+
// TODO: Vector type not supported yet.
366+
if (isa<VectorType>(II->getType()))
367+
continue;
368+
auto IID = II->getIntrinsicID();
369+
auto IntBitWidth = II->getType()->getScalarSizeInBits();
370+
if (IntBitWidth == 8 || IntBitWidth == 16 || IntBitWidth == 32 ||
371+
IntBitWidth == 64)
372+
continue;
373+
if (IID == Intrinsic::fptosi_sat) {
374+
// Identify sext (user of II). Make sure that's the only use of II.
375+
auto *User = II->getUniqueUndroppableUser();
376+
if (!User || !isa<SExtInst>(User))
377+
continue;
378+
auto *SExtI = dyn_cast<SExtInst>(User);
379+
auto *NewIType = SExtI->getType();
380+
IRBuilder<> IRB(II);
381+
auto *NewII = IRB.CreateIntrinsic(
382+
IID, {NewIType, II->getOperand(0)->getType()}, II->getOperand(0));
383+
Constant *MaxVal = ConstantInt::get(
384+
NewIType, APInt::getSignedMaxValue(IntBitWidth).getSExtValue());
385+
Constant *MinVal = ConstantInt::get(
386+
NewIType, APInt::getSignedMinValue(IntBitWidth).getSExtValue());
387+
auto *GTMax = IRB.CreateICmp(CmpInst::ICMP_SGE, NewII, MaxVal);
388+
auto *LTMin = IRB.CreateICmp(CmpInst::ICMP_SLE, NewII, MinVal);
389+
auto *SatMax = IRB.CreateSelect(GTMax, MaxVal, NewII);
390+
auto *SatMin = IRB.CreateSelect(LTMin, MinVal, SatMax);
391+
SExtI->replaceAllUsesWith(SatMin);
392+
ToErase.push_back(SExtI);
393+
ToErase.push_back(II);
394+
}
395+
if (IID == Intrinsic::fptoui_sat) {
396+
// Identify zext (user of II). Make sure that's the only use of II.
397+
auto *User = II->getUniqueUndroppableUser();
398+
if (!User || !isa<ZExtInst>(User))
399+
continue;
400+
auto *ZExtI = dyn_cast<ZExtInst>(User);
401+
auto *NewIType = ZExtI->getType();
402+
IRBuilder<> IRB(II);
403+
auto *NewII = IRB.CreateIntrinsic(
404+
IID, {NewIType, II->getOperand(0)->getType()}, II->getOperand(0));
405+
Constant *MaxVal = ConstantInt::get(
406+
NewIType, APInt::getMaxValue(IntBitWidth).getZExtValue());
407+
auto *GTMax = IRB.CreateICmp(CmpInst::ICMP_UGE, NewII, MaxVal);
408+
auto *SatMax = IRB.CreateSelect(GTMax, MaxVal, NewII);
409+
ZExtI->replaceAllUsesWith(SatMax);
410+
ToErase.push_back(ZExtI);
411+
ToErase.push_back(II);
412+
}
413+
}
414+
}
415+
for (Instruction *V : ToErase) {
416+
assert(V->user_empty());
417+
V->dropAllReferences();
418+
V->eraseFromParent();
419+
}
420+
}
421+
}
422+
325423
bool SPIRVRegularizeLLVMBase::runRegularizeLLVM(Module &Module) {
326424
M = &Module;
327425
Ctx = &M->getContext();
@@ -404,6 +502,7 @@ bool SPIRVRegularizeLLVMBase::regularize() {
404502
eraseUselessFunctions(M);
405503
addKernelEntryPoint(M);
406504
expandSYCLTypeUsing(M);
505+
cleanupConversionToNonStdIntegers(M);
407506

408507
for (auto I = M->begin(), E = M->end(); I != E;) {
409508
Function *F = &(*I++);

lib/SPIRV/SPIRVRegularizeLLVM.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@ class SPIRVRegularizeLLVMBase {
9696
void expandVEDWithSYCLTypeSRetArg(llvm::Function *F);
9797
void expandVIDWithSYCLTypeByValComp(llvm::Function *F);
9898

99+
// It is possible that incoming LLVM IR conversion instructions convert
100+
// floating point to non-standard integer types. Such types are not supported
101+
// in SPIR-V. This function cleans up such code and removes occurence of
102+
// non-standard integer types.
103+
void cleanupConversionToNonStdIntegers(llvm::Module *M);
104+
99105
// According to the specification, the operands of a shift instruction must be
100106
// a scalar/vector of integer. When LLVM-IR contains a shift instruction with
101107
// i1 operands, they are treated as a bool. We need to extend them to i32 to
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
;; Ensure @llvm.fptosi.sat.* and @llvm.fptoui.sat.* intrinsics are translated
2+
3+
; RUN: llvm-as %s -o %t.bc
4+
; RUN: llvm-spirv %t.bc -spirv-text -o - | FileCheck %s --check-prefix=CHECK-SPIRV
5+
; RUN: llvm-spirv %t.bc -o %t.spv
6+
; RUN: spirv-val %t.spv
7+
; RUN: llvm-spirv -r %t.spv -o %t.rev.bc
8+
; RUN: llvm-dis < %t.rev.bc | FileCheck %s --check-prefix=CHECK-LLVM
9+
10+
target datalayout = "e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64"
11+
target triple = "spir64-unknown-unknown"
12+
13+
; CHECK-SPIRV-DAG: Capability Kernel
14+
; CHECK-SPIRV-DAG: Decorate [[SAT1:[0-9]+]] SaturatedConversion
15+
; CHECK-SPIRV-DAG: Decorate [[SAT2:[0-9]+]] SaturatedConversion
16+
17+
; CHECK-SPIRV-DAG: TypeInt [[INT64TY:[0-9]+]] 64
18+
; CHECK-SPIRV-DAG: TypeBool [[BOOLTY:[0-9]+]]
19+
; CHECK-SPIRV-DAG: Constant [[INT64TY]] [[I2SMAX:[0-9]+]] 1
20+
; CHECK-SPIRV-DAG: Constant [[INT64TY]] [[I2SMIN:[0-9]+]] 4294967294
21+
; CHECK-SPIRV-DAG: ConvertFToS [[INT64TY]] [[SAT1]]
22+
; CHECK-SPIRV-DAG: SGreaterThanEqual [[BOOLTY]] [[SGERES:[0-9]+]] [[SAT1]] [[I2SMAX]]
23+
; CHECK-SPIRV-DAG: SLessThanEqual [[BOOLTY]] [[SLERES:[0-9]+]] [[SAT1]] [[I2SMIN]]
24+
; CHECK-SPIRV-DAG: Select [[INT64TY]] [[SELRES1:[0-9]+]] [[SGERES]] [[I2SMAX]] [[SAT1]]
25+
; CHECK-SPIRV-DAG: Select [[INT64TY]] [[SELRES2:[0-9]+]] [[SLERES]] [[I2SMIN]] [[SELRES1]]
26+
27+
; CHECK-LLVM-DAG: define spir_kernel
28+
; CHECK-LLVM-DAG: %[[R1:[0-9]+]] = {{.*}} i64 {{.*}}convert_long_satf(float %input)
29+
; CHECK-LLVM-DAG: %[[R2:[0-9]+]] = icmp sge i64 %[[R1]], 1
30+
; CHECK-LLVM-DAG: %[[R3:[0-9]+]] = icmp sle i64 %[[R1]], -2
31+
; CHECK-LLVM-DAG: %[[R4:[0-9]+]] = select i1 %[[R2]], i64 1, i64 %[[R1]]
32+
; CHECK-LLVM-DAG: %[[R5:[0-9]+]] = select i1 %[[R3]], i64 -2, i64 %[[R4]]
33+
34+
define spir_kernel void @testfunction_float_to_signed_i2(float %input) {
35+
entry:
36+
%0 = call i2 @llvm.fptosi.sat.i2.f32(float %input)
37+
%1 = sext i2 %0 to i64
38+
ret void
39+
}
40+
declare i2 @llvm.fptosi.sat.i2.f32(float)
41+
42+
; CHECK-SPIRV-DAG: Constant [[INT64TY]] [[I2UMAX:[0-9]+]] 3
43+
; CHECK-SPIRV-DAG: ConvertFToU [[INT64TY]] [[SAT2]]
44+
; CHECK-SPIRV-DAG: UGreaterThanEqual [[BOOLTY]] [[UGERES:[0-9]+]] [[SAT2]] [[I2UMAX]]
45+
; CHECK-SPIRV-DAG: Select [[INT64TY]] [[SELRES1U:[0-9]+]] [[UGERES]] [[I2UMAX]] [[SAT2]]
46+
; CHECK-LLVM-DAG: define spir_kernel
47+
; CHECK-LLVM-DAG: %[[R1:[0-9]+]] = {{.*}} i64 {{.*}}convert_ulong_satf(float %input)
48+
; CHECK-LLVM-DAG: %[[R2:[0-9]+]] = icmp uge i64 %[[R1]], 3
49+
; CHECK-LLVM-DAG: %[[R3:[0-9]+]] = select i1 %[[R2]], i64 3, i64 %[[R1]]
50+
51+
define spir_kernel void @testfunction_float_to_unsigned_i2(float %input) {
52+
entry:
53+
%0 = call i2 @llvm.fptoui.sat.i2.f32(float %input)
54+
%1 = zext i2 %0 to i64
55+
ret void
56+
}
57+
declare i2 @llvm.fptoui.sat.i2.f32(float)

0 commit comments

Comments
 (0)