From 73b29dfbea791321ae626388955edf8e7253cfc6 Mon Sep 17 00:00:00 2001 From: Dmitry Sidorov Date: Fri, 24 Oct 2025 18:44:14 +0200 Subject: [PATCH 1/2] Add SPIRVLowerAlloca pass The pass reorders alloca instructions in the function, placing them in the entry basic block. In case if the moved alloca is not accompanied by lifetime start/end intrinsics - they are inserted to guard lifetime of the allocated object. Resolves https://github.com/KhronosGroup/SPIRV-LLVM-Translator/issues/3389 Signed-off-by: Dmitry Sidorov --- include/LLVMSPIRVLib.h | 4 + lib/SPIRV/CMakeLists.txt | 1 + lib/SPIRV/SPIRVLowerAlloca.cpp | 292 ++++++++++++++++++ lib/SPIRV/SPIRVLowerAlloca.h | 79 +++++ lib/SPIRV/SPIRVWriter.cpp | 2 + .../INTEL/SPV_INTEL_task_sequence/sret-get.ll | 11 +- test/instructions/alloca.ll | 184 +++++++++++ 7 files changed, 570 insertions(+), 3 deletions(-) create mode 100644 lib/SPIRV/SPIRVLowerAlloca.cpp create mode 100644 lib/SPIRV/SPIRVLowerAlloca.h create mode 100644 test/instructions/alloca.ll diff --git a/include/LLVMSPIRVLib.h b/include/LLVMSPIRVLib.h index 48834d8be8..6bea3d440b 100644 --- a/include/LLVMSPIRVLib.h +++ b/include/LLVMSPIRVLib.h @@ -56,6 +56,7 @@ void initializeOCLTypeToSPIRVLegacyPass(PassRegistry &); void initializeSPIRVLowerBoolLegacyPass(PassRegistry &); void initializeSPIRVLowerConstExprLegacyPass(PassRegistry &); void initializeSPIRVLowerOCLBlocksLegacyPass(PassRegistry &); +void initializeSPIRVLowerAllocaLegacyPass(PassRegistry &); void initializeSPIRVLowerMemmoveLegacyPass(PassRegistry &); void initializeSPIRVLowerLLVMIntrinsicLegacyPass(PassRegistry &); void initializeSPIRVRegularizeLLVMLegacyPass(PassRegistry &); @@ -218,6 +219,9 @@ ModulePass *createSPIRVLowerConstExprLegacy(); /// Create a pass for removing function pointers related to OCL 2.0 blocks ModulePass *createSPIRVLowerOCLBlocksLegacy(); +/// Create a pass for hoisting allocas to entry block with lifetime intrinsics. +ModulePass *createSPIRVLowerAllocaLegacy(); + /// Create a pass for lowering llvm.memmove to llvm.memcpys with a temporary /// variable. ModulePass *createSPIRVLowerMemmoveLegacy(); diff --git a/lib/SPIRV/CMakeLists.txt b/lib/SPIRV/CMakeLists.txt index 1c32ce7137..e54cd380cb 100644 --- a/lib/SPIRV/CMakeLists.txt +++ b/lib/SPIRV/CMakeLists.txt @@ -10,6 +10,7 @@ set(SRC_LIST OCLUtil.cpp VectorComputeUtil.cpp SPIRVBuiltinHelper.cpp + SPIRVLowerAlloca.cpp SPIRVLowerBitCastToNonStandardType.cpp SPIRVLowerBool.cpp SPIRVLowerConstExpr.cpp diff --git a/lib/SPIRV/SPIRVLowerAlloca.cpp b/lib/SPIRV/SPIRVLowerAlloca.cpp new file mode 100644 index 0000000000..1756ad6de7 --- /dev/null +++ b/lib/SPIRV/SPIRVLowerAlloca.cpp @@ -0,0 +1,292 @@ +//===- SPIRVLowerAlloca.cpp - Hoist allocas to entry block ----------------===// +// +// The LLVM/SPIRV Translator +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +// Copyright (c) 2025 The Khronos Group Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal with the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimers. +// Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimers in the documentation +// and/or other materials provided with the distribution. +// Neither the names of The Khronos Group, nor the names of its +// contributors may be used to endorse or promote products derived from this +// Software without specific prior written permission. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH +// THE SOFTWARE. +// +//===----------------------------------------------------------------------===// +// +// This file implements moving alloca instructions to the entry block and +// inserting appropriate lifetime intrinsics to satisfy SPIR-V requirements +// that all OpVariable instructions must be in the first block of a function. +// +//===----------------------------------------------------------------------===// + +#include "SPIRVLowerAlloca.h" +#include "SPIRVInternal.h" +#include "libSPIRV/SPIRVDebug.h" + +#include "llvm/ADT/SmallVector.h" +#include "llvm/Analysis/PostDominators.h" +#include "llvm/IR/Dominators.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/IntrinsicInst.h" +#include "llvm/IR/PassManager.h" + +#define DEBUG_TYPE "spvalloca" + +using namespace llvm; +using namespace SPIRV; + +namespace SPIRV { + +// Check if there's already a lifetime.start intrinsic for the given alloca. +static bool hasLifetimeStart(AllocaInst *AI) { + for (User *U : AI->users()) { + if (auto *II = dyn_cast(U)) { + if (II->getIntrinsicID() == Intrinsic::lifetime_start) + return true; + } + } + return false; +} + +// Find all basic blocks that use the alloca (directly or through bitcasts/GEPs). +static void findAllocaUsers(Value *V, SmallPtrSetImpl &UserBlocks, + SmallPtrSetImpl &Visited) { + if (!Visited.insert(V).second) + return; + + for (User *U : V->users()) { + if (auto *Inst = dyn_cast(U)) { + UserBlocks.insert(Inst->getParent()); + + // Follow through bitcasts, GEPs, etc. + if (isa(Inst) || isa(Inst) || + isa(Inst)) { + findAllocaUsers(Inst, UserBlocks, Visited); + } + } + } +} + +// Find the best location to insert lifetime.end. +// This should be a block that post-dominates all uses of the alloca. +static BasicBlock *findLifetimeEndLocation(AllocaInst *AI, + BasicBlock *AllocaBB, + Function &F) { + SmallPtrSet UserBlocks; + SmallPtrSet Visited; + + findAllocaUsers(AI, UserBlocks, Visited); + + // If the alloca is only used in its original block, end lifetime there. + if (UserBlocks.size() == 1 && UserBlocks.count(AllocaBB)) { + return AllocaBB; + } + + // Otherwise, we need to find a common post-dominator of all user blocks. + // For simplicity, we'll use the function exit block(s). + // A more sophisticated approach would use post-dominator analysis. + + // Find all return blocks. + SmallVector ReturnBlocks; + for (BasicBlock &BB : F) { + if (isa(BB.getTerminator()) || + isa(BB.getTerminator())) { + ReturnBlocks.push_back(&BB); + } + } + + // If there's a single return block, use it. + if (ReturnBlocks.size() == 1) { + return ReturnBlocks[0]; + } + + // For multiple returns or complex control flow, we'll insert lifetime.end + // at the beginning of each return block. + // Return nullptr to signal we need multiple insertions. + return nullptr; +} + +static bool processFunction(Function &F, LLVMContext *Context) { + if (F.isDeclaration()) + return false; + + BasicBlock &EntryBB = F.getEntryBlock(); + SmallVector AllocasToMove; + + // Find all allocas not in the entry block. + for (BasicBlock &BB : F) { + if (&BB == &EntryBB) + continue; + + for (Instruction &I : BB) { + if (auto *AI = dyn_cast(&I)) { + // Skip VLA. + if (isa(AI->getArraySize())) + AllocasToMove.push_back(AI); + } + } + } + + if (AllocasToMove.empty()) + return false; + + bool Changed = false; + IRBuilder<> Builder(*Context); + + // Find the insertion point in the entry block. + // Place after existing allocas. + Instruction *InsertPoint = &*EntryBB.getFirstInsertionPt(); + for (Instruction &I : EntryBB) { + if (isa(&I)) + InsertPoint = I.getNextNode(); + else + break; + } + + for (AllocaInst *AI : AllocasToMove) { + BasicBlock *OriginalBB = AI->getParent(); + + // Move the alloca to the entry block. + AI->removeFromParent(); + AI->insertBefore(*InsertPoint->getParent(), InsertPoint->getIterator()); + Changed = true; + + // Check if there's already a lifetime.start. + bool HasLifetimeStart = hasLifetimeStart(AI); + + if (!HasLifetimeStart) { + // Insert lifetime.start at the original location. + Builder.SetInsertPoint(OriginalBB, OriginalBB->getFirstInsertionPt()); + + // Calculate size for lifetime intrinsic. + const DataLayout &DL = F.getParent()->getDataLayout(); + Type *AllocatedType = AI->getAllocatedType(); + uint64_t Size = 0; + if (AllocatedType->isSized()) { + Size = DL.getTypeAllocSize(AllocatedType); + if (AI->isArrayAllocation()) { + if (auto *CI = dyn_cast(AI->getArraySize())) { + Size *= CI->getZExtValue(); + } else { + // For dynamic arrays, use -1 (unknown size). + Size = -1ULL; + } + } + } else { + Size = -1ULL; + } + + Builder.CreateLifetimeStart(AI); + } + + // Insert lifetime.end. + BasicBlock *EndBB = findLifetimeEndLocation(AI, OriginalBB, F); + + if (EndBB) { + // Single end location. + Builder.SetInsertPoint(EndBB->getTerminator()); + + const DataLayout &DL = F.getParent()->getDataLayout(); + Type *AllocatedType = AI->getAllocatedType(); + uint64_t Size = 0; + if (AllocatedType->isSized()) { + Size = DL.getTypeAllocSize(AllocatedType); + if (AI->isArrayAllocation()) { + if (auto *CI = dyn_cast(AI->getArraySize())) { + Size *= CI->getZExtValue(); + } else { + Size = -1ULL; + } + } + } else { + Size = -1ULL; + } + + Builder.CreateLifetimeEnd(AI); + } else { + // Multiple return blocks - insert before each return. + const DataLayout &DL = F.getParent()->getDataLayout(); + Type *AllocatedType = AI->getAllocatedType(); + uint64_t Size = 0; + if (AllocatedType->isSized()) { + Size = DL.getTypeAllocSize(AllocatedType); + if (AI->isArrayAllocation()) { + if (auto *CI = dyn_cast(AI->getArraySize())) { + Size *= CI->getZExtValue(); + } else { + Size = -1ULL; + } + } + } else { + Size = -1ULL; + } + + for (BasicBlock &BB : F) { + Instruction *Term = BB.getTerminator(); + if (isa(Term) || isa(Term)) { + Builder.SetInsertPoint(Term); + Builder.CreateLifetimeEnd(AI); + } + } + } + } + + return Changed; +} + +bool SPIRVLowerAllocaBase::runLowerAlloca(Module &M) { + Context = &M.getContext(); + bool Changed = false; + + for (Function &F : M) { + Changed |= processFunction(F, Context); + } + + verifyRegularizationPass(M, "SPIRVLowerAlloca"); + return Changed; +} + +PreservedAnalyses SPIRVLowerAllocaPass::run(Module &M, + ModuleAnalysisManager &MAM) { + return runLowerAlloca(M) ? PreservedAnalyses::none() + : PreservedAnalyses::all(); +} + +SPIRVLowerAllocaLegacy::SPIRVLowerAllocaLegacy() : ModulePass(ID) {} + +bool SPIRVLowerAllocaLegacy::runOnModule(Module &M) { + return runLowerAlloca(M); +} + +char SPIRVLowerAllocaLegacy::ID = 0; + +} // namespace SPIRV + +using namespace SPIRV; + +INITIALIZE_PASS(SPIRVLowerAllocaLegacy, "spvalloca", + "Hoist allocas to entry block with lifetime intrinsics", false, false) + +ModulePass *llvm::createSPIRVLowerAllocaLegacy() { + return new SPIRVLowerAllocaLegacy(); +} diff --git a/lib/SPIRV/SPIRVLowerAlloca.h b/lib/SPIRV/SPIRVLowerAlloca.h new file mode 100644 index 0000000000..867a46e44d --- /dev/null +++ b/lib/SPIRV/SPIRVLowerAlloca.h @@ -0,0 +1,79 @@ +//===- SPIRVLowerAlloca.h - Alloca hoisting --------*- C++ -*-===// +// +// The LLVM/SPIR-V Translator +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +// Copyright (c) 2025 The Khronos Group Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal with the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimers. +// Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimers in the documentation +// and/or other materials provided with the distribution. +// Neither the names of The Khronos Group, nor the names of its +// contributors may be used to endorse or promote products derived from this +// Software without specific prior written permission. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH +// THE SOFTWARE. +// +//===----------------------------------------------------------------------===// +// +// This file implements moving alloca instructions to the entry block and +// inserting appropriate lifetime intrinsics. +// +//===----------------------------------------------------------------------===// + +#ifndef SPIRV_SPIRVLOWERALLOCA_H +#define SPIRV_SPIRVLOWERALLOCA_H + +#include "llvm/IR/Module.h" +#include "llvm/IR/PassManager.h" +#include "llvm/Pass.h" + +namespace SPIRV { + +class SPIRVLowerAllocaBase { +public: + SPIRVLowerAllocaBase() : Context(nullptr) {} + + bool runLowerAlloca(llvm::Module &M); + +private: + llvm::LLVMContext *Context; +}; + +class SPIRVLowerAllocaPass : public llvm::PassInfoMixin, + public SPIRVLowerAllocaBase { +public: + llvm::PreservedAnalyses run(llvm::Module &M, + llvm::ModuleAnalysisManager &MAM); + + static bool isRequired() { return true; } +}; + +class SPIRVLowerAllocaLegacy : public llvm::ModulePass, + public SPIRVLowerAllocaBase { +public: + SPIRVLowerAllocaLegacy(); + bool runOnModule(llvm::Module &M) override; + + static char ID; +}; + +} // namespace SPIRV + +#endif // SPIRV_SPIRVLOWERALLOCA_H diff --git a/lib/SPIRV/SPIRVWriter.cpp b/lib/SPIRV/SPIRVWriter.cpp index db8c510d7c..3caaa29a1d 100644 --- a/lib/SPIRV/SPIRVWriter.cpp +++ b/lib/SPIRV/SPIRVWriter.cpp @@ -51,6 +51,7 @@ #include "SPIRVInstruction.h" #include "SPIRVInternal.h" #include "SPIRVLLVMUtil.h" +#include "SPIRVLowerAlloca.h" #include "SPIRVLowerBitCastToNonStandardType.h" #include "SPIRVLowerBool.h" #include "SPIRVLowerConstExpr.h" @@ -7253,6 +7254,7 @@ void addPassesForSPIRV(ModulePassManager &PassMgr, PassMgr.addPass(SPIRVLowerOCLBlocksPass()); PassMgr.addPass(OCLToSPIRVPass()); PassMgr.addPass(SPIRVRegularizeLLVMPass()); + PassMgr.addPass(SPIRVLowerAllocaPass()); PassMgr.addPass(SPIRVLowerConstExprPass()); PassMgr.addPass(SPIRVLowerBoolPass()); PassMgr.addPass(SPIRVLowerMemmovePass()); diff --git a/test/extensions/INTEL/SPV_INTEL_task_sequence/sret-get.ll b/test/extensions/INTEL/SPV_INTEL_task_sequence/sret-get.ll index 19b9953225..349242f331 100644 --- a/test/extensions/INTEL/SPV_INTEL_task_sequence/sret-get.ll +++ b/test/extensions/INTEL/SPV_INTEL_task_sequence/sret-get.ll @@ -8,8 +8,8 @@ ; RUN: llvm-dis %t.rev.bc ; RUN: FileCheck < %t.rev.ll %s --check-prefix=CHECK-LLVM -; CHECK-SPIRV: TypeTaskSequenceINTEL [[#TypeTS:]] -; CHECK-SPIRV: TypeStruct [[#TypeStruct:]] +; CHECK-SPIRV-DAG: TypeTaskSequenceINTEL [[#TypeTS:]] +; CHECK-SPIRV-DAG: TypeStruct [[#TypeStruct:]] ; CHECK-SPIRV: TaskSequenceCreateINTEL [[#TypeTS]] [[#TSCreateId:]] ; CHECK-SPIRV: TaskSequenceGetINTEL [[#TypeStruct]] [[#Get:]] [[#TSCreateId]] @@ -17,10 +17,15 @@ ; CHECK-LLVM: %struct.FunctionPacket = type <{ float, i8, [3 x i8] }> -; CHECK-LLVM: %[[TSCreate:[a-z0-9.]+]] = call spir_func target("spirv.TaskSequenceINTEL") @_Z66__spirv_TaskSequenceCreateINTEL_RPU3AS125__spirv_TaskSequenceINTELPiiiii(ptr @_Z4multii, i32 -1, i32 -1, i32 1, i32 32) +; Alloca is moved to entry block by SPIRVLowerAlloca pass +; CHECK-LLVM: entry: ; CHECK-LLVM: %[[Var:[a-z0-9.]+]] = alloca %struct.FunctionPacket +; CHECK-LLVM: %[[TSCreate:[a-z0-9.]+]] = call spir_func target("spirv.TaskSequenceINTEL") @_Z66__spirv_TaskSequenceCreateINTEL_RPU3AS125__spirv_TaskSequenceINTELPiiiii(ptr @_Z4multii, i32 -1, i32 -1, i32 1, i32 32) +; Lifetime intrinsics wrap usage in the loop +; CHECK-LLVM: call void @llvm.lifetime.start.p0(ptr %[[Var]]) ; CHECK-LLVM: %[[Ptr:[a-z0-9.]+]] = addrspacecast ptr %[[Var]] to ptr addrspace(4) ; CHECK-LLVM: call spir_func void @_Z28__spirv_TaskSequenceGetINTEL{{.*}}(ptr addrspace(4) sret(%struct.FunctionPacket) %[[Ptr]], target("spirv.TaskSequenceINTEL") %[[TSCreate]]) +; CHECK-LLVM: call void @llvm.lifetime.end.p0(ptr %[[Var]]) 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-unknown-unknown" diff --git a/test/instructions/alloca.ll b/test/instructions/alloca.ll new file mode 100644 index 0000000000..d36479e798 --- /dev/null +++ b/test/instructions/alloca.ll @@ -0,0 +1,184 @@ +; RUN: llvm-spirv %s -o %t.spv +; RUN: spirv-val %t.spv +; RUN: llvm-spirv %s -spirv-text -o - | FileCheck %s --check-prefix=CHECK-SPIRV +; RUN: llvm-spirv -r %t.spv -o - | llvm-dis -o - | FileCheck %s --check-prefix=CHECK-LLVM + +; This test checks that alloca instructions not in the entry block are hoisted +; to the entry block with appropriate lifetime intrinsics, as required by SPIR-V. +; +; Test scenarios: +; 1. Simple case: alloca in non-entry block +; 2. Multiple allocas in different non-entry blocks +; 3. Alloca with multiple return points +; 4. Alloca already in entry block (should not be modified) + +target datalayout = "e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-G1" +target triple = "spir64-unknown-unknown" + +; All Name declarations appear together at the top +; CHECK-SPIRV-DAG: Name [[F:[0-9]+]] "simple_case" +; CHECK-SPIRV-DAG: Name [[CONVERSION:[0-9]+]] "conversion" +; CHECK-SPIRV-DAG: Name [[TOP:[0-9]+]] "top" +; CHECK-SPIRV-DAG: Name [[AS:[0-9]+]] "As" +; CHECK-SPIRV-DAG: Name [[MULTI:[0-9]+]] "test_multiple_allocas" +; CHECK-SPIRV-DAG: Name [[ENTRY:[0-9]+]] "entry" +; CHECK-SPIRV-DAG: Name [[BB1:[0-9]+]] "bb1" +; CHECK-SPIRV-DAG: Name [[BB2:[0-9]+]] "bb2" +; CHECK-SPIRV-DAG: Name [[EXIT:[0-9]+]] "exit" +; CHECK-SPIRV-DAG: Name [[VAR1:[0-9]+]] "var1" +; CHECK-SPIRV-DAG: Name [[VAR2:[0-9]+]] "var2" +; CHECK-SPIRV-DAG: Name [[MULTI_RET:[0-9]+]] "test_multiple_returns" +; CHECK-SPIRV-DAG: Name [[VAR:[0-9]+]] "var" +; CHECK-SPIRV-DAG: Name [[NO_MOVE:[0-9]+]] "test_entry_alloca" +; CHECK-SPIRV-DAG: Name [[VAR_ENTRY:[0-9]+]] "var_entry" + +; CHECK-SPIRV: TypeInt [[I64:[0-9]+]] 64 0 +; CHECK-SPIRV: Constant [[I64]] [[CONST1:[0-9]+]] 1 0 +; CHECK-SPIRV: TypeArray [[ARR:[0-9]+]] [[I64]] [[CONST1]] +; CHECK-SPIRV: TypePointer [[PTR:[0-9]+]] 7 [[ARR]] + +; Test 1: Simple case - alloca in non-entry block +; CHECK-SPIRV: Function {{[0-9]+}} [[F]] 0 +; CHECK-SPIRV: Label [[CONVERSION]] +; Verify OpVariable is in the first block (entry block) +; CHECK-SPIRV-NEXT: Variable [[PTR]] [[AS]] 7 +; CHECK-SPIRV-NEXT: Branch [[TOP]] +; CHECK-SPIRV: Label [[TOP]] +; Verify lifetime intrinsics are inserted in the original block +; CHECK-SPIRV-NEXT: LifetimeStart [[AS]] 0 +; CHECK-SPIRV-NEXT: LifetimeStop [[AS]] 0 +; CHECK-SPIRV-NEXT: Return + +; CHECK-LLVM: define spir_kernel void @simple_case() +; CHECK-LLVM: conversion: +; Verify alloca is moved to entry block +; CHECK-LLVM-NEXT: %As = alloca [1 x i64], align 8 +; CHECK-LLVM-NEXT: br label %top +; CHECK-LLVM: top: +; Verify lifetime intrinsics are in the original block +; CHECK-LLVM-NEXT: call void @llvm.lifetime.start.p0(ptr %As) +; CHECK-LLVM-NEXT: call void @llvm.lifetime.end.p0(ptr %As) +; CHECK-LLVM-NEXT: ret void + +define spir_kernel void @simple_case() { +conversion: + br label %top + +top: ; preds = %conversion + %As = alloca [1 x i64], align 8 + ret void +} + +; Test 2: Multiple allocas in different non-entry blocks +; CHECK-SPIRV: Function {{[0-9]+}} [[MULTI]] +; CHECK-SPIRV: Label [[ENTRY]] +; Both variables should be in entry block +; CHECK-SPIRV: Variable {{[0-9]+}} [[VAR1]] 7 +; CHECK-SPIRV-NEXT: Variable {{[0-9]+}} [[VAR2]] 7 + +; CHECK-SPIRV: Label [[BB1]] +; Lifetime start/end for var1 wraps usage in its original block +; CHECK-SPIRV: LifetimeStart [[VAR1]] +; CHECK-SPIRV: LifetimeStop [[VAR1]] + +; CHECK-SPIRV: Label [[BB2]] +; Lifetime start/end for var2 wraps usage in its original block +; CHECK-SPIRV: LifetimeStart [[VAR2]] +; CHECK-SPIRV: LifetimeStop [[VAR2]] + +; CHECK-SPIRV: Label [[EXIT]] +; No lifetime intrinsics at final return +; CHECK-SPIRV: Return + +; CHECK-LLVM: define spir_kernel void @test_multiple_allocas(i32 %cond) +; CHECK-LLVM: entry: +; Both allocas moved to entry +; CHECK-LLVM: %var1 = alloca i32, align 4 +; CHECK-LLVM-NEXT: %var2 = alloca i64, align 8 +; CHECK-LLVM: bb1: +; Lifetime intrinsics wrap usage in original block +; CHECK-LLVM: call void @llvm.lifetime.start.p0(ptr %var1) +; CHECK-LLVM: call void @llvm.lifetime.end.p0(ptr %var1) +; CHECK-LLVM: bb2: +; CHECK-LLVM: call void @llvm.lifetime.start.p0(ptr %var2) +; CHECK-LLVM: call void @llvm.lifetime.end.p0(ptr %var2) +; CHECK-LLVM: exit: +; CHECK-LLVM: ret void + +define spir_kernel void @test_multiple_allocas(i32 %cond) { +entry: + %cmp = icmp eq i32 %cond, 0 + br i1 %cmp, label %bb1, label %bb2 + +bb1: + %var1 = alloca i32, align 4 + store i32 42, ptr %var1, align 4 + br label %exit + +bb2: + %var2 = alloca i64, align 8 + store i64 123, ptr %var2, align 8 + br label %exit + +exit: + ret void +} + +; Test 3: Alloca with multiple return points +; CHECK-SPIRV: Function {{[0-9]+}} [[MULTI_RET]] +; CHECK-SPIRV: Label {{[0-9]+}} +; Variable in entry block +; CHECK-SPIRV-NEXT: Variable {{[0-9]+}} [[VAR]] 7 + +; Lifetime intrinsics only in the block where alloca was originally used +; CHECK-SPIRV: LifetimeStart [[VAR]] +; CHECK-SPIRV: LifetimeStop [[VAR]] +; CHECK-SPIRV-NEXT: Return +; The else block has no lifetime intrinsics (alloca wasn't used there) +; CHECK-SPIRV: Return + +; CHECK-LLVM: define spir_kernel void @test_multiple_returns(i32 %cond) +; CHECK-LLVM: entry: +; CHECK-LLVM: %var = alloca i32, align 4 +; CHECK-LLVM: then: +; Lifetime intrinsics wrap usage in the original block +; CHECK-LLVM: call void @llvm.lifetime.start.p0(ptr %var) +; CHECK-LLVM: call void @llvm.lifetime.end.p0(ptr %var) +; CHECK-LLVM-NEXT: ret void +; CHECK-LLVM: else: +; No lifetime intrinsics in else (alloca wasn't originally here) +; CHECK-LLVM-NEXT: ret void + +define spir_kernel void @test_multiple_returns(i32 %cond) { +entry: + %cmp = icmp eq i32 %cond, 0 + br i1 %cmp, label %then, label %else + +then: + %var = alloca i32, align 4 + store i32 1, ptr %var, align 4 + ret void + +else: + ret void +} + +; Test 4: Alloca already in entry block (should not be modified) +; CHECK-SPIRV: Function {{[0-9]+}} [[NO_MOVE]] +; CHECK-SPIRV: Label {{[0-9]+}} +; CHECK-SPIRV-NEXT: Variable {{[0-9]+}} [[VAR_ENTRY]] 7 +; No lifetime intrinsics should be added if already in entry +; CHECK-SPIRV-NOT: LifetimeStart [[VAR_ENTRY]] + +; CHECK-LLVM: define spir_kernel void @test_entry_alloca() +; CHECK-LLVM: entry: +; CHECK-LLVM-NEXT: %var_entry = alloca i32, align 4 +; No lifetime intrinsics added +; CHECK-LLVM-NOT: call void @llvm.lifetime.start.p0(ptr %var_entry) + +define spir_kernel void @test_entry_alloca() { +entry: + %var_entry = alloca i32, align 4 + store i32 5, ptr %var_entry, align 4 + ret void +} From d21f221bc875cb4457defcce20bbfaea6b177a3d Mon Sep 17 00:00:00 2001 From: "Sidorov, Dmitry" Date: Thu, 30 Oct 2025 07:31:56 -0700 Subject: [PATCH 2/2] wip Signed-off-by: Sidorov, Dmitry --- lib/SPIRV/SPIRVLowerAlloca.cpp | 15 ++++++++------- lib/SPIRV/SPIRVLowerAlloca.h | 4 ++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/SPIRV/SPIRVLowerAlloca.cpp b/lib/SPIRV/SPIRVLowerAlloca.cpp index 1756ad6de7..10b4529b31 100644 --- a/lib/SPIRV/SPIRVLowerAlloca.cpp +++ b/lib/SPIRV/SPIRVLowerAlloca.cpp @@ -68,7 +68,8 @@ static bool hasLifetimeStart(AllocaInst *AI) { return false; } -// Find all basic blocks that use the alloca (directly or through bitcasts/GEPs). +// Find all basic blocks that use the alloca (directly or through +// bitcasts/GEPs). static void findAllocaUsers(Value *V, SmallPtrSetImpl &UserBlocks, SmallPtrSetImpl &Visited) { if (!Visited.insert(V).second) @@ -89,9 +90,8 @@ static void findAllocaUsers(Value *V, SmallPtrSetImpl &UserBlocks, // Find the best location to insert lifetime.end. // This should be a block that post-dominates all uses of the alloca. -static BasicBlock *findLifetimeEndLocation(AllocaInst *AI, - BasicBlock *AllocaBB, - Function &F) { +static BasicBlock *findLifetimeEndLocation(AllocaInst *AI, BasicBlock *AllocaBB, + Function &F) { SmallPtrSet UserBlocks; SmallPtrSet Visited; @@ -140,8 +140,8 @@ static bool processFunction(Function &F, LLVMContext *Context) { for (Instruction &I : BB) { if (auto *AI = dyn_cast(&I)) { - // Skip VLA. - if (isa(AI->getArraySize())) + // Skip VLA. + if (isa(AI->getArraySize())) AllocasToMove.push_back(AI); } } @@ -285,7 +285,8 @@ char SPIRVLowerAllocaLegacy::ID = 0; using namespace SPIRV; INITIALIZE_PASS(SPIRVLowerAllocaLegacy, "spvalloca", - "Hoist allocas to entry block with lifetime intrinsics", false, false) + "Hoist allocas to entry block with lifetime intrinsics", false, + false) ModulePass *llvm::createSPIRVLowerAllocaLegacy() { return new SPIRVLowerAllocaLegacy(); diff --git a/lib/SPIRV/SPIRVLowerAlloca.h b/lib/SPIRV/SPIRVLowerAlloca.h index 867a46e44d..8461fd42db 100644 --- a/lib/SPIRV/SPIRVLowerAlloca.h +++ b/lib/SPIRV/SPIRVLowerAlloca.h @@ -57,7 +57,7 @@ class SPIRVLowerAllocaBase { }; class SPIRVLowerAllocaPass : public llvm::PassInfoMixin, - public SPIRVLowerAllocaBase { + public SPIRVLowerAllocaBase { public: llvm::PreservedAnalyses run(llvm::Module &M, llvm::ModuleAnalysisManager &MAM); @@ -66,7 +66,7 @@ class SPIRVLowerAllocaPass : public llvm::PassInfoMixin, }; class SPIRVLowerAllocaLegacy : public llvm::ModulePass, - public SPIRVLowerAllocaBase { + public SPIRVLowerAllocaBase { public: SPIRVLowerAllocaLegacy(); bool runOnModule(llvm::Module &M) override;