Skip to content

Commit fb1be9b

Browse files
[SPIR-V] Insert a bitcast before load/store instruction to keep SPIR-V code valid (#84069)
This PR introduces a step after instruction selection where instructions can be traversed from the perspective of their validity from the specification point of view. The PR adds also a way to correct load/store when there is a type mismatch contradicting the specification -- an additional bitcast is inserted to keep types consistent. Correspondent test cases are added and existing test cases are corrected. This PR helps to successfully validate with the `spirv-val` tool (https://github.com/KhronosGroup/SPIRV-Tools) some output that previously led to validation errors and crashes of back translation from SPIRV to LLVM IR from the side of SPIRV Translator project (https://github.com/KhronosGroup/SPIRV-LLVM-Translator). The added step of bringing instructions to required by the specification type correspondence can be (should be and will be) extended beyond load/store instructions to ensure validity rules of other SPIRV instructions related to type inference.
1 parent b6a3400 commit fb1be9b

File tree

9 files changed

+203
-51
lines changed

9 files changed

+203
-51
lines changed

llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "llvm/IR/InstIterator.h"
2121
#include "llvm/IR/InstVisitor.h"
2222
#include "llvm/IR/IntrinsicsSPIRV.h"
23+
#include "llvm/IR/TypedPointerType.h"
2324

2425
#include <queue>
2526

@@ -446,7 +447,8 @@ void SPIRVEmitIntrinsics::insertPtrCastOrAssignTypeInstr(Instruction *I,
446447

447448
for (unsigned OpIdx = 0; OpIdx < CI->arg_size(); OpIdx++) {
448449
Value *ArgOperand = CI->getArgOperand(OpIdx);
449-
if (!isa<PointerType>(ArgOperand->getType()))
450+
if (!isa<PointerType>(ArgOperand->getType()) &&
451+
!isa<TypedPointerType>(ArgOperand->getType()))
450452
continue;
451453

452454
// Constants (nulls/undefs) are handled in insertAssignPtrTypeIntrs()

llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp

Lines changed: 48 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "SPIRVSubtarget.h"
2121
#include "SPIRVTargetMachine.h"
2222
#include "SPIRVUtils.h"
23+
#include "llvm/IR/TypedPointerType.h"
2324

2425
using namespace llvm;
2526
SPIRVGlobalRegistry::SPIRVGlobalRegistry(unsigned PointerSize)
@@ -420,9 +421,10 @@ Register
420421
SPIRVGlobalRegistry::getOrCreateConstNullPtr(MachineIRBuilder &MIRBuilder,
421422
SPIRVType *SpvType) {
422423
const Type *LLVMTy = getTypeForSPIRVType(SpvType);
423-
const PointerType *LLVMPtrTy = cast<PointerType>(LLVMTy);
424+
const TypedPointerType *LLVMPtrTy = cast<TypedPointerType>(LLVMTy);
424425
// Find a constant in DT or build a new one.
425-
Constant *CP = ConstantPointerNull::get(const_cast<PointerType *>(LLVMPtrTy));
426+
Constant *CP = ConstantPointerNull::get(PointerType::get(
427+
LLVMPtrTy->getElementType(), LLVMPtrTy->getAddressSpace()));
426428
Register Res = DT.find(CP, CurMF);
427429
if (!Res.isValid()) {
428430
LLT LLTy = LLT::pointer(LLVMPtrTy->getAddressSpace(), PointerSize);
@@ -517,6 +519,13 @@ Register SPIRVGlobalRegistry::buildGlobalVariable(
517519
LLT RegLLTy = LLT::pointer(MRI->getType(ResVReg).getAddressSpace(), 32);
518520
MRI->setType(Reg, RegLLTy);
519521
assignSPIRVTypeToVReg(BaseType, Reg, MIRBuilder.getMF());
522+
} else {
523+
// Our knowledge about the type may be updated.
524+
// If that's the case, we need to update a type
525+
// associated with the register.
526+
SPIRVType *DefType = getSPIRVTypeForVReg(ResVReg);
527+
if (!DefType || DefType != BaseType)
528+
assignSPIRVTypeToVReg(BaseType, Reg, MIRBuilder.getMF());
520529
}
521530

522531
// If it's a global variable with name, output OpName for it.
@@ -705,33 +714,37 @@ SPIRVType *SPIRVGlobalRegistry::createSPIRVType(
705714
}
706715
return getOpTypeFunction(RetTy, ParamTypes, MIRBuilder);
707716
}
708-
if (auto PType = dyn_cast<PointerType>(Ty)) {
709-
SPIRVType *SpvElementType;
710-
// At the moment, all opaque pointers correspond to i8 element type.
711-
// TODO: change the implementation once opaque pointers are supported
712-
// in the SPIR-V specification.
713-
SpvElementType = getOrCreateSPIRVIntegerType(8, MIRBuilder);
714-
// Get access to information about available extensions
715-
const SPIRVSubtarget *ST =
716-
static_cast<const SPIRVSubtarget *>(&MIRBuilder.getMF().getSubtarget());
717-
auto SC = addressSpaceToStorageClass(PType->getAddressSpace(), *ST);
718-
// Null pointer means we have a loop in type definitions, make and
719-
// return corresponding OpTypeForwardPointer.
720-
if (SpvElementType == nullptr) {
721-
if (!ForwardPointerTypes.contains(Ty))
722-
ForwardPointerTypes[PType] = getOpTypeForwardPointer(SC, MIRBuilder);
723-
return ForwardPointerTypes[PType];
724-
}
725-
// If we have forward pointer associated with this type, use its register
726-
// operand to create OpTypePointer.
727-
if (ForwardPointerTypes.contains(PType)) {
728-
Register Reg = getSPIRVTypeID(ForwardPointerTypes[PType]);
729-
return getOpTypePointer(SC, SpvElementType, MIRBuilder, Reg);
730-
}
731-
732-
return getOrCreateSPIRVPointerType(SpvElementType, MIRBuilder, SC);
717+
unsigned AddrSpace = 0xFFFF;
718+
if (auto PType = dyn_cast<TypedPointerType>(Ty))
719+
AddrSpace = PType->getAddressSpace();
720+
else if (auto PType = dyn_cast<PointerType>(Ty))
721+
AddrSpace = PType->getAddressSpace();
722+
else
723+
report_fatal_error("Unable to convert LLVM type to SPIRVType", true);
724+
SPIRVType *SpvElementType;
725+
// At the moment, all opaque pointers correspond to i8 element type.
726+
// TODO: change the implementation once opaque pointers are supported
727+
// in the SPIR-V specification.
728+
SpvElementType = getOrCreateSPIRVIntegerType(8, MIRBuilder);
729+
// Get access to information about available extensions
730+
const SPIRVSubtarget *ST =
731+
static_cast<const SPIRVSubtarget *>(&MIRBuilder.getMF().getSubtarget());
732+
auto SC = addressSpaceToStorageClass(AddrSpace, *ST);
733+
// Null pointer means we have a loop in type definitions, make and
734+
// return corresponding OpTypeForwardPointer.
735+
if (SpvElementType == nullptr) {
736+
if (!ForwardPointerTypes.contains(Ty))
737+
ForwardPointerTypes[Ty] = getOpTypeForwardPointer(SC, MIRBuilder);
738+
return ForwardPointerTypes[Ty];
739+
}
740+
// If we have forward pointer associated with this type, use its register
741+
// operand to create OpTypePointer.
742+
if (ForwardPointerTypes.contains(Ty)) {
743+
Register Reg = getSPIRVTypeID(ForwardPointerTypes[Ty]);
744+
return getOpTypePointer(SC, SpvElementType, MIRBuilder, Reg);
733745
}
734-
llvm_unreachable("Unable to convert LLVM type to SPIRVType");
746+
747+
return getOrCreateSPIRVPointerType(SpvElementType, MIRBuilder, SC);
735748
}
736749

737750
SPIRVType *SPIRVGlobalRegistry::restOfCreateSPIRVType(
@@ -1139,11 +1152,13 @@ SPIRVType *SPIRVGlobalRegistry::getOrCreateSPIRVPointerType(
11391152
SPIRV::StorageClass::StorageClass SC) {
11401153
const Type *PointerElementType = getTypeForSPIRVType(BaseType);
11411154
unsigned AddressSpace = storageClassToAddressSpace(SC);
1142-
Type *LLVMTy =
1143-
PointerType::get(const_cast<Type *>(PointerElementType), AddressSpace);
1155+
Type *LLVMTy = TypedPointerType::get(const_cast<Type *>(PointerElementType),
1156+
AddressSpace);
1157+
// check if this type is already available
11441158
Register Reg = DT.find(PointerElementType, AddressSpace, CurMF);
11451159
if (Reg.isValid())
11461160
return getSPIRVTypeForVReg(Reg);
1161+
// create a new type
11471162
auto MIB = BuildMI(MIRBuilder.getMBB(), MIRBuilder.getInsertPt(),
11481163
MIRBuilder.getDebugLoc(),
11491164
MIRBuilder.getTII().get(SPIRV::OpTypePointer))
@@ -1155,22 +1170,10 @@ SPIRVType *SPIRVGlobalRegistry::getOrCreateSPIRVPointerType(
11551170
}
11561171

11571172
SPIRVType *SPIRVGlobalRegistry::getOrCreateSPIRVPointerType(
1158-
SPIRVType *BaseType, MachineInstr &I, const SPIRVInstrInfo &TII,
1173+
SPIRVType *BaseType, MachineInstr &I, const SPIRVInstrInfo &,
11591174
SPIRV::StorageClass::StorageClass SC) {
1160-
const Type *PointerElementType = getTypeForSPIRVType(BaseType);
1161-
unsigned AddressSpace = storageClassToAddressSpace(SC);
1162-
Type *LLVMTy =
1163-
PointerType::get(const_cast<Type *>(PointerElementType), AddressSpace);
1164-
Register Reg = DT.find(PointerElementType, AddressSpace, CurMF);
1165-
if (Reg.isValid())
1166-
return getSPIRVTypeForVReg(Reg);
1167-
MachineBasicBlock &BB = *I.getParent();
1168-
auto MIB = BuildMI(BB, I, I.getDebugLoc(), TII.get(SPIRV::OpTypePointer))
1169-
.addDef(createTypeVReg(CurMF->getRegInfo()))
1170-
.addImm(static_cast<uint32_t>(SC))
1171-
.addUse(getSPIRVTypeID(BaseType));
1172-
DT.add(PointerElementType, AddressSpace, CurMF, getSPIRVTypeID(MIB));
1173-
return finishCreatingSPIRVType(LLVMTy, MIB);
1175+
MachineIRBuilder MIRBuilder(I);
1176+
return getOrCreateSPIRVPointerType(BaseType, MIRBuilder, SC);
11741177
}
11751178

11761179
Register SPIRVGlobalRegistry::getOrCreateUndef(MachineInstr &I,

llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class SPIRVGlobalRegistry {
3434
DenseMap<const MachineFunction *, DenseMap<Register, SPIRVType *>>
3535
VRegToTypeMap;
3636

37+
// Map LLVM Type* to <MF, Reg>
3738
SPIRVGeneralDuplicatesTracker DT;
3839

3940
DenseMap<SPIRVType *, const Type *> SPIRVToLLVMType;

llvm/lib/Target/SPIRV/SPIRVISelLowering.cpp

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@
1212

1313
#include "SPIRVISelLowering.h"
1414
#include "SPIRV.h"
15+
#include "SPIRVInstrInfo.h"
16+
#include "SPIRVRegisterBankInfo.h"
17+
#include "SPIRVRegisterInfo.h"
18+
#include "SPIRVSubtarget.h"
19+
#include "SPIRVTargetMachine.h"
20+
#include "llvm/CodeGen/MachineInstrBuilder.h"
21+
#include "llvm/CodeGen/MachineRegisterInfo.h"
1522
#include "llvm/IR/IntrinsicsSPIRV.h"
1623

1724
#define DEBUG_TYPE "spirv-lower"
@@ -74,3 +81,76 @@ bool SPIRVTargetLowering::getTgtMemIntrinsic(IntrinsicInfo &Info,
7481
}
7582
return false;
7683
}
84+
85+
// Insert a bitcast before the instruction to keep SPIR-V code valid
86+
// when there is a type mismatch between results and operand types.
87+
static void validatePtrTypes(const SPIRVSubtarget &STI,
88+
MachineRegisterInfo *MRI, SPIRVGlobalRegistry &GR,
89+
MachineInstr &I, SPIRVType *ResType,
90+
unsigned OpIdx) {
91+
Register OpReg = I.getOperand(OpIdx).getReg();
92+
SPIRVType *TypeInst = MRI->getVRegDef(OpReg);
93+
SPIRVType *OpType = GR.getSPIRVTypeForVReg(
94+
TypeInst && TypeInst->getOpcode() == SPIRV::OpFunctionParameter
95+
? TypeInst->getOperand(1).getReg()
96+
: OpReg);
97+
if (!ResType || !OpType || OpType->getOpcode() != SPIRV::OpTypePointer)
98+
return;
99+
SPIRVType *ElemType = GR.getSPIRVTypeForVReg(OpType->getOperand(2).getReg());
100+
if (!ElemType || ElemType == ResType)
101+
return;
102+
// There is a type mismatch between results and operand types
103+
// and we insert a bitcast before the instruction to keep SPIR-V code valid
104+
SPIRV::StorageClass::StorageClass SC =
105+
static_cast<SPIRV::StorageClass::StorageClass>(
106+
OpType->getOperand(1).getImm());
107+
MachineInstr *PrevI = I.getPrevNode();
108+
MachineBasicBlock &MBB = *I.getParent();
109+
MachineBasicBlock::iterator InsPt =
110+
PrevI ? PrevI->getIterator() : MBB.begin();
111+
MachineIRBuilder MIB(MBB, InsPt);
112+
SPIRVType *NewPtrType = GR.getOrCreateSPIRVPointerType(ResType, MIB, SC);
113+
if (!GR.isBitcastCompatible(NewPtrType, OpType))
114+
report_fatal_error(
115+
"insert validation bitcast: incompatible result and operand types");
116+
Register NewReg = MRI->createGenericVirtualRegister(LLT::scalar(32));
117+
bool Res = MIB.buildInstr(SPIRV::OpBitcast)
118+
.addDef(NewReg)
119+
.addUse(GR.getSPIRVTypeID(NewPtrType))
120+
.addUse(OpReg)
121+
.constrainAllUses(*STI.getInstrInfo(), *STI.getRegisterInfo(),
122+
*STI.getRegBankInfo());
123+
if (!Res)
124+
report_fatal_error("insert validation bitcast: cannot constrain all uses");
125+
MRI->setRegClass(NewReg, &SPIRV::IDRegClass);
126+
GR.assignSPIRVTypeToVReg(NewPtrType, NewReg, MIB.getMF());
127+
I.getOperand(OpIdx).setReg(NewReg);
128+
}
129+
130+
// TODO: the logic of inserting additional bitcast's is to be moved
131+
// to pre-IRTranslation passes eventually
132+
void SPIRVTargetLowering::finalizeLowering(MachineFunction &MF) const {
133+
MachineRegisterInfo *MRI = &MF.getRegInfo();
134+
SPIRVGlobalRegistry &GR = *STI.getSPIRVGlobalRegistry();
135+
GR.setCurrentFunc(MF);
136+
for (MachineFunction::iterator I = MF.begin(), E = MF.end(); I != E; ++I) {
137+
MachineBasicBlock *MBB = &*I;
138+
for (MachineBasicBlock::iterator MBBI = MBB->begin(), MBBE = MBB->end();
139+
MBBI != MBBE;) {
140+
MachineInstr &MI = *MBBI++;
141+
switch (MI.getOpcode()) {
142+
case SPIRV::OpLoad:
143+
// OpLoad <ResType>, ptr %Op implies that %Op is a pointer to <ResType>
144+
validatePtrTypes(STI, MRI, GR, MI,
145+
GR.getSPIRVTypeForVReg(MI.getOperand(0).getReg()), 2);
146+
break;
147+
case SPIRV::OpStore:
148+
// OpStore ptr %Op, <Obj> implies that %Op points to the <Obj>'s type
149+
validatePtrTypes(STI, MRI, GR, MI,
150+
GR.getSPIRVTypeForVReg(MI.getOperand(1).getReg()), 0);
151+
break;
152+
}
153+
}
154+
}
155+
TargetLowering::finalizeLowering(MF);
156+
}

llvm/lib/Target/SPIRV/SPIRVISelLowering.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,19 @@
1414
#ifndef LLVM_LIB_TARGET_SPIRV_SPIRVISELLOWERING_H
1515
#define LLVM_LIB_TARGET_SPIRV_SPIRVISELLOWERING_H
1616

17+
#include "SPIRVGlobalRegistry.h"
1718
#include "llvm/CodeGen/TargetLowering.h"
1819

1920
namespace llvm {
2021
class SPIRVSubtarget;
2122

2223
class SPIRVTargetLowering : public TargetLowering {
24+
const SPIRVSubtarget &STI;
25+
2326
public:
2427
explicit SPIRVTargetLowering(const TargetMachine &TM,
25-
const SPIRVSubtarget &STI)
26-
: TargetLowering(TM) {}
28+
const SPIRVSubtarget &ST)
29+
: TargetLowering(TM), STI(ST) {}
2730

2831
// Stop IRTranslator breaking up FMA instrs to preserve types information.
2932
bool isFMAFasterThanFMulAndFAdd(const MachineFunction &MF,
@@ -47,6 +50,11 @@ class SPIRVTargetLowering : public TargetLowering {
4750
bool getTgtMemIntrinsic(IntrinsicInfo &Info, const CallInst &I,
4851
MachineFunction &MF,
4952
unsigned Intrinsic) const override;
53+
54+
// Call the default implementation and finalize target lowering by inserting
55+
// extra instructions required to preserve validity of SPIR-V code imposed by
56+
// the standard.
57+
void finalizeLowering(MachineFunction &MF) const override;
5058
};
5159
} // namespace llvm
5260

llvm/test/CodeGen/SPIRV/constant/global-constants.ll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
; RUN: llc -O0 -mtriple=spirv32-unknown-unknown %s -o - | FileCheck %s
2+
; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
23

34
@global = addrspace(1) constant i32 1 ; OpenCL global memory
45
@constant = addrspace(2) constant i32 2 ; OpenCL constant memory
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
; RUN: llc -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s
2+
; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
3+
4+
; CHECK-DAG: %[[#TYLONG:]] = OpTypeInt 32 0
5+
; CHECK-DAG: %[[#TYSTRUCTLONG:]] = OpTypeStruct %[[#TYLONG]]
6+
; CHECK-DAG: %[[#TYARRAY:]] = OpTypeArray %[[#TYSTRUCTLONG]] %[[#]]
7+
; CHECK-DAG: %[[#TYSTRUCT:]] = OpTypeStruct %[[#TYARRAY]]
8+
; CHECK-DAG: %[[#TYSTRUCTPTR:]] = OpTypePointer Function %[[#TYSTRUCT]]
9+
; CHECK-DAG: %[[#TYLONGPTR:]] = OpTypePointer Function %[[#TYLONG]]
10+
; CHECK: %[[#PTRTOSTRUCT:]] = OpFunctionParameter %[[#TYSTRUCTPTR]]
11+
; CHECK: %[[#PTRTOLONG:]] = OpBitcast %[[#TYLONGPTR]] %[[#PTRTOSTRUCT]]
12+
; CHECK: OpLoad %[[#TYLONG]] %[[#PTRTOLONG]]
13+
14+
%struct.S = type { i32 }
15+
%struct.__wrapper_class = type { [7 x %struct.S] }
16+
17+
define spir_kernel void @foo(ptr noundef byval(%struct.__wrapper_class) align 4 %_arg_Arr) {
18+
entry:
19+
%val = load i32, ptr %_arg_Arr
20+
ret void
21+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
; RUN: llc -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s
2+
; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
3+
4+
; CHECK-DAG: %[[#TYLONG:]] = OpTypeInt 32 0
5+
; CHECK-DAG: %[[#TYLONGPTR:]] = OpTypePointer Function %[[#TYLONG]]
6+
; CHECK-DAG: %[[#TYSTRUCT:]] = OpTypeStruct %[[#TYLONG]]
7+
; CHECK-DAG: %[[#CONST:]] = OpConstant %[[#TYLONG]] 3
8+
; CHECK-DAG: %[[#TYSTRUCTPTR:]] = OpTypePointer Function %[[#TYSTRUCT]]
9+
; CHECK: OpFunction
10+
; CHECK: %[[#ARGPTR1:]] = OpFunctionParameter %[[#TYLONGPTR]]
11+
; CHECK: OpStore %[[#ARGPTR1]] %[[#CONST:]]
12+
; CHECK: OpFunction
13+
; CHECK: %[[#OBJ:]] = OpFunctionParameter %[[#TYSTRUCT]]
14+
; CHECK: %[[#ARGPTR2:]] = OpFunctionParameter %[[#TYLONGPTR]]
15+
; CHECK: %[[#PTRTOSTRUCT:]] = OpBitcast %[[#TYSTRUCTPTR]] %[[#ARGPTR2]]
16+
; CHECK: OpStore %[[#PTRTOSTRUCT]] %[[#OBJ]]
17+
18+
%struct.S = type { i32 }
19+
%struct.__wrapper_class = type { [7 x %struct.S] }
20+
21+
define spir_kernel void @foo(%struct.S %arg, ptr %ptr) {
22+
entry:
23+
store %struct.S %arg, ptr %ptr
24+
ret void
25+
}
26+
27+
define spir_kernel void @bar(ptr %ptr) {
28+
entry:
29+
store i32 3, ptr %ptr
30+
ret void
31+
}

llvm/test/CodeGen/SPIRV/spirv-load-store.ll

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
; RUN: llc -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s
2+
; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
23
;; Translate SPIR-V friendly OpLoad and OpStore calls
34

4-
; CHECK: %[[#CONST:]] = OpConstant %[[#]] 42
5-
; CHECK: OpStore %[[#PTR:]] %[[#CONST]] Volatile|Aligned 4
6-
; CHECK: %[[#]] = OpLoad %[[#]] %[[#PTR]]
5+
; CHECK-DAG: %[[#TYLONG:]] = OpTypeInt 32 0
6+
; CHECK-DAG: %[[#TYFLOAT:]] = OpTypeFloat 64
7+
; CHECK-DAG: %[[#TYFLOATPTR:]] = OpTypePointer CrossWorkgroup %[[#TYFLOAT]]
8+
; CHECK-DAG: %[[#CONST:]] = OpConstant %[[#TYLONG]] 42
9+
; CHECK: OpStore %[[#PTRTOLONG:]] %[[#CONST]] Volatile|Aligned 4
10+
; CHECK: %[[#PTRTOFLOAT:]] = OpBitcast %[[#TYFLOATPTR]] %[[#PTRTOLONG]]
11+
; CHECK: OpLoad %[[#TYFLOAT]] %[[#PTRTOFLOAT]]
712

813
define weak_odr dso_local spir_kernel void @foo(i32 addrspace(1)* %var) {
914
entry:

0 commit comments

Comments
 (0)