In the previous installment of this series we saw basically how to install flang and we ran a simple smoke test. In this post we will see a high level overview of what happens when we compile a Fortran program using flang. We will also compare it with what usually happens with clang.
Driver
The process of obtaining a program, from source code to something that we can execute in some system, has been traditionally called compilation. But actually it is a process that involves several steps. The set of tools that allows us to perform these steps is usually called a toolchain. In general, nothing prevents us from using each tool individually when building a program but commonly a tool called driver is used instead. The driver knows how the toolchain works and how it has to invoke the different tools in order to achieve the expected result.
In LLVM the current driver for C/C++ is called clang. Unfortunately this is confusing, because clang can also act as a compiler (when the first option is -cc1
) and as an assembler (when the first option is -cc1as
). When we need to distinguish between clang the driver and clang the compiler/assembler, we will call the former clang (or the driver) and the latter cc1/cc1as.
When clang (the driver) runs, it analyzes its command line. Depending on the flags it knows it has to do more or less steps. By default a C/C++ driver has to do the following steps: preprocess, compile, assemble and link. Clang can do a few more specific steps but these are not relevant. Some steps may have to be omitted, for instance a preprocessed file (usually with extension .i
or .ii
) does not have to be processed. If the command line includes -c
, no linking happens, if the command line includes -S
the code is not assembled so the output of the driver is just assembly code. Some flags are only relevant for some part of the whole process: some apply only to the preprocessor (like -D and -I flags for macros and include paths), others apply only to the compilation itself (like -W
flags for warnings) and others apply to the link step (like -L
or -l
for library paths and libraries). The driver by default behaves like the gcc
driver. There is also clang-cl
which behaves like the cl.exe
driver of Microsoft Visual Studio C/C++ .
Entry point: the driver tool
The entry point of the driver is in llvm/tools/clang/tools/driver/driver.cpp
. It does a few checks to see if it is cc1
or cc1as
and if it is not it will create a Compilation
and it will execute it. And this is where all the magic happens. Everything else in that file just takes care of handling the errors that might have happened during compilation. in the snippet below argv
is the argv
of the main
function. Line 459 below simply checks if the compilation C
has been created successfully, if so it executes it.
llvm/tools/clang/tools/driver/driver.cpp
456 std::unique_ptr<Compilation> C(TheDriver.BuildCompilation(argv));
457 int Res = 0;
458 SmallVector<std::pair<int, const Command *>, 4> FailingCommands;
459 if (C.get())
460 Res = TheDriver.ExecuteCompilation(*C, FailingCommands);
The long journey of bulding a compilation
The intriguing function BuildCompilation
is defined in llvm/tools/clang/lib/Driver/Driver.cpp
. Note that this is inside lib
which means this code belongs ot the reusable and modular parts of clang (so if you need a C/C++ compiler, you can use the classes and functions defined in lib
to build your personalised C/C++ compiler). The file driver.cpp
inside tools
is just a tool that uses these components.
One of the first things BuildCompilation
does is determining what the driver is expected to do.
Detecting the mode of the driver
Detecting the mode of the driver lets us know whether we have to behave like a gcc-style driver, cl.exe driver or flang driver. This impacts how command options are analyzed and also may impact some of the steps done by the driver.
llvm/tools/clang/lib/Driver/Driver.cpp
568 // We look for the driver mode option early, because the mode can affect
569 // how other options are parsed.
570 ParseDriverMode(ClangExecutable, ArgList.slice(1));
This function infers the mode of the driver, the set of modes it supports are described in llvm/tools/clang/include/clang/Driver/Driver.h
and by default the GCCMode
is chosen.
llvm/tools/clang/include/clang/Driver/Driver.h
71 enum DriverMode {
72 GCCMode,
73 GXXMode,71
74 CPPMode,
75 CLMode,
76 FortranMode
77 } Mode;
If you follow this function you will see that it does this
llvm/tools/clang/lib/Driver/Driver.cpp
94 void Driver::ParseDriverMode(StringRef ProgramName,
95 ArrayRef<const char *> Args) {
96 auto Default = ToolChain::getTargetAndModeFromProgramName(ProgramName);
Function getTargetAndModeFromProgramName
is defined in llvm/tools/clang/lib/Driver/ToolChain.cpp
.
llvm/tools/clang/lib/Driver/ToolChain.cpp
166 std::pair<std::string, std::string>
167 ToolChain::getTargetAndModeFromProgramName(StringRef PN) {
168 std::string ProgName = normalizeProgramName(PN);
169 const DriverSuffix *DS = parseDriverSuffix(ProgName);
The function parseDriverSuffix
tries hard to infer the driver mode in many different ways but eventually calls FindDriverSuffix
.
llvm/tools/clang/lib/Driver/ToolChain.cpp
139 const DriverSuffix *parseDriverSuffix(StringRef ProgName) {
140 // Try to infer frontend type and default target from the program name by
141 // comparing it against DriverSuffixes in order.
142
143 // If there is a match, the function tries to identify a target as prefix.
144 // E.g. "x86_64-linux-clang" as interpreted as suffix "clang" with target
145 // prefix "x86_64-linux". If such a target prefix is found, it may be
146 // added via -target as implicit first argument.
147 const DriverSuffix *DS = FindDriverSuffix(ProgName);
148
149 if (!DS) {
150 // Try again after stripping any trailing version number:
151 // clang++3.5 -> clang++
152 ProgName = ProgName.rtrim("0123456789.");
153 DS = FindDriverSuffix(ProgName);
154 }
155
156 if (!DS) {
157 // Try again after stripping trailing -component.
158 // clang++-tot -> clang++
159 ProgName = ProgName.slice(0, ProgName.rfind('-'));
160 DS = FindDriverSuffix(ProgName);
161 }
162 return DS;
163}
llvm/tools/clang/lib/Driver/ToolChain.cpp
102const DriverSuffix *FindDriverSuffix(StringRef ProgName) {
103 // A list of known driver suffixes. Suffixes are compared against the
104 // program name in order. If there is a match, the frontend type is updated as
105 // necessary by applying the ModeFlag.
106 static const DriverSuffix DriverSuffixes[] = {
107 {"clang", nullptr},
108 {"clang++", "--driver-mode=g++"},
109 {"clang-c++", "--driver-mode=g++"},
110 {"clang-cc", nullptr},
111 {"clang-cpp", "--driver-mode=cpp"},
112 {"clang-g++", "--driver-mode=g++"},
113 {"clang-gcc", nullptr},
114 {"clang-cl", "--driver-mode=cl"},
115 {"cc", nullptr},
116 {"cpp", "--driver-mode=cpp"},
117 {"cl", "--driver-mode=cl"},
118 {"++", "--driver-mode=g++"},
119 {"flang", "--driver-mode=fortran"},
120 };
121
122 for (size_t i = 0; i < llvm::array_lengthof(DriverSuffixes); ++i)
123 if (ProgName.endswith(DriverSuffixes[i].Suffix))
124 return &DriverSuffixes[i];
125 return nullptr;
126}
What all this means? It means that if we invoke the driver as flang
it will configure itself in Fortran mode. We are not seeing it in this chapter, but when the driver is in Fortran mode it adds a few extra libraries that are required at runtime by Fortran programs.
Analysis of the command line
Let's go back to BuildCompilation
. At some point the arguments of the driver are processed and a set of inputs is constructed.
llvm/tools/clang/lib/Driver/Driver.cpp
669 // Construct the list of inputs.
670 InputList Inputs;
671 BuildInputs(C->getDefaultToolChain(), *TranslatedArgs, Inputs);
The function BuildInputs
is a bit complicated basically because, even if we know the mode of the driver, we allow passing inputs of different kind (source, objects, archives, etc.) or if they are source code, of different languages. This means that an invocation like flang -c myfileA.c myfileB.f90
the driver must compile myfileA.c
as a C file and myfileB.f90
as a Fortran file. This is done using the extension file. Sadly things are not that easy as there are positional flags, like -x, that can override the meaning of the later files (so no checking of the extension occurs), but we do not need to know so much detail. The function that checks the extension is in Types.cpp
and is called lookupTypeForExtension
. It is a bit long to paste it all so I shortened it a bit.
llvm/tools/clang/lib/Driver/Types.cpp
types::ID types::lookupTypeForExtension(llvm::StringRef Ext) {
return llvm::StringSwitch<types::ID>(Ext)
.Case("c", TY_C)
.Case("C", TY_CXX)
.Case("F", TY_F_FixedForm)
.Case("f", TY_PP_F_FixedForm)
// (.. omitted ..)
.Case("for", TY_PP_F_FixedForm)
.Case("FOR", TY_PP_F_FixedForm)
.Case("fpp", TY_F_FixedForm)
.Case("FPP", TY_F_FixedForm)
.Case("f90", TY_PP_F_FreeForm)
.Case("f95", TY_PP_F_FreeForm)
.Case("f03", TY_PP_F_FreeForm)
.Case("f08", TY_PP_F_FreeForm)
.Case("F90", TY_F_FreeForm)
.Case("F95", TY_F_FreeForm)
.Case("F03", TY_F_FreeForm)
.Case("F08", TY_F_FreeForm)
// (.. omitted ..)
For the case we care, which is basically Fortran, we see that there is a plethora of extensions assumed to be Fortran. Files with .f
are not to be preprocessed, while files with .F
are to be preprocessed. All the cases are there to accomodate names that historically have been given to Fortran files, including those that encode the Fortran version like .f95
or .f03
. BuildInputs
in Driver.cpp
, above, will build a list of pairs 〈input file, file type〉 that encodes the type of input we found.
It's time to take action
With the list of inputs built in BuildInputs
it is time to build the actions that the driver has to do, based on the arguments of the command line. This is done in the function BuildActions
. A first step of this function is determine the final phase of the driver. As mentioned above the driver has to do more or less steps depending on the options.
void Driver::BuildActions(Compilation &C, DerivedArgList &Args,
const InputList &Inputs, ActionList &Actions) const {
// (.. omitted ..)
Arg *FinalPhaseArg;
phases::ID FinalPhase = getFinalPhase(Args, &FinalPhaseArg);
Function getFinalPhase
uses the arguments in the command line to determine which is the last phase to perform. For instance if there is the -c
option, it only assembles while if there is the -S
option it runs only up to the backend (for the generation of the assembly). There are several other flags that impact the final phase.
llvm/tools/clang/lib/Driver/Driver.cpp
169 // Determine which compilation mode we are in. We look for options which
170 // affect the phase, starting with the earliest phases, and record which
171 // option we used to determine the final phase.
172 phases::ID Driver::getFinalPhase(const DerivedArgList &DAL,
173 Arg **FinalPhaseArg) const {
174 Arg *PhaseArg = nullptr;
175 phases::ID FinalPhase;
176
177 // -{E,EP,P,M,MM} only run the preprocessor.
178 if (CCCIsCPP() || (PhaseArg = DAL.getLastArg(options::OPT_E)) ||
179 (PhaseArg = DAL.getLastArg(options::OPT__SLASH_EP)) ||
180 (PhaseArg = DAL.getLastArg(options::OPT_M, options::OPT_MM)) ||
181 (PhaseArg = DAL.getLastArg(options::OPT__SLASH_P))) {
182 FinalPhase = phases::Preprocess;
183
184 // --precompile only runs up to precompilation.
185 } else if ((PhaseArg = DAL.getLastArg(options::OPT__precompile))) {
186 FinalPhase = phases::Precompile;
187
188 // -{fsyntax-only,-analyze,emit-ast} only run up to the compiler.
189 } else if ((PhaseArg = DAL.getLastArg(options::OPT_fsyntax_only)) ||
190 (PhaseArg = DAL.getLastArg(options::OPT_module_file_info)) ||
191 (PhaseArg = DAL.getLastArg(options::OPT_verify_pch)) ||
192 (PhaseArg = DAL.getLastArg(options::OPT_rewrite_objc)) ||
193 (PhaseArg = DAL.getLastArg(options::OPT_rewrite_legacy_objc)) ||
194 (PhaseArg = DAL.getLastArg(options::OPT__migrate)) ||
195 (PhaseArg = DAL.getLastArg(options::OPT__analyze,
196 options::OPT__analyze_auto)) ||
197 (PhaseArg = DAL.getLastArg(options::OPT_emit_ast))) {
198 FinalPhase = phases::Compile;
199
200 // -S only runs up to the backend.
201 } else if ((PhaseArg = DAL.getLastArg(options::OPT_S))) {
202 FinalPhase = phases::Backend;
203
204 // -c compilation only runs up to the assembler.
205 } else if ((PhaseArg = DAL.getLastArg(options::OPT_c))) {
206 FinalPhase = phases::Assemble;
207
208 // Otherwise do everything.
209 } else
210 FinalPhase = phases::Link;
211
212 if (FinalPhaseArg)
213 *FinalPhaseArg = PhaseArg;
214
215 return FinalPhase;
216 }
Back to BuildActions
, now we can actually build the actions required for each input file.
llvm/tools/clang/lib/Driver/Driver.cpp
2455 llvm::SmallVector<phases::ID, phases::MaxNumberOfPhases> PL;
2656 for (auto &I : Inputs) {
2457 types::ID InputType = I.first;
2458 const Arg *InputArg = I.second;
2459
2460 PL.clear();
2461 types::getCompilationPhases(InputType, PL);
The required compilation phases will depend on the input type, so this is again defined in Types.cpp
. Generally preprocessed files have their own preprocessing phase but Fortran files can be preprocessed at the same time as they are compiled (in a step that we will see later, called the "upper part" of the Fortran compiler). In fact, Fortran files, line 298-301, go through a different sequence of phases compared to other inputs: FortranFrontEnd
, then Compile
, then Backend
.
llvm/tools/clang/lib/Driver/Types.cpp
284 void types::getCompilationPhases(ID Id, llvm::SmallVectorImpl<phases::ID> &P) {
285 if (Id != TY_Object) {
286 // Delegate preprocessing to the "upper" part of Fortran compiler,
287 // preprocess for other preprocessable inputs
288 if (getPreprocessedType(Id) != TY_INVALID && !isFortran(Id)) {
289 P.push_back(phases::Preprocess);
290 }
291
292 if (getPrecompiledType(Id) != TY_INVALID) {
293 P.push_back(phases::Precompile);
294 }
295
296 if (!onlyPrecompileType(Id)) {
297 if (!onlyAssembleType(Id)) {
298 if (isFortran(Id)) {
299 P.push_back(phases::FortranFrontend);
300 P.push_back(phases::Compile);
301 P.push_back(phases::Backend);
302 } else {
303 P.push_back(phases::Compile);
304 P.push_back(phases::Backend);
305 }
306 }
307 P.push_back(phases::Assemble);
308 }
309 }
310
311 if (!onlyPrecompileType(Id)) {
312 P.push_back(phases::Link);
313 }
314 assert(0 < P.size() && "Not enough phases in list");
315 assert(P.size() <= phases::MaxNumberOfPhases && "Too many phases in list");
316 }
Back to BuildActions
again, now that we know the phases that the input has to perform, it is time to construct an action for each phase. When we are linking, all inputs will end in the Link phase, so we queue these phases aside and then we create a final action for them (after the loop). If not linking, a step generates some output that we will add to our list of actions.
llvm/tools/clang/lib/Driver/Driver.cpp
// Build the pipeline for this file.
Action *Current = C.MakeAction<InputAction>(*InputArg, InputType);
for (SmallVectorImpl<phases::ID>::iterator i = PL.begin(), e = PL.end();
i != e; ++i) {
phases::ID Phase = *i;
// We are done if this step is past what the user requested.
if (Phase > FinalPhase)
break;
// Queue linker inputs.
if (Phase == phases::Link) {
assert((i + 1) == e && "linking must be final compilation step.");
LinkerInputs.push_back(Current);
Current = nullptr;
break;
}
// Otherwise construct the appropriate action.
auto *NewCurrent = ConstructPhaseAction(C, Args, Phase, Current);
// (.. omitted ..)
Current = NewCurrent;
// (.. omitted ..)
}
// If we ended with something, add to the output list.
if (Current)
Actions.push_back(Current);
// Add a link action if necessary.
if (!LinkerInputs.empty()) {
Action *LA = C.MakeAction<LinkJobAction>(LinkerInputs, types::TY_Image);
// (.. omitted ..)
Actions.push_back(LA);
}
Each action, except the InputAction
that receives the input argument and its type, is built in ConstructPhaseAction
using the previous phase and the output type it generates. Note that this function does not handle a Link
phase as it handled in BuildActions
(see code above wehre a LinkJobAction
is built). Again this is a long function so let's see a shortened version of it.
llvm/tools/clang/lib/Driver/Driver.cpp
Action *Driver::ConstructPhaseAction(Compilation &C, const ArgList &Args,
phases::ID Phase, Action *Input) const {
// (.. omitted ..)
case phases::Preprocess: {
types::ID OutputTy;
// (.. omitted ..)
OutputTy = types::getPreprocessedType(OutputTy);
return C.MakeAction<PreprocessJobAction>(Input, OutputTy);
}
case phases::FortranFrontend: {
return C.MakeAction<FortranFrontendJobAction>(Input,
types::TY_LLVM_IR);
}
case phases::Compile: {
if (Args.hasArg(options::OPT_fsyntax_only))
return C.MakeAction<CompileJobAction>(Input, types::TY_Nothing);
// (.. omitted ..)
return C.MakeAction<CompileJobAction>(Input, types::TY_LLVM_BC);
}
// (.. omitted ..)
case phases::Backend: {
// (.. omitted ..)
return C.MakeAction<BackendJobAction>(Input, types::TY_PP_Asm);
}
case phases::Assemble:
return C.MakeAction<AssembleJobAction>(std::move(Input), types::TY_Object);
}
// (.. omitted ..)
}
If we recall the set of phases for C/C++ we have that first they are Preprocess (.c
→ .i
or for C++ .cpp
→ .ii
), then, Compile (.i
/.ii
→ .bc
), then Backend (.bc
→ .s
) and then Assemble (.s
→ .o
). A .bc
file is a LLVM bitcode file which is a binary representation of the LLVM IR (represented in the code above as types::TY_LLVM_BC)
. But as we saw above, a Fortran file follows a slightly different process: FortranFrontend (.f
/.F
→ .llvm
), Compile (.llvm
→ .bc
), Backend (.bc
→ .s
) and Assemble (.s
→ .o
). A .llvm file is the textual representation of the LLVM IR and can represent exactly the same as a .bc file (but note that the backend step only consumes .bc
files). These actions are defined in llvm/tools/clang/include/clang/Action.h
and in general they are very thin classes that inherit from the Action
class.
Jobs for everyone
Now function BuildActions
ends and we go back to BuildCompilation
. The code now proceeds to BuildJobs
. It's main purpose is to build one or more jobs for each action.
llvm/tools/clang/lib/Driver/Driver.cpp
void Driver::BuildJobs(Compilation &C) const {
// (.. omitted ..)
for (Action *A : C.getActions()) {
// (.. omitted ..)
BuildJobsForAction(C, A, &C.getDefaultToolChain(),
/*BoundArch*/ StringRef(),
/*AtTopLevel*/ true,
/*MultipleArchs*/ ArchNames.size() > 1,
/*LinkingOutput*/ LinkingOutput, CachedResults,
/*TargetDeviceOffloadKind*/ Action::OFK_None);
}
// (.. omitted ..)
}
There is a bit of complexity here because the driver caches invocation of the same inputs more than once (to be honest I fail to see a non-obvious example beyond repeating the same input in the command line, maybe this happens in offloading contexts that are very out of the scope of this post). So BuildJobsForAction
calls BuildJobsForActionNoCache
and in practice the two functions do the same, the former does caching and calls the latter if the result has not been cached yet. The name job is a bit misleading here because these two functions return an InputInfo
object which basically is a tuple containing the original file that motivated the creation of the input, the associated action and the type of the file (e.g.: object, Fortran free-form source, C code preprocessed, etc.). That said, at the end of BuildJobsForActionNoCache
, the function Tool::ConstructJob
is invoked. So now let's try to spell out this function because it does so many things at once. Fortunately, for us, most of the complexity is caused by extra steps required by offloading which we do not care at all.
It may not be obvious from what we have seen so far, but because of the way we built the actions, each action has a reference to another action that represents its inputs. For instance, when linking, there will be just a single (top-level) LinkJobAction
action that will have as inputs all the objects built by some AssembleJobAction
action. So this function traverses this graph starting from the final actions towards its inputs. Recall that the for every input there is at least one InputAction
from them (we created it in BuildActions
above), so this is where we stop the graph traversal: we simply return an InputInfo
using the current Action
(an InputAction
) and the argument in the command line that motivated the creation of this InputAction
.
InputInfo Driver::BuildJobsForActionNoCache(
Compilation &C, const Action *A, const ToolChain *TC, StringRef BoundArch,
bool AtTopLevel, bool MultipleArchs, const char *LinkingOutput,
std::map<std::pair<const Action *, std::string>, InputInfo> &CachedResults,
Action::OffloadKind TargetDeviceOffloadKind) const {
// (.. omitted ..))
if (const InputAction *IA = dyn_cast<InputAction>(A)) {
// (.. omitted ..))
return InputInfo(A, &Input, /* BaseInput = */ "");
}
From now on all the actions are going to be JobAction
which is a common class for actions that use a tool to do its job (in contrast to InputAction
). Thus we need some tool to generate the output of this action (which may be the input of another action). This is done using a class called ToolSelector
. An object of class ToolSelector
is created passing the JobAction
and the ToolChain
(and a couple more of flags we don't mind now).
Then we ask that object for a tool suitable for the current action using the Inputs of this JobAction
(they will be other Action
s) to give us a suitable Tool
. This function can use a function from the ToolChain
class called SelectTool
which returns the tool suitable for an action. But before it does this, ToolSelector::getTool
attempts to collapse several actions in a single tool. For example, cc1
(the C/C++ compiler proper) can genrate directly object files from C input files. So it does not make sense to do .c
→ .i
, then .i
→ .bc
, .bc
→ .s
and then .s
→ .o
if the tool can actually do .c
→ .o
in a single invocation. When the function ToolSelector::getTool
combines the action (like in the case of C/C++) it also updates the Inputs
, so for the case of Assembly
(.s
→ .o
) actions in C/C++ the inputs where Backend
actions (.bc
→ .s
) but now they are the InputAction
(the .c
/.cpp
source files in the command line).
In the case we care the most, Fortran, ToolSelector::getTool
does not combine anything as the flang frontend will just take a .F
/.f
and generate a .llvm
(textual LLVM IR).
const ActionList *Inputs = &A->getInputs();
const JobAction *JA = cast<JobAction>(A);
ToolSelector TS(JA, *TC, C, isSaveTempsEnabled(), embedBitcodeInObject());
const Tool *T = TS.getTool(Inputs, CollapsedOffloadActions);
Once we have a suitable tool for the action we can build jobs for the inputs. We do this recursively invoking BuildJobsForAction
. This will compute the jobs of the actions previous to this one (the inputs to the current action). Note that SubJobAtTopLevel
for the kind of actions we care will always evaluate as false. This is because when linking only the Link action is top level. When compiling only (-c
option) more than one Assemble
action can be TopLevel
(depending on the number of .c
files in the input).
for (const Action *Input : *Inputs) {
// Treat dsymutil and verify sub-jobs as being at the top-level too, they
// shouldn't get temporary output names.
// FIXME: Clean this up.
bool SubJobAtTopLevel =
AtTopLevel && (isa<DsymutilJobAction>(A) || isa<VerifyJobAction>(A));
InputInfos.push_back(BuildJobsForAction(
C, Input, TC, BoundArch, SubJobAtTopLevel, MultipleArchs, LinkingOutput,
CachedResults, A->getOffloadingDeviceKind()));
}
Once we have built the jobs of the inputs of our task we can create the job of the current task (now that we have the tool).
T->ConstructJob(
C, *JA, Result, InputInfos,
C.getArgsForToolChain(TC, BoundArch, JA->getOffloadingDeviceKind()),
LinkingOutput);
<p. This will construct each tool individually. There are classes for each possible tool: Clang
(for cc1), ClangAs
(for cc1as) and FlangFrontend
(for flang). These are defined in llvm/tools/clang/lib/Driver/Tools.cpp
. We’ll get back to them later. </p>
After the jobs have been built, BuildJobsForActionNoCache
, BuildJobsForAction
and BuildJobs
do a few more of uninteresting bookkeeping but in practical terms they are done. So we go back to BuildCompilation
. Building the jobs is the last thing it does, so we're back to the driver. As a reminder where we started.
llvm/tools/clang/tools/driver/driver.cpp
456 std::unique_ptr<Compilation> C(TheDriver.BuildCompilation(argv));
457 int Res = 0;
458 SmallVector<std::pair<int, const Command *>, 4> FailingCommands;
459 if (C.get())
460 Res = TheDriver.ExecuteCompilation(*C, FailingCommands);
Now, the built compilation is executed in ExecuteCompilation
. It basically executes the jobs we created when building the compilation.
llvm/tools/clang/tools/driver/driver.cpp
int Driver::ExecuteCompilation(
Compilation &C,
SmallVectorImpl<std::pair<int, const Command *>> &FailingCommands) {
// Just print if -### was present.
if (C.getArgs().hasArg(options::OPT__HASH_HASH_HASH)) {
C.getJobs().Print(llvm::errs(), "\n", true);
return 0;
}
// If there were errors building the compilation, quit now.
if (Diags.hasErrorOccurred())
return 1;
// Set up response file names for each command, if necessary
for (auto &Job : C.getJobs())
setUpResponseFiles(C, Job);
C.ExecuteJobs(C.getJobs(), FailingCommands);
// (.. omitted ..)
}
And the compilation has fully completed!
A tool for every task
Above we mentioned that every JobAction
invokes a tool to generate its output using the inputs. How does this really work?
Toolchain
We have mentioned several times above the class ToolChain
. Objects of this class know how to invoke the tools in every environment. The toolchain depends on the target which in LLVM is represented using a triple. The triple is loosely based on the GNU triple as used by the GNU toolchain. For instance a triple like x86_64-pc-linux-gnu
uses a Linux toolchain while a triple like x86_64-apple-darwin16.6.0
uses a Darwin toolchain style. When it comes to tools, usually only the assembler and the linker change, the tools provided by LLVM (like cc1 or the flang front end) are not configurable. That said, toolchains may have different defaults or require different configurations. All this information is encoded in the ToolChain
class.
We stated above that when creating the jobs for an action we need a tool. ToolSelector::getTool
calls ToolChain::SelectTool
to do this. By default it prioritizes cc1
or cc1as
if it can process the input specified, otherwise it falls back to ToolChain::getTool
that can be overriden by the ToolChain
.
llvm/tools/clang/lib/Driver/ToolChain.cpp
347 Tool *ToolChain::SelectTool(const JobAction &JA) const {
348 if (getDriver().ShouldUseClangCompiler(JA)) return getClang();
349 Action::ActionClass AC = JA.getKind();
350 if (AC == Action::AssembleJobClass && useIntegratedAs())
351 return getClangAs();
352 return getTool(AC);
353 }
The base ToolChain
class provides the following getTool
implementation. The few ToolChain
that override this function forward to the base implementation if they do not have to provide specific behaviour.
llvm/tools/clang/lib/Driver/ToolChain.cpp
255 Tool *ToolChain::getTool(Action::ActionClass AC) const {
256 switch (AC) {
257 case Action::AssembleJobClass:
258 return getAssemble();
259
260 case Action::LinkJobClass:
261 return getLink();
262
263 case Action::InputClass:
264 case Action::BindArchClass:
265 case Action::OffloadClass:
266 case Action::LipoJobClass:
267 case Action::DsymutilJobClass:
268 case Action::VerifyDebugInfoJobClass:
269 llvm_unreachable("Invalid tool kind.");
270
271 case Action::CompileJobClass:
272 case Action::PrecompileJobClass:
273 case Action::PreprocessJobClass:
274 case Action::AnalyzeJobClass:
275 case Action::MigrateJobClass:
276 case Action::VerifyPCHJobClass:
277 case Action::BackendJobClass:
278 return getClang();
279
280 case Action::OffloadBundlingJobClass:
281 case Action::OffloadUnbundlingJobClass:
282 return getOffloadBundler();
283
284 case Action::FortranFrontendJobClass:
285 return getFlangFrontend();
286 }
287
288 llvm_unreachable("Invalid tool kind.");
289 }
Flang frontend
Recall that when the input is a Fortran file, the phase for the action is a FortranFrontend
which generates a FortranFrontendJobAction
and this one uses the tool specified in ToolChain::getFlangFrontend
.
llvm/tools/clang/lib/Driver/ToolChain.cpp
231 Tool *ToolChain::getFlangFrontend() const {
232 if (!FlangFrontend)
233 FlangFrontend.reset(new tools::FlangFrontend(*this));
234 return FlangFrontend.get();
235 }
This is the tool that will execute the Fortran front end phases. It is defined in Tools.h
.
llvm/tools/clang/lib/Driver/Tools.h
129 class LLVM_LIBRARY_VISIBILITY FlangFrontend : public Tool {
130 public:
131 FlangFrontend(const ToolChain &TC)
132 : Tool("flang:frontend",
133 "Fortran frontend to LLVM", TC,
134 RF_Full) {}
135
136 bool hasGoodDiagnostics() const override { return true; }
137 bool hasIntegratedAssembler() const override { return false; }
138 bool hasIntegratedCPP() const override { return false; }
139
140 void ConstructJob(Compilation &C, const JobAction &JA,
141 const InputInfo &Output, const InputInfoList &Inputs,
142 const llvm::opt::ArgList &TCArgs,
143 const char *LinkingOutput) const override;
144 };
The function ConstructJob
(that is effectively called from BuildJobsForActionNoCache
as we saw above) is a very long function, but the key elements of it is that it adds to the compilation a couple of commands.
llvm/tools/clang/lib/Driver/Tools.cpp
void FlangFrontend::ConstructJob(Compilation &C, const JobAction &JA,
const InputInfo &Output, const InputInfoList &Inputs,
const ArgList &Args, const char *LinkingOutput) const {
// (.. omit ..)
const char *UpperExec = Args.MakeArgString(getToolChain().GetProgramPath("flang1"));
// (.. omit ..)
C.addCommand(llvm::make_unique<Command>(JA, *this, UpperExec, UpperCmdArgs, Inputs));
// (.. omit ..)
const char *LowerExec = Args.MakeArgString(getToolChain().GetProgramPath("flang2"));
// (.. omit ..)
C.addCommand(llvm::make_unique<Command>(JA, *this, LowerExec, LowerCmdArgs, Inputs));
}
Basically the execution of the FlangFrontend
entails running two programs, called flang1
and flang2
. Now it is a bit early to explain what they do but basically flang1
(upper Fortran) parses the Fortran code and generates an intermediate representation that is handed to flang2
(lower Fortran). flang2
is the responsible to generate the LLVM IR output.
Overall workflow
The option -### of the driver can be used to see the commands that it would invoke (in ExecuteCompilation
). If we retake our test.f90
from the last chapter, we can ask flang what commands it wil execute.
$ flang -### -o test test.f90
clang version 4.0.1 (https://github.com/flang-compiler/clang.git 17f442716e8e6a5a642be5bd9e4f86b1aaf2f372) (https://github.com/llvm-mirror/llvm.git c8fccc53ed66d505898f8850bcc690c977a7c9a7)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: install/bin
"install/bin/flang1" "test.f90" "-opt" "0" "-terse" "1" "-inform" "warn" "-nohpf" "-nostatic" "-y" "129" "2" "-inform" "warn" "-x" "19" "0x400000" "-quad" "-x" "59" "4" "-x" "15" "2" "-x" "49" "0x400004" "-x" "51" "0x20" "-x" "57" "0x4c" "-x" "58" "0x10000" "-x" "124" "0x1000" "-tp" "px" "-x" "57" "0xfb0000" "-x" "58" "0x78031040" "-x" "47" "0x08" "-x" "48" "4608" "-x" "49" "0x100" "-stdinc" "install/bin/../include:/usr/local/include:install/bin/../lib/clang/4.0.1/include:/usr/include/x86_64-linux-gnu:/include:/usr/include" "-def" "unix" "-def" "__unix" "-def" "__unix__" "-def" "linux" "-def" "__linux" "-def" "__linux__" "-def" "__NO_MATH_INLINES" "-def" "__LP64__" "-def" "__x86_64" "-def" "__x86_64__" "-def" "__LONG_MAX__=9223372036854775807L" "-def" "__SIZE_TYPE__=unsigned long int" "-def" "__PTRDIFF_TYPE__=long int" "-def" "__THROW=" "-def" "__extension__=" "-def" "__amd_64__amd64__" "-def" "__k8" "-def" "__k8__" "-def" "__PGLLVM__" "-freeform" "-vect" "48" "-y" "54" "1" "-x" "70" "0x40000000" "-y" "163" "0xc0000000" "-x" "189" "0x10" "-stbfile" "/tmp/test-82de19.stb" "-modexport" "/tmp/test-82de19.cmod" "-modindex" "/tmp/test-82de19.cmdx" "-output" "/tmp/test-82de19.ilm"
"install/bin/flang2" "/tmp/test-82de19.ilm" "-ieee" "1" "-x" "6" "0x100" "-x" "42" "0x400000" "-y" "129" "4" "-x" "129" "0x400" "-fn" "test.f90" "-opt" "0" "-terse" "1" "-inform" "warn" "-y" "129" "2" "-inform" "warn" "-x" "51" "0x20" "-x" "119" "0xa10000" "-x" "122" "0x40" "-x" "123" "0x1000" "-x" "127" "4" "-x" "127" "17" "-x" "19" "0x400000" "-x" "28" "0x40000" "-x" "120" "0x10000000" "-x" "70" "0x8000" "-x" "122" "1" "-x" "125" "0x20000" "-quad" "-x" "59" "4" "-tp" "px" "-x" "120" "0x1000" "-x" "124" "0x1400" "-y" "15" "2" "-x" "57" "0x3b0000" "-x" "58" "0x48000000" "-x" "49" "0x100" "-astype" "0" "-x" "183" "4" "-x" "121" "0x800" "-x" "54" "0x10" "-x" "70" "0x40000000" "-x" "249" "40" "-x" "124" "1" "-y" "163" "0xc0000000" "-x" "189" "0x10" "-y" "189" "0x4000000" "-x" "183" "0x10" "-stbfile" "/tmp/test-82de19.stb" "-asm" "/tmp/test-82de19.ll"
"install/bin/clang-4.0" "-cc1" "-triple" "x86_64-unknown-linux-gnu" "-emit-obj" "-mrelax-all" "-disable-free" "-main-file-name" "test.f90" "-mrelocation-model" "static" "-mthread-model" "posix" "-mdisable-fp-elim" "-fmath-errno" "-masm-verbose" "-mconstructor-aliases" "-munwind-tables" "-fuse-init-array" "-target-cpu" "x86-64" "-dwarf-column-info" "-debugger-tuning=gdb" "-resource-dir" "install/bin/../lib/clang/4.0.1" "-fdebug-compilation-dir" "test" "-ferror-limit" "19" "-fmessage-length" "190" "-fobjc-runtime=gcc" "-fdiagnostics-show-option" "-fcolor-diagnostics" "-o" "/tmp/test-bcb4ec.o" "-x" "ir" "/tmp/test-82de19.ll"
"/usr/bin/ld" "--hash-style=both" "--eh-frame-hdr" "-m" "elf_x86_64" "-dynamic-linker" "/lib64/ld-linux-x86-64.so.2" "-o" "test" "/usr/lib/gcc/x86_64-linux-gnu/6.3.0/../../../x86_64-linux-gnu/crt1.o" "/usr/lib/gcc/x86_64-linux-gnu/6.3.0/../../../x86_64-linux-gnu/crti.o" "/usr/lib/gcc/x86_64-linux-gnu/6.3.0/crtbegin.o" "-L/usr/lib/gcc/x86_64-linux-gnu/6.3.0" "-L/usr/lib/gcc/x86_64-linux-gnu/6.3.0/../../../x86_64-linux-gnu" "-L/lib/x86_64-linux-gnu" "-L/lib/../lib64" "-L/usr/lib/x86_64-linux-gnu" "-L/usr/lib/gcc/x86_64-linux-gnu/6.3.0/../../.." "-Linstall/bin/../lib" "-L/lib" "-L/usr/lib" "/tmp/test-bcb4ec.o" "-lflangmain" "-lflang" "-lflangrti" "-lompstub" "-lm" "-lrt" "-lpthread" "-lgcc" "--as-needed" "-lgcc_s" "--no-as-needed" "-lc" "-lgcc" "--as-needed" "-lgcc_s" "--no-as-needed" "/usr/lib/gcc/x86_64-linux-gnu/6.3.0/crtend.o" "/usr/lib/gcc/x86_64-linux-gnu/6.3.0/../../../x86_64-linux-gnu/crtn.o"
Wow, that is barely readable. But we can filter the output using grep. I annotate every invocation with its action.
$ flang -### -o test test.f90 2>&1 | grep -o "^ \"[^\"]\+\""
"install/bin/flang1" # [FortranFrontEnd]
"install/bin/flang2" # .f/F → .ll [FortranFrontEnd]
"install/bin/clang-4.0" # .ll → .o [Compile+Backend+Assemble]
"/usr/bin/ld" # .o → (exe) [Link)
The first two commands (flang1 and flang2) are due to the FortranFrontendJobAction
. The invocation for clang-4.0
is caused by the collapsing the phases of Assemble
(.ll
→ .bc
) and Backend
(.bc
→ .s
) and Assemble (.s
→ .o
) into a single step (.ll
→ .o
). A way to disable the collapsing of phases is using the flag -save-temps
.
$ flang -### -o test test.f90 -save-temps 2>&1 | grep -o "^ \"[^\"]\+\""
"install/bin/flang1" # [FortranFrontEnd]
"install/bin/flang2" # .f/F → .ll [FortranFrontEnd]
"install/bin/clang-4.0" # .ll → .bc [Compile]
"install/bin/clang-4.0" # .bc → .s [Backend]
"install/bin/clang-4.0" # .s → .o [Assemble]
"/usr/bin/ld" # .o → (exe) [Link]
Well, this post is too long already. In the next chapter we will see what flang1 does.