In previous post, we have got the canonical SIL, so it is time to lower SIL and to generate LLVM IR. In this post, the result we gonna get from canonical SIL is showed as following:
$ swiftc -emit-ir hello.swift
; ModuleID = '<swift-imported-modules>'
source_filename = "<swift-imported-modules>"target
datalayout = "e-m:o-i64:64-i128:128-n32:64-S128"
target triple = "arm64-apple-macosx11.0.0"
...
After we get canonical SIL, Swift compiler will do some preparations before generating LLVM IR, which is SIL Lowering as depicted as following:
// During SIL Lowering, passes may see partially lowered SIL, which is
// inconsistent with the current (canonical) stage. We don't change the SIL
// stage until lowering is complete. Consequently, any pass added to this
// PassManager needs to be able to handle the output of the previous pass. If
// the function pass needs to read SIL from other functions, it may be best to
// convert it to a module pass to ensure that the SIL input is always at the
// same stage of lowering.
void swift::runSILLoweringPasses(SILModule &Module) {
auto &opts = Module.getOptions();
executePassPipelinePlan(&Module,
SILPassPipelinePlan::getLoweringPassPipeline(opts),
/*isMandatory*/ true);
assert(Module.getStage() == SILStage::Lowered);
}
/// Mandatory IRGen preparation. It is the caller's job to set the set stage to
/// "lowered" after running this pipeline.
SILPassPipelinePlan
SILPassPipelinePlan::getLoweringPassPipeline(const SILOptions &Options) {
SILPassPipelinePlan P(Options);
P.startPipeline("Address Lowering");
P.addOwnershipModelEliminator();
P.addIRGenPrepare();
P.addAddressLowering();
return P;
}
Afterward, it goes on to generate IR.
auto IRModule = generateIR(
IRGenOpts, Invocation.getTBDGenOptions(), std::move(SM), PSPs,
OutputFilename, MSF, HashGlobal, ParallelOutputFilenames);
...
static GeneratedModule
generateIR(const IRGenOptions &IRGenOpts, const TBDGenOptions &TBDOpts,
std::unique_ptr<SILModule> SM,
const PrimarySpecificPaths &PSPs,
StringRef OutputFilename, ModuleOrSourceFile MSF,
llvm::GlobalVariable *&HashGlobal,
ArrayRef<std::string> parallelOutputFilenames) {
...
return performIRGeneration(SF, IRGenOpts, TBDOpts,
std::move(SM), OutputFilename, PSPs,
SF->getPrivateDiscriminator().str(),
&HashGlobal);
...
}
And the actual IR generation precedure is in IRGenRequest::evaluate() method.
/// Generates LLVM IR, runs the LLVM passes and produces the output file.
/// All this is done in a single thread.
GeneratedModule IRGenRequest::evaluate(Evaluator &evaluator,
IRGenDescriptor desc) const {
...
// If we've been provided a SILModule, use it. Otherwise request the lowered
// SIL for the file or module.
auto SILMod = std::unique_ptr<SILModule>(desc.SILMod);
...
auto filesToEmit = desc.getFilesToEmit();
auto *primaryFile =
dyn_cast_or_null<SourceFile>(desc.Ctx.dyn_cast<FileUnit *>());
IRGenerator irgen(Opts, *SILMod);
auto targetMachine = irgen.createTargetMachine();
...
// Create the IR emitter.
IRGenModule IGM(irgen, std::move(targetMachine), primaryFile, desc.ModuleName,
PSPs.OutputFilename, PSPs.MainInputFilenameForDebugInfo,
desc.PrivateDiscriminator);
initLLVMModule(IGM, *SILMod);
// Run SIL level IRGen preparation passes.
runIRGenPreparePasses(*SILMod, IGM);
{
FrontendStatsTracer tracer(Ctx.Stats, "IRGen");
// Emit the module contents.
irgen.emitGlobalTopLevel(desc.getLinkerDirectives());
for (auto *file : filesToEmit) {
if (auto *nextSF = dyn_cast<SourceFile>(file)) {
IGM.emitSourceFile(*nextSF);
} else if (auto *nextSFU = dyn_cast<SynthesizedFileUnit>(file)) {
IGM.emitSynthesizedFileUnit(*nextSFU);
} else {
file->collectLinkLibraries([&IGM](LinkLibrary LinkLib) {
IGM.addLinkLibrary(LinkLib);
});
}
}
// Okay, emit any definitions that we suddenly need.
irgen.emitLazyDefinitions();
// Register our info with the runtime if needed.
if (Opts.UseJIT) {
IGM.emitBuiltinReflectionMetadata();
IGM.emitRuntimeRegistration();
} else {
// Emit protocol conformances into a section we can recognize at runtime.
// In JIT mode these are manually registered above.
IGM.emitSwiftProtocols();
IGM.emitProtocolConformances();
IGM.emitTypeMetadataRecords();
IGM.emitBuiltinReflectionMetadata();
IGM.emitReflectionMetadataVersion();
irgen.emitEagerClassInitialization();
irgen.emitDynamicReplacements();
}
// Emit symbols for eliminated dead methods.
IGM.emitVTableStubs();
...
}
...
embedBitcode(IGM.getModule(), Opts);
...
return std::move(IGM).intoGeneratedModule();
}
Actually the process is not that complicated. Because SIL is closer to LLVM IR and their modules have similar content organization, i.e. Module -> Function -> Basic Block -> Instruction. Therefore the translation from SIL to LLVM IR is to the language offerred by LLVM IR to implement the semantics of SIL one by one. The coding details are much like the code generation chapter of LLVM Kaleidoscope Tutorial, but with more sophisticated structures and programming patterns to improve performance and flexibility.
If readers are interested into the translation details, readers can dive into each emitXXX method of IRGenModule class.
From now on, the main parts of Swift Compiler Frontend are walked throught, and we gonna start learning the Middle-end, LLVM IR and the Backend of Swift Compiler, those stay mainly in the llvm project. Have fun for next journey.