From 5cc87b424be87db4247f34ae5477be8b09a573e9 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 31 Jan 2024 12:24:35 +0100 Subject: [PATCH] [AsmParser] Add missing globals declarations in incomplete IR mode (#79855) If `-allow-incomplete-ir` is enabled, automatically insert declarations for missing globals. If a global is only used in calls with the same function type, insert a function declaration with that type. Otherwise, insert a dummy i8 global. The fallback case could be extended with various heuristics (e.g. we could look at load/store types), but I've chosen to keep it simple for now, because I'm unsure to what degree this would really useful without more experience. I expect that in most cases the declaration type doesn't really matter (note that the type of an external global specifies a *minimum* size only, not a precise size). This is a followup to https://github.com/llvm/llvm-project/pull/78421. --- llvm/lib/AsmParser/LLParser.cpp | 44 ++++++++++++++----- .../Assembler/incomplete-ir-declarations.ll | 20 +++++++++ 2 files changed, 52 insertions(+), 12 deletions(-) create mode 100644 llvm/test/Assembler/incomplete-ir-declarations.ll diff --git a/llvm/lib/AsmParser/LLParser.cpp b/llvm/lib/AsmParser/LLParser.cpp index d6c5993797de11..e111ca9c7e6b5f 100644 --- a/llvm/lib/AsmParser/LLParser.cpp +++ b/llvm/lib/AsmParser/LLParser.cpp @@ -303,15 +303,7 @@ bool LLParser::validateEndOfModule(bool UpgradeDebugInfo) { "use of undefined comdat '$" + ForwardRefComdats.begin()->first + "'"); - // Automatically create declarations for intrinsics. Intrinsics can only be - // called directly, so the call function type directly determines the - // declaration function type. for (const auto &[Name, Info] : make_early_inc_range(ForwardRefVals)) { - if (!StringRef(Name).starts_with("llvm.")) - continue; - - // Don't do anything if the intrinsic is called with different function - // types. This would result in a verifier error anyway. auto GetCommonFunctionType = [](Value *V) -> FunctionType * { FunctionType *FTy = nullptr; for (User *U : V->users()) { @@ -322,10 +314,38 @@ bool LLParser::validateEndOfModule(bool UpgradeDebugInfo) { } return FTy; }; - if (FunctionType *FTy = GetCommonFunctionType(Info.first)) { - Function *Fn = - Function::Create(FTy, GlobalValue::ExternalLinkage, Name, M); - Info.first->replaceAllUsesWith(Fn); + + auto GetDeclarationType = [&](StringRef Name, Value *V) -> Type * { + // Automatically create declarations for intrinsics. Intrinsics can only + // be called directly, so the call function type directly determines the + // declaration function type. + if (Name.starts_with("llvm.")) + // Don't do anything if the intrinsic is called with different function + // types. This would result in a verifier error anyway. + return GetCommonFunctionType(V); + + if (AllowIncompleteIR) { + // If incomplete IR is allowed, also add declarations for + // non-intrinsics. First check whether this global is only used in + // calls with the same type, in which case we'll insert a function. + if (auto *Ty = GetCommonFunctionType(V)) + return Ty; + + // Otherwise, fall back to using a dummy i8 type. + return Type::getInt8Ty(Context); + } + return nullptr; + }; + + if (Type *Ty = GetDeclarationType(Name, Info.first)) { + GlobalValue *GV; + if (auto *FTy = dyn_cast(Ty)) + GV = Function::Create(FTy, GlobalValue::ExternalLinkage, Name, M); + else + GV = new GlobalVariable(*M, Ty, /*isConstant*/ false, + GlobalValue::ExternalLinkage, + /*Initializer*/ nullptr, Name); + Info.first->replaceAllUsesWith(GV); Info.first->eraseFromParent(); ForwardRefVals.erase(Name); } diff --git a/llvm/test/Assembler/incomplete-ir-declarations.ll b/llvm/test/Assembler/incomplete-ir-declarations.ll new file mode 100644 index 00000000000000..6cdff80883c272 --- /dev/null +++ b/llvm/test/Assembler/incomplete-ir-declarations.ll @@ -0,0 +1,20 @@ +; RUN: opt -S -allow-incomplete-ir < %s | FileCheck %s + +; CHECK: @fn2 = external global i8 +; CHECK: @g1 = external global i8 +; CHECK: @g2 = external global i8 +; CHECK: @g3 = external global i8 + +; CHECK: declare void @fn1(i32) + +define ptr @test() { + call void @fn1(i32 0) + call void @fn1(i32 1) + call void @fn2(i32 2) + call void @fn2(i32 2, i32 3) + load i32, ptr @g1 + store i32 0, ptr @g1 + load i32, ptr @g1 + load i64, ptr @g2 + ret ptr @g3 +}