3 changes: 3 additions & 0 deletions llvm/lib/LTO/LTOBackend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#include "llvm/Support/ToolOutputFile.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Target/TargetMachine.h"
#include "llvm/Transforms/IPO/WholeProgramDevirt.h"
#include "llvm/Transforms/Scalar/LoopPassManager.h"
#include "llvm/Transforms/Utils/FunctionImportUtils.h"
#include "llvm/Transforms/Utils/SplitModule.h"
Expand Down Expand Up @@ -560,6 +561,8 @@ Error lto::thinBackend(const Config &Conf, unsigned Task, AddStreamFn AddStream,
// the module, if applicable.
Mod.setPartialSampleProfileRatio(CombinedIndex);

updatePublicTypeTestCalls(Mod, CombinedIndex.withWholeProgramVisibility());

if (Conf.CodeGenOnly) {
codegen(Conf, TM.get(), AddStream, Task, Mod, CombinedIndex);
return finalizeOptimizationRemarks(std::move(DiagnosticOutputFile));
Expand Down
12 changes: 12 additions & 0 deletions llvm/lib/LTO/LTOCodeGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,8 @@ bool LTOCodeGenerator::optimize() {
// linker option in the old LTO API, but this call allows it to be specified
// via the internal option. Must be done before WPD invoked via the optimizer
// pipeline run below.
updatePublicTypeTestCalls(*MergedModule,
/* WholeProgramVisibilityEnabledInLTO */ false);
updateVCallVisibilityInModule(*MergedModule,
/* WholeProgramVisibilityEnabledInLTO */ false,
// FIXME: This needs linker information via a
Expand All @@ -539,6 +541,16 @@ bool LTOCodeGenerator::optimize() {
// Add an appropriate DataLayout instance for this module...
MergedModule->setDataLayout(TargetMach->createDataLayout());

if (!SaveIRBeforeOptPath.empty()) {
std::error_code EC;
raw_fd_ostream OS(SaveIRBeforeOptPath, EC, sys::fs::OF_None);
if (EC)
report_fatal_error(Twine("Failed to open ") + SaveIRBeforeOptPath +
" to save optimized bitcode\n");
WriteBitcodeToFile(*MergedModule, OS,
/* ShouldPreserveUseListOrder */ true);
}

ModuleSummaryIndex CombinedIndex(false);
TargetMach = createTargetMachine();
if (!opt(Config, TargetMach.get(), 0, *MergedModule, /*IsThinLTO=*/false,
Expand Down
6 changes: 6 additions & 0 deletions llvm/lib/LTO/ThinLTOCodeGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,10 @@ ProcessThinLTOModule(Module &TheModule, ModuleSummaryIndex &Index,
bool DisableCodeGen, StringRef SaveTempsDir,
bool Freestanding, unsigned OptLevel, unsigned count,
bool DebugPassManager) {
// See comment at call to updateVCallVisibilityInIndex() for why
// WholeProgramVisibilityEnabledInLTO is false.
updatePublicTypeTestCalls(TheModule,
/* WholeProgramVisibilityEnabledInLTO */ false);

// "Benchmark"-like optimization: single-source case
bool SingleModule = (ModuleMap.size() == 1);
Expand Down Expand Up @@ -1047,6 +1051,8 @@ void ThinLTOCodeGenerator::run() {
// Currently there is no support for enabling whole program visibility via a
// linker option in the old LTO API, but this call allows it to be specified
// via the internal option. Must be done before WPD below.
if (hasWholeProgramVisibility(/* WholeProgramVisibilityEnabledInLTO */ false))
Index->setWithWholeProgramVisibility();
updateVCallVisibilityInIndex(*Index,
/* WholeProgramVisibilityEnabledInLTO */ false,
// FIXME: This needs linker information via a
Expand Down
61 changes: 37 additions & 24 deletions llvm/lib/Transforms/IPO/LowerTypeTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1778,35 +1778,48 @@ void LowerTypeTestsModule::replaceDirectCalls(Value *Old, Value *New) {
Old->replaceUsesWithIf(New, isDirectCall);
}

static void dropTypeTests(Module &M, Function &TypeTestFunc) {
for (Use &U : llvm::make_early_inc_range(TypeTestFunc.uses())) {
auto *CI = cast<CallInst>(U.getUser());
// Find and erase llvm.assume intrinsics for this llvm.type.test call.
for (Use &CIU : llvm::make_early_inc_range(CI->uses()))
if (auto *Assume = dyn_cast<AssumeInst>(CIU.getUser()))
Assume->eraseFromParent();
// If the assume was merged with another assume, we might have a use on a
// phi (which will feed the assume). Simply replace the use on the phi
// with "true" and leave the merged assume.
if (!CI->use_empty()) {
assert(
all_of(CI->users(), [](User *U) -> bool { return isa<PHINode>(U); }));
CI->replaceAllUsesWith(ConstantInt::getTrue(M.getContext()));
}
CI->eraseFromParent();
}
}

bool LowerTypeTestsModule::lower() {
Function *TypeTestFunc =
M.getFunction(Intrinsic::getName(Intrinsic::type_test));

if (DropTypeTests && TypeTestFunc) {
for (Use &U : llvm::make_early_inc_range(TypeTestFunc->uses())) {
auto *CI = cast<CallInst>(U.getUser());
// Find and erase llvm.assume intrinsics for this llvm.type.test call.
for (Use &CIU : llvm::make_early_inc_range(CI->uses()))
if (auto *Assume = dyn_cast<AssumeInst>(CIU.getUser()))
Assume->eraseFromParent();
// If the assume was merged with another assume, we might have a use on a
// phi (which will feed the assume). Simply replace the use on the phi
// with "true" and leave the merged assume.
if (!CI->use_empty()) {
assert(all_of(CI->users(),
[](User *U) -> bool { return isa<PHINode>(U); }));
CI->replaceAllUsesWith(ConstantInt::getTrue(M.getContext()));
}
CI->eraseFromParent();
if (DropTypeTests) {
if (TypeTestFunc)
dropTypeTests(M, *TypeTestFunc);
// Normally we'd have already removed all @llvm.public.type.test calls,
// except for in the case where we originally were performing ThinLTO but
// decided not to in the backend.
Function *PublicTypeTestFunc =
M.getFunction(Intrinsic::getName(Intrinsic::public_type_test));
if (PublicTypeTestFunc)
dropTypeTests(M, *PublicTypeTestFunc);
if (TypeTestFunc || PublicTypeTestFunc) {
// We have deleted the type intrinsics, so we no longer have enough
// information to reason about the liveness of virtual function pointers
// in GlobalDCE.
for (GlobalVariable &GV : M.globals())
GV.eraseMetadata(LLVMContext::MD_vcall_visibility);
return true;
}

// We have deleted the type intrinsics, so we no longer have enough
// information to reason about the liveness of virtual function pointers
// in GlobalDCE.
for (GlobalVariable &GV : M.globals())
GV.eraseMetadata(LLVMContext::MD_vcall_visibility);

return true;
return false;
}

// If only some of the modules were split, we cannot correctly perform
Expand Down
8 changes: 8 additions & 0 deletions llvm/lib/Transforms/IPO/ThinLTOBitcodeWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,14 @@ void promoteTypeIds(Module &M, StringRef ModuleId) {
}
}

if (Function *PublicTypeTestFunc =
M.getFunction(Intrinsic::getName(Intrinsic::public_type_test))) {
for (const Use &U : PublicTypeTestFunc->uses()) {
auto CI = cast<CallInst>(U.getUser());
ExternalizeTypeId(CI, 1);
}
}

if (Function *TypeCheckedLoadFunc =
M.getFunction(Intrinsic::getName(Intrinsic::type_checked_load))) {
for (const Use &U : TypeCheckedLoadFunc->uses()) {
Expand Down
35 changes: 31 additions & 4 deletions llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -773,15 +773,14 @@ PreservedAnalyses WholeProgramDevirtPass::run(Module &M,
return PreservedAnalyses::none();
}

namespace llvm {
// Enable whole program visibility if enabled by client (e.g. linker) or
// internal option, and not force disabled.
static bool hasWholeProgramVisibility(bool WholeProgramVisibilityEnabledInLTO) {
bool hasWholeProgramVisibility(bool WholeProgramVisibilityEnabledInLTO) {
return (WholeProgramVisibilityEnabledInLTO || WholeProgramVisibility) &&
!DisableWholeProgramVisibility;
}

namespace llvm {

/// If whole program visibility asserted, then upgrade all public vcall
/// visibility metadata on vtable definitions to linkage unit visibility in
/// Module IR (for regular or hybrid LTO).
Expand All @@ -790,7 +789,7 @@ void updateVCallVisibilityInModule(
const DenseSet<GlobalValue::GUID> &DynamicExportSymbols) {
if (!hasWholeProgramVisibility(WholeProgramVisibilityEnabledInLTO))
return;
for (GlobalVariable &GV : M.globals())
for (GlobalVariable &GV : M.globals()) {
// Add linkage unit visibility to any variable with type metadata, which are
// the vtable definitions. We won't have an existing vcall_visibility
// metadata on vtable definitions with public visibility.
Expand All @@ -800,6 +799,34 @@ void updateVCallVisibilityInModule(
// linker, as we have no information on their eventual use.
!DynamicExportSymbols.count(GV.getGUID()))
GV.setVCallVisibilityMetadata(GlobalObject::VCallVisibilityLinkageUnit);
}
}

void updatePublicTypeTestCalls(Module &M,
bool WholeProgramVisibilityEnabledInLTO) {
Function *PublicTypeTestFunc =
M.getFunction(Intrinsic::getName(Intrinsic::public_type_test));
if (!PublicTypeTestFunc)
return;
if (hasWholeProgramVisibility(WholeProgramVisibilityEnabledInLTO)) {
Function *TypeTestFunc =
Intrinsic::getDeclaration(&M, Intrinsic::type_test);
for (Use &U : make_early_inc_range(PublicTypeTestFunc->uses())) {
auto *CI = cast<CallInst>(U.getUser());
auto *NewCI = CallInst::Create(
TypeTestFunc, {CI->getArgOperand(0), CI->getArgOperand(1)}, None, "",
CI);
CI->replaceAllUsesWith(NewCI);
CI->eraseFromParent();
}
} else {
auto *True = ConstantInt::getTrue(M.getContext());
for (Use &U : make_early_inc_range(PublicTypeTestFunc->uses())) {
auto *CI = cast<CallInst>(U.getUser());
CI->replaceAllUsesWith(True);
CI->eraseFromParent();
}
}
}

/// If whole program visibility asserted, then upgrade all public vcall
Expand Down
35 changes: 35 additions & 0 deletions llvm/test/LTO/X86/public-type-test.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
; Test to ensure that the LTO API (legacy and new) lowers @llvm.public.type.test.

; RUN: llvm-as < %s > %t1
; RUN: llvm-lto -exported-symbol=_main %t1 -o %t2 --lto-save-before-opt --whole-program-visibility
; RUN: llvm-dis -o - %t2.0.preopt.bc | FileCheck %s --check-prefix=HIDDEN
; RUN: llvm-lto -exported-symbol=_main %t1 -o %t2 --lto-save-before-opt
; RUN: llvm-dis -o - %t2.0.preopt.bc | FileCheck %s --check-prefix=PUBLIC

; RUN: llvm-lto2 run %t1 -save-temps -pass-remarks=. \
; RUN: -whole-program-visibility \
; RUN: -o %t2 \
; RUN: -r=%t1,_main,px
; RUN: llvm-dis %t2.0.0.preopt.bc -o - | FileCheck %s --check-prefix=HIDDEN
; RUN: llvm-lto2 run %t1 -save-temps -pass-remarks=. \
; RUN: -o %t2 \
; RUN: -r=%t1,_main,px
; RUN: llvm-dis %t2.0.0.preopt.bc -o - | FileCheck %s --check-prefix=PUBLIC

; PUBLIC-NOT: call {{.*}}@llvm.public.type.test
; PUBLIC-NOT: call {{.*}}@llvm.type.test
; HIDDEN-NOT: call {{.*}}@llvm.public.type.test
; HIDDEN: call {{.*}}@llvm.type.test

target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx10.9"

define i32 @main(ptr %vtable) {
entry:
%p = call i1 @llvm.public.type.test(ptr %vtable, metadata !"_ZTS1A")
call void @llvm.assume(i1 %p)
ret i32 0
}

declare void @llvm.assume(i1)
declare i1 @llvm.public.type.test(ptr, metadata)
55 changes: 55 additions & 0 deletions llvm/test/ThinLTO/X86/devirt_vcall_vis_public.ll
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@
; RUN: -whole-program-visibility \
; RUN: -o %t3 \
; RUN: -r=%t2.o,test,px \
; RUN: -r=%t2.o,test_public,px \
; RUN: -r=%t2.o,_ZN1A1nEi,p \
; RUN: -r=%t2.o,_ZN1B1fEi,p \
; RUN: -r=%t2.o,_ZN1C1fEi,p \
; RUN: -r=%t2.o,_ZN1D1mEi,p \
; RUN: -r=%t2.o,_ZTV1B,px \
; RUN: -r=%t2.o,_ZTV1C,px \
; RUN: -r=%t2.o,_ZTV1D,px 2>&1 | FileCheck %s --check-prefix=REMARK
; RUN: llvm-dis %t3.1.0.preopt.bc -o - | FileCheck %s --check-prefix=CHECK-TT
; RUN: llvm-dis %t3.1.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-IR

; Hybrid WPD
Expand All @@ -26,6 +28,7 @@
; RUN: -whole-program-visibility \
; RUN: -o %t3 \
; RUN: -r=%t.o,test,px \
; RUN: -r=%t.o,test_public,px \
; RUN: -r=%t.o,_ZN1A1nEi,p \
; RUN: -r=%t.o,_ZN1B1fEi,p \
; RUN: -r=%t.o,_ZN1C1fEi,p \
Expand All @@ -40,6 +43,7 @@
; RUN: -r=%t.o,_ZTV1B,px \
; RUN: -r=%t.o,_ZTV1C,px \
; RUN: -r=%t.o,_ZTV1D,px 2>&1 | FileCheck %s --check-prefix=REMARK --dump-input=fail
; RUN: llvm-dis %t3.1.0.preopt.bc -o - | FileCheck %s --check-prefix=CHECK-TT
; RUN: llvm-dis %t3.1.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-IR

; Regular LTO WPD
Expand All @@ -48,15 +52,19 @@
; RUN: -whole-program-visibility \
; RUN: -o %t5 \
; RUN: -r=%t4.o,test,px \
; RUN: -r=%t4.o,test_public,px \
; RUN: -r=%t4.o,_ZN1A1nEi,p \
; RUN: -r=%t4.o,_ZN1B1fEi,p \
; RUN: -r=%t4.o,_ZN1C1fEi,p \
; RUN: -r=%t4.o,_ZN1D1mEi,p \
; RUN: -r=%t4.o,_ZTV1B,px \
; RUN: -r=%t4.o,_ZTV1C,px \
; RUN: -r=%t4.o,_ZTV1D,px 2>&1 | FileCheck %s --check-prefix=REMARK
; RUN: llvm-dis %t5.0.0.preopt.bc -o - | FileCheck %s --check-prefix=CHECK-TT
; RUN: llvm-dis %t5.0.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-IR

; REMARK-DAG: single-impl: devirtualized a call to _ZN1A1nEi
; REMARK-DAG: single-impl: devirtualized a call to _ZN1D1mEi
; REMARK-DAG: single-impl: devirtualized a call to _ZN1A1nEi
; REMARK-DAG: single-impl: devirtualized a call to _ZN1D1mEi

Expand All @@ -67,19 +75,22 @@
; RUN: llvm-lto2 run %t2.o -save-temps -pass-remarks=. \
; RUN: -o %t3 \
; RUN: -r=%t2.o,test,px \
; RUN: -r=%t2.o,test_public,px \
; RUN: -r=%t2.o,_ZN1A1nEi,p \
; RUN: -r=%t2.o,_ZN1B1fEi,p \
; RUN: -r=%t2.o,_ZN1C1fEi,p \
; RUN: -r=%t2.o,_ZN1D1mEi,p \
; RUN: -r=%t2.o,_ZTV1B,px \
; RUN: -r=%t2.o,_ZTV1C,px \
; RUN: -r=%t2.o,_ZTV1D,px 2>&1 | FileCheck %s --implicit-check-not single-impl --allow-empty
; RUN: llvm-dis %t3.1.0.preopt.bc -o - | FileCheck %s --check-prefix=CHECK-TT
; RUN: llvm-dis %t3.1.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-NODEVIRT-IR

; Hybrid WPD
; RUN: llvm-lto2 run %t.o -save-temps -pass-remarks=. \
; RUN: -o %t3 \
; RUN: -r=%t.o,test,px \
; RUN: -r=%t.o,test_public,px \
; RUN: -r=%t.o,_ZN1A1nEi,p \
; RUN: -r=%t.o,_ZN1B1fEi,p \
; RUN: -r=%t.o,_ZN1C1fEi,p \
Expand All @@ -94,19 +105,22 @@
; RUN: -r=%t.o,_ZTV1B,px \
; RUN: -r=%t.o,_ZTV1C,px \
; RUN: -r=%t.o,_ZTV1D,px 2>&1 | FileCheck %s --implicit-check-not single-impl --allow-empty
; RUN: llvm-dis %t3.1.0.preopt.bc -o - | FileCheck %s --check-prefix=CHECK-TT
; RUN: llvm-dis %t3.1.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-NODEVIRT-IR

; Regular LTO WPD
; RUN: llvm-lto2 run %t4.o -save-temps -pass-remarks=. \
; RUN: -o %t5 \
; RUN: -r=%t4.o,test,px \
; RUN: -r=%t4.o,test_public,px \
; RUN: -r=%t4.o,_ZN1A1nEi,p \
; RUN: -r=%t4.o,_ZN1B1fEi,p \
; RUN: -r=%t4.o,_ZN1C1fEi,p \
; RUN: -r=%t4.o,_ZN1D1mEi,p \
; RUN: -r=%t4.o,_ZTV1B,px \
; RUN: -r=%t4.o,_ZTV1C,px \
; RUN: -r=%t4.o,_ZTV1D,px 2>&1 | FileCheck %s --implicit-check-not single-impl --allow-empty
; RUN: llvm-dis %t5.0.0.preopt.bc -o - | FileCheck %s --check-prefix=CHECK-TT
; RUN: llvm-dis %t5.0.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-NODEVIRT-IR

; Try index-based WPD again with both -whole-program-visibility and
Expand All @@ -117,15 +131,19 @@
; RUN: -disable-whole-program-visibility \
; RUN: -o %t3 \
; RUN: -r=%t2.o,test,px \
; RUN: -r=%t2.o,test_public,px \
; RUN: -r=%t2.o,_ZN1A1nEi,p \
; RUN: -r=%t2.o,_ZN1B1fEi,p \
; RUN: -r=%t2.o,_ZN1C1fEi,p \
; RUN: -r=%t2.o,_ZN1D1mEi,p \
; RUN: -r=%t2.o,_ZTV1B,px \
; RUN: -r=%t2.o,_ZTV1C,px \
; RUN: -r=%t2.o,_ZTV1D,px 2>&1 | FileCheck %s --implicit-check-not single-impl --allow-empty
; RUN: llvm-dis %t3.1.0.preopt.bc -o - | FileCheck %s --check-prefix=CHECK-TT
; RUN: llvm-dis %t3.1.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-NODEVIRT-IR

; CHECK-TT-NOT: call {{.*}}@llvm.public.type.test

target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-grtev4-linux-gnu"

Expand Down Expand Up @@ -175,7 +193,44 @@ entry:
; CHECK-IR-LABEL: ret i32
; CHECK-IR-LABEL: }

; CHECK-IR-LABEL: define i32 @test_public
define i32 @test_public(ptr %obj, ptr %obj2, i32 %a) {
entry:
%vtable = load ptr, ptr %obj
%p = call i1 @llvm.public.type.test(ptr %vtable, metadata !"_ZTS1A")
call void @llvm.assume(i1 %p)
%fptrptr = getelementptr ptr, ptr %vtable, i32 1
%fptr1 = load ptr, ptr %fptrptr, align 8

; Check that the call was devirtualized.
; CHECK-IR: %call = tail call i32 @_ZN1A1nEi
; CHECK-NODEVIRT-IR: %call = tail call i32 %fptr1
%call = tail call i32 %fptr1(ptr nonnull %obj, i32 %a)

%fptr22 = load ptr, ptr %vtable, align 8

; We still have to call it as virtual.
; CHECK-IR: %call3 = tail call i32 %fptr22
; CHECK-NODEVIRT-IR: %call3 = tail call i32 %fptr22
%call3 = tail call i32 %fptr22(ptr nonnull %obj, i32 %call)

%vtable2 = load ptr, ptr %obj2
%p2 = call i1 @llvm.public.type.test(ptr %vtable2, metadata !4)
call void @llvm.assume(i1 %p2)

%fptr33 = load ptr, ptr %vtable2, align 8

; Check that the call was devirtualized.
; CHECK-IR: %call4 = tail call i32 @_ZN1D1mEi
; CHECK-NODEVIRT-IR: %call4 = tail call i32 %fptr33
%call4 = tail call i32 %fptr33(ptr nonnull %obj2, i32 %call3)
ret i32 %call4
}
; CHECK-IR-LABEL: ret i32
; CHECK-IR-LABEL: }

declare i1 @llvm.type.test(ptr, metadata)
declare i1 @llvm.public.type.test(ptr, metadata)
declare void @llvm.assume(i1)

define i32 @_ZN1B1fEi(ptr %this, i32 %a) #0 {
Expand Down
25 changes: 25 additions & 0 deletions llvm/test/ThinLTO/X86/public-type-test.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
; Test to ensure that the legacy LTO API lowers @llvm.public.type.test.

; RUN: opt -module-summary %s -o %t.bc
; RUN: llvm-lto --thinlto-action=run -exported-symbol=_main %t.bc --thinlto-save-temps=%t2
; RUN: llvm-dis -o - %t20.2.internalized.bc | FileCheck %s --check-prefix=PUBLIC
; RUN: llvm-lto --thinlto-action=run -exported-symbol=_main %t.bc --thinlto-save-temps=%t2 --whole-program-visibility
; RUN: llvm-dis -o - %t20.2.internalized.bc | FileCheck %s --check-prefix=HIDDEN

; PUBLIC-NOT: call {{.*}}@llvm.public.type.test
; PUBLIC-NOT: call {{.*}}@llvm.type.test
; HIDDEN-NOT: call {{.*}}@llvm.public.type.test
; HIDDEN: call {{.*}}@llvm.type.test

target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx10.9"

define i32 @main(ptr %vtable) {
entry:
%p = call i1 @llvm.public.type.test(ptr %vtable, metadata !"_ZTS1A")
call void @llvm.assume(i1 %p)
ret i32 0
}

declare void @llvm.assume(i1)
declare i1 @llvm.public.type.test(ptr, metadata)
7 changes: 7 additions & 0 deletions llvm/tools/llvm-lto/llvm-lto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,10 @@ static cl::opt<bool>
cl::desc("Print pass management debugging information"),
cl::cat(LTOCategory));

static cl::opt<bool>
LTOSaveBeforeOpt("lto-save-before-opt", cl::init(false),
cl::desc("Save the IR before running optimizations"));

namespace {

struct ModuleInfo {
Expand Down Expand Up @@ -1069,6 +1073,9 @@ int main(int argc, char **argv) {
CodeGen.setFileType(*FT);

if (!OutputFilename.empty()) {
if (LTOSaveBeforeOpt)
CodeGen.setSaveIRBeforeOptPath(OutputFilename + ".0.preopt.bc");

if (SaveLinkedModuleFile) {
std::string ModuleFilename = OutputFilename;
ModuleFilename += ".linked.bc";
Expand Down