diff --git a/llvm/lib/Transforms/IPO/ElimAvailExtern.cpp b/llvm/lib/Transforms/IPO/ElimAvailExtern.cpp --- a/llvm/lib/Transforms/IPO/ElimAvailExtern.cpp +++ b/llvm/lib/Transforms/IPO/ElimAvailExtern.cpp @@ -12,22 +12,82 @@ //===----------------------------------------------------------------------===// #include "llvm/Transforms/IPO/ElimAvailExtern.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/Statistic.h" #include "llvm/IR/Constant.h" +#include "llvm/IR/DebugInfoMetadata.h" #include "llvm/IR/Function.h" #include "llvm/IR/GlobalValue.h" #include "llvm/IR/GlobalVariable.h" +#include "llvm/IR/MDBuilder.h" #include "llvm/IR/Module.h" +#include "llvm/Support/CommandLine.h" #include "llvm/Transforms/IPO.h" #include "llvm/Transforms/Utils/GlobalStatus.h" +#include "llvm/Transforms/Utils/ModuleUtils.h" using namespace llvm; #define DEBUG_TYPE "elim-avail-extern" -STATISTIC(NumFunctions, "Number of functions removed"); +cl::opt ConvertToLocal( + "avail-extern-to-local", cl::Hidden, + cl::desc("Convert available_externally into locals, renaming them " + "to avoid link-time clashes.")); + +STATISTIC(NumRemovals, "Number of functions removed"); +STATISTIC(NumConversions, "Number of functions converted"); STATISTIC(NumVariables, "Number of global variables removed"); +void deleteFunction(Function &F) { + // This will set the linkage to external + F.deleteBody(); + ++NumRemovals; +} + +/// Create a copy of the thinlto import, mark it local, and redirect direct +/// calls to the copy. Only direct calls are replaced, so that e.g. indirect +/// call function pointer tests would use the global identity of the function. +/// +/// Currently, Value Profiling ("VP") MD_prof data isn't updated to refer to the +/// clone's GUID (which will be different, because the name and linkage is +/// different), under the assumption that the last consumer of this data is +/// upstream the pipeline (e.g. ICP). +static void convertToLocalCopy(Module &M, Function &F) { + assert(F.hasAvailableExternallyLinkage()); + assert(!F.isDeclaration()); + // If we can't find a single use that's a call, just delete the function. + if (F.uses().end() == llvm::find_if(F.uses(), [&](Use &U) { + return isa(U.getUser()); + })) + return deleteFunction(F); + + auto OrigName = F.getName().str(); + // Build a new name. We still need the old name (see below). + // We could just rely on internal linking allowing 2 modules have internal + // functions with the same name, but that just creates more trouble than + // necessary e.g. distinguishing profiles or debugging. Instead, we append the + // module identifier. + auto NewName = OrigName + ".__uniq" + getUniqueModuleId(&M); + F.setName(NewName); + if (auto *SP = F.getSubprogram()) + SP->replaceLinkageName(MDString::get(F.getParent()->getContext(), NewName)); + + F.setLinkage(GlobalValue::InternalLinkage); + // Now make a declaration for the old name. We'll use it if there are non-call + // uses. For those, it would be incorrect to replace them with the local copy: + // for example, one such use could be taking the address of the function and + // passing it to an external function, which, in turn, might compare the + // function pointer to the original (non-local) function pointer, e.g. as part + // of indirect call promotion. + auto *Decl = + Function::Create(F.getFunctionType(), GlobalValue::ExternalLinkage, + F.getAddressSpace(), OrigName, F.getParent()); + F.replaceUsesWithIf(Decl, + [&](Use &U) { return !isa(U.getUser()); }); + ++NumConversions; +} + static bool eliminateAvailableExternally(Module &M) { bool Changed = false; @@ -43,19 +103,21 @@ } GV.removeDeadConstantUsers(); GV.setLinkage(GlobalValue::ExternalLinkage); - NumVariables++; + ++NumVariables; Changed = true; } // Drop the bodies of available externally functions. - for (Function &F : M) { - if (!F.hasAvailableExternallyLinkage()) + for (Function &F : llvm::make_early_inc_range(M)) { + if (F.isDeclaration() || !F.hasAvailableExternallyLinkage()) continue; - if (!F.isDeclaration()) - // This will set the linkage to external - F.deleteBody(); + + if (ConvertToLocal) + convertToLocalCopy(M, F); + else + deleteFunction(F); + F.removeDeadConstantUsers(); - NumFunctions++; Changed = true; } diff --git a/llvm/test/Transforms/EliminateAvailableExternally/transform-to-local.ll b/llvm/test/Transforms/EliminateAvailableExternally/transform-to-local.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/EliminateAvailableExternally/transform-to-local.ll @@ -0,0 +1,29 @@ +; REQUIRES: asserts +; RUN: opt -passes=elim-avail-extern -avail-extern-to-local -stats -S 2>&1 < %s | FileCheck %s + + +declare void @call_out(ptr %fct) + +define available_externally hidden void @f() { + ret void +} + +define available_externally hidden void @g() { + ret void +} + +define void @hello(ptr %g) { + call void @f() + %f = load ptr, ptr @f + call void @call_out(ptr %f) + ret void +} + +; CHECK: define internal void @f.__uniq.{{[0-9|a-f]*}}() +; CHECK: declare hidden void @g() +; CHECK: call void @f.__uniq.{{[0-9|a-f]*}}() +; CHECK-NEXT: load ptr, ptr @f +; CHECK-NEXT: call void @call_out(ptr %f) +; CHECK: Statistics Collected +; CHECK: 1 elim-avail-extern - Number of functions converted +; CHECK: 1 elim-avail-extern - Number of functions removed \ No newline at end of file