Skip to content

[LTO] Setup LTO pipeline and swift-lto tool #32233

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

Closed
Closed
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
4 changes: 4 additions & 0 deletions include/swift/AST/DiagnosticsFrontend.def
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,10 @@ REMARK(interface_file_lock_failure,none,

REMARK(interface_file_lock_timed_out,none,
"timed out waiting to acquire lock file for module interface '%0'", (StringRef))
ERROR(invalid_serialized_module,none,
"unable to register invalid serialized module", ())
ERROR(unable_to_load_serialized_module,none,
"unable to load serialized module '%0'", (StringRef))

// Dependency Verifier Diagnostics
ERROR(dependency_cascading_mismatch,none,
Expand Down
71 changes: 71 additions & 0 deletions include/swift/LTO/LTO.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//===--- LTO.cpp - Swift LTO ----------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#ifndef SWIFT_LTO_H
#define SWIFT_LTO_H

#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/MemoryBuffer.h"
#include <functional>
#include <memory>

#include "swift/AST/ASTContext.h"
#include "swift/AST/IRGenOptions.h"
#include "swift/ClangImporter/ClangImporterOptions.h"
#include "swift/Frontend/ModuleInterfaceLoader.h"
#include "swift/Frontend/PrintingDiagnosticConsumer.h"
#include "swift/Serialization/Validation.h"

namespace swift {

class ASTContext;

namespace lto {

using GetStreamFn = std::function<std::unique_ptr<llvm::raw_ostream>(
llvm::StringRef ModuleName)>;

class LTOPipeline {
llvm::SmallVector<llvm::StringRef, 4> RuntimeLibraryPaths;
llvm::SmallVector<llvm::StringRef, 4> RuntimeLibraryImportPaths;
llvm::StringRef RuntimeResourcePath;
llvm::SmallVector<Identifier, 2> ModuleNames;
LangOptions LangOpts;
ClangImporterOptions ClangOpts;
TypeCheckerOptions TCOpts;
SearchPathOptions SearchPathOpts;
SourceManager SM;
DiagnosticEngine Diags;
PrintingDiagnosticConsumer PrintDiags;
std::unique_ptr<ASTContext> Ctx;
MemoryBufferSerializedModuleLoader *MBL;

public:
LTOPipeline(llvm::SmallVector<llvm::StringRef, 4> RuntimeLibraryPaths,
llvm::SmallVector<llvm::StringRef, 4> RuntimeLibraryImportPaths,
llvm::StringRef RuntimeResourcePath)
: RuntimeLibraryPaths(RuntimeLibraryPaths),
RuntimeLibraryImportPaths(RuntimeLibraryImportPaths),
RuntimeResourcePath(RuntimeResourcePath), Diags(SM) {}
bool addModule(std::unique_ptr<llvm::MemoryBuffer> Buffer);
bool emitLLVMModules(GetStreamFn GetStream);

private:
ASTContext *createASTContext(serialization::ValidationInfo info,
serialization::ExtendedValidationInfo extInfo);
};

} // namespace lto
} // namespace swift

#endif // SWIFT_LTO_H
1 change: 1 addition & 0 deletions lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ add_subdirectory(IDE)
add_subdirectory(Immediate)
add_subdirectory(IRGen)
add_subdirectory(LLVMPasses)
add_subdirectory(LTO)
add_subdirectory(Markup)
add_subdirectory(Migrator)
add_subdirectory(Option)
Expand Down
1 change: 1 addition & 0 deletions lib/FrontendTool/FrontendTool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1384,6 +1384,7 @@ static bool serializeSIB(SILModule *SM, const PrimarySpecificPaths &PSPs,
serializationOpts.OutputPath = moduleOutputPath.c_str();
serializationOpts.SerializeAllSIL = true;
serializationOpts.IsSIB = true;
serializationOpts.SerializeOptionsForDebugging = true;
Copy link
Member Author

Choose a reason for hiding this comment

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

I'll refine after #32237 merged. I'll serialize options only when -lto=swift is given.


serialize(MSF, serializationOpts, SM);
return Context.hadError();
Expand Down
7 changes: 7 additions & 0 deletions lib/LTO/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
add_swift_host_library(swiftLTO STATIC
LTO.cpp)
target_link_libraries(swiftLTO PRIVATE
swiftFrontend
swiftIRGen
swiftSILGen
swiftSILOptimizer)
122 changes: 122 additions & 0 deletions lib/LTO/LTO.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
//===--- LTO.cpp - Swift LTO ----------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#include "swift/LTO/LTO.h"
#include "swift/AST/DiagnosticsFrontend.h"
#include "swift/AST/IRGenRequests.h"
#include "swift/ClangImporter/ClangImporter.h"
#include "swift/SIL/SILModule.h"
#include "swift/Serialization/SerializedModuleLoader.h"
#include "llvm/Bitcode/BitcodeWriter.h"
#include "llvm/IR/Module.h"

namespace swift {

namespace lto {

using namespace llvm;

bool LTOPipeline::addModule(std::unique_ptr<MemoryBuffer> Buffer) {
serialization::ExtendedValidationInfo extendedInfo;
serialization::ValidationInfo info =
serialization::validateSerializedAST(Buffer->getBuffer(), &extendedInfo);
if (info.status != serialization::Status::Valid) {
Diags.diagnose(SourceLoc(), diag::invalid_serialized_module);
return true;
}

if (!Ctx) {
Ctx.reset(createASTContext(info, extendedInfo));
}

MBL->registerMemoryBuffer(info.name, std::move(Buffer));

ModuleNames.emplace_back(Ctx->getIdentifier(info.name));
return false;
}

bool LTOPipeline::emitLLVMModules(GetStreamFn GetStream) {
IRGenOptions Opts = {};
Opts.OutputKind = IRGenOutputKind::Module;

for (auto &ModuleName : ModuleNames) {
std::vector<swift::Located<swift::Identifier>> AccessPath;
AccessPath.emplace_back(ModuleName, SourceLoc());
auto SwiftModule = Ctx->getModule(AccessPath);
if (!SwiftModule) {
Diags.diagnose(SourceLoc(), diag::unable_to_load_serialized_module,
ModuleName.get());
return true;
}
Lowering::TypeConverter Types(*SwiftModule);
SILOptions SILOpts = {};
auto SM = performASTLowering(SwiftModule, Types, SILOpts);
// TODO: Propagate input file name through SIB to enable debug info
const PrimarySpecificPaths PSPs;
auto GeneratedMod =
performIRGeneration(Opts, SM->getSwiftModule(), std::move(SM),
ModuleName.get(), PSPs, ArrayRef<std::string>());
auto LLVMMod = GeneratedMod.getModule();
if (auto OS = GetStream(LLVMMod->getName())) {
WriteBitcodeToFile(*LLVMMod, *OS);
}
}
return false;
}

ASTContext *
LTOPipeline::createASTContext(serialization::ValidationInfo info,
serialization::ExtendedValidationInfo extInfo) {
auto Ctx = ASTContext::get(LangOpts, TCOpts, SearchPathOpts, SM, Diags);
Diags.addConsumer(PrintDiags);
LangOpts.setTarget(Triple(info.targetTriple));
SearchPathOpts.SDKPath = extInfo.getSDKPath();

SearchPathOpts.RuntimeLibraryPaths.insert(
SearchPathOpts.RuntimeLibraryPaths.end(), RuntimeLibraryPaths.begin(),
RuntimeLibraryPaths.end());
SearchPathOpts.RuntimeLibraryImportPaths.insert(
SearchPathOpts.RuntimeLibraryImportPaths.end(),
RuntimeLibraryImportPaths.begin(), RuntimeLibraryImportPaths.end());
SearchPathOpts.RuntimeResourcePath = RuntimeResourcePath;

// MARK: Setup module loaders
std::unique_ptr<ClangImporter> clangImporter =
ClangImporter::create(*Ctx, ClangOpts, "", nullptr);
auto const &Clang = clangImporter->getClangInstance();
std::string ModuleCachePath = getModuleCachePathFromClang(Clang);

auto MIL = ModuleInterfaceLoader::create(*Ctx, ModuleCachePath, "", nullptr,
ModuleLoadingMode::PreferSerialized);
Ctx->addModuleLoader(std::move(MIL));
auto MBL = MemoryBufferSerializedModuleLoader::create(
*Ctx, nullptr, ModuleLoadingMode::OnlySerialized, true);
this->MBL = MBL.get();

auto SML = SerializedModuleLoader::create(
*Ctx, nullptr, ModuleLoadingMode::OnlySerialized, true);

Ctx->addModuleLoader(std::move(MBL));
Ctx->addModuleLoader(std::move(SML));
Ctx->addModuleLoader(std::move(clangImporter), /*isClang*/ true);

registerIRGenRequestFunctions(Ctx->evaluator);
registerSILOptimizerRequestFunctions(Ctx->evaluator);
registerParseRequestFunctions(Ctx->evaluator);
registerTypeCheckerRequestFunctions(Ctx->evaluator);
registerSILGenRequestFunctions(Ctx->evaluator);
registerIRGenSILTransforms(*Ctx);
return Ctx;
}

} // namespace lto
} // namespace swift
3 changes: 3 additions & 0 deletions test/LTO/Inputs/lib.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
public struct LibX {}

public func getLibX() -> LibX { return LibX() }
3 changes: 3 additions & 0 deletions test/LTO/Inputs/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import lib

_ = getLibX()
24 changes: 24 additions & 0 deletions test/LTO/pipeline.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// RUN: %empty-directory(%t)
// RUN: cd %t && %target-swiftc_driver -emit-module %S/Inputs/lib.swift
// RUN: cd %t && %target-swift-frontend -emit-sib %S/Inputs/lib.swift -parse-as-library
// RUN: cd %t && %target-swift-frontend -emit-sib -I%t %S/Inputs/main.swift

// Examine loading order
// RUN: cd %t && %swift-lto main.sib lib.sib
// RUN: cd %t && %llvm-dis lib.bc -o - | %FileCheck %s -check-prefix=CHECK-LIB
// RUN: cd %t && %llvm-dis main.bc -o - | %FileCheck %s -check-prefix=CHECK-MAIN

// RUN: cd %t && %swift-lto lib.sib main.sib
Copy link
Member

Choose a reason for hiding this comment

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

This test doesn't actually check anything ... what is the purpose of the test?

Copy link
Member Author

Choose a reason for hiding this comment

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

This test checks that modules can be deserialized using the pipeline and ensure that the pipeline can resolve dependent modules regardless of input order.

Copy link
Member

Choose a reason for hiding this comment

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

I think Im not understanding something then. The test doesn't verify that the module are deserialized and the dependency modules are resolved. It just assumes that if the process exited everything is correct.

Copy link
Member Author

Choose a reason for hiding this comment

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

I see. added more checks

// RUN: cd %t && %llvm-dis lib.bc -o - | %FileCheck %s -check-prefix=CHECK-LIB
// RUN: cd %t && %llvm-dis main.bc -o - | %FileCheck %s -check-prefix=CHECK-MAIN

// CHECK-LIB: ModuleID = 'lib.bc'
// CHECK-LIB: define hidden swiftcc void @"$s3lib4LibXVACycfC"()
// CHECK-LIB: define swiftcc void @"$s3lib7getLibXAA0C1XVyF"()
// CHECK-LIB: call swiftcc void @"$s3lib4LibXVACycfC"

// CHECK-MAIN: ModuleID = 'main.bc'
// CHECK-MAIN: define i32 @main
// CHECK-MAIN: call swiftcc void @"$s3lib7getLibXAA0C1XVyF"
// CHECK-MAIN: declare swiftcc void @"$s3lib7getLibXAA0C1XVyF"()

1 change: 1 addition & 0 deletions test/lit.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,7 @@ if '-disable-astscope-lookup' in config.swift_test_options:
if '-disable-parser-lookup' in config.swift_test_options:
config.available_features.add("disable-parser-lookup")
config.substitutions.append( ('%swift-indent', config.swift_indent) )
config.substitutions.append( ('%swift-lto', '%s lto' % config.sil_llvm_gen) )
config.substitutions.append( ('%llvm-link', config.llvm_link) )
config.substitutions.append( ('%swift-llvm-opt', config.swift_llvm_opt) )
config.substitutions.append( ('%llvm-dwarfdump', config.llvm_dwarfdump) )
Expand Down
2 changes: 2 additions & 0 deletions tools/sil-llvm-gen/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
add_swift_host_tool(sil-llvm-gen
SILLLVMGen.cpp
swift_lto_main.cpp
SWIFT_COMPONENT tools
)
target_link_libraries(sil-llvm-gen
Expand All @@ -8,6 +9,7 @@ target_link_libraries(sil-llvm-gen
swiftIRGen
swiftSILGen
swiftSILOptimizer
swiftLTO
# Clang libraries included to appease the linker on linux.
clangBasic
clangCodeGen)
7 changes: 7 additions & 0 deletions tools/sil-llvm-gen/SILLLVMGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,19 @@ static llvm::cl::opt<bool>
// without being given the address of a function in the main executable).
void anchorForGetMainExecutable() {}

extern llvm::cl::SubCommand LTOSubcommand;
int swift_lto_main(int argc, char **argv);

int main(int argc, char **argv) {
PROGRAM_START(argc, argv);
INITIALIZE_LLVM();

llvm::cl::ParseCommandLineOptions(argc, argv, "Swift LLVM IR Generator\n");

if (LTOSubcommand) {
return swift_lto_main(argc, argv);
}

if (PrintStats)
llvm::EnableStatistics();

Expand Down
90 changes: 90 additions & 0 deletions tools/sil-llvm-gen/swift_lto_main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//===--- swift_lto_main.cpp -----------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
///
/// \file
///
/// This is a tool for reading sib files and running LTO passes upon them.
///
//===----------------------------------------------------------------------===//

#include "swift/Basic/LLVMInitialize.h"
#include "swift/Frontend/Frontend.h"
#include "swift/LTO/LTO.h"
#include "swift/Strings.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/MemoryBuffer.h"
#include <cstdio>
#include <memory>
using namespace swift;

llvm::cl::SubCommand LTOSubcommand("lto", "Swift LTO Tool");

static llvm::cl::list<std::string>
InputFilenames(llvm::cl::Positional, llvm::cl::desc("[input files...]"),
llvm::cl::OneOrMore, llvm::cl::sub(LTOSubcommand));

// This function isn't referenced outside its translation unit, but it
// can't use the "static" keyword because its address is used for
// getMainExecutable (since some platforms don't support taking the
// address of main, and some platforms can't implement getMainExecutable
// without being given the address of a function in the main executable).
void anchorForGetMainExecutableInSwiftLTO() {}

// This tool is combined with sil-llvm-gen to reduce link time.
// This entrypoint is invoked from SILLLVMGen.cpp when user invoke
// lto subcommand.
int swift_lto_main(int argc, char **argv) {
CompilerInvocation Invocation;

Invocation.setMainExecutablePath(llvm::sys::fs::getMainExecutable(
argv[0],
reinterpret_cast<void *>(&anchorForGetMainExecutableInSwiftLTO)));

auto SearchPathOpts = Invocation.getSearchPathOptions();
llvm::SmallVector<llvm::StringRef, 4> RuntimeLibraryPaths;
llvm::SmallVector<llvm::StringRef, 4> RuntimeLibraryImportPaths;
RuntimeLibraryPaths.insert(RuntimeLibraryPaths.begin(),
SearchPathOpts.RuntimeLibraryPaths.begin(),
SearchPathOpts.RuntimeLibraryPaths.end());
RuntimeLibraryImportPaths.insert(
RuntimeLibraryImportPaths.begin(),
SearchPathOpts.RuntimeLibraryImportPaths.begin(),
SearchPathOpts.RuntimeLibraryImportPaths.end());
lto::LTOPipeline Pipeline(RuntimeLibraryPaths, RuntimeLibraryImportPaths,
SearchPathOpts.RuntimeResourcePath);

for (auto InputFilename : InputFilenames) {
// Load the input file.
auto FileBufOrErr = llvm::MemoryBuffer::getFileOrSTDIN(InputFilename);
if (!FileBufOrErr) {
fprintf(stderr, "Error! Failed to open file: %s\n",
InputFilename.c_str());
exit(-1);
}

if (Pipeline.addModule(std::move(FileBufOrErr.get()))) {
fprintf(stderr, "Error! Failed to load serialized module: %s\n",
InputFilename.c_str());
exit(-1);
}
}

Pipeline.emitLLVMModules([&](StringRef ModuleName) {
std::error_code EC;
std::unique_ptr<llvm::raw_ostream> RawOS =
std::make_unique<llvm::raw_fd_ostream>(ModuleName.str() + ".bc", EC);
if (EC)
return std::unique_ptr<llvm::raw_ostream>(nullptr);
return RawOS;
});
Copy link
Member

Choose a reason for hiding this comment

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

Can you clarify why exact this tool is needed? This really feels like its executing sil-opt + swift-llvm-gen. Is there a reason that you cannot just pipe the tools in the test?

Copy link
Member Author

@kateinoigakukun kateinoigakukun Jun 12, 2020

Choose a reason for hiding this comment

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

The tool accepts SIB files for multiple modules and optimizes them at once. This is something swift-llvm-gen does not do. If piping sil-opt and swift-llvm-gen, it's impossible to optimize across modules.

And the main purpose of this tool is simulating linker integration without linker. So it doesn't match sil-opt and swift-llvm-gen's purposes.

Copy link
Member

@compnerd compnerd Jun 14, 2020

Choose a reason for hiding this comment

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

But you can load the SIB file in sil-opt, run it through the pipeline and write that out, then use swift-llvm-gen to generate IR from the SIB.

If piping sil-opt and swift-llvm-gen, it's impossible to optimize across modules.

I think Im not understanding the missing piece in the tools. Can you explain that and write that up in the commit message as well? I think its critical to explain the deficiency.

And the main purpose of this tool is simulating linker integration without linker. So it doesn't match sil-opt and swift-llvm-gen's purposes.

I disagree on this. Its not simulating linker integration - its running the SIL pipeline on a set of SIB files. That is not related to linking. Running the SIL pipeline is precisely what sil-opt does. You seem to generate LLVM IR from the binary at the end, and thats precisely what swift-llvm-gen does - generates LLVM IR from SIL/SIB. That is where the disconnect is, and what Im trying to understand.

Copy link
Member Author

@kateinoigakukun kateinoigakukun Jun 14, 2020

Choose a reason for hiding this comment

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

As you say, this swift-lto and sil-opt have the same part of roles in optimizing SIBs. But the sil-opt also accept SIL as input, whereas the LTO pipeline only accepts SIBs. The LTO pipeline accepts inputs only from linkers, so SILs will never be inputted. If LTO pipeline accepts SILs as input, the LTO pipeline becomes more complex only for testing.

For example, SIL doesn't serialize SDKPath, etc., so we have to assemble those missing pieces of information before passing input them into the pipeline.

So creating new tool that only accepts only SIBs helps keep the tool simple.

And sil-opt really depends on Frontend module that assumes that input module is always one. But LTO pipeline is independent from the module because LTO pipeline needs to be acceptable for multi-modules. This difference makes it difficult to merge these tools.

Copy link
Member

Choose a reason for hiding this comment

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

Right, sil-opt accepts SIL and SIB, but that makes it more flexible and keeps everything in one place :).

The flags are precisely what all the LTO work is about - its how to thread all the appropriate flags through the driver into the frontend and the linker. I think that is expected. That will also be needed for this tool.

So given that the tools can accomplish what is needed, the question is why is the new tool better than building on the existing tools? Is it going to be far more code to just add support for multiple files to sil-opt than writing a new tool?

Copy link
Member Author

@kateinoigakukun kateinoigakukun Jun 15, 2020

Choose a reason for hiding this comment

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

Perhaps I've made a big misunderstanding. Do you mean that the linker will run sil-opt directly?

I was thinking that LTOPipeline class will be linked to the linker. And swift-lto tool was only going to be used for testing.

return 0;
}