1 //===--- CompilerInstance.cpp ---------------------------------------------===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 // Coding style: https://mlir.llvm.org/getting_started/DeveloperGuide/
11 //===----------------------------------------------------------------------===//
13 #include "flang/Frontend/CompilerInstance.h"
14 #include "flang/Common/Fortran-features.h"
15 #include "flang/Frontend/CompilerInvocation.h"
16 #include "flang/Frontend/TextDiagnosticPrinter.h"
17 #include "flang/Parser/parsing.h"
18 #include "flang/Parser/provenance.h"
19 #include "flang/Semantics/semantics.h"
20 #include "flang/Support/Timing.h"
21 #include "mlir/Support/RawOstreamExtras.h"
22 #include "clang/Basic/DiagnosticFrontend.h"
23 #include "llvm/ADT/StringExtras.h"
24 #include "llvm/MC/TargetRegistry.h"
25 #include "llvm/Pass.h"
26 #include "llvm/Support/Errc.h"
27 #include "llvm/Support/Error.h"
28 #include "llvm/Support/FileSystem.h"
29 #include "llvm/Support/Path.h"
30 #include "llvm/Support/raw_ostream.h"
31 #include "llvm/TargetParser/TargetParser.h"
32 #include "llvm/TargetParser/Triple.h"
34 using namespace Fortran::frontend
;
36 CompilerInstance::CompilerInstance()
37 : invocation(new CompilerInvocation()),
38 allSources(new Fortran::parser::AllSources()),
39 allCookedSources(new Fortran::parser::AllCookedSources(*allSources
)),
40 parsing(new Fortran::parser::Parsing(*allCookedSources
)) {
41 // TODO: This is a good default during development, but ultimately we should
42 // give the user the opportunity to specify this.
43 allSources
->set_encoding(Fortran::parser::Encoding::UTF_8
);
46 CompilerInstance::~CompilerInstance() {
47 assert(outputFiles
.empty() && "Still output files in flight?");
50 void CompilerInstance::setInvocation(
51 std::shared_ptr
<CompilerInvocation
> value
) {
52 invocation
= std::move(value
);
55 void CompilerInstance::setSemaOutputStream(raw_ostream
&value
) {
56 ownedSemaOutputStream
.release();
57 semaOutputStream
= &value
;
60 void CompilerInstance::setSemaOutputStream(std::unique_ptr
<raw_ostream
> value
) {
61 ownedSemaOutputStream
.swap(value
);
62 semaOutputStream
= ownedSemaOutputStream
.get();
65 // Helper method to generate the path of the output file. The following logic
67 // 1. If the user specifies the output file via `-o`, then use that (i.e.
68 // the outputFilename parameter).
69 // 2. If the user does not specify the name of the output file, derive it from
70 // the input file (i.e. inputFilename + extension)
71 // 3. If the output file is not specified and the input file is `-`, then set
72 // the output file to `-` as well.
73 static std::string
getOutputFilePath(llvm::StringRef outputFilename
,
74 llvm::StringRef inputFilename
,
75 llvm::StringRef extension
) {
77 // Output filename _is_ specified. Just use that.
78 if (!outputFilename
.empty())
79 return std::string(outputFilename
);
81 // Output filename _is not_ specified. Derive it from the input file name.
82 std::string outFile
= "-";
83 if (!extension
.empty() && (inputFilename
!= "-")) {
84 llvm::SmallString
<128> path(inputFilename
);
85 llvm::sys::path::replace_extension(path
, extension
);
86 outFile
= std::string(path
);
92 std::unique_ptr
<llvm::raw_pwrite_stream
>
93 CompilerInstance::createDefaultOutputFile(bool binary
, llvm::StringRef baseName
,
94 llvm::StringRef extension
) {
96 // Get the path of the output file
97 std::string outputFilePath
=
98 getOutputFilePath(getFrontendOpts().outputFile
, baseName
, extension
);
100 // Create the output file
101 llvm::Expected
<std::unique_ptr
<llvm::raw_pwrite_stream
>> os
=
102 createOutputFileImpl(outputFilePath
, binary
);
104 // If successful, add the file to the list of tracked output files and
107 outputFiles
.emplace_back(OutputFile(outputFilePath
));
108 return std::move(*os
);
111 // If unsuccessful, issue an error and return Null
112 unsigned diagID
= getDiagnostics().getCustomDiagID(
113 clang::DiagnosticsEngine::Error
, "unable to open output file '%0': '%1'");
114 getDiagnostics().Report(diagID
)
115 << outputFilePath
<< llvm::errorToErrorCode(os
.takeError()).message();
119 llvm::Expected
<std::unique_ptr
<llvm::raw_pwrite_stream
>>
120 CompilerInstance::createOutputFileImpl(llvm::StringRef outputFilePath
,
123 // Creates the file descriptor for the output file
124 std::unique_ptr
<llvm::raw_fd_ostream
> os
;
126 std::error_code error
;
127 os
.reset(new llvm::raw_fd_ostream(
128 outputFilePath
, error
,
129 (binary
? llvm::sys::fs::OF_None
: llvm::sys::fs::OF_TextWithCRLF
)));
131 return llvm::errorCodeToError(error
);
134 // For seekable streams, just return the stream corresponding to the output
136 if (!binary
|| os
->supportsSeeking())
137 return std::move(os
);
139 // For non-seekable streams, we need to wrap the output stream into something
140 // that supports 'pwrite' and takes care of the ownership for us.
141 return std::make_unique
<llvm::buffer_unique_ostream
>(std::move(os
));
144 void CompilerInstance::clearOutputFiles(bool eraseFiles
) {
145 for (OutputFile
&of
: outputFiles
)
146 if (!of
.filename
.empty() && eraseFiles
)
147 llvm::sys::fs::remove(of
.filename
);
152 bool CompilerInstance::executeAction(FrontendAction
&act
) {
153 CompilerInvocation
&invoc
= this->getInvocation();
155 llvm::Triple targetTriple
{llvm::Triple(invoc
.getTargetOpts().triple
)};
157 // Set some sane defaults for the frontend.
158 invoc
.setDefaultFortranOpts();
159 // Update the fortran options based on user-based input.
160 invoc
.setFortranOpts();
161 // Set the encoding to read all input files in based on user input.
162 allSources
->set_encoding(invoc
.getFortranOpts().encoding
);
163 if (!setUpTargetMachine())
165 // Create the semantics context
166 semaContext
= invoc
.getSemanticsCtx(*allCookedSources
, getTargetMachine());
167 // Set options controlling lowering to FIR.
168 invoc
.setLoweringOptions();
170 if (invoc
.getEnableTimers()) {
171 llvm::TimePassesIsEnabled
= true;
173 timingStreamMLIR
= std::make_unique
<Fortran::support::string_ostream
>();
174 timingStreamLLVM
= std::make_unique
<Fortran::support::string_ostream
>();
175 timingStreamCodeGen
= std::make_unique
<Fortran::support::string_ostream
>();
177 timingMgr
.setEnabled(true);
178 timingMgr
.setDisplayMode(mlir::DefaultTimingManager::DisplayMode::Tree
);
180 Fortran::support::createTimingFormatterText(*timingStreamMLIR
));
182 // Creating a new TimingScope will automatically start the timer. Since this
183 // is the top-level timer, this is ok because it will end up capturing the
184 // time for all the bookkeeping and other tasks that take place between
185 // parsing, lowering etc. for which finer-grained timers will be created.
186 timingScopeRoot
= timingMgr
.getRootScope();
189 // Run the frontend action `act` for every input file.
190 for (const FrontendInputFile
&fif
: getFrontendOpts().inputs
) {
191 if (act
.beginSourceFile(*this, fif
)) {
192 if (llvm::Error err
= act
.execute()) {
193 consumeError(std::move(err
));
199 if (timingMgr
.isEnabled()) {
200 timingScopeRoot
.stop();
202 // Write the timings to the associated output stream and clear all timers.
203 // We need to provide another stream because the TimingManager will attempt
204 // to print in its destructor even if it has been cleared. By the time that
205 // destructor runs, the output streams will have been destroyed, so give it
209 Fortran::support::createTimingFormatterText(mlir::thread_safe_nulls()));
211 // This prints the timings in "reverse" order, starting from code
212 // generation, followed by LLVM-IR optimizations, then MLIR optimizations
213 // and transformations and the frontend. If any of the steps are disabled,
214 // for instance because code generation was not performed, the strings
216 if (!timingStreamCodeGen
->str().empty())
217 llvm::errs() << timingStreamCodeGen
->str() << "\n";
219 if (!timingStreamLLVM
->str().empty())
220 llvm::errs() << timingStreamLLVM
->str() << "\n";
222 if (!timingStreamMLIR
->str().empty())
223 llvm::errs() << timingStreamMLIR
->str() << "\n";
226 return !getDiagnostics().getClient()->getNumErrors();
229 void CompilerInstance::createDiagnostics(clang::DiagnosticConsumer
*client
,
230 bool shouldOwnClient
) {
232 createDiagnostics(&getDiagnosticOpts(), client
, shouldOwnClient
);
235 clang::IntrusiveRefCntPtr
<clang::DiagnosticsEngine
>
236 CompilerInstance::createDiagnostics(clang::DiagnosticOptions
*opts
,
237 clang::DiagnosticConsumer
*client
,
238 bool shouldOwnClient
) {
239 clang::IntrusiveRefCntPtr
<clang::DiagnosticIDs
> diagID(
240 new clang::DiagnosticIDs());
241 clang::IntrusiveRefCntPtr
<clang::DiagnosticsEngine
> diags(
242 new clang::DiagnosticsEngine(diagID
, opts
));
244 // Create the diagnostic client for reporting errors or for
245 // implementing -verify.
247 diags
->setClient(client
, shouldOwnClient
);
249 diags
->setClient(new TextDiagnosticPrinter(llvm::errs(), opts
));
254 // Get feature string which represents combined explicit target features
255 // for AMD GPU and the target features specified by the user
257 getExplicitAndImplicitAMDGPUTargetFeatures(clang::DiagnosticsEngine
&diags
,
258 const TargetOptions
&targetOpts
,
259 const llvm::Triple triple
) {
260 llvm::StringRef cpu
= targetOpts
.cpu
;
261 llvm::StringMap
<bool> implicitFeaturesMap
;
262 // Get the set of implicit target features
263 llvm::AMDGPU::fillAMDGPUFeatureMap(cpu
, triple
, implicitFeaturesMap
);
265 // Add target features specified by the user
266 for (auto &userFeature
: targetOpts
.featuresAsWritten
) {
267 std::string userKeyString
= userFeature
.substr(1);
268 implicitFeaturesMap
[userKeyString
] = (userFeature
[0] == '+');
272 llvm::AMDGPU::insertWaveSizeFeature(cpu
, triple
, implicitFeaturesMap
);
273 if (HasError
.first
) {
274 unsigned diagID
= diags
.getCustomDiagID(clang::DiagnosticsEngine::Error
,
275 "Unsupported feature ID: %0");
276 diags
.Report(diagID
) << HasError
.second
;
277 return std::string();
280 llvm::SmallVector
<std::string
> featuresVec
;
281 for (auto &implicitFeatureItem
: implicitFeaturesMap
) {
282 featuresVec
.push_back((llvm::Twine(implicitFeatureItem
.second
? "+" : "-") +
283 implicitFeatureItem
.first().str())
286 llvm::sort(featuresVec
);
287 return llvm::join(featuresVec
, ",");
290 // Get feature string which represents combined explicit target features
291 // for NVPTX and the target features specified by the user/
292 // TODO: Have a more robust target conf like `clang/lib/Basic/Targets/NVPTX.cpp`
294 getExplicitAndImplicitNVPTXTargetFeatures(clang::DiagnosticsEngine
&diags
,
295 const TargetOptions
&targetOpts
,
296 const llvm::Triple triple
) {
297 llvm::StringRef cpu
= targetOpts
.cpu
;
298 llvm::StringMap
<bool> implicitFeaturesMap
;
299 std::string errorMsg
;
302 // Add target features specified by the user
303 for (auto &userFeature
: targetOpts
.featuresAsWritten
) {
304 llvm::StringRef
userKeyString(llvm::StringRef(userFeature
).drop_front(1));
305 implicitFeaturesMap
[userKeyString
.str()] = (userFeature
[0] == '+');
306 // Check if the user provided a PTX version
307 if (userKeyString
.starts_with("ptx"))
311 // Set the default PTX version to `ptx61` if none was provided.
312 // TODO: set the default PTX version based on the chip.
314 implicitFeaturesMap
["ptx61"] = true;
316 // Set the compute capability.
317 implicitFeaturesMap
[cpu
.str()] = true;
319 llvm::SmallVector
<std::string
> featuresVec
;
320 for (auto &implicitFeatureItem
: implicitFeaturesMap
) {
321 featuresVec
.push_back((llvm::Twine(implicitFeatureItem
.second
? "+" : "-") +
322 implicitFeatureItem
.first().str())
325 llvm::sort(featuresVec
);
326 return llvm::join(featuresVec
, ",");
329 std::string
CompilerInstance::getTargetFeatures() {
330 const TargetOptions
&targetOpts
= getInvocation().getTargetOpts();
331 const llvm::Triple
triple(targetOpts
.triple
);
333 // Clang does not append all target features to the clang -cc1 invocation.
334 // Some target features are parsed implicitly by clang::TargetInfo child
335 // class. Clang::TargetInfo classes are the basic clang classes and
336 // they cannot be reused by Flang.
337 // That's why we need to extract implicit target features and add
338 // them to the target features specified by the user
339 if (triple
.isAMDGPU()) {
340 return getExplicitAndImplicitAMDGPUTargetFeatures(getDiagnostics(),
342 } else if (triple
.isNVPTX()) {
343 return getExplicitAndImplicitNVPTXTargetFeatures(getDiagnostics(),
346 return llvm::join(targetOpts
.featuresAsWritten
.begin(),
347 targetOpts
.featuresAsWritten
.end(), ",");
350 bool CompilerInstance::setUpTargetMachine() {
351 const TargetOptions
&targetOpts
= getInvocation().getTargetOpts();
352 const std::string
&theTriple
= targetOpts
.triple
;
356 const llvm::Target
*theTarget
=
357 llvm::TargetRegistry::lookupTarget(theTriple
, error
);
359 getDiagnostics().Report(clang::diag::err_fe_unable_to_create_target
)
363 // Create `TargetMachine`
364 const auto &CGOpts
= getInvocation().getCodeGenOpts();
365 std::optional
<llvm::CodeGenOptLevel
> OptLevelOrNone
=
366 llvm::CodeGenOpt::getLevel(CGOpts
.OptimizationLevel
);
367 assert(OptLevelOrNone
&& "Invalid optimization level!");
368 llvm::CodeGenOptLevel OptLevel
= *OptLevelOrNone
;
369 std::string featuresStr
= getTargetFeatures();
370 std::optional
<llvm::CodeModel::Model
> cm
= getCodeModel(CGOpts
.CodeModel
);
372 llvm::TargetOptions tOpts
= llvm::TargetOptions();
373 tOpts
.EnableAIXExtendedAltivecABI
= targetOpts
.EnableAIXExtendedAltivecABI
;
375 targetMachine
.reset(theTarget
->createTargetMachine(
376 theTriple
, /*CPU=*/targetOpts
.cpu
,
377 /*Features=*/featuresStr
, /*Options=*/tOpts
,
378 /*Reloc::Model=*/CGOpts
.getRelocationModel(),
379 /*CodeModel::Model=*/cm
, OptLevel
));
380 assert(targetMachine
&& "Failed to create TargetMachine");
381 if (cm
.has_value()) {
382 const llvm::Triple
triple(theTriple
);
383 if ((cm
== llvm::CodeModel::Medium
|| cm
== llvm::CodeModel::Large
) &&
384 triple
.getArch() == llvm::Triple::x86_64
) {
385 targetMachine
->setLargeDataThreshold(CGOpts
.LargeDataThreshold
);