Skip to content

Commit 4f0fee7

Browse files
committed
[Linker] Propagate nobuiltin attributes when linking known libcalls
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 long make any gurantees 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 20d653f commit 4f0fee7

File tree

5 files changed

+124
-5
lines changed

5 files changed

+124
-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: 47 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(
@@ -1605,14 +1611,27 @@ Error IRLinker::run() {
16051611

16061612
DstM.setTargetTriple(SrcTriple.merge(DstTriple));
16071613

1614+
// Update the target triple's libcall information if it was changed.
1615+
Libcalls.updateLibcalls(Triple(DstM.getTargetTriple()));
1616+
16081617
// Loop over all of the linked values to compute type mappings.
16091618
computeTypeMapping();
16101619

1620+
bool AddsLibcalls;
16111621
std::reverse(Worklist.begin(), Worklist.end());
16121622
while (!Worklist.empty()) {
16131623
GlobalValue *GV = Worklist.back();
16141624
Worklist.pop_back();
16151625

1626+
// If the module already contains libcall functions we need every function
1627+
// linked in to have `nobuiltin` attributes. Otherwise check if this is a
1628+
// libcall definition.
1629+
if (Function *F = dyn_cast<Function>(GV); F && Libcalls.hasLibcalls())
1630+
F->setAttributes(F->getAttributes().addFnAttribute(F->getContext(),
1631+
Attribute::NoBuiltin));
1632+
else
1633+
AddsLibcalls = Libcalls.checkLibcalls(*GV);
1634+
16161635
// Already mapped.
16171636
if (ValueMap.find(GV) != ValueMap.end() ||
16181637
IndirectSymbolValueMap.find(GV) != IndirectSymbolValueMap.end())
@@ -1675,6 +1694,14 @@ Error IRLinker::run() {
16751694
}
16761695
}
16771696

1697+
// If we have imported a recognized libcall function we can no longer make any
1698+
// reasonable optimizations based off of its semantics. Add the 'nobuiltin'
1699+
// attribute to every function to suppress libcall detection.
1700+
if (AddsLibcalls)
1701+
for (Function &F : DstM.functions())
1702+
F.setAttributes(F.getAttributes().addFnAttribute(DstM.getContext(),
1703+
Attribute::NoBuiltin));
1704+
16781705
// Merge the module flags into the DstM module.
16791706
return linkModuleFlagsMetadata();
16801707
}
@@ -1757,6 +1784,22 @@ bool IRMover::IdentifiedStructTypeSet::hasType(StructType *Ty) {
17571784
return I == NonOpaqueStructTypes.end() ? false : *I == Ty;
17581785
}
17591786

1787+
void IRMover::LibcallHandler::updateLibcalls(const Triple &TheTriple) {
1788+
if (Triples.count(TheTriple.getTriple()))
1789+
return;
1790+
Triples.insert(Saver.save(TheTriple.getTriple()));
1791+
1792+
// Collect the names of runtime functions that the target may want to call.
1793+
TargetLibraryInfoImpl TLII(TheTriple);
1794+
TargetLibraryInfo TLI(TLII);
1795+
for (unsigned I = 0, E = static_cast<unsigned>(LibFunc::NumLibFuncs); I != E;
1796+
++I) {
1797+
LibFunc F = static_cast<LibFunc>(I);
1798+
if (TLI.has(F))
1799+
Libcalls.insert(TLI.getName(F));
1800+
}
1801+
}
1802+
17601803
IRMover::IRMover(Module &M) : Composite(M) {
17611804
TypeFinder StructTypes;
17621805
StructTypes.run(M, /* OnlyNamed */ false);
@@ -1778,8 +1821,8 @@ Error IRMover::move(std::unique_ptr<Module> Src,
17781821
ArrayRef<GlobalValue *> ValuesToLink,
17791822
LazyCallback AddLazyFor, bool IsPerformingImport) {
17801823
IRLinker TheIRLinker(Composite, SharedMDs, IdentifiedStructTypes,
1781-
std::move(Src), ValuesToLink, std::move(AddLazyFor),
1782-
IsPerformingImport);
1824+
std::move(Src), ValuesToLink, Libcalls,
1825+
std::move(AddLazyFor), IsPerformingImport);
17831826
Error E = TheIRLinker.run();
17841827
Composite.dropTriviallyDeadConstantArrays();
17851828
return E;

llvm/test/Linker/Inputs/strlen.ll

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
target triple = "x86_64-unknown-linux-gnu"
2+
3+
define i64 @strlen(ptr %s) {
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+
}

llvm/test/Linker/libcalls.ll

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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+
declare i64 @strlen(ptr)
10+
11+
define i64 @foo() {
12+
entry:
13+
%0 = load ptr, ptr @str, align 8
14+
%call = call i64 @strlen(ptr noundef %0)
15+
ret i64 %call
16+
}
17+
18+
; CHECK1: define i64 @foo() #[[ATTR0:[0-9]+]]
19+
; CHECK1: define i64 @strlen(ptr [[S:%.*]]) #[[ATTR0]]
20+
21+
; CHECK2: define i64 @strlen(ptr [[S:%.*]]) #[[ATTR0:[0-9]+]]
22+
; CHECK2: define i64 @foo() #[[ATTR0]]
23+
24+
; CHECK1: attributes #[[ATTR0]] = { nobuiltin }
25+
; CHECK2: attributes #[[ATTR0]] = { nobuiltin }

0 commit comments

Comments
 (0)