From c08eaddde6be2376c8d3e14345be79f6da0f8665 Mon Sep 17 00:00:00 2001 From: Jeroen Dobbelaere Date: Mon, 14 Jun 2021 14:52:29 +0200 Subject: [PATCH] Intrinsic::getName: require a Module argument Ensure that we provide a `Module` when checking if a rename of an intrinsic is necessary. This fixes the issue that was detected by https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=32288 (as mentioned by @fhahn), after committing D91250. Note that the `LLVMIntrinsicCopyOverloadedName` is being deprecated in favor of `LLVMIntrinsicCopyOverloadedName2`. Reviewed By: nikic Differential Revision: https://reviews.llvm.org/D99173 --- docs/ReleaseNotes.rst | 4 ++ include/llvm-c/Core.h | 16 +++-- include/llvm/IR/Intrinsics.h | 28 ++++---- lib/CodeGen/MachineOperand.cpp | 2 +- lib/CodeGen/ReplaceWithVeclib.cpp | 2 +- .../SelectionDAG/SelectionDAGDumper.cpp | 2 +- lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp | 2 +- lib/IR/AutoUpgrade.cpp | 22 ++++--- lib/IR/Core.cpp | 13 +++- lib/IR/Function.cpp | 42 ++++++++---- .../Scalar/LowerMatrixIntrinsics.cpp | 2 +- test/Assembler/auto_upgrade_intrinsics.ll | 64 +++++++++++++++++-- 12 files changed, 151 insertions(+), 48 deletions(-) diff --git a/docs/ReleaseNotes.rst b/docs/ReleaseNotes.rst index 98117a24fce..bb88699fc76 100644 --- a/docs/ReleaseNotes.rst +++ b/docs/ReleaseNotes.rst @@ -121,6 +121,10 @@ Changes to the OCaml bindings Changes to the C API -------------------- +* The C API function ``LLVMIntrinsicCopyOverloadedName`` has been deprecated. + Please migrate to ``LLVMIntrinsicCopyOverloadedName2`` which takes an extra + module argument and which also handles unnamed types. + ('D99173' '_) Changes to the Go bindings -------------------------- diff --git a/include/llvm-c/Core.h b/include/llvm-c/Core.h index 7a17b694317..1a5e763cfc6 100644 --- a/include/llvm-c/Core.h +++ b/include/llvm-c/Core.h @@ -2514,6 +2514,12 @@ LLVMTypeRef LLVMIntrinsicGetType(LLVMContextRef Ctx, unsigned ID, */ const char *LLVMIntrinsicGetName(unsigned ID, size_t *NameLength); +/** Deprecated: Use LLVMIntrinsicCopyOverloadedName2 instead. */ +const char *LLVMIntrinsicCopyOverloadedName(unsigned ID, + LLVMTypeRef *ParamTypes, + size_t ParamCount, + size_t *NameLength); + /** * Copies the name of an overloaded intrinsic identified by a given list of * parameter types. @@ -2521,12 +2527,14 @@ const char *LLVMIntrinsicGetName(unsigned ID, size_t *NameLength); * Unlike LLVMIntrinsicGetName, the caller is responsible for freeing the * returned string. * + * This version also supports unnamed types. + * * @see llvm::Intrinsic::getName() */ -const char *LLVMIntrinsicCopyOverloadedName(unsigned ID, - LLVMTypeRef *ParamTypes, - size_t ParamCount, - size_t *NameLength); +const char *LLVMIntrinsicCopyOverloadedName2(LLVMModuleRef Mod, unsigned ID, + LLVMTypeRef *ParamTypes, + size_t ParamCount, + size_t *NameLength); /** * Obtain if the intrinsic identified by the given ID is overloaded. diff --git a/include/llvm/IR/Intrinsics.h b/include/llvm/IR/Intrinsics.h index ae84ee8f354..15275001ab4 100644 --- a/include/llvm/IR/Intrinsics.h +++ b/include/llvm/IR/Intrinsics.h @@ -55,21 +55,23 @@ namespace Intrinsic { /// version of getName if overloads are required. StringRef getName(ID id); - /// Return the LLVM name for an intrinsic, such as "llvm.ppc.altivec.lvx". - /// Note, this version of getName supports overloads, but not unnamed types. - /// It is less efficient than the StringRef version of this function. If no - /// overloads are required, it is safe to use this version, but better to use - /// the StringRef version. - std::string getName(ID Id, ArrayRef Tys); + /// Return the LLVM name for an intrinsic, without encoded types for + /// overloading, such as "llvm.ssa.copy". + StringRef getBaseName(ID id); - /// Return the LLVM name for an intrinsic, such as "llvm.ssa.copy.p0s_s.1". - /// Note, this version of getName supports overloads and unnamed types, but is - /// less efficient than the StringRef version of this function. If no + /// Return the LLVM name for an intrinsic, such as "llvm.ppc.altivec.lvx" or + /// "llvm.ssa.copy.p0s_s.1". Note, this version of getName supports overloads. + /// This is less efficient than the StringRef version of this function. If no /// overloads are required, it is safe to use this version, but better to use - /// the StringRef version. A function type FT can be provided to avoid - /// computing it. It is used (or computed) if one of the types is based on an - /// unnamed type. - std::string getName(ID Id, ArrayRef Tys, Module *M, FunctionType *FT); + /// the StringRef version. If one of the types is based on an unnamed type, a + /// function type will be computed. Providing FT will avoid this computation. + std::string getName(ID Id, ArrayRef Tys, Module *M, + FunctionType *FT = nullptr); + + /// Return the LLVM name for an intrinsic. This is a special version only to + /// be used by LLVMIntrinsicCopyOverloadedName. It only supports overloads + /// based on named types. + std::string getNameNoUnnamedTypes(ID Id, ArrayRef Tys); /// Return the function type for an intrinsic. FunctionType *getType(LLVMContext &Context, ID id, diff --git a/lib/CodeGen/MachineOperand.cpp b/lib/CodeGen/MachineOperand.cpp index c0ae0d27f42..145423a2ad8 100644 --- a/lib/CodeGen/MachineOperand.cpp +++ b/lib/CodeGen/MachineOperand.cpp @@ -935,7 +935,7 @@ void MachineOperand::print(raw_ostream &OS, ModuleSlotTracker &MST, case MachineOperand::MO_IntrinsicID: { Intrinsic::ID ID = getIntrinsicID(); if (ID < Intrinsic::num_intrinsics) - OS << "intrinsic(@" << Intrinsic::getName(ID, None) << ')'; + OS << "intrinsic(@" << Intrinsic::getBaseName(ID) << ')'; else if (IntrinsicInfo) OS << "intrinsic(@" << IntrinsicInfo->getName(ID) << ')'; else diff --git a/lib/CodeGen/ReplaceWithVeclib.cpp b/lib/CodeGen/ReplaceWithVeclib.cpp index 139860bcbf1..1619381967c 100644 --- a/lib/CodeGen/ReplaceWithVeclib.cpp +++ b/lib/CodeGen/ReplaceWithVeclib.cpp @@ -142,7 +142,7 @@ static bool replaceWithCallToVeclib(const TargetLibraryInfo &TLI, // converted to scalar above. std::string ScalarName; if (Intrinsic::isOverloaded(IntrinsicID)) { - ScalarName = Intrinsic::getName(IntrinsicID, ScalarTypes); + ScalarName = Intrinsic::getName(IntrinsicID, ScalarTypes, CI.getModule()); } else { ScalarName = Intrinsic::getName(IntrinsicID).str(); } diff --git a/lib/CodeGen/SelectionDAG/SelectionDAGDumper.cpp b/lib/CodeGen/SelectionDAG/SelectionDAGDumper.cpp index 7bba8d30eb3..73c207e589f 100644 --- a/lib/CodeGen/SelectionDAG/SelectionDAGDumper.cpp +++ b/lib/CodeGen/SelectionDAG/SelectionDAGDumper.cpp @@ -145,7 +145,7 @@ std::string SDNode::getOperationName(const SelectionDAG *G) const { unsigned OpNo = getOpcode() == ISD::INTRINSIC_WO_CHAIN ? 0 : 1; unsigned IID = cast(getOperand(OpNo))->getZExtValue(); if (IID < Intrinsic::num_intrinsics) - return Intrinsic::getName((Intrinsic::ID)IID, None); + return Intrinsic::getBaseName((Intrinsic::ID)IID).str(); else if (!G) return "Unknown intrinsic"; else if (const TargetIntrinsicInfo *TII = G->getTarget().getIntrinsicInfo()) diff --git a/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp b/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp index 3d035e87592..e049be94fcf 100644 --- a/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp +++ b/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp @@ -3777,7 +3777,7 @@ void SelectionDAGISel::CannotYetSelect(SDNode *N) { unsigned iid = cast(N->getOperand(HasInputChain))->getZExtValue(); if (iid < Intrinsic::num_intrinsics) - Msg << "intrinsic %" << Intrinsic::getName((Intrinsic::ID)iid, None); + Msg << "intrinsic %" << Intrinsic::getBaseName((Intrinsic::ID)iid); else if (const TargetIntrinsicInfo *TII = TM.getIntrinsicInfo()) Msg << "target intrinsic %" << TII->getName(iid); else diff --git a/lib/IR/AutoUpgrade.cpp b/lib/IR/AutoUpgrade.cpp index 7e14a075fc3..851e8e45f47 100644 --- a/lib/IR/AutoUpgrade.cpp +++ b/lib/IR/AutoUpgrade.cpp @@ -778,7 +778,7 @@ static bool UpgradeIntrinsicFunction1(Function *F, Function *&NewFn) { Intrinsic::lifetime_start : Intrinsic::invariant_start; auto Args = F->getFunctionType()->params(); Type* ObjectPtr[1] = {Args[1]}; - if (F->getName() != Intrinsic::getName(ID, ObjectPtr)) { + if (F->getName() != Intrinsic::getName(ID, ObjectPtr, F->getParent())) { rename(F); NewFn = Intrinsic::getDeclaration(F->getParent(), ID, ObjectPtr); return true; @@ -792,7 +792,7 @@ static bool UpgradeIntrinsicFunction1(Function *F, Function *&NewFn) { auto Args = F->getFunctionType()->params(); Type* ObjectPtr[1] = {Args[IsLifetimeEnd ? 1 : 2]}; - if (F->getName() != Intrinsic::getName(ID, ObjectPtr)) { + if (F->getName() != Intrinsic::getName(ID, ObjectPtr, F->getParent())) { rename(F); NewFn = Intrinsic::getDeclaration(F->getParent(), ID, ObjectPtr); return true; @@ -814,7 +814,8 @@ static bool UpgradeIntrinsicFunction1(Function *F, Function *&NewFn) { case 'm': { if (Name.startswith("masked.load.")) { Type *Tys[] = { F->getReturnType(), F->arg_begin()->getType() }; - if (F->getName() != Intrinsic::getName(Intrinsic::masked_load, Tys)) { + if (F->getName() != + Intrinsic::getName(Intrinsic::masked_load, Tys, F->getParent())) { rename(F); NewFn = Intrinsic::getDeclaration(F->getParent(), Intrinsic::masked_load, @@ -825,7 +826,8 @@ static bool UpgradeIntrinsicFunction1(Function *F, Function *&NewFn) { if (Name.startswith("masked.store.")) { auto Args = F->getFunctionType()->params(); Type *Tys[] = { Args[0], Args[1] }; - if (F->getName() != Intrinsic::getName(Intrinsic::masked_store, Tys)) { + if (F->getName() != + Intrinsic::getName(Intrinsic::masked_store, Tys, F->getParent())) { rename(F); NewFn = Intrinsic::getDeclaration(F->getParent(), Intrinsic::masked_store, @@ -837,7 +839,8 @@ static bool UpgradeIntrinsicFunction1(Function *F, Function *&NewFn) { // to the new overload which includes an address space if (Name.startswith("masked.gather.")) { Type *Tys[] = {F->getReturnType(), F->arg_begin()->getType()}; - if (F->getName() != Intrinsic::getName(Intrinsic::masked_gather, Tys)) { + if (F->getName() != + Intrinsic::getName(Intrinsic::masked_gather, Tys, F->getParent())) { rename(F); NewFn = Intrinsic::getDeclaration(F->getParent(), Intrinsic::masked_gather, Tys); @@ -847,7 +850,8 @@ static bool UpgradeIntrinsicFunction1(Function *F, Function *&NewFn) { if (Name.startswith("masked.scatter.")) { auto Args = F->getFunctionType()->params(); Type *Tys[] = {Args[0], Args[1]}; - if (F->getName() != Intrinsic::getName(Intrinsic::masked_scatter, Tys)) { + if (F->getName() != + Intrinsic::getName(Intrinsic::masked_scatter, Tys, F->getParent())) { rename(F); NewFn = Intrinsic::getDeclaration(F->getParent(), Intrinsic::masked_scatter, Tys); @@ -928,7 +932,8 @@ static bool UpgradeIntrinsicFunction1(Function *F, Function *&NewFn) { if (Name.startswith("objectsize.")) { Type *Tys[2] = { F->getReturnType(), F->arg_begin()->getType() }; if (F->arg_size() == 2 || F->arg_size() == 3 || - F->getName() != Intrinsic::getName(Intrinsic::objectsize, Tys)) { + F->getName() != + Intrinsic::getName(Intrinsic::objectsize, Tys, F->getParent())) { rename(F); NewFn = Intrinsic::getDeclaration(F->getParent(), Intrinsic::objectsize, Tys); @@ -941,7 +946,8 @@ static bool UpgradeIntrinsicFunction1(Function *F, Function *&NewFn) { if (Name == "prefetch") { // Handle address space overloading. Type *Tys[] = {F->arg_begin()->getType()}; - if (F->getName() != Intrinsic::getName(Intrinsic::prefetch, Tys)) { + if (F->getName() != + Intrinsic::getName(Intrinsic::prefetch, Tys, F->getParent())) { rename(F); NewFn = Intrinsic::getDeclaration(F->getParent(), Intrinsic::prefetch, Tys); diff --git a/lib/IR/Core.cpp b/lib/IR/Core.cpp index 084cb8c71d1..8a7060c148c 100644 --- a/lib/IR/Core.cpp +++ b/lib/IR/Core.cpp @@ -2411,7 +2411,18 @@ const char *LLVMIntrinsicCopyOverloadedName(unsigned ID, size_t *NameLength) { auto IID = llvm_map_to_intrinsic_id(ID); ArrayRef Tys(unwrap(ParamTypes), ParamCount); - auto Str = llvm::Intrinsic::getName(IID, Tys); + auto Str = llvm::Intrinsic::getNameNoUnnamedTypes(IID, Tys); + *NameLength = Str.length(); + return strdup(Str.c_str()); +} + +const char *LLVMIntrinsicCopyOverloadedName2(LLVMModuleRef Mod, unsigned ID, + LLVMTypeRef *ParamTypes, + size_t ParamCount, + size_t *NameLength) { + auto IID = llvm_map_to_intrinsic_id(ID); + ArrayRef Tys(unwrap(ParamTypes), ParamCount); + auto Str = llvm::Intrinsic::getName(IID, Tys, unwrap(Mod)); *NameLength = Str.length(); return strdup(Str.c_str()); } diff --git a/lib/IR/Function.cpp b/lib/IR/Function.cpp index 25c59d03c7b..8f1148d461e 100644 --- a/lib/IR/Function.cpp +++ b/lib/IR/Function.cpp @@ -831,37 +831,53 @@ static std::string getMangledTypeStr(Type *Ty, bool &HasUnnamedType) { return Result; } +StringRef Intrinsic::getBaseName(ID id) { + assert(id < num_intrinsics && "Invalid intrinsic ID!"); + return IntrinsicNameTable[id]; +} + StringRef Intrinsic::getName(ID id) { assert(id < num_intrinsics && "Invalid intrinsic ID!"); assert(!Intrinsic::isOverloaded(id) && "This version of getName does not support overloading"); - return IntrinsicNameTable[id]; + return getBaseName(id); } -std::string Intrinsic::getName(ID Id, ArrayRef Tys, Module *M, - FunctionType *FT) { - assert(Id < num_intrinsics && "Invalid intrinsic ID!"); +static std::string getIntrinsicNameImpl(Intrinsic::ID Id, ArrayRef Tys, + Module *M, FunctionType *FT, + bool EarlyModuleCheck) { + + assert(Id < Intrinsic::num_intrinsics && "Invalid intrinsic ID!"); assert((Tys.empty() || Intrinsic::isOverloaded(Id)) && "This version of getName is for overloaded intrinsics only"); + (void)EarlyModuleCheck; + assert((!EarlyModuleCheck || M || + !any_of(Tys, [](Type *T) { return isa(T); })) && + "Intrinsic overloading on pointer types need to provide a Module"); bool HasUnnamedType = false; - std::string Result(IntrinsicNameTable[Id]); - for (Type *Ty : Tys) { + std::string Result(Intrinsic::getBaseName(Id)); + for (Type *Ty : Tys) Result += "." + getMangledTypeStr(Ty, HasUnnamedType); - } - assert((M || !HasUnnamedType) && "unnamed types need a module"); - if (M && HasUnnamedType) { + if (HasUnnamedType) { + assert(M && "unnamed types need a module"); if (!FT) - FT = getType(M->getContext(), Id, Tys); + FT = Intrinsic::getType(M->getContext(), Id, Tys); else - assert((FT == getType(M->getContext(), Id, Tys)) && + assert((FT == Intrinsic::getType(M->getContext(), Id, Tys)) && "Provided FunctionType must match arguments"); return M->getUniqueIntrinsicName(Result, Id, FT); } return Result; } -std::string Intrinsic::getName(ID Id, ArrayRef Tys) { - return getName(Id, Tys, nullptr, nullptr); +std::string Intrinsic::getName(ID Id, ArrayRef Tys, Module *M, + FunctionType *FT) { + assert(M && "We need to have a Module"); + return getIntrinsicNameImpl(Id, Tys, M, FT, true); +} + +std::string Intrinsic::getNameNoUnnamedTypes(ID Id, ArrayRef Tys) { + return getIntrinsicNameImpl(Id, Tys, nullptr, nullptr, false); } /// IIT_Info - These are enumerators that describe the entries returned by the diff --git a/lib/Transforms/Scalar/LowerMatrixIntrinsics.cpp b/lib/Transforms/Scalar/LowerMatrixIntrinsics.cpp index 088cbf4210e..ab27e5b9c3e 100644 --- a/lib/Transforms/Scalar/LowerMatrixIntrinsics.cpp +++ b/lib/Transforms/Scalar/LowerMatrixIntrinsics.cpp @@ -1841,7 +1841,7 @@ public: return; } IntrinsicInst *II = dyn_cast(CI); - write(StringRef(Intrinsic::getName(II->getIntrinsicID(), {})) + write(Intrinsic::getBaseName(II->getIntrinsicID()) .drop_front(StringRef("llvm.matrix.").size())); write("."); std::string Tmp; diff --git a/test/Assembler/auto_upgrade_intrinsics.ll b/test/Assembler/auto_upgrade_intrinsics.ll index a847787a32e..4758c7e1348 100644 --- a/test/Assembler/auto_upgrade_intrinsics.ll +++ b/test/Assembler/auto_upgrade_intrinsics.ll @@ -2,15 +2,14 @@ ; RUN: llvm-as < %s | llvm-dis | FileCheck %s ; RUN: verify-uselistorder %s +%0 = type opaque; + declare i8 @llvm.ctlz.i8(i8) declare i16 @llvm.ctlz.i16(i16) declare i32 @llvm.ctlz.i32(i32) declare i42 @llvm.ctlz.i42(i42) ; Not a power-of-2 -declare i32 @llvm.objectsize.i32(i8*, i1) nounwind readonly - - define void @test.ctlz(i8 %a, i16 %b, i32 %c, i42 %d) { ; CHECK: @test.ctlz @@ -51,6 +50,7 @@ entry: @a = private global [60 x i8] zeroinitializer, align 1 +declare i32 @llvm.objectsize.i32(i8*, i1) nounwind readonly define i32 @test.objectsize() { ; CHECK-LABEL: @test.objectsize( ; CHECK: @llvm.objectsize.i32.p0i8(i8* getelementptr inbounds ([60 x i8], [60 x i8]* @a, i32 0, i32 0), i1 false, i1 false, i1 false) @@ -66,6 +66,24 @@ define i64 @test.objectsize.2() { ret i64 %s } +@u = private global [60 x %0*] zeroinitializer, align 1 + +declare i32 @llvm.objectsize.i32.unnamed(%0**, i1) nounwind readonly +define i32 @test.objectsize.unnamed() { +; CHECK-LABEL: @test.objectsize.unnamed( +; CHECK: @llvm.objectsize.i32.p0p0s_s.0(%0** getelementptr inbounds ([60 x %0*], [60 x %0*]* @u, i32 0, i32 0), i1 false, i1 false, i1 false) + %s = call i32 @llvm.objectsize.i32.unnamed(%0** getelementptr inbounds ([60 x %0*], [60 x %0*]* @u, i32 0, i32 0), i1 false) + ret i32 %s +} + +declare i64 @llvm.objectsize.i64.p0p0s_s.0(%0**, i1) nounwind readonly +define i64 @test.objectsize.unnamed.2() { +; CHECK-LABEL: @test.objectsize.unnamed.2( +; CHECK: @llvm.objectsize.i64.p0p0s_s.0(%0** getelementptr inbounds ([60 x %0*], [60 x %0*]* @u, i32 0, i32 0), i1 false, i1 false, i1 false) + %s = call i64 @llvm.objectsize.i64.p0p0s_s.0(%0** getelementptr inbounds ([60 x %0*], [60 x %0*]* @u, i32 0, i32 0), i1 false) + ret i64 %s +} + declare <2 x double> @llvm.masked.load.v2f64(<2 x double>* %ptrs, i32, <2 x i1> %mask, <2 x double> %src0) define <2 x double> @tests.masked.load(<2 x double>* %ptr, <2 x i1> %mask, <2 x double> %passthru) { @@ -116,6 +134,20 @@ define void @tests.invariant.start.end() { ret void } +declare {}* @llvm.invariant.start.unnamed(i64, %0** nocapture) nounwind readonly +declare void @llvm.invariant.end.unnamed({}*, i64, %0** nocapture) nounwind + +define void @tests.invariant.start.end.unnamed() { + ; CHECK-LABEL: @tests.invariant.start.end.unnamed( + %a = alloca %0* + %i = call {}* @llvm.invariant.start.unnamed(i64 1, %0** %a) + ; CHECK: call {}* @llvm.invariant.start.p0p0s_s.0 + store %0* null, %0** %a + call void @llvm.invariant.end.unnamed({}* %i, i64 1, %0** %a) + ; CHECK: call void @llvm.invariant.end.p0p0s_s.0 + ret void +} + @__stack_chk_guard = external global i8* declare void @llvm.stackprotectorcheck(i8**) @@ -140,6 +172,20 @@ define void @tests.lifetime.start.end() { ret void } +declare void @llvm.lifetime.start.unnamed(i64, %0** nocapture) nounwind readonly +declare void @llvm.lifetime.end.unnamed(i64, %0** nocapture) nounwind + +define void @tests.lifetime.start.end.unnamed() { + ; CHECK-LABEL: @tests.lifetime.start.end.unnamed( + %a = alloca %0* + call void @llvm.lifetime.start.unnamed(i64 1, %0** %a) + ; CHECK: call void @llvm.lifetime.start.p0p0s_s.0(i64 1, %0** %a) + store %0* null, %0** %a + call void @llvm.lifetime.end.unnamed(i64 1, %0** %a) + ; CHECK: call void @llvm.lifetime.end.p0p0s_s.0(i64 1, %0** %a) + ret void +} + declare void @llvm.prefetch(i8*, i32, i32, i32) define void @test.prefetch(i8* %ptr) { ; CHECK-LABEL: @test.prefetch( @@ -156,10 +202,20 @@ define void @test.prefetch.2(i8* %ptr) { ret void } +declare void @llvm.prefetch.unnamed(%0**, i32, i32, i32) +define void @test.prefetch.unnamed(%0** %ptr) { +; CHECK-LABEL: @test.prefetch.unnamed( +; CHECK: @llvm.prefetch.p0p0s_s.0(%0** %ptr, i32 0, i32 3, i32 2) + call void @llvm.prefetch.unnamed(%0** %ptr, i32 0, i32 3, i32 2) + ret void +} + ; This is part of @test.objectsize(), since llvm.objectsize declaration gets ; emitted at the end. ; CHECK: declare i32 @llvm.objectsize.i32.p0i8 - +; CHECK: declare i32 @llvm.objectsize.i32.p0p0s_s.0 ; CHECK: declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture) ; CHECK: declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture) +; CHECK: declare void @llvm.lifetime.start.p0p0s_s.0(i64 immarg, %0** nocapture) +; CHECK: declare void @llvm.lifetime.end.p0p0s_s.0(i64 immarg, %0** nocapture)