Skip to content

Commit c857425

Browse files
asudarsasys-ce-bb
authored andcommitted
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]> Original commit: KhronosGroup/SPIRV-LLVM-Translator@14301c295d3dc8e
1 parent 01ceb0c commit c857425

File tree

3 files changed

+162
-0
lines changed

3 files changed

+162
-0
lines changed

llvm-spirv/lib/SPIRV/SPIRVRegularizeLLVM.cpp

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

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

468567
for (auto &GV : M->globals()) {
469568
SPIRVBuiltinVariableKind Kind;

llvm-spirv/lib/SPIRV/SPIRVRegularizeLLVM.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@ class SPIRVRegularizeLLVMBase {
9191
void expandVEDWithSYCLTypeSRetArg(llvm::Function *F);
9292
void expandVIDWithSYCLTypeByValComp(llvm::Function *F);
9393

94+
// It is possible that incoming LLVM IR conversion instructions convert
95+
// floating point to non-standard integer types. Such types are not supported
96+
// in SPIR-V. This function cleans up such code and removes occurence of
97+
// non-standard integer types.
98+
void cleanupConversionToNonStdIntegers(llvm::Module *M);
99+
94100
// According to the specification, the operands of a shift instruction must be
95101
// a scalar/vector of integer. When LLVM-IR contains a shift instruction with
96102
// 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)