Skip to content

Commit aa16de6

Browse files
authored
[Linker] Propagate nobuiltin attributes when linking known libcalls (#89431)
Summary: As discussed in https://discourse.llvm.org/t/rfc-libc-ffreestanding-fno-builtin. LLVM ascribes special semantics to several functions that are known to be `libcalls`. These are functions that middle-end optimizations may transforms calls into or perform optimizations based off of known semantics. However, these assumptions require an opaque function call to be known valid. In situations like LTO or IR linking it is possible to bring a libcall definition into the current module. Once this happens, we can no longer make any guarantees about the semantics of these functions. We currently attempt to solve this by preventing all inlining if the called function has `no-builtin` https://reviews.llvm.org/D74162. However, this is overly pessimistic as it prevents all inlining even for non-libcall functions. This patch modifies the IRMover class to track known libcalls enabled for the given target. If we encounter a known libcall during IR linking, we then need to append the `nobuiltin` attribute to the destination module. Afterwards, all new definitions we link in will be applied as well.
1 parent 58a94b1 commit aa16de6

File tree

5 files changed

+156
-5
lines changed

5 files changed

+156
-5
lines changed

llvm/include/llvm/Linker/IRMover.h

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@
1212
#include "llvm/ADT/ArrayRef.h"
1313
#include "llvm/ADT/DenseSet.h"
1414
#include "llvm/ADT/FunctionExtras.h"
15+
#include "llvm/ADT/StringSet.h"
16+
#include "llvm/IR/GlobalValue.h"
17+
#include "llvm/Support/StringSaver.h"
18+
#include "llvm/TargetParser/Triple.h"
1519
#include <functional>
1620

1721
namespace llvm {
1822
class Error;
19-
class GlobalValue;
2023
class Metadata;
2124
class Module;
2225
class StructType;
@@ -60,6 +63,33 @@ class IRMover {
6063
bool hasType(StructType *Ty);
6164
};
6265

66+
/// Utility for handling linking of known libcall functions. If a merged
67+
/// module contains a recognized library call we can no longer perform any
68+
/// libcall related transformations.
69+
class LibcallHandler {
70+
bool HasLibcalls = false;
71+
72+
StringSet<> Libcalls;
73+
StringSet<> Triples;
74+
75+
BumpPtrAllocator Alloc;
76+
StringSaver Saver;
77+
78+
public:
79+
LibcallHandler() : Saver(Alloc) {}
80+
81+
void updateLibcalls(const Triple &TheTriple);
82+
83+
bool checkLibcalls(GlobalValue &GV) {
84+
if (HasLibcalls)
85+
return false;
86+
return HasLibcalls = isa<Function>(&GV) && !GV.isDeclaration() &&
87+
Libcalls.count(GV.getName());
88+
}
89+
90+
bool hasLibcalls() const { return HasLibcalls; }
91+
};
92+
6393
IRMover(Module &M);
6494

6595
typedef std::function<void(GlobalValue &)> ValueAdder;
@@ -84,6 +114,7 @@ class IRMover {
84114
Module &Composite;
85115
IdentifiedStructTypeSet IdentifiedStructTypes;
86116
MDMapT SharedMDs; ///< A Metadata map to use for all calls to \a move().
117+
LibcallHandler Libcalls;
87118
};
88119

89120
} // End llvm namespace

llvm/lib/Linker/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ add_llvm_component_library(LLVMLinker
99
intrinsics_gen
1010

1111
LINK_COMPONENTS
12+
Analysis
1213
Core
1314
Object
1415
Support

llvm/lib/Linker/IRMover.cpp

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "llvm/ADT/SetVector.h"
1313
#include "llvm/ADT/SmallPtrSet.h"
1414
#include "llvm/ADT/SmallString.h"
15+
#include "llvm/Analysis/TargetLibraryInfo.h"
1516
#include "llvm/IR/AutoUpgrade.h"
1617
#include "llvm/IR/Constants.h"
1718
#include "llvm/IR/DebugInfoMetadata.h"
@@ -399,6 +400,9 @@ class IRLinker {
399400
/// A metadata map that's shared between IRLinker instances.
400401
MDMapT &SharedMDs;
401402

403+
/// A list of libcalls that the current target may call.
404+
IRMover::LibcallHandler &Libcalls;
405+
402406
/// Mapping of values from what they used to be in Src, to what they are now
403407
/// in DstM. ValueToValueMapTy is a ValueMap, which involves some overhead
404408
/// due to the use of Value handles which the Linker doesn't actually need,
@@ -540,10 +544,12 @@ class IRLinker {
540544
IRLinker(Module &DstM, MDMapT &SharedMDs,
541545
IRMover::IdentifiedStructTypeSet &Set, std::unique_ptr<Module> SrcM,
542546
ArrayRef<GlobalValue *> ValuesToLink,
543-
IRMover::LazyCallback AddLazyFor, bool IsPerformingImport)
547+
IRMover::LibcallHandler &Libcalls, IRMover::LazyCallback AddLazyFor,
548+
bool IsPerformingImport)
544549
: DstM(DstM), SrcM(std::move(SrcM)), AddLazyFor(std::move(AddLazyFor)),
545550
TypeMap(Set), GValMaterializer(*this), LValMaterializer(*this),
546-
SharedMDs(SharedMDs), IsPerformingImport(IsPerformingImport),
551+
SharedMDs(SharedMDs), Libcalls(Libcalls),
552+
IsPerformingImport(IsPerformingImport),
547553
Mapper(ValueMap, RF_ReuseAndMutateDistinctMDs | RF_IgnoreMissingLocals,
548554
&TypeMap, &GValMaterializer),
549555
IndirectSymbolMCID(Mapper.registerAlternateMappingContext(
@@ -561,6 +567,13 @@ class IRLinker {
561567
};
562568
}
563569

570+
static void addNoBuiltinAttributes(Function &F) {
571+
F.setAttributes(
572+
F.getAttributes().addFnAttribute(F.getContext(), "no-builtins"));
573+
F.setAttributes(
574+
F.getAttributes().addFnAttribute(F.getContext(), Attribute::NoBuiltin));
575+
}
576+
564577
/// The LLVM SymbolTable class autorenames globals that conflict in the symbol
565578
/// table. This is good for all clients except for us. Go through the trouble
566579
/// to force this back.
@@ -1605,14 +1618,26 @@ Error IRLinker::run() {
16051618

16061619
DstM.setTargetTriple(SrcTriple.merge(DstTriple));
16071620

1621+
// Update the target triple's libcall information if it was changed.
1622+
Libcalls.updateLibcalls(Triple(DstM.getTargetTriple()));
1623+
16081624
// Loop over all of the linked values to compute type mappings.
16091625
computeTypeMapping();
16101626

1627+
bool AddsLibcalls = false;
16111628
std::reverse(Worklist.begin(), Worklist.end());
16121629
while (!Worklist.empty()) {
16131630
GlobalValue *GV = Worklist.back();
16141631
Worklist.pop_back();
16151632

1633+
// If the module already contains libcall functions we need every function
1634+
// linked in to have `nobuiltin` attributes. Otherwise check if this is a
1635+
// libcall definition.
1636+
if (Function *F = dyn_cast<Function>(GV); F && Libcalls.hasLibcalls())
1637+
addNoBuiltinAttributes(*F);
1638+
else
1639+
AddsLibcalls = Libcalls.checkLibcalls(*GV);
1640+
16161641
// Already mapped.
16171642
if (ValueMap.find(GV) != ValueMap.end() ||
16181643
IndirectSymbolValueMap.find(GV) != IndirectSymbolValueMap.end())
@@ -1675,6 +1700,13 @@ Error IRLinker::run() {
16751700
}
16761701
}
16771702

1703+
// If we have imported a recognized libcall function we can no longer make any
1704+
// reasonable optimizations based off of its semantics. Add the 'nobuiltin'
1705+
// attribute to every function to suppress libcall detection.
1706+
if (AddsLibcalls)
1707+
for (Function &F : DstM.functions())
1708+
addNoBuiltinAttributes(F);
1709+
16781710
// Merge the module flags into the DstM module.
16791711
return linkModuleFlagsMetadata();
16801712
}
@@ -1757,6 +1789,22 @@ bool IRMover::IdentifiedStructTypeSet::hasType(StructType *Ty) {
17571789
return I == NonOpaqueStructTypes.end() ? false : *I == Ty;
17581790
}
17591791

1792+
void IRMover::LibcallHandler::updateLibcalls(const Triple &TheTriple) {
1793+
if (Triples.count(TheTriple.getTriple()))
1794+
return;
1795+
Triples.insert(Saver.save(TheTriple.getTriple()));
1796+
1797+
// Collect the names of runtime functions that the target may want to call.
1798+
TargetLibraryInfoImpl TLII(TheTriple);
1799+
TargetLibraryInfo TLI(TLII);
1800+
for (unsigned I = 0, E = static_cast<unsigned>(LibFunc::NumLibFuncs); I != E;
1801+
++I) {
1802+
LibFunc F = static_cast<LibFunc>(I);
1803+
if (TLI.has(F))
1804+
Libcalls.insert(TLI.getName(F));
1805+
}
1806+
}
1807+
17601808
IRMover::IRMover(Module &M) : Composite(M) {
17611809
TypeFinder StructTypes;
17621810
StructTypes.run(M, /* OnlyNamed */ false);
@@ -1772,14 +1820,25 @@ IRMover::IRMover(Module &M) : Composite(M) {
17721820
for (const auto *MD : StructTypes.getVisitedMetadata()) {
17731821
SharedMDs[MD].reset(const_cast<MDNode *>(MD));
17741822
}
1823+
1824+
// Check the composite module for any already present libcalls. If we define
1825+
// these then it is important to mark any imported functions as 'nobuiltin'.
1826+
Libcalls.updateLibcalls(Triple(Composite.getTargetTriple()));
1827+
for (Function &F : Composite.functions())
1828+
if (Libcalls.checkLibcalls(F))
1829+
break;
1830+
1831+
if (Libcalls.hasLibcalls())
1832+
for (Function &F : Composite.functions())
1833+
addNoBuiltinAttributes(F);
17751834
}
17761835

17771836
Error IRMover::move(std::unique_ptr<Module> Src,
17781837
ArrayRef<GlobalValue *> ValuesToLink,
17791838
LazyCallback AddLazyFor, bool IsPerformingImport) {
17801839
IRLinker TheIRLinker(Composite, SharedMDs, IdentifiedStructTypes,
1781-
std::move(Src), ValuesToLink, std::move(AddLazyFor),
1782-
IsPerformingImport);
1840+
std::move(Src), ValuesToLink, Libcalls,
1841+
std::move(AddLazyFor), IsPerformingImport);
17831842
Error E = TheIRLinker.run();
17841843
Composite.dropTriviallyDeadConstantArrays();
17851844
return E;

llvm/test/Linker/Inputs/strlen.ll

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
target triple = "x86_64-unknown-linux-gnu"
2+
3+
define i64 @strlen(ptr %s) #0 {
4+
entry:
5+
br label %for.cond
6+
7+
for.cond:
8+
%s.addr.0 = phi ptr [ %s, %entry ], [ %incdec.ptr, %for.cond ]
9+
%0 = load i8, ptr %s.addr.0, align 1
10+
%tobool.not = icmp eq i8 %0, 0
11+
%incdec.ptr = getelementptr inbounds i8, ptr %s.addr.0, i64 1
12+
br i1 %tobool.not, label %for.end, label %for.cond
13+
14+
for.end:
15+
%sub.ptr.lhs.cast = ptrtoint ptr %s.addr.0 to i64
16+
%sub.ptr.rhs.cast = ptrtoint ptr %s to i64
17+
%sub.ptr.sub = sub i64 %sub.ptr.lhs.cast, %sub.ptr.rhs.cast
18+
ret i64 %sub.ptr.sub
19+
}
20+
21+
attributes #0 = { noinline }

llvm/test/Linker/libcalls.ll

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
; RUN: llvm-link %s %S/Inputs/strlen.ll -S -o - 2>%t.a.err | FileCheck %s --check-prefix=CHECK1
2+
; RUN: llvm-link %S/Inputs/strlen.ll %s -S -o - 2>%t.a.err | FileCheck %s --check-prefix=CHECK2
3+
4+
target triple = "x86_64-unknown-linux-gnu"
5+
6+
@.str = private unnamed_addr constant [7 x i8] c"string\00", align 1
7+
@str = dso_local global ptr @.str, align 8
8+
9+
define void @foo() #0 {
10+
ret void
11+
}
12+
13+
declare i64 @strlen(ptr)
14+
15+
define void @bar() #0 {
16+
ret void
17+
}
18+
19+
define i64 @baz() #0 {
20+
entry:
21+
%0 = load ptr, ptr @str, align 8
22+
%call = call i64 @strlen(ptr noundef %0)
23+
ret i64 %call
24+
}
25+
26+
attributes #0 = { noinline }
27+
28+
; CHECK1: define void @foo() #[[ATTR0:[0-9]+]]
29+
; CHECK1: define void @bar() #[[ATTR0:[0-9]+]]
30+
; CHECK1: define i64 @baz() #[[ATTR0:[0-9]+]]
31+
; CHECK1: define i64 @strlen(ptr [[S:%.*]]) #[[ATTR0]]
32+
33+
; CHECK2: define i64 @strlen(ptr [[S:%.*]]) #[[ATTR0:[0-9]+]]
34+
; CHECK2: define void @foo() #[[ATTR0:[0-9]+]]
35+
; CHECK2: define void @bar() #[[ATTR0:[0-9]+]]
36+
; CHECK2: define i64 @baz() #[[ATTR0]]
37+
38+
; CHECK1: attributes #[[ATTR0]] = { nobuiltin noinline "no-builtins" }
39+
; CHECK2: attributes #[[ATTR0]] = { nobuiltin noinline "no-builtins" }

0 commit comments

Comments
 (0)