Skip to content

Commit ed22029

Browse files
[SPIR-V] Address the case when optimization uses GEP operator and GenCode creates G_PTR_ADD to convey the semantics (#107880)
When running SPIR-V Backend with optimization levels higher than 0, we observe GEP Operator's as a new factor, massively used to convey the semantics of the original LLVM IR. Previously, an issue related to GEP Operator was mentioned and fixed on the consumer side of toolchains (see, for example, Khronos Trandslator Issue KhronosGroup/SPIRV-LLVM-Translator#2486 and PR KhronosGroup/SPIRV-LLVM-Translator#2487). However, there is a case when GenCode creates G_PTR_ADD to convey the original semantics under optimization levels higher than 0 where it's SPIR-V Backend that fails to translate source LLVM IR correctly. Consider the following reproducer: ``` %struct = type { i32, [257 x i8], [257 x i8], [129 x i8], i32, i64, i64, i64, i64, i64, i64 } @mem = linkonce_odr dso_local addrspace(1) global %struct zeroinitializer, align 8 define weak dso_local spir_func void @__devicelib_assert_fail(ptr addrspace(4) noundef %expr, i32 noundef %line, i1 %fl) { entry: %cmp = icmp eq i32 %line, 0 br i1 %cmp, label %lbl, label %exit lbl: store i32 %line, ptr addrspace(1) getelementptr inbounds (i8, ptr addrspace(1) @mem, i64 648), align 8 br i1 %fl, label %lbl, label %exit exit: ret void } ``` converted to the following machine instructions by SPIR-V Backend: ``` %4:type(s64) = OpTypeInt 32, 0 %22:type(s64) = OpTypePointer 5, %4:type(s64) %2:type(s64) = OpTypeInt 8, 0 %28:type(s64) = OpTypePointer 5, %2:type(s64) %10:pid(p1) = G_GLOBAL_VALUE @mem %36:type(s64) = OpTypeStruct %4:type(s64), %32:type(s64), %32:type(s64), %34:type(s64), %4:type(s64), %35:type(s64), %35:type(s64), %35:type(s64), %35:type(s64), %35:type(s64), %35:type(s64) %37:iid(s32) = G_INTRINSIC_W_SIDE_EFFECTS intrinsic(@llvm.spv.const.composite) %8:iid(s32) = ASSIGN_TYPE %37:iid(s32), %36:type(s64) G_INTRINSIC_W_SIDE_EFFECTS intrinsic(@llvm.spv.init.global), %10:pid(p1), %8:iid(s32) %29:pid(p1) = nuw G_PTR_ADD %10:pid, %16:iid(s64) %15:pid(p1) = nuw ASSIGN_TYPE %29:pid(p1), %28:type(s64) %27:pid(p2) = G_BITCAST %15:pid(p1) %17:pid(p2) = ASSIGN_TYPE %27:pid(p2), %22:type(s64) G_STORE %1:iid(s32), %17:pid(p2) :: (store (s32) into %ir.3, align 8, addrspace 1) ``` On the next stage of instruction selection this `G_PTR_ADD`-related pattern would be interpreted as an initialization of a global variable and converted to an invalid constant GEP pattern that, in its turn, would fail to be verified by LLVM during back translation from SPIR-V to LLVM IR. This PR introduces a fix for the problem by adding one more case of `G_PTR_ADD` translation, when we use a non-const GEP to convey the meaning. The reproducer is attached as a new test case.
1 parent 0856f12 commit ed22029

File tree

4 files changed

+141
-5
lines changed

4 files changed

+141
-5
lines changed

llvm/lib/Target/SPIRV/SPIRVDuplicatesTracker.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
#include "SPIRVDuplicatesTracker.h"
1515

16+
#define DEBUG_TYPE "build-dep-graph"
17+
1618
using namespace llvm;
1719

1820
template <typename T>
@@ -63,6 +65,18 @@ void SPIRVGeneralDuplicatesTracker::buildDepsGraph(
6365
if (MI->getOpcode() == SPIRV::OpConstantFunctionPointerINTEL && i == 2)
6466
continue;
6567
MachineOperand *RegOp = &VRegDef->getOperand(0);
68+
LLVM_DEBUG({
69+
if (Reg2Entry.count(RegOp) == 0 &&
70+
(MI->getOpcode() != SPIRV::OpVariable || i != 3)) {
71+
dbgs() << "Unexpected pattern while building a dependency "
72+
"graph.\nInstruction: ";
73+
MI->print(dbgs());
74+
dbgs() << "Operand: ";
75+
Op.print(dbgs());
76+
dbgs() << "\nOperand definition: ";
77+
VRegDef->print(dbgs());
78+
}
79+
});
6680
assert((MI->getOpcode() == SPIRV::OpVariable && i == 3) ||
6781
Reg2Entry.count(RegOp));
6882
if (Reg2Entry.count(RegOp))

llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -607,10 +607,7 @@ bool SPIRVInstructionSelector::spvSelect(Register ResVReg,
607607
case TargetOpcode::G_ADDRSPACE_CAST:
608608
return selectAddrSpaceCast(ResVReg, ResType, I);
609609
case TargetOpcode::G_PTR_ADD: {
610-
// Currently, we get G_PTR_ADD only as a result of translating
611-
// global variables, initialized with constant expressions like GV + Const
612-
// (see test opencl/basic/progvar_prog_scope_init.ll).
613-
// TODO: extend the handler once we have other cases.
610+
// Currently, we get G_PTR_ADD only applied to global variables.
614611
assert(I.getOperand(1).isReg() && I.getOperand(2).isReg());
615612
Register GV = I.getOperand(1).getReg();
616613
MachineRegisterInfo::def_instr_iterator II = MRI->def_instr_begin(GV);
@@ -619,8 +616,68 @@ bool SPIRVInstructionSelector::spvSelect(Register ResVReg,
619616
(*II).getOpcode() == TargetOpcode::COPY ||
620617
(*II).getOpcode() == SPIRV::OpVariable) &&
621618
isImm(I.getOperand(2), MRI));
622-
Register Idx = buildZerosVal(GR.getOrCreateSPIRVIntegerType(32, I, TII), I);
619+
// It may be the initialization of a global variable.
620+
bool IsGVInit = false;
621+
for (MachineRegisterInfo::use_instr_iterator
622+
UseIt = MRI->use_instr_begin(I.getOperand(0).getReg()),
623+
UseEnd = MRI->use_instr_end();
624+
UseIt != UseEnd; UseIt = std::next(UseIt)) {
625+
if ((*UseIt).getOpcode() == TargetOpcode::G_GLOBAL_VALUE ||
626+
(*UseIt).getOpcode() == SPIRV::OpVariable) {
627+
IsGVInit = true;
628+
break;
629+
}
630+
}
623631
MachineBasicBlock &BB = *I.getParent();
632+
if (!IsGVInit) {
633+
SPIRVType *GVType = GR.getSPIRVTypeForVReg(GV);
634+
SPIRVType *GVPointeeType = GR.getPointeeType(GVType);
635+
SPIRVType *ResPointeeType = GR.getPointeeType(ResType);
636+
if (GVPointeeType && ResPointeeType && GVPointeeType != ResPointeeType) {
637+
// Build a new virtual register that is associated with the required
638+
// data type.
639+
Register NewVReg = MRI->createGenericVirtualRegister(MRI->getType(GV));
640+
MRI->setRegClass(NewVReg, MRI->getRegClass(GV));
641+
// Having a correctly typed base we are ready to build the actually
642+
// required GEP. It may not be a constant though, because all Operands
643+
// of OpSpecConstantOp is to originate from other const instructions,
644+
// and only the AccessChain named opcodes accept a global OpVariable
645+
// instruction. We can't use an AccessChain opcode because of the type
646+
// mismatch between result and base types.
647+
if (!GR.isBitcastCompatible(ResType, GVType))
648+
report_fatal_error(
649+
"incompatible result and operand types in a bitcast");
650+
Register ResTypeReg = GR.getSPIRVTypeID(ResType);
651+
MachineInstrBuilder MIB =
652+
BuildMI(BB, I, I.getDebugLoc(), TII.get(SPIRV::OpBitcast))
653+
.addDef(NewVReg)
654+
.addUse(ResTypeReg)
655+
.addUse(GV);
656+
return MIB.constrainAllUses(TII, TRI, RBI) &&
657+
BuildMI(BB, I, I.getDebugLoc(),
658+
TII.get(STI.isVulkanEnv()
659+
? SPIRV::OpInBoundsAccessChain
660+
: SPIRV::OpInBoundsPtrAccessChain))
661+
.addDef(ResVReg)
662+
.addUse(ResTypeReg)
663+
.addUse(NewVReg)
664+
.addUse(I.getOperand(2).getReg())
665+
.constrainAllUses(TII, TRI, RBI);
666+
} else {
667+
return BuildMI(BB, I, I.getDebugLoc(), TII.get(SPIRV::OpSpecConstantOp))
668+
.addDef(ResVReg)
669+
.addUse(GR.getSPIRVTypeID(ResType))
670+
.addImm(
671+
static_cast<uint32_t>(SPIRV::Opcode::InBoundsPtrAccessChain))
672+
.addUse(GV)
673+
.addUse(I.getOperand(2).getReg())
674+
.constrainAllUses(TII, TRI, RBI);
675+
}
676+
}
677+
// It's possible to translate G_PTR_ADD to OpSpecConstantOp: either to
678+
// initialize a global variable with a constant expression (e.g., the test
679+
// case opencl/basic/progvar_prog_scope_init.ll), or for another use case
680+
Register Idx = buildZerosVal(GR.getOrCreateSPIRVIntegerType(32, I, TII), I);
624681
auto MIB = BuildMI(BB, I, I.getDebugLoc(), TII.get(SPIRV::OpSpecConstantOp))
625682
.addDef(ResVReg)
626683
.addUse(GR.getSPIRVTypeID(ResType))

llvm/lib/Target/SPIRV/SPIRVSymbolicOperands.td

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1628,6 +1628,7 @@ multiclass OpcodeOperand<bits<32> value> {
16281628
defm : SymbolicOperandWithRequirements<OpcodeOperand, value, NAME, 0, 0, [], []>;
16291629
}
16301630
// TODO: implement other mnemonics.
1631+
defm InBoundsAccessChain : OpcodeOperand<66>;
16311632
defm InBoundsPtrAccessChain : OpcodeOperand<70>;
16321633
defm PtrCastToGeneric : OpcodeOperand<121>;
16331634
defm Bitcast : OpcodeOperand<124>;
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
; RUN: llc -O2 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s
2+
; RUN: %if spirv-tools %{ llc -O2 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
3+
4+
; RUN: llc -O2 -mtriple=spirv32-unknown-unknown %s -o - | FileCheck %s
5+
; RUN: %if spirv-tools %{ llc -O2 -mtriple=spirv32-unknown-unknown %s -o - -filetype=obj | spirv-val %}
6+
7+
; CHECK-DAG: %[[#Char:]] = OpTypeInt 8 0
8+
; CHECK-DAG: %[[#PtrChar:]] = OpTypePointer CrossWorkgroup %[[#Char]]
9+
; CHECK-DAG: %[[#Int:]] = OpTypeInt 32 0
10+
; CHECK-DAG: %[[#PtrInt:]] = OpTypePointer CrossWorkgroup %[[#Int]]
11+
; CHECK-DAG: %[[#C648:]] = OpConstant %[[#]] 648
12+
; CHECK-DAG: %[[#Struct:]] = OpTypeStruct %[[#]] %[[#]] %[[#]] %[[#]] %[[#]] %[[#]] %[[#]] %[[#]] %[[#]] %[[#]] %[[#]]
13+
; CHECK-DAG: %[[#VarInit:]] = OpConstantNull %[[#Struct]]
14+
; CHECK-DAG: %[[#PtrStruct:]] = OpTypePointer CrossWorkgroup %[[#Struct]]
15+
; CHECK-DAG: %[[#Var:]] = OpVariable %[[#PtrStruct]] CrossWorkgroup %[[#VarInit]]
16+
; CHECK-DAG: %[[#Bytes:]] = OpVariable %[[#PtrChar]] CrossWorkgroup %[[#]]
17+
; CHECK-DAG: %[[#BytesGEP:]] = OpSpecConstantOp %[[#PtrChar]] 70 %[[#Bytes]] %[[#C648]]
18+
19+
; CHECK: OpFunction
20+
; CHECK: %[[#]] = OpFunctionParameter %[[#]]
21+
; CHECK: %[[#Line:]] = OpFunctionParameter %[[#Int]]
22+
; CHECK: %[[#]] = OpFunctionParameter %[[#]]
23+
; CHECK: %[[#Casted:]] = OpBitcast %[[#PtrChar]] %[[#Var]]
24+
; CHECK: %[[#AddrChar:]] = OpInBoundsPtrAccessChain %[[#PtrChar]] %[[#Casted]] %[[#C648]]
25+
; CHECK: %[[#AddrInt:]] = OpBitcast %[[#PtrInt]] %[[#AddrChar]]
26+
; CHECK: OpStore %[[#AddrInt]] %[[#Line]]
27+
28+
%struct = type { i32, [257 x i8], [257 x i8], [129 x i8], i32, i64, i64, i64, i64, i64, i64 }
29+
@Mem = linkonce_odr dso_local addrspace(1) global %struct zeroinitializer, align 8
30+
31+
define weak dso_local spir_func void @foo(ptr addrspace(4) noundef %expr, i32 noundef %line, i1 %fl) {
32+
entry:
33+
%cmp = icmp eq i32 %line, 0
34+
br i1 %cmp, label %lbl, label %exit
35+
36+
lbl:
37+
store i32 %line, ptr addrspace(1) getelementptr inbounds (i8, ptr addrspace(1) @Mem, i64 648), align 8
38+
br i1 %fl, label %lbl, label %exit
39+
40+
exit:
41+
ret void
42+
}
43+
44+
; CHECK: OpFunction
45+
; CHECK: %[[#]] = OpFunctionParameter %[[#]]
46+
; CHECK: %[[#Line2:]] = OpFunctionParameter %[[#Int]]
47+
; CHECK: %[[#]] = OpFunctionParameter %[[#]]
48+
; CHECK: %[[#AddrInt2:]] = OpBitcast %[[#PtrInt]] %[[#BytesGEP]]
49+
; CHECK: OpStore %[[#AddrInt2]] %[[#Line2]]
50+
51+
@Bytes = linkonce_odr dso_local addrspace(1) global i8 zeroinitializer, align 8
52+
53+
define weak dso_local spir_func void @bar(ptr addrspace(4) noundef %expr, i32 noundef %line, i1 %fl) {
54+
entry:
55+
%cmp = icmp eq i32 %line, 0
56+
br i1 %cmp, label %lbl, label %exit
57+
58+
lbl:
59+
store i32 %line, ptr addrspace(1) getelementptr inbounds (i8, ptr addrspace(1) @Bytes, i64 648), align 8
60+
br i1 %fl, label %lbl, label %exit
61+
62+
exit:
63+
ret void
64+
}

0 commit comments

Comments
 (0)