In previous post, we know how the swift compiler recevies its argument in llvm::opt::InputArgList from command line. Here we gonna find out how the arguments (llvm:opt::Arg's in llvm::opt::InputArgList) become Actions.
In run_driver function from swift driver tool, /wtsc/swift/tools/driver/driver.cpp, we have following code snippet.
/* /wtsc/swift/tools/driver/driver.cpp */
...
std::unique_ptr<llvm::opt::InputArgList> ArgList =
TheDriver.parseArgStrings(ArrayRef<const char*>(argv).slice(1));
if (Diags.hadAnyError())
return 1;
std::unique_ptr<ToolChain> TC = TheDriver.buildToolChain(*ArgList);
if (Diags.hadAnyError())
return 1;
std::unique_ptr<Compilation> C =
TheDriver.buildCompilation(*TC, std::move(ArgList));
...
After we get the ArgList, we use it to create a toolchain, then use that toolchain, a toolchain is a set of available tools for compilation on a specific platform like Linux, and ArgList to build a compilation by the driver (instance of swift::Driver). Why do we need toolchain to in swift::Driver::buildCompilation method? First is different platform has diferent tools for compilation that accept different arguments, therefore when we need to validate arguments, we need toolchain. Second is outputs, include intermediate resulting files or final ones, are toolchain specific, that is the outputs of compilation depends on the toolchain. Third, later on, after we get a list of actions, it needs toolchain to translate actions into jobs. Thus, to build a compilation, we need ArgList and Toolchain.
The conversion from Args to Actions happens inside swift::Driver::buildCompilation method.
/* /wtsc/swift/lib/Driver/Driver.cpp */
...
std::unique_ptr<Compilation>
Driver::buildCompilation(const ToolChain &TC,
std::unique_ptr<llvm::opt::InputArgList> ArgList) {
const std::string workingDirectory = computeWorkingDirectory(ArgList.get());
std::unique_ptr<DerivedArgList> TranslatedArgList(
translateInputAndPathArgs(*ArgList, workingDirectory));
...
// Construct the list of inputs.
InputFileList Inputs;
buildInputs(TC, *TranslatedArgList, Inputs);
...
// Determine the OutputInfo for the driver.
OutputInfo OI;
bool BatchMode = false;
OI.CompilerMode = computeCompilerMode(*TranslatedArgList, Inputs, BatchMode);
buildOutputInfo(TC, *TranslatedArgList, BatchMode, Inputs, OI);
...
// Construct the graph of Actions.
SmallVector<const Action *, 8> TopLevelActions;
// TC means Toolchain
// OI means OutputInfo
// C means Compilation
buildActions(TopLevelActions, TC, OI,
whyIgnoreIncrementallity.empty() ? &outOfDateMap : nullptr, *C);
...
}
...
Here is following code snippet of buildActions, which is too long to digest instantly. We need a way out to facilitate our journey through the method of swift::Driver:buildActions. We can use lldb to let us go over each executed statement of swift::Driver::buildActions when we want to compile our helloworld example.
cd /wtsc/helloworld
lldb -- /wtsc/usr/bin/swiftc Helloworld.swift -o helloworld
...
(lldb) b Driver.cpp:1940
...
(lldb) run
...
(lldb) n
...
(lldb) p variable
We use this way to see which statement will be executed and the values of some significant variables during this run of swiftc.
After we get into buildActions, the OI.CompilerMode is OutputInfo::Mode::StandardCompile, then we get into that case for standard compile.
The gist of case of standard compile is to loop over input files, and create related actions for each one. For our helloworld example, we have an input file named Helloworld.swift. For this input file, it create an InputAction, then a CompileJobAction with that InputAction as input, and record all the CompileJobAction into AllLinkerInputs. Becuase we have only one input file, the AllLinkerInputs contains one CompileJobAction. After the loop, buildActions method create an DynamicLinkJobAction with AllLinkerInputs and AutolinkExtractJobAction as its input due to our demo platform is ELF platfrom. Finally, the TopLevelActions adds the DynamicLinkJobAction.
Moreover every action is created with swift::driver::Compilation::createAction method.
/* /wtsc/swift/include/swift/Driver/Compilation.h */
...
template <typename SpecificAction, typename... Args>
SpecificAction *createAction(Args &&...args) {
auto newAction = new SpecificAction(std::forward<Args>(args)...);
Actions.emplace_back(newAction);
return newAction;
}
...
We can see inside that method, after create a new action, before return that action, it will containt the new action into Actions. Therefore the instance of swift::driver::Compilation knows what actions are created. Thus when we want to show actions by passing argument -driver-print-jobs to swiftc, it just loops over Actions member and print each action out. As the following code snippet shows.
/* /wtsc/swift/lib/Driver/Driver.cpp */
...
std::unique_ptr<Compilation>
Driver::buildCompilation(const ToolChain &TC,
std::unique_ptr<llvm::opt::InputArgList> ArgList) {
...
if (DriverPrintActions) {
printActions(*C);
return nullptr;
}
...
}
...
void Driver::printActions(const Compilation &C) const {
llvm::DenseMap<const Action *, unsigned> Ids;
for (const Action *A : C.getActions()) {
::printActions(A, Ids);
}
}
...
Now, we know somehow how to convert arguments to actions, and action would take other actions as input, that will make an dependent graph of actions.
Next post will shows how to translate actions to jobs using Toolchain.