struct FunctionSummaryYaml {
unsigned Linkage;
bool NotEligibleToImport, Live, IsLocal;
+ std::vector<uint64_t> Refs;
std::vector<uint64_t> TypeTests;
std::vector<FunctionSummary::VFuncId> TypeTestAssumeVCalls,
TypeCheckedLoadVCalls;
io.mapOptional("NotEligibleToImport", summary.NotEligibleToImport);
io.mapOptional("Live", summary.Live);
io.mapOptional("Local", summary.IsLocal);
+ io.mapOptional("Refs", summary.Refs);
io.mapOptional("TypeTests", summary.TypeTests);
io.mapOptional("TypeTestAssumeVCalls", summary.TypeTestAssumeVCalls);
io.mapOptional("TypeCheckedLoadVCalls", summary.TypeCheckedLoadVCalls);
io.setError("key not an integer");
return;
}
- auto P = V.emplace(KeyInt, /*IsAnalysis=*/false);
- auto &Elem = (*P.first).second;
+ if (!V.count(KeyInt))
+ V.emplace(KeyInt, /*IsAnalysis=*/false);
+ auto &Elem = V.find(KeyInt)->second;
for (auto &FSum : FSums) {
+ std::vector<ValueInfo> Refs;
+ for (auto &RefGUID : FSum.Refs) {
+ if (!V.count(RefGUID))
+ V.emplace(RefGUID, /*IsAnalysis=*/false);
+ Refs.push_back(ValueInfo(/*IsAnalysis=*/false, &*V.find(RefGUID)));
+ }
Elem.SummaryList.push_back(llvm::make_unique<FunctionSummary>(
GlobalValueSummary::GVFlags(
static_cast<GlobalValue::LinkageTypes>(FSum.Linkage),
FSum.NotEligibleToImport, FSum.Live, FSum.IsLocal),
- 0, FunctionSummary::FFlags{}, ArrayRef<ValueInfo>{},
+ 0, FunctionSummary::FFlags{}, Refs,
ArrayRef<FunctionSummary::EdgeTy>{}, std::move(FSum.TypeTests),
std::move(FSum.TypeTestAssumeVCalls),
std::move(FSum.TypeCheckedLoadVCalls),
for (auto &P : V) {
std::vector<FunctionSummaryYaml> FSums;
for (auto &Sum : P.second.SummaryList) {
- if (auto *FSum = dyn_cast<FunctionSummary>(Sum.get()))
+ if (auto *FSum = dyn_cast<FunctionSummary>(Sum.get())) {
+ std::vector<uint64_t> Refs;
+ for (auto &VI : FSum->refs())
+ Refs.push_back(VI.getGUID());
FSums.push_back(FunctionSummaryYaml{
FSum->flags().Linkage,
static_cast<bool>(FSum->flags().NotEligibleToImport),
static_cast<bool>(FSum->flags().Live),
- static_cast<bool>(FSum->flags().DSOLocal), FSum->type_tests(),
- FSum->type_test_assume_vcalls(), FSum->type_checked_load_vcalls(),
+ static_cast<bool>(FSum->flags().DSOLocal), Refs,
+ FSum->type_tests(), FSum->type_test_assume_vcalls(),
+ FSum->type_checked_load_vcalls(),
FSum->type_test_assume_const_vcalls(),
FSum->type_checked_load_const_vcalls()});
+ }
}
if (!FSums.empty())
io.mapRequired(llvm::utostr(P.first).c_str(), FSums);
unsigned CurUniqueId = 0;
SmallVector<MDNode *, 2> Types;
+ // Cross-DSO CFI emits jumptable entries for exported functions as well as
+ // address taken functions in case they are address taken in other modules.
+ const bool CrossDsoCfi = M.getModuleFlag("Cross-DSO CFI") != nullptr;
+
struct ExportedFunctionInfo {
CfiFunctionLinkage Linkage;
MDNode *FuncMD; // {name, linkage, type[, type...]}
};
DenseMap<StringRef, ExportedFunctionInfo> ExportedFunctions;
if (ExportSummary) {
+ // A set of all functions that are address taken by a live global object.
+ DenseSet<GlobalValue::GUID> AddressTaken;
+ for (auto &I : *ExportSummary)
+ for (auto &GVS : I.second.SummaryList)
+ if (GVS->isLive())
+ for (auto &Ref : GVS->refs())
+ AddressTaken.insert(Ref.getGUID());
+
NamedMDNode *CfiFunctionsMD = M.getNamedMetadata("cfi.functions");
if (CfiFunctionsMD) {
for (auto FuncMD : CfiFunctionsMD->operands()) {
assert(FuncMD->getNumOperands() >= 2);
StringRef FunctionName =
cast<MDString>(FuncMD->getOperand(0))->getString();
- if (!ExportSummary->isGUIDLive(GlobalValue::getGUID(
- GlobalValue::dropLLVMManglingEscape(FunctionName))))
- continue;
CfiFunctionLinkage Linkage = static_cast<CfiFunctionLinkage>(
cast<ConstantAsMetadata>(FuncMD->getOperand(1))
->getValue()
->getUniqueInteger()
.getZExtValue());
+ const GlobalValue::GUID GUID = GlobalValue::getGUID(
+ GlobalValue::dropLLVMManglingEscape(FunctionName));
+ // Do not emit jumptable entries for functions that are not-live and
+ // have no live references (and are not exported with cross-DSO CFI.)
+ if (!ExportSummary->isGUIDLive(GUID))
+ continue;
+ if (!AddressTaken.count(GUID)) {
+ if (!CrossDsoCfi || Linkage != CFL_Definition)
+ continue;
+
+ bool Exported = false;
+ if (auto VI = ExportSummary->getValueInfo(GUID))
+ for (auto &GVS : VI.getSummaryList())
+ if (GVS->isLive() && !GlobalValue::isLocalLinkage(GVS->linkage()))
+ Exported = true;
+
+ if (!Exported)
+ continue;
+ }
auto P = ExportedFunctions.insert({FunctionName, {Linkage, FuncMD}});
if (!P.second && P.first->second.Linkage != CFL_Definition)
P.first->second = {Linkage, FuncMD};
bool IsDefinition = !GO.isDeclarationForLinker();
bool IsExported = false;
- if (isa<Function>(GO) && ExportedFunctions.count(GO.getName())) {
- IsDefinition |= ExportedFunctions[GO.getName()].Linkage == CFL_Definition;
- IsExported = true;
+ if (Function *F = dyn_cast<Function>(&GO)) {
+ if (ExportedFunctions.count(F->getName())) {
+ IsDefinition |= ExportedFunctions[F->getName()].Linkage == CFL_Definition;
+ IsExported = true;
+ // TODO: The logic here checks only that the function is address taken,
+ // not that the address takers are live. This can be updated to check
+ // their liveness and emit fewer jumptable entries once monolithic LTO
+ // builds also emit summaries.
+ } else if (!F->hasAddressTaken()) {
+ if (!CrossDsoCfi || !IsDefinition || F->hasLocalLinkage())
+ continue;
+ }
}
auto *GTM =
ret void
}
-define i1 @_start(i8* %p) {
- %1 = call i1 @llvm.type.test(i8* %p, metadata !"typeid1")
- call void @f1()
- call void @f2()
- ret i1 %1
+define i1 @_start(i1 %i) {
+ %1 = select i1 %i, void ()* @f1, void ()* @f2
+ %2 = bitcast void ()* %1 to i8*
+ %3 = call i1 @llvm.type.test(i8* %2, metadata !"typeid1")
+ ret i1 %3
}
declare i1 @llvm.type.test(i8*, metadata)
; RUN: opt -thinlto-bc %s -o %t1.bc
; RUN: llvm-lto2 run -thinlto-distributed-indexes %t1.bc -o %t.out -save-temps \
; RUN: -r %t1.bc,foo,plx \
-; RUN: -r %t1.bc,bar,x
+; RUN: -r %t1.bc,bar,x \
+; RUN: -r %t1.bc,addrtaken,px
; RUN: llvm-bcanalyzer -dump %t.out.index.bc | FileCheck %s --check-prefix=COMBINED
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
ret i1 %x
}
-declare !type !0 void @bar()
+declare !type !0 i1 @bar(i8*)
+
+; Functions must be address taken to have jump table entries emitted
+define void @addrtaken(i1 %i) {
+ %1 = select i1 %i, i1(i8*)* @foo, i1(i8*)* @bar
+ ret void
+}
declare i1 @llvm.type.test(i8* %ptr, metadata %type) nounwind readnone
--- /dev/null
+---
+GlobalValueMap:
+ 42:
+ - Live: true
+ # guid("f"), guid("f2"), guid("f3"), guid("g"), guid("h"), guid("external"), guid("external_weak")
+ Refs: [14740650423002898831, 8471399308421654326, 4197650231481825559, 13146401226427987378, 8124147457056772133, 5224464028922159466, 5227079976482001346]
+ TypeTests: [14276520915468743435, 15427464259790519041] # guid("typeid1"), guid("typeid2")
+ 14740650423002898831: # guid("f")
+ - Linkage: 0 # external
+ Live: true
+ 8471399308421654326: # guid("f2")
+ - Linkage: 0 # external
+ Live: true
+ 4197650231481825559: # guid("f3")
+ - Linkage: 0 # external
+ Live: true
+ 13146401226427987378: # guid("g")
+ - Linkage: 0 # external
+ Live: true
+ 8124147457056772133: # guid("h")
+ - Linkage: 0 # external
+ Live: true
+ 5224464028922159466: # guid("external")
+ - Linkage: 0 # external
+ Live: true
+ 5227079976482001346: # guid("external_weak")
+ - Linkage: 9 # extern_weak
+ Live: true
+...
--- /dev/null
+---
+GlobalValueMap:
+ 42:
+ - Live: true
+ Refs: [16594175687743574550, 2415377257478301385] # guid("external_addrtaken"), guid("external_addrtaken2")
+ TypeTests: [14276520915468743435, 15427464259790519041] # guid("typeid1"), guid("typeid2")
+ 5224464028922159466: # guid("external")
+ - Linkage: 0 # external
+ Live: true
+ 16430208882958242304: # guid("external2")
+ - Linkage: 0 # external
+ Live: true
+ 16594175687743574550: # guid("external_addrtaken")
+ - Linkage: 0 # external
+ Live: true
+ 2415377257478301385: # guid("external_addrtaken2")
+ - Linkage: 0 # external
+ Live: true
+ 15859245615183425489: # guid("internal")
+ - Linkage: 7 # internal
+ Live: true
+...
-; RUN: opt -S %s -lowertypetests -lowertypetests-summary-action=export -lowertypetests-read-summary=%S/Inputs/use-typeid1-typeid2.yaml | FileCheck %s
+; RUN: opt -S %s -lowertypetests -lowertypetests-summary-action=export -lowertypetests-read-summary=%S/Inputs/exported-funcs.yaml | FileCheck %s
;
-; CHECK: @alias1 = weak alias void (), void ()* @f
-; CHECK: @alias2 = hidden alias void (), void ()* @f
-; CHECK: declare !type !1 void @alias3()
+; CHECK: @alias1 = weak alias void (), void ()* @external_addrtaken
+; CHECK: @alias2 = hidden alias void (), void ()* @external_addrtaken
; CHECK-NOT: @alias3 = alias
+; CHECK-NOT: @not_present
target triple = "x86_64-unknown-linux"
!cfi.functions = !{!0, !2, !3}
!aliases = !{!4, !5, !6}
-!0 = !{!"f", i8 0, !1}
+!0 = !{!"external_addrtaken", i8 0, !1}
!1 = !{i64 0, !"typeid1"}
!2 = !{!"alias1", i8 1, !1}
; alias2 not included here, this could happen if the only reference to alias2
; is in a module compiled without cfi-icall
!3 = !{!"alias3", i8 1, !1}
-!4 = !{!"alias1", !"f", i8 0, i8 1}
-!5 = !{!"alias2", !"f", i8 1, i8 0}
+!4 = !{!"alias1", !"external_addrtaken", i8 0, i8 1}
+!5 = !{!"alias2", !"external_addrtaken", i8 1, i8 0}
!6 = !{!"alias3", !"not_present", i8 0, i8 0}
--- /dev/null
+; Test that external functions have jumptable entries emitted even if they are
+; not address-taken when Cross-DSO CFI is used, but not otherwise.
+
+; RUN: opt -S -lowertypetests -lowertypetests-summary-action=export -lowertypetests-read-summary=%S/Inputs/exported-funcs.yaml < %s | FileCheck --check-prefixes=CHECK,CROSSDSO %s
+; RUN: cat %s | grep -v "llvm.module.flags" | opt -S -lowertypetests -lowertypetests-summary-action=export -lowertypetests-read-summary=%S/Inputs/exported-funcs.yaml | FileCheck --check-prefixes=CHECK,NORMAL %s
+
+target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+;;; Defined in the ThinLTO portion of the build (e.g. the summary)
+; CROSSDSO: declare !type !1 !type !2 hidden void @external.cfi()
+; NORMAL: declare !type !1 !type !2 void @external()
+declare !type !1 !type !2 void @external()
+
+; Don't emit jumptable entries for external declarations/non-external definitions
+; CHECK-NOT: @external2
+; CHECK-NOT: @internal
+
+;;; Defined in the regular LTO portion of the build
+; CROSSDSO: define hidden void @regularlto_external.cfi()
+; NORMAL: define void @regularlto_external()
+define void @regularlto_external() !type !1 !type !2 {
+ ret void
+}
+
+; CHECK: define internal void @regularlto_internal()
+define internal void @regularlto_internal() !type !1 !type !2 {
+ ret void
+}
+
+!cfi.functions = !{!0, !3, !4}
+!llvm.module.flags = !{!5}
+
+!0 = !{!"external", i8 0, !1, !2}
+!1 = !{i64 0, !"typeid1"}
+!2 = !{i64 0, i64 1234}
+!3 = !{!"external2", i8 1, !1, !2}
+!4 = !{!"internal", i8 0, !1, !2}
+!5 = !{i32 4, !"Cross-DSO CFI", i32 1}
-; RUN: opt -S -lowertypetests -lowertypetests-summary-action=export -lowertypetests-read-summary=%S/Inputs/use-typeid1-typeid2.yaml -lowertypetests-write-summary=%t < %s | FileCheck %s
+; RUN: opt -S -lowertypetests -lowertypetests-summary-action=export -lowertypetests-read-summary=%S/Inputs/export-icall.yaml -lowertypetests-write-summary=%t < %s | FileCheck %s
; RUN: FileCheck --check-prefix=SUMMARY %s < %t
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
-; RUN: opt -S %s -lowertypetests -lowertypetests-summary-action=export -lowertypetests-read-summary=%S/Inputs/use-typeid1-typeid2.yaml | FileCheck %s
+; RUN: opt -S %s -lowertypetests -lowertypetests-summary-action=export -lowertypetests-read-summary=%S/Inputs/exported-funcs.yaml | FileCheck %s
;
-; CHECK: module asm ".symver exported_and_symver, alias1"
-; CHECK-NOT: .symver exported
-; CHECK-NOT: .symver symver
+; CHECK: module asm ".symver external_addrtaken, alias1"
+; CHECK-NOT: .symver external_addrtaken2
+; CHECK-NOT: .symver not_exported
target triple = "x86_64-unknown-linux"
!cfi.functions = !{!0, !1}
!symvers = !{!3, !4}
-!0 = !{!"exported_and_symver", i8 2, !2}
-!1 = !{!"exported", i8 2, !2}
+!0 = !{!"external_addrtaken", i8 0, !2}
+!1 = !{!"external_addrtaken2", i8 0, !2}
!2 = !{i64 0, !"typeid1"}
-!3 = !{!"exported_and_symver", !"alias1"}
-!4 = !{!"symver", !"alias2"}
+!3 = !{!"external_addrtaken", !"alias1"}
+!4 = !{!"not_exported", !"alias2"}
ret void
}
+declare void @takeaddr(void()*, void()*, void()*, void()*, void()*)
+define void @addrtaken() {
+ call void @takeaddr(void()* @f1, void()* @g1, void()* @f2, void()* @g2, void()* @h2)
+ ret void
+}
+
!0 = !{i32 0, !"typeid1"}
!1 = !{i32 0, !"typeid2"}
-; RUN: opt -S -lowertypetests -mtriple=x86_64-unknown-linux-gnu < %s | FileCheck --check-prefix=X64 %s
-; RUN: opt -S -lowertypetests -mtriple=wasm32-unknown-unknown < %s | FileCheck --check-prefix=WASM32 %s
+; RUN: opt -S -lowertypetests -mtriple=x86_64-unknown-linux-gnu < %s | FileCheck --check-prefixes=CHECK,X64 %s
+; RUN: opt -S -lowertypetests -mtriple=wasm32-unknown-unknown < %s | FileCheck --check-prefixes=CHECK,WASM32 %s
; Tests that we correctly handle external references, including the case where
; all functions in a bitset are external references.
; WASM32: private constant [0 x i8] zeroinitializer
-; WASM32: declare !type !{{[0-9]+}} void @foo()
-declare !type !0 void @foo()
+; WASM32: declare !type !{{[0-9]+}} !wasm.index !{{[0-9]+}} void @foo1()
+declare !type !0 void @foo1()
+; WASM32: declare !type !{{[0-9]+}} void @foo2()
+declare !type !1 void @foo2()
+; CHECK-LABEL: @bar
define i1 @bar(i8* %ptr) {
- ; X64: icmp eq i64 {{.*}}, ptrtoint (void ()* @[[JT:.*]] to i64)
- ; WASM32: ret i1 false
- %p = call i1 @llvm.type.test(i8* %ptr, metadata !"void")
+ ; CHECK: %[[ICMP:[0-9]+]] = icmp eq
+ ; CHECK: ret i1 %[[ICMP]]
+ %p = call i1 @llvm.type.test(i8* %ptr, metadata !"type1")
ret i1 %p
}
+; CHECK-LABEL: @baz
+define i1 @baz(i8* %ptr) {
+ ; CHECK: ret i1 false
+ %p = call i1 @llvm.type.test(i8* %ptr, metadata !"type2")
+ ret i1 %p
+}
+
+; CHECK-LABEL: @addrtaken
+define void()* @addrtaken() {
+ ; X64: ret void ()* @[[JT:.*]]
+ ret void()* @foo1
+}
+
declare i1 @llvm.type.test(i8* %ptr, metadata %bitset) nounwind readnone
-!0 = !{i64 0, !"void"}
-; WASM-NOT: !{i64 0}
-; WASM-NOT: !{i64 1}
+!0 = !{i64 0, !"type1"}
+!1 = !{i64 0, !"type2"}
; X64: define private void @[[JT]]() #{{.*}} align {{.*}} {
-; X64: call void asm sideeffect "jmp ${0:c}@plt\0Aint3\0Aint3\0Aint3\0A", "s"(void ()* @foo)
+; X64: call void asm sideeffect "jmp ${0:c}@plt\0Aint3\0Aint3\0Aint3\0A", "s"(void ()* @foo1)
-; RUN: opt -S -lowertypetests -lowertypetests-summary-action=export -lowertypetests-read-summary=%S/Inputs/use-typeid1-typeid2.yaml -lowertypetests-write-summary=%t < %s | FileCheck %s
+; RUN: opt -S -lowertypetests -lowertypetests-summary-action=export -lowertypetests-read-summary=%S/Inputs/exported-funcs.yaml -lowertypetests-write-summary=%t < %s | FileCheck %s
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"
-declare !type !2 extern_weak void @h(i8)
+declare !type !2 extern_weak void @external_addrtaken(i8)
!cfi.functions = !{!0, !1}
-!0 = !{!"h", i8 2, !2}
-!1 = !{!"h", i8 0, !2}
+!0 = !{!"external_addrtaken", i8 2, !2}
+!1 = !{!"external_addrtaken", i8 0, !2}
!2 = !{i64 0, !"typeid1"}
-; CHECK-DAG: @h = alias void (i8), bitcast
+; CHECK-DAG: @external_addrtaken = alias void (i8), bitcast