1//===--- CompilerInstance.cpp ---------------------------------------------===//
2//
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
6//
7//===----------------------------------------------------------------------===//
8//
9// Coding style: https://mlir.llvm.org/getting_started/DeveloperGuide/
10//
11//===----------------------------------------------------------------------===//
12
13#include "flang/Frontend/CompilerInstance.h"
14#include "flang/Frontend/CompilerInvocation.h"
15#include "flang/Frontend/TextDiagnosticPrinter.h"
16#include "flang/Parser/parsing.h"
17#include "flang/Parser/provenance.h"
18#include "flang/Semantics/semantics.h"
19#include "flang/Support/Fortran-features.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"
33
34using namespace Fortran::frontend;
35
36CompilerInstance::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);
44}
45
46CompilerInstance::~CompilerInstance() {
47 assert(outputFiles.empty() && "Still output files in flight?");
48}
49
50void CompilerInstance::setInvocation(
51 std::shared_ptr<CompilerInvocation> value) {
52 invocation = std::move(value);
53}
54
55void CompilerInstance::setSemaOutputStream(raw_ostream &value) {
56 ownedSemaOutputStream.release();
57 semaOutputStream = &value;
58}
59
60void CompilerInstance::setSemaOutputStream(std::unique_ptr<raw_ostream> value) {
61 ownedSemaOutputStream.swap(value);
62 semaOutputStream = ownedSemaOutputStream.get();
63}
64
65// Helper method to generate the path of the output file. The following logic
66// applies:
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.
73static std::string getOutputFilePath(llvm::StringRef outputFilename,
74 llvm::StringRef inputFilename,
75 llvm::StringRef extension) {
76
77 // Output filename _is_ specified. Just use that.
78 if (!outputFilename.empty())
79 return std::string(outputFilename);
80
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);
87 }
88
89 return outFile;
90}
91
92std::unique_ptr<llvm::raw_pwrite_stream>
93CompilerInstance::createDefaultOutputFile(bool binary, llvm::StringRef baseName,
94 llvm::StringRef extension) {
95
96 // Get the path of the output file
97 std::string outputFilePath =
98 getOutputFilePath(getFrontendOpts().outputFile, baseName, extension);
99
100 // Create the output file
101 llvm::Expected<std::unique_ptr<llvm::raw_pwrite_stream>> os =
102 createOutputFileImpl(outputFilePath, binary);
103
104 // If successful, add the file to the list of tracked output files and
105 // return.
106 if (os) {
107 outputFiles.emplace_back(OutputFile(outputFilePath));
108 return std::move(*os);
109 }
110
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();
116 return nullptr;
117}
118
119llvm::Expected<std::unique_ptr<llvm::raw_pwrite_stream>>
120CompilerInstance::createOutputFileImpl(llvm::StringRef outputFilePath,
121 bool binary) {
122
123 // Creates the file descriptor for the output file
124 std::unique_ptr<llvm::raw_fd_ostream> os;
125
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)));
130 if (error) {
131 return llvm::errorCodeToError(error);
132 }
133
134 // For seekable streams, just return the stream corresponding to the output
135 // file.
136 if (!binary || os->supportsSeeking())
137 return std::move(os);
138
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));
142}
143
144void CompilerInstance::clearOutputFiles(bool eraseFiles) {
145 for (OutputFile &of : outputFiles)
146 if (!of.filename.empty() && eraseFiles)
147 llvm::sys::fs::remove(of.filename);
148
149 outputFiles.clear();
150}
151
152bool CompilerInstance::executeAction(FrontendAction &act) {
153 CompilerInvocation &invoc = this->getInvocation();
154
155 llvm::Triple targetTriple{llvm::Triple(invoc.getTargetOpts().triple)};
156
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())
164 return false;
165 // Set options controlling lowering to FIR.
166 invoc.setLoweringOptions();
167
168 if (invoc.getEnableTimers()) {
169 llvm::TimePassesIsEnabled = true;
170
171 timingStreamMLIR = std::make_unique<Fortran::support::string_ostream>();
172 timingStreamLLVM = std::make_unique<Fortran::support::string_ostream>();
173 timingStreamCodeGen = std::make_unique<Fortran::support::string_ostream>();
174
175 timingMgr.setEnabled(true);
176 timingMgr.setDisplayMode(mlir::DefaultTimingManager::DisplayMode::Tree);
177 timingMgr.setOutput(
178 Fortran::support::createTimingFormatterText(*timingStreamMLIR));
179
180 // Creating a new TimingScope will automatically start the timer. Since this
181 // is the top-level timer, this is ok because it will end up capturing the
182 // time for all the bookkeeping and other tasks that take place between
183 // parsing, lowering etc. for which finer-grained timers will be created.
184 timingScopeRoot = timingMgr.getRootScope();
185 }
186
187 // Run the frontend action `act` for every input file.
188 for (const FrontendInputFile &fif : getFrontendOpts().inputs) {
189 if (act.beginSourceFile(*this, fif)) {
190 if (llvm::Error err = act.execute()) {
191 consumeError(std::move(err));
192 }
193 act.endSourceFile();
194 }
195 }
196
197 if (timingMgr.isEnabled()) {
198 timingScopeRoot.stop();
199
200 // Write the timings to the associated output stream and clear all timers.
201 // We need to provide another stream because the TimingManager will attempt
202 // to print in its destructor even if it has been cleared. By the time that
203 // destructor runs, the output streams will have been destroyed, so give it
204 // a null stream.
205 timingMgr.print();
206 timingMgr.setOutput(
207 Fortran::support::createTimingFormatterText(mlir::thread_safe_nulls()));
208
209 // This prints the timings in "reverse" order, starting from code
210 // generation, followed by LLVM-IR optimizations, then MLIR optimizations
211 // and transformations and the frontend. If any of the steps are disabled,
212 // for instance because code generation was not performed, the strings
213 // will be empty.
214 if (!timingStreamCodeGen->str().empty())
215 llvm::errs() << timingStreamCodeGen->str() << "\n";
216
217 if (!timingStreamLLVM->str().empty())
218 llvm::errs() << timingStreamLLVM->str() << "\n";
219
220 if (!timingStreamMLIR->str().empty())
221 llvm::errs() << timingStreamMLIR->str() << "\n";
222 }
223
224 return !getDiagnostics().getClient()->getNumErrors();
225}
226
227void CompilerInstance::createDiagnostics(clang::DiagnosticConsumer *client,
228 bool shouldOwnClient) {
229 diagnostics = createDiagnostics(getDiagnosticOpts(), client, shouldOwnClient);
230}
231
232clang::IntrusiveRefCntPtr<clang::DiagnosticsEngine>
233CompilerInstance::createDiagnostics(clang::DiagnosticOptions &opts,
234 clang::DiagnosticConsumer *client,
235 bool shouldOwnClient) {
236 clang::IntrusiveRefCntPtr<clang::DiagnosticIDs> diagID(
237 new clang::DiagnosticIDs());
238 clang::IntrusiveRefCntPtr<clang::DiagnosticsEngine> diags(
239 new clang::DiagnosticsEngine(diagID, opts));
240
241 // Create the diagnostic client for reporting errors or for
242 // implementing -verify.
243 if (client) {
244 diags->setClient(client, shouldOwnClient);
245 } else {
246 diags->setClient(new TextDiagnosticPrinter(llvm::errs(), opts));
247 }
248 return diags;
249}
250
251// Get feature string which represents combined explicit target features
252// for AMD GPU and the target features specified by the user
253static std::string
254getExplicitAndImplicitAMDGPUTargetFeatures(clang::DiagnosticsEngine &diags,
255 const TargetOptions &targetOpts,
256 const llvm::Triple triple) {
257 llvm::StringRef cpu = targetOpts.cpu;
258 llvm::StringMap<bool> implicitFeaturesMap;
259 // Get the set of implicit target features
260 llvm::AMDGPU::fillAMDGPUFeatureMap(GPU: cpu, T: triple, Features&: implicitFeaturesMap);
261
262 // Add target features specified by the user
263 for (auto &userFeature : targetOpts.featuresAsWritten) {
264 std::string userKeyString = userFeature.substr(1);
265 implicitFeaturesMap[userKeyString] = (userFeature[0] == '+');
266 }
267
268 auto HasError =
269 llvm::AMDGPU::insertWaveSizeFeature(GPU: cpu, T: triple, Features&: implicitFeaturesMap);
270 if (HasError.first) {
271 unsigned diagID = diags.getCustomDiagID(L: clang::DiagnosticsEngine::Error,
272 FormatString: "Unsupported feature ID: %0");
273 diags.Report(DiagID: diagID) << HasError.second;
274 return std::string();
275 }
276
277 llvm::SmallVector<std::string> featuresVec;
278 for (auto &implicitFeatureItem : implicitFeaturesMap) {
279 featuresVec.push_back(Elt: (llvm::Twine(implicitFeatureItem.second ? "+" : "-") +
280 implicitFeatureItem.first().str())
281 .str());
282 }
283 llvm::sort(C&: featuresVec);
284 return llvm::join(R&: featuresVec, Separator: ",");
285}
286
287// Get feature string which represents combined explicit target features
288// for NVPTX and the target features specified by the user/
289// TODO: Have a more robust target conf like `clang/lib/Basic/Targets/NVPTX.cpp`
290static std::string
291getExplicitAndImplicitNVPTXTargetFeatures(clang::DiagnosticsEngine &diags,
292 const TargetOptions &targetOpts,
293 const llvm::Triple triple) {
294 llvm::StringRef cpu = targetOpts.cpu;
295 llvm::StringMap<bool> implicitFeaturesMap;
296 std::string errorMsg;
297 bool ptxVer = false;
298
299 // Add target features specified by the user
300 for (auto &userFeature : targetOpts.featuresAsWritten) {
301 llvm::StringRef userKeyString(llvm::StringRef(userFeature).drop_front(1));
302 implicitFeaturesMap[userKeyString.str()] = (userFeature[0] == '+');
303 // Check if the user provided a PTX version
304 if (userKeyString.starts_with("ptx"))
305 ptxVer = true;
306 }
307
308 // Set the default PTX version to `ptx61` if none was provided.
309 // TODO: set the default PTX version based on the chip.
310 if (!ptxVer)
311 implicitFeaturesMap["ptx61"] = true;
312
313 // Set the compute capability.
314 implicitFeaturesMap[cpu.str()] = true;
315
316 llvm::SmallVector<std::string> featuresVec;
317 for (auto &implicitFeatureItem : implicitFeaturesMap) {
318 featuresVec.push_back(Elt: (llvm::Twine(implicitFeatureItem.second ? "+" : "-") +
319 implicitFeatureItem.first().str())
320 .str());
321 }
322 llvm::sort(C&: featuresVec);
323 return llvm::join(R&: featuresVec, Separator: ",");
324}
325
326std::string CompilerInstance::getTargetFeatures() {
327 const TargetOptions &targetOpts = getInvocation().getTargetOpts();
328 const llvm::Triple triple(targetOpts.triple);
329
330 // Clang does not append all target features to the clang -cc1 invocation.
331 // Some target features are parsed implicitly by clang::TargetInfo child
332 // class. Clang::TargetInfo classes are the basic clang classes and
333 // they cannot be reused by Flang.
334 // That's why we need to extract implicit target features and add
335 // them to the target features specified by the user
336 if (triple.isAMDGPU()) {
337 return getExplicitAndImplicitAMDGPUTargetFeatures(getDiagnostics(),
338 targetOpts, triple);
339 } else if (triple.isNVPTX()) {
340 return getExplicitAndImplicitNVPTXTargetFeatures(getDiagnostics(),
341 targetOpts, triple);
342 }
343 return llvm::join(targetOpts.featuresAsWritten.begin(),
344 targetOpts.featuresAsWritten.end(), ",");
345}
346
347bool CompilerInstance::setUpTargetMachine() {
348 const TargetOptions &targetOpts = getInvocation().getTargetOpts();
349 const std::string &theTriple = targetOpts.triple;
350
351 // Create `Target`
352 std::string error;
353 const llvm::Target *theTarget =
354 llvm::TargetRegistry::lookupTarget(theTriple, error);
355 if (!theTarget) {
356 getDiagnostics().Report(clang::diag::err_fe_unable_to_create_target)
357 << error;
358 return false;
359 }
360 // Create `TargetMachine`
361 const auto &CGOpts = getInvocation().getCodeGenOpts();
362 std::optional<llvm::CodeGenOptLevel> OptLevelOrNone =
363 llvm::CodeGenOpt::getLevel(CGOpts.OptimizationLevel);
364 assert(OptLevelOrNone && "Invalid optimization level!");
365 llvm::CodeGenOptLevel OptLevel = *OptLevelOrNone;
366 std::string featuresStr = getTargetFeatures();
367 std::optional<llvm::CodeModel::Model> cm = getCodeModel(CGOpts.CodeModel);
368
369 llvm::TargetOptions tOpts = llvm::TargetOptions();
370 tOpts.EnableAIXExtendedAltivecABI = targetOpts.EnableAIXExtendedAltivecABI;
371
372 targetMachine.reset(theTarget->createTargetMachine(
373 llvm::Triple(theTriple), /*CPU=*/targetOpts.cpu,
374 /*Features=*/featuresStr, /*Options=*/tOpts,
375 /*Reloc::Model=*/CGOpts.getRelocationModel(),
376 /*CodeModel::Model=*/cm, OptLevel));
377 assert(targetMachine && "Failed to create TargetMachine");
378 if (cm.has_value()) {
379 const llvm::Triple triple(theTriple);
380 if ((cm == llvm::CodeModel::Medium || cm == llvm::CodeModel::Large) &&
381 triple.getArch() == llvm::Triple::x86_64) {
382 targetMachine->setLargeDataThreshold(CGOpts.LargeDataThreshold);
383 }
384 }
385 return true;
386}
387

source code of flang/lib/Frontend/CompilerInstance.cpp