diff --git a/llvm/include/llvm/AsmParser/LLParser.h b/llvm/include/llvm/AsmParser/LLParser.h --- a/llvm/include/llvm/AsmParser/LLParser.h +++ b/llvm/include/llvm/AsmParser/LLParser.h @@ -131,6 +131,10 @@ /// function. PerFunctionState *BlockAddressPFS; + // References to dso_local_equivalent. The key is the global's ValID, the + // value is a placeholder value that will be replaced. + std::map ForwardRefDSOLocalEquivalents; + // Attribute builder reference information. std::map > ForwardRefAttrGroups; std::map NumberedAttrBuilders; diff --git a/llvm/lib/AsmParser/LLParser.cpp b/llvm/lib/AsmParser/LLParser.cpp --- a/llvm/lib/AsmParser/LLParser.cpp +++ b/llvm/lib/AsmParser/LLParser.cpp @@ -215,6 +215,31 @@ return error(ForwardRefBlockAddresses.begin()->first.Loc, "expected function name in blockaddress"); + // If there are entries in ForwardRefDSOLocalEquivalents at this point, they + // are references after the function was defined. Resolve those now. + for (auto &Iter : ForwardRefDSOLocalEquivalents) { + GlobalValue *GV = nullptr; + const ValID &GVRef = Iter.first; + if (GVRef.Kind == ValID::t_GlobalName) + GV = M->getNamedValue(GVRef.StrVal); + else if (GVRef.UIntVal < NumberedVals.size()) + GV = dyn_cast(NumberedVals[GVRef.UIntVal]); + + if (!GV) + return error(GVRef.Loc, "unknown function '" + GVRef.StrVal + + "' referenced by dso_local_equivalent"); + + if (!GV->getValueType()->isFunctionTy()) + return error(GVRef.Loc, + "expected a function, alias to function, or ifunc " + "in dso_local_equivalent"); + + auto *Equiv = DSOLocalEquivalent::get(GV); + Iter.second->replaceAllUsesWith(Equiv); + Iter.second->eraseFromParent(); + } + ForwardRefDSOLocalEquivalents.clear(); + for (const auto &NT : NumberedTypes) if (NT.second.second.isValid()) return error(NT.second.second, @@ -3428,7 +3453,21 @@ GV = M->getNamedValue(Fn.StrVal); } - assert(GV && "Could not find a corresponding global variable"); + if (!GV) { + // Make a placeholder global variable as a placeholder for this reference. + GlobalValue *&FwdRef = ForwardRefDSOLocalEquivalents + .try_emplace(std::move(Fn), nullptr) + .first->second; + if (!FwdRef) { + FwdRef = new GlobalVariable(*M, Type::getInt8Ty(Context), false, + GlobalValue::InternalLinkage, nullptr, "", + nullptr, GlobalValue::NotThreadLocal); + } + + ID.ConstantVal = FwdRef; + ID.Kind = ValID::t_Constant; + return false; + } if (!GV->getValueType()->isFunctionTy()) return error(Fn.Loc, "expected a function, alias to function, or ifunc " diff --git a/llvm/test/CodeGen/X86/dso_local_equivalent.ll b/llvm/test/CodeGen/X86/dso_local_equivalent.ll --- a/llvm/test/CodeGen/X86/dso_local_equivalent.ll +++ b/llvm/test/CodeGen/X86/dso_local_equivalent.ll @@ -97,3 +97,121 @@ call void dso_local_equivalent @dso_local_ifunc_func() ret void } + +;; PR57815 +;; Ensure dso_local_equivalent works the exact same way as the previous +;; examples but with forward-referenced symbols. + +; CHECK: call_forward_func: +; CHECK: callq forward_extern_func@PLT +define void @call_forward_func() { + call void dso_local_equivalent @forward_extern_func() + ret void +} + +; CHECK: call_forward_hidden_func: +; CHECK: callq forward_hidden_func{{$}} +define void @call_forward_hidden_func() { + call void dso_local_equivalent @forward_hidden_func() + ret void +} + +; CHECK: call_forward_protected_func: +; CHECK: callq forward_protected_func{{$}} +define void @call_forward_protected_func() { + call void dso_local_equivalent @forward_protected_func() + ret void +} + +; CHECK: call_forward_dso_local_func: +; CHECK: callq forward_dso_local_func{{$}} +define void @call_forward_dso_local_func() { + call void dso_local_equivalent @forward_dso_local_func() + ret void +} + +; CHECK: call_forward_internal_func: +; CHECK: callq forward_internal_func{{$}} +define void @call_forward_internal_func() { + call void dso_local_equivalent @forward_internal_func() + ret void +} + +declare hidden void @forward_hidden_func() +declare protected void @forward_protected_func() +declare dso_local void @forward_dso_local_func() +define internal void @forward_internal_func() { +entry: + ret void +} +define private void @forward_private_func() { +entry: + ret void +} + +; CHECK: call_forward_alias_func: +; CHECK: callq forward_alias_func@PLT +define void @call_forward_alias_func() { + call void dso_local_equivalent @forward_alias_func() + ret void +} + +; CHECK: call_forward_dso_local_alias_func: +; CHECK: callq .Lforward_dso_local_alias_func$local{{$}} +define void @call_forward_dso_local_alias_func() { + call void dso_local_equivalent @forward_dso_local_alias_func() + ret void +} + +define void @forward_aliasee_func() { +entry: + ret void +} + +@forward_alias_func = alias void (), ptr @forward_aliasee_func +@forward_dso_local_alias_func = dso_local alias void (), ptr @forward_aliasee_func + +; If an ifunc is not dso_local already, then we should still emit a stub for it +; to ensure it will be dso_local. +; CHECK: call_forward_ifunc_func: +; CHECK: callq forward_ifunc_func@PLT +define void @call_forward_ifunc_func() { + call void dso_local_equivalent @forward_ifunc_func() + ret void +} + +; CHECK: call_forward_dso_local_ifunc_func: +; CHECK: callq forward_dso_local_ifunc_func{{$}} +define void @call_forward_dso_local_ifunc_func() { + call void dso_local_equivalent @forward_dso_local_ifunc_func() + ret void +} + +@forward_ifunc_func = ifunc void (), ptr @resolver +@forward_dso_local_ifunc_func = dso_local ifunc void (), ptr @resolver + +;; Test "no-named" variables +; CHECK: call_no_name_hidden: +; CHECK: callq __unnamed_{{[0-9]+}}{{$}} +define void @call_no_name_hidden() { + call void dso_local_equivalent @0() + ret void +} + +; CHECK: call_no_name_extern: +; CHECK: callq __unnamed_{{[0-9]+}}@PLT +define void @call_no_name_extern() { + call void dso_local_equivalent @1() + ret void +} + +declare hidden void @0() +declare void @1() + +;; Note that we keep this at the very end because llc emits this after all the +;; functions. +; CHECK: const: +; CHECK: .long forward_extern_func@PLT +@const = constant i32 trunc (i64 ptrtoint (ptr dso_local_equivalent @forward_extern_func to i64) to i32) + +declare void @forward_extern_func() diff --git a/llvm/test/CodeGen/X86/dso_local_equivalent_errors.ll b/llvm/test/CodeGen/X86/dso_local_equivalent_errors.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/X86/dso_local_equivalent_errors.ll @@ -0,0 +1,19 @@ +; RUN: split-file %s %t +; RUN: not llc -mtriple=x86_64-linux-gnu -o - %t/undefined_func.ll 2>&1 | FileCheck %s -check-prefix=UNDEFINED +; RUN: not llc -mtriple=x86_64-linux-gnu -o - %t/invalid_arg.ll 2>&1 | FileCheck %s -check-prefix=INVALID + +;--- undefined_func.ll +; UNDEFINED: error: unknown function 'undefined_func' referenced by dso_local_equivalent +define void @call_undefined() { + call void dso_local_equivalent @undefined_func() + ret void +} + +;--- invalid_arg.ll +; INVALID: error: expected a function, alias to function, or ifunc in dso_local_equivalent +define void @call_global_var() { + call void dso_local_equivalent @glob() + ret void +} + +@glob = constant i32 1