From 73e65fcbbb74214f076a622d00987028f5c16ae6 Mon Sep 17 00:00:00 2001 From: Alexey Sotkin Date: Mon, 26 Sep 2016 17:33:12 +0300 Subject: [PATCH] Translation of LLVM intrinsics (#185) An LLVM intrinsic should not be transalated to SPIRV as a function call. Either it's semantics is represented via SPIRV instruction(s) or an error is reported. --- lib/SPIRV/SPIRVWriter.cpp | 113 +++++++++++++++++++++------- lib/SPIRV/libSPIRV/SPIRVErrorEnum.h | 1 + test/SPIRV/llvm.fma.ll | 34 +++++++++ test/SPIRV/llvm.fmuladd.ll | 43 +++++++++++ test/SPIRV/llvm.memset.ll | 42 +++++++++++ 5 files changed, 206 insertions(+), 27 deletions(-) create mode 100644 test/SPIRV/llvm.fma.ll create mode 100644 test/SPIRV/llvm.fmuladd.ll create mode 100644 test/SPIRV/llvm.memset.ll diff --git a/lib/SPIRV/SPIRVWriter.cpp b/lib/SPIRV/SPIRVWriter.cpp index 12bb3e25935..7cee3b52b09 100644 --- a/lib/SPIRV/SPIRVWriter.cpp +++ b/lib/SPIRV/SPIRVWriter.cpp @@ -64,6 +64,7 @@ #include "llvm/IR/Function.h" #include "llvm/IR/InstrTypes.h" #include "llvm/IR/Instructions.h" +#include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/Module.h" #include "llvm/IR/Operator.h" #include "llvm/IR/Verifier.h" @@ -165,6 +166,10 @@ class LLVMToSPIRV: public ModulePass { DbgTran(nullptr, SMod){ } + virtual const char* getPassName() const { + return "LLVMToSPIRV"; + } + bool runOnModule(Module &Mod) override { M = &Mod; Ctx = &M->getContext(); @@ -183,7 +188,7 @@ class LLVMToSPIRV: public ModulePass { SPIRVType *transType(Type *T); SPIRVType *transSPIRVOpaqueType(Type *T); - SPIRVValue *getTranslatedValue(Value *); + SPIRVValue *getTranslatedValue(Value *) const; // Translation functions bool transAddressingMode(); @@ -194,6 +199,7 @@ class LLVMToSPIRV: public ModulePass { bool transSourceLanguage(); bool transExtension(); bool transBuiltinSet(); + SPIRVValue *transIntrinsicInst(IntrinsicInst *Intrinsic, SPIRVBasicBlock *BB); SPIRVValue *transCallInst(CallInst *Call, SPIRVBasicBlock *BB); bool transDecoration(Value *V, SPIRVValue *BV); SPIRVWord transFunctionControlMask(CallInst *); @@ -310,8 +316,8 @@ class LLVMToSPIRV: public ModulePass { SPIRVValue * -LLVMToSPIRV::getTranslatedValue(Value *V) { - LLVMToSPIRVValueMap::iterator Loc = ValueMap.find(V); +LLVMToSPIRV::getTranslatedValue(Value *V) const { + auto Loc = ValueMap.find(V); if (Loc != ValueMap.end()) return Loc->second; return nullptr; @@ -628,9 +634,17 @@ LLVMToSPIRV::transSPIRVOpaqueType(Type *T) { SPIRVFunction * LLVMToSPIRV::transFunctionDecl(Function *F) { - if (auto BF= getTranslatedValue(F)) + if (auto BF = getTranslatedValue(F)) return static_cast(BF); + if (F->isIntrinsic()) { + // We should not translate LLVM intrinsics as a function + assert(none_of(F->user_begin(), F->user_end(), + [this](User *U){ return getTranslatedValue(U);}) && + "LLVM intrinsics shouldn't be called in SPIRV"); + return nullptr; + } + SPIRVTypeFunction *BFT = static_cast(transType( getAnalysis().getAdaptedType(F))); SPIRVFunction *BF = static_cast(mapValue(F, @@ -795,11 +809,11 @@ LLVMToSPIRV::transValue(Value *V, SPIRVBasicBlock *BB, bool CreateForward) { "Invalid SPIRV BB"); auto BV = transValueWithoutDecoration(V, BB, CreateForward); + if (!BV || !transDecoration(V, BV)) + return nullptr; std::string name = V->getName(); if (!name.empty()) // Don't erase the name, which BM might already have BM->setName(BV, name); - if(!transDecoration(V, BV)) - return nullptr; return BV; } @@ -1103,6 +1117,11 @@ LLVMToSPIRV::transValueWithoutDecoration(Value *V, SPIRVBasicBlock *BB, BB)); } + if (IntrinsicInst *II = dyn_cast(V)) { + SPIRVValue *BV = transIntrinsicInst(II, BB); + return BV ? mapValue(V, BV) : nullptr; + } + if (CallInst *CI = dyn_cast(V)) return mapValue(V, transCallInst(CI, BB)); @@ -1196,6 +1215,67 @@ LLVMToSPIRV::transSpcvCast(CallInst* CI, SPIRVBasicBlock *BB) { return oclTransSpvcCastSampler(CI, BB); } +SPIRVValue * +LLVMToSPIRV::transIntrinsicInst(IntrinsicInst *II, SPIRVBasicBlock *BB) { + auto getMemoryAccess = [](MemIntrinsic *MI)->std::vector { + std::vector MemoryAccess(1, MemoryAccessMaskNone); + if (SPIRVWord AlignVal = MI->getAlignment()) { + MemoryAccess[0] |= MemoryAccessAlignedMask; + MemoryAccess.push_back(AlignVal); + } + if (MI->isVolatile()) + MemoryAccess[0] |= MemoryAccessVolatileMask; + return MemoryAccess; + }; + + switch (II->getIntrinsicID()) { + case Intrinsic::fmuladd : { + // For llvm.fmuladd.* fusion is not guaranteed. If a fused multiply-add + // is required the corresponding llvm.fma.* intrinsic function should be + // used instead. + SPIRVType *Ty = transType(II->getType()); + SPIRVValue *Mul = BM->addBinaryInst(OpFMul, Ty, + transValue(II->getArgOperand(0), BB), + transValue(II->getArgOperand(1), BB), + BB); + return BM->addBinaryInst(OpFAdd, Ty, Mul, + transValue(II->getArgOperand(2), BB), BB); + } + case Intrinsic::memset : { + // If memset is used for zero-initialization, find a variable which is being + // initialized and store null constant of the same type to this variable + MemSetInst *MSI = cast(II); + AllocaInst *AI = dyn_cast(MSI->getDest()); + ConstantInt *Val = dyn_cast(MSI->getValue()); + ConstantInt *Len = dyn_cast(MSI->getLength()); + if (AI && Val && Val->isZero() && Len && + AI->getAlignment() == MSI->getAlignment() && Len->getZExtValue()*8 == + M->getDataLayout()->getTypeSizeInBits(AI->getAllocatedType())) { + SPIRVValue *Var = transValue(MSI->getDest(), BB); + assert(Var && Var->isVariable()); + auto *VarTy = static_cast(Var->getType()); + return BM->addStoreInst(Var, BM->addNullConstant(VarTy->getElementType()), + getMemoryAccess(MSI), BB); + } + assert(!"Can't translate llvm.memset with non-zero value argument"); + } + break; + case Intrinsic::memcpy : + return BM->addCopyMemorySizedInst( + transValue(II->getOperand(0), BB), + transValue(II->getOperand(1), BB), + transValue(II->getOperand(2), BB), + getMemoryAccess(cast(II)), + BB); + default: + // LLVM intrinsic functions shouldn't get to SPIRV, because they + // would have no definition there. + BM->getErrorLog().checkError(false, SPIRVEC_InvalidFunctionCall, + II->getName().str(), "", __FILE__, __LINE__); + } + return nullptr; +} + SPIRVValue * LLVMToSPIRV::transCallInst(CallInst *CI, SPIRVBasicBlock *BB) { SPIRVExtInstSetKind ExtSetKind = SPIRVEIS_Count; @@ -1207,27 +1287,6 @@ LLVMToSPIRV::transCallInst(CallInst *CI, SPIRVBasicBlock *BB) { if (MangledName.startswith(SPCV_CAST)) return transSpcvCast(CI, BB); - if (MangledName.startswith("llvm.memcpy")) { - std::vector MemoryAccess; - - if (isa(CI->getOperand(4)) && - dyn_cast(CI->getOperand(4)) - ->getZExtValue() == 1) - MemoryAccess.push_back(MemoryAccessVolatileMask); - if (isa(CI->getOperand(3))) { - MemoryAccess.push_back(MemoryAccessAlignedMask); - MemoryAccess.push_back(dyn_cast(CI->getOperand(3)) - ->getZExtValue()); - } - - return BM->addCopyMemorySizedInst( - transValue(CI->getOperand(0), BB), - transValue(CI->getOperand(1), BB), - transValue(CI->getOperand(2), BB), - MemoryAccess, - BB); - } - if (oclIsBuiltin(MangledName, &DemangledName) || isDecoratedSPIRVFunc(F, &DemangledName)) if (auto BV = transBuiltinToInst(DemangledName, MangledName, CI, BB)) diff --git a/lib/SPIRV/libSPIRV/SPIRVErrorEnum.h b/lib/SPIRV/libSPIRV/SPIRVErrorEnum.h index 45b969e1f88..a026bb34b48 100644 --- a/lib/SPIRV/libSPIRV/SPIRVErrorEnum.h +++ b/lib/SPIRV/libSPIRV/SPIRVErrorEnum.h @@ -5,3 +5,4 @@ _SPIRV_OP(InvalidAddressingModel, "Expects 0-2.") _SPIRV_OP(InvalidMemoryModel, "Expects 0-3.") _SPIRV_OP(InvalidFunctionControlMask,"") _SPIRV_OP(InvalidBuiltinSetName, "Expects OpenCL.std.") +_SPIRV_OP(InvalidFunctionCall, "Unexpected llvm intrinsic:") diff --git a/test/SPIRV/llvm.fma.ll b/test/SPIRV/llvm.fma.ll new file mode 100644 index 00000000000..b30cc809d35 --- /dev/null +++ b/test/SPIRV/llvm.fma.ll @@ -0,0 +1,34 @@ +; Translator should not translate llvm intrinsic calls straight forward. +; It either represnts intrinsic's semantics with SPIRV instruction(s), or +; reports an error. +; XFAIL: * +; RUN: llvm-as %s -o %t.bc +; RUN: llvm-spirv %t.bc + +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" +target triple = "spir64" + +; Function Attrs: nounwind +define spir_func void @foo(float %a, float %b, float %c) #0 { +entry: + %0 = call float @llvm.fma.f32(float %a, float %b, float %c) + ret void +} + +; Function Attrs: nounwind readnone +declare float @llvm.fma.f32(float, float, float) #1 + +attributes #0 = { nounwind "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-realign-stack" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" } +attributes #1 = { nounwind readnone } + +!opencl.enable.FP_CONTRACT = !{} +!opencl.spir.version = !{!0} +!opencl.ocl.version = !{!1} +!opencl.used.extensions = !{!2} +!opencl.used.optional.core.features = !{!3} +!opencl.compiler.options = !{!2} + +!0 = !{i32 1, i32 2} +!1 = !{i32 2, i32 0} +!2 = !{} +!3 = !{!"cl_doubles"} diff --git a/test/SPIRV/llvm.fmuladd.ll b/test/SPIRV/llvm.fmuladd.ll new file mode 100644 index 00000000000..62a6b521015 --- /dev/null +++ b/test/SPIRV/llvm.fmuladd.ll @@ -0,0 +1,43 @@ +; RUN: llvm-as %s -o %t.bc +; RUN: llvm-spirv %t.bc -spirv-text -o - | FileCheck %s + +; CHECK-NOT: llvm.fmuladd + +; CHECK: TypeFloat [[f32:[0-9]+]] 32 +; CHECK: TypeFloat [[f64:[0-9]+]] 64 + +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" +target triple = "spir64" + +; Function Attrs: nounwind +define spir_func void @foo(float %a, float %b, float %c, double %x, double %y, double %z) #0 { +entry: + %0 = call float @llvm.fmuladd.f32(float %a, float %b, float %c) +; CHECK: FMul [[f32]] [[mul32:[0-9]+]] {{[0-9]+}} {{[0-9]+}} +; CHECK-NEXT: FAdd [[f32]] {{[0-9]+}} [[mul32]] {{[0-9]+}} + %1 = call double @llvm.fmuladd.f64(double %x, double %y, double %z) +; CHECK: FMul [[f64]] [[mul64:[0-9]+]] {{[0-9]+}} {{[0-9]+}} +; CHECK-NEXT: FAdd [[f64]] {{[0-9]+}} [[mul64]] {{[0-9]+}} +ret void +} + +; Function Attrs: nounwind readnone +declare float @llvm.fmuladd.f32(float, float, float) #1 + +; Function Attrs: nounwind readnone +declare double @llvm.fmuladd.f64(double, double, double) #1 + +attributes #0 = { nounwind "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-realign-stack" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" } +attributes #1 = { nounwind readnone } + +!opencl.enable.FP_CONTRACT = !{} +!opencl.spir.version = !{!0} +!opencl.ocl.version = !{!1} +!opencl.used.extensions = !{!2} +!opencl.used.optional.core.features = !{!3} +!opencl.compiler.options = !{!2} + +!0 = !{i32 1, i32 2} +!1 = !{i32 2, i32 0} +!2 = !{} +!3 = !{!"cl_doubles"} diff --git a/test/SPIRV/llvm.memset.ll b/test/SPIRV/llvm.memset.ll new file mode 100644 index 00000000000..2f67c67aae1 --- /dev/null +++ b/test/SPIRV/llvm.memset.ll @@ -0,0 +1,42 @@ +; RUN: llvm-as %s -o %t.bc +; RUN: llvm-spirv %t.bc -spirv-text -o - | FileCheck %s + +; CHECK-NOT: llvm.memset + +; CHECK: TypeInt [[int32:[0-9]+]] 32 +; CHECK: Constant [[int32]] [[fiftieen:[0-9]+]] 15 +; CHECK: TypeArray [[int32x15:[0-9]+]] [[int32]] [[fiftieen]] +; CHECK: TypePointer [[int32x15Ptr:[0-9]+]] 7 [[int32x15]] +; CHECK: ConstantNull [[int32x15]] [[ConstantNull:[0-9]+]] + +; CHECK: Variable [[int32x15Ptr:[0-9]+]] [[mem:[0-9]+]] 7 +; CHECK: Store [[mem]] [[ConstantNull]] + +target datalayout = "e-p:32:32-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64" +target triple = "spir" + +; Function Attrs: nounwind +define spir_func void @test() #0 { +entry: + %mem = alloca [15 x i32], align 4 + %0 = bitcast [15 x i32]* %mem to i8* + call void @llvm.memset.p0i8.i32(i8* %0, i8 0, i32 60, i32 4, i1 false) + ret void +} + +; Function Attrs: nounwind +declare void @llvm.memset.p0i8.i32(i8* nocapture, i8, i32, i32, i1) #1 + +attributes #0 = { nounwind "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-realign-stack" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" } +attributes #1 = { nounwind } + +!opencl.enable.FP_CONTRACT = !{} +!opencl.spir.version = !{!0} +!opencl.ocl.version = !{!1} +!opencl.used.extensions = !{!2} +!opencl.used.optional.core.features = !{!2} +!opencl.compiler.options = !{!2} + +!0 = !{i32 1, i32 2} +!1 = !{i32 2, i32 0} +!2 = !{}