Skip to content

[Linker] Propagate nobuiltin attributes when linking known libcalls #89431

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion llvm/include/llvm/Linker/IRMover.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/FunctionExtras.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/IR/GlobalValue.h"
#include "llvm/Support/StringSaver.h"
#include "llvm/TargetParser/Triple.h"
#include <functional>

namespace llvm {
class Error;
class GlobalValue;
class Metadata;
class Module;
class StructType;
Expand Down Expand Up @@ -60,6 +63,33 @@ class IRMover {
bool hasType(StructType *Ty);
};

/// Utility for handling linking of known libcall functions. If a merged
/// module contains a recognized library call we can no longer perform any
/// libcall related transformations.
class LibcallHandler {
bool HasLibcalls = false;

StringSet<> Libcalls;
StringSet<> Triples;

BumpPtrAllocator Alloc;
StringSaver Saver;

public:
LibcallHandler() : Saver(Alloc) {}

void updateLibcalls(const Triple &TheTriple);

bool checkLibcalls(GlobalValue &GV) {
if (HasLibcalls)
return false;
return HasLibcalls = isa<Function>(&GV) && !GV.isDeclaration() &&
Libcalls.count(GV.getName());
}

bool hasLibcalls() const { return HasLibcalls; }
};

IRMover(Module &M);

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

} // End llvm namespace
Expand Down
1 change: 1 addition & 0 deletions llvm/lib/Linker/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ add_llvm_component_library(LLVMLinker
intrinsics_gen

LINK_COMPONENTS
Analysis
Core
Object
Support
Expand Down
67 changes: 63 additions & 4 deletions llvm/lib/Linker/IRMover.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/Analysis/TargetLibraryInfo.h"
#include "llvm/IR/AutoUpgrade.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/DebugInfoMetadata.h"
Expand Down Expand Up @@ -399,6 +400,9 @@ class IRLinker {
/// A metadata map that's shared between IRLinker instances.
MDMapT &SharedMDs;

/// A list of libcalls that the current target may call.
IRMover::LibcallHandler &Libcalls;

/// Mapping of values from what they used to be in Src, to what they are now
/// in DstM. ValueToValueMapTy is a ValueMap, which involves some overhead
/// due to the use of Value handles which the Linker doesn't actually need,
Expand Down Expand Up @@ -540,10 +544,12 @@ class IRLinker {
IRLinker(Module &DstM, MDMapT &SharedMDs,
IRMover::IdentifiedStructTypeSet &Set, std::unique_ptr<Module> SrcM,
ArrayRef<GlobalValue *> ValuesToLink,
IRMover::LazyCallback AddLazyFor, bool IsPerformingImport)
IRMover::LibcallHandler &Libcalls, IRMover::LazyCallback AddLazyFor,
bool IsPerformingImport)
: DstM(DstM), SrcM(std::move(SrcM)), AddLazyFor(std::move(AddLazyFor)),
TypeMap(Set), GValMaterializer(*this), LValMaterializer(*this),
SharedMDs(SharedMDs), IsPerformingImport(IsPerformingImport),
SharedMDs(SharedMDs), Libcalls(Libcalls),
IsPerformingImport(IsPerformingImport),
Mapper(ValueMap, RF_ReuseAndMutateDistinctMDs | RF_IgnoreMissingLocals,
&TypeMap, &GValMaterializer),
IndirectSymbolMCID(Mapper.registerAlternateMappingContext(
Expand All @@ -561,6 +567,13 @@ class IRLinker {
};
}

static void addNoBuiltinAttributes(Function &F) {
F.setAttributes(
F.getAttributes().addFnAttribute(F.getContext(), "no-builtins"));
F.setAttributes(
F.getAttributes().addFnAttribute(F.getContext(), Attribute::NoBuiltin));
}

/// The LLVM SymbolTable class autorenames globals that conflict in the symbol
/// table. This is good for all clients except for us. Go through the trouble
/// to force this back.
Expand Down Expand Up @@ -1605,14 +1618,26 @@ Error IRLinker::run() {

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

// Update the target triple's libcall information if it was changed.
Libcalls.updateLibcalls(Triple(DstM.getTargetTriple()));

// Loop over all of the linked values to compute type mappings.
computeTypeMapping();

bool AddsLibcalls = false;
std::reverse(Worklist.begin(), Worklist.end());
while (!Worklist.empty()) {
GlobalValue *GV = Worklist.back();
Worklist.pop_back();

// If the module already contains libcall functions we need every function
// linked in to have `nobuiltin` attributes. Otherwise check if this is a
// libcall definition.
if (Function *F = dyn_cast<Function>(GV); F && Libcalls.hasLibcalls())
addNoBuiltinAttributes(*F);
else
AddsLibcalls = Libcalls.checkLibcalls(*GV);

// Already mapped.
if (ValueMap.find(GV) != ValueMap.end() ||
IndirectSymbolValueMap.find(GV) != IndirectSymbolValueMap.end())
Expand Down Expand Up @@ -1675,6 +1700,13 @@ Error IRLinker::run() {
}
}

// If we have imported a recognized libcall function we can no longer make any
// reasonable optimizations based off of its semantics. Add the 'nobuiltin'
// attribute to every function to suppress libcall detection.
if (AddsLibcalls)
for (Function &F : DstM.functions())
addNoBuiltinAttributes(F);

// Merge the module flags into the DstM module.
return linkModuleFlagsMetadata();
}
Expand Down Expand Up @@ -1757,6 +1789,22 @@ bool IRMover::IdentifiedStructTypeSet::hasType(StructType *Ty) {
return I == NonOpaqueStructTypes.end() ? false : *I == Ty;
}

void IRMover::LibcallHandler::updateLibcalls(const Triple &TheTriple) {
if (Triples.count(TheTriple.getTriple()))
return;
Triples.insert(Saver.save(TheTriple.getTriple()));

// Collect the names of runtime functions that the target may want to call.
TargetLibraryInfoImpl TLII(TheTriple);
TargetLibraryInfo TLI(TLII);
for (unsigned I = 0, E = static_cast<unsigned>(LibFunc::NumLibFuncs); I != E;
++I) {
LibFunc F = static_cast<LibFunc>(I);
if (TLI.has(F))
Libcalls.insert(TLI.getName(F));
}
}

IRMover::IRMover(Module &M) : Composite(M) {
TypeFinder StructTypes;
StructTypes.run(M, /* OnlyNamed */ false);
Expand All @@ -1772,14 +1820,25 @@ IRMover::IRMover(Module &M) : Composite(M) {
for (const auto *MD : StructTypes.getVisitedMetadata()) {
SharedMDs[MD].reset(const_cast<MDNode *>(MD));
}

// Check the composite module for any already present libcalls. If we define
// these then it is important to mark any imported functions as 'nobuiltin'.
Libcalls.updateLibcalls(Triple(Composite.getTargetTriple()));
for (Function &F : Composite.functions())
if (Libcalls.checkLibcalls(F))
break;

if (Libcalls.hasLibcalls())
for (Function &F : Composite.functions())
addNoBuiltinAttributes(F);
}

Error IRMover::move(std::unique_ptr<Module> Src,
ArrayRef<GlobalValue *> ValuesToLink,
LazyCallback AddLazyFor, bool IsPerformingImport) {
IRLinker TheIRLinker(Composite, SharedMDs, IdentifiedStructTypes,
std::move(Src), ValuesToLink, std::move(AddLazyFor),
IsPerformingImport);
std::move(Src), ValuesToLink, Libcalls,
std::move(AddLazyFor), IsPerformingImport);
Error E = TheIRLinker.run();
Composite.dropTriviallyDeadConstantArrays();
return E;
Expand Down
21 changes: 21 additions & 0 deletions llvm/test/Linker/Inputs/strlen.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
target triple = "x86_64-unknown-linux-gnu"

define i64 @strlen(ptr %s) #0 {
entry:
br label %for.cond

for.cond:
%s.addr.0 = phi ptr [ %s, %entry ], [ %incdec.ptr, %for.cond ]
%0 = load i8, ptr %s.addr.0, align 1
%tobool.not = icmp eq i8 %0, 0
%incdec.ptr = getelementptr inbounds i8, ptr %s.addr.0, i64 1
br i1 %tobool.not, label %for.end, label %for.cond

for.end:
%sub.ptr.lhs.cast = ptrtoint ptr %s.addr.0 to i64
%sub.ptr.rhs.cast = ptrtoint ptr %s to i64
%sub.ptr.sub = sub i64 %sub.ptr.lhs.cast, %sub.ptr.rhs.cast
ret i64 %sub.ptr.sub
}

attributes #0 = { noinline }
39 changes: 39 additions & 0 deletions llvm/test/Linker/libcalls.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
; RUN: llvm-link %s %S/Inputs/strlen.ll -S -o - 2>%t.a.err | FileCheck %s --check-prefix=CHECK1
; RUN: llvm-link %S/Inputs/strlen.ll %s -S -o - 2>%t.a.err | FileCheck %s --check-prefix=CHECK2

target triple = "x86_64-unknown-linux-gnu"

@.str = private unnamed_addr constant [7 x i8] c"string\00", align 1
@str = dso_local global ptr @.str, align 8

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add another function here, so the order in the output should be
something
strlen
something
and all should be annotated.

define void @foo() #0 {
ret void
}

declare i64 @strlen(ptr)

define void @bar() #0 {
ret void
}

define i64 @baz() #0 {
entry:
%0 = load ptr, ptr @str, align 8
%call = call i64 @strlen(ptr noundef %0)
ret i64 %call
}

attributes #0 = { noinline }

; CHECK1: define void @foo() #[[ATTR0:[0-9]+]]
; CHECK1: define void @bar() #[[ATTR0:[0-9]+]]
; CHECK1: define i64 @baz() #[[ATTR0:[0-9]+]]
; CHECK1: define i64 @strlen(ptr [[S:%.*]]) #[[ATTR0]]

; CHECK2: define i64 @strlen(ptr [[S:%.*]]) #[[ATTR0:[0-9]+]]
; CHECK2: define void @foo() #[[ATTR0:[0-9]+]]
; CHECK2: define void @bar() #[[ATTR0:[0-9]+]]
; CHECK2: define i64 @baz() #[[ATTR0]]

; CHECK1: attributes #[[ATTR0]] = { nobuiltin noinline "no-builtins" }
; CHECK2: attributes #[[ATTR0]] = { nobuiltin noinline "no-builtins" }
Loading