diff --git a/llvm/lib/Linker/IRMover.cpp b/llvm/lib/Linker/IRMover.cpp --- a/llvm/lib/Linker/IRMover.cpp +++ b/llvm/lib/Linker/IRMover.cpp @@ -19,6 +19,8 @@ #include "llvm/IR/Function.h" #include "llvm/IR/GVMaterializer.h" #include "llvm/IR/GlobalValue.h" +#include "llvm/IR/Instruction.h" +#include "llvm/IR/Instructions.h" #include "llvm/IR/Intrinsics.h" #include "llvm/IR/Module.h" #include "llvm/IR/PseudoProbe.h" @@ -526,6 +528,9 @@ void prepareCompileUnitsForImport(); void linkNamedMDNodes(); + /// Update attributes while linking. + void updateAttributes(GlobalValue &GV); + public: IRLinker(Module &DstM, MDMapT &SharedMDs, IRMover::IdentifiedStructTypeSet &Set, std::unique_ptr SrcM, @@ -636,6 +641,7 @@ if (ForIndirectSymbol || shouldLink(New, *SGV)) setError(linkGlobalValueBody(*New, *SGV)); + updateAttributes(*New); return New; } @@ -1527,6 +1533,33 @@ return InlineAsm; } +void IRLinker::updateAttributes(GlobalValue &GV) { + /// Remove nocallback attribute while linking, because nocallback attribute + /// indicates that the function is only allowed to jump back into caller's + /// module only by a return or an exception. When modules are linked, this + /// property cannot be guaranteed anymore. For example, the nocallback + /// function may contain a call to another module. But if we merge its caller + /// and callee module here, and not the module containing the nocallback + /// function definition itself, the nocallback property will be violated + /// (since the nocallback function will call back into the newly merged module + /// containing both its caller and callee). This could happen if the module + /// containing the nocallback function definition is native code, so it does + /// not participate in the LTO link. Note if the nocallback function does + /// participate in the LTO link, and thus ends up in the merged module + /// containing its caller and callee, removing the attribute doesn't hurt as + /// it has no effect on definitions in the same module. + if (auto *F = dyn_cast(&GV)) { + if (!F->isIntrinsic()) + F->removeFnAttr(llvm::Attribute::NoCallback); + + // Remove nocallback attribute when it is on a call-site. + for (BasicBlock &BB : *F) + for (Instruction &I : BB) + if (CallInst *CI = dyn_cast(&I)) + CI->removeFnAttr(Attribute::NoCallback); + } +} + Error IRLinker::run() { // Ensure metadata materialized before value mapping. if (SrcM->getMaterializer()) diff --git a/llvm/test/Linker/Inputs/drop-attribute.ll b/llvm/test/Linker/Inputs/drop-attribute.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Linker/Inputs/drop-attribute.ll @@ -0,0 +1,4 @@ +define void @test_nocallback_declaration_definition_linked_in() { +entry: + ret void +} diff --git a/llvm/test/Linker/drop-attribute.ll b/llvm/test/Linker/drop-attribute.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Linker/drop-attribute.ll @@ -0,0 +1,44 @@ +; RUN: llvm-link %s %p/Inputs/drop-attribute.ll -S -o - | FileCheck %s + +; Test case that checks that nocallback attribute is dropped during linking. + +; CHECK: define i32 @main() +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @test_nocallback_definition() +; Test that checks that nocallback attribute on a call-site is dropped. +; CHECK-NEXT: call void @test_nocallback_call_site(){{$}} +; CHECK-NEXT: %0 = call float @llvm.sqrt.f32(float undef) +; CHECK-NEXT: call void @test_nocallback_declaration_definition_not_linked_in() +; CHECK-NEXT: call void @test_nocallback_declaration_definition_linked_in() +define i32 @main() { +entry: + call void @test_nocallback_definition() + call void @test_nocallback_call_site() nocallback + call float @llvm.sqrt.f32(float undef) + call void @test_nocallback_declaration_definition_not_linked_in() + call void @test_nocallback_declaration_definition_linked_in() + ret i32 0 +} + +; Test that checks that nocallback attribute on a definition is dropped. +; CHECK: define void @test_nocallback_definition() +define void @test_nocallback_definition() nocallback { + ret void +} + +; Test that checks that nocallback attribute on a declaration when a definition is linked in is dropped. +; CHECK: declare void @test_nocallback_call_site(){{$}} +declare void @test_nocallback_call_site() + +; Test that checks that nocallback attribute on an intrinsic is NOT dropped. +; CHECK: ; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn +; CHECK-NEXT: declare float @llvm.sqrt.f32(float) #0 +declare float @llvm.sqrt.f32(float) nocallback + +; Test that checks that nocallback attribute on a declaration when a definition is not linked in is dropped. +; CHECK: declare void @test_nocallback_declaration_definition_not_linked_in(){{$}} +declare void @test_nocallback_declaration_definition_not_linked_in() nocallback + +; Test that checks that nocallback attribute on a declaration when a definition is linked in is dropped. +; CHECK: define void @test_nocallback_declaration_definition_linked_in() {{{$}} +declare void @test_nocallback_declaration_definition_linked_in() nocallback