xref: /llvm-project/flang/lib/Frontend/CompilerInstance.cpp (revision d1d952c206efc3a651270c69331b180330ac3efc)
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/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"
33 
34 using namespace Fortran::frontend;
35 
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);
44 }
45 
46 CompilerInstance::~CompilerInstance() {
47   assert(outputFiles.empty() && "Still output files in flight?");
48 }
49 
50 void CompilerInstance::setInvocation(
51     std::shared_ptr<CompilerInvocation> value) {
52   invocation = std::move(value);
53 }
54 
55 void CompilerInstance::setSemaOutputStream(raw_ostream &value) {
56   ownedSemaOutputStream.release();
57   semaOutputStream = &value;
58 }
59 
60 void 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.
73 static 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 
92 std::unique_ptr<llvm::raw_pwrite_stream>
93 CompilerInstance::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 
119 llvm::Expected<std::unique_ptr<llvm::raw_pwrite_stream>>
120 CompilerInstance::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 
144 void 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 
152 bool 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   // Create the semantics context
166   semaContext = invoc.getSemanticsCtx(*allCookedSources, getTargetMachine());
167   // Set options controlling lowering to FIR.
168   invoc.setLoweringOptions();
169 
170   if (invoc.getEnableTimers()) {
171     llvm::TimePassesIsEnabled = true;
172 
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>();
176 
177     timingMgr.setEnabled(true);
178     timingMgr.setDisplayMode(mlir::DefaultTimingManager::DisplayMode::Tree);
179     timingMgr.setOutput(
180         Fortran::support::createTimingFormatterText(*timingStreamMLIR));
181 
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();
187   }
188 
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));
194       }
195       act.endSourceFile();
196     }
197   }
198 
199   if (timingMgr.isEnabled()) {
200     timingScopeRoot.stop();
201 
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
206     // a null stream.
207     timingMgr.print();
208     timingMgr.setOutput(
209         Fortran::support::createTimingFormatterText(mlir::thread_safe_nulls()));
210 
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
215     // will be empty.
216     if (!timingStreamCodeGen->str().empty())
217       llvm::errs() << timingStreamCodeGen->str() << "\n";
218 
219     if (!timingStreamLLVM->str().empty())
220       llvm::errs() << timingStreamLLVM->str() << "\n";
221 
222     if (!timingStreamMLIR->str().empty())
223       llvm::errs() << timingStreamMLIR->str() << "\n";
224   }
225 
226   return !getDiagnostics().getClient()->getNumErrors();
227 }
228 
229 void CompilerInstance::createDiagnostics(clang::DiagnosticConsumer *client,
230                                          bool shouldOwnClient) {
231   diagnostics =
232       createDiagnostics(&getDiagnosticOpts(), client, shouldOwnClient);
233 }
234 
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));
243 
244   // Create the diagnostic client for reporting errors or for
245   // implementing -verify.
246   if (client) {
247     diags->setClient(client, shouldOwnClient);
248   } else {
249     diags->setClient(new TextDiagnosticPrinter(llvm::errs(), opts));
250   }
251   return diags;
252 }
253 
254 // Get feature string which represents combined explicit target features
255 // for AMD GPU and the target features specified by the user
256 static std::string
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);
264 
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] == '+');
269   }
270 
271   auto HasError =
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();
278   }
279 
280   llvm::SmallVector<std::string> featuresVec;
281   for (auto &implicitFeatureItem : implicitFeaturesMap) {
282     featuresVec.push_back((llvm::Twine(implicitFeatureItem.second ? "+" : "-") +
283                            implicitFeatureItem.first().str())
284                               .str());
285   }
286   llvm::sort(featuresVec);
287   return llvm::join(featuresVec, ",");
288 }
289 
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`
293 static std::string
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;
300   bool ptxVer = false;
301 
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"))
308       ptxVer = true;
309   }
310 
311   // Set the default PTX version to `ptx61` if none was provided.
312   // TODO: set the default PTX version based on the chip.
313   if (!ptxVer)
314     implicitFeaturesMap["ptx61"] = true;
315 
316   // Set the compute capability.
317   implicitFeaturesMap[cpu.str()] = true;
318 
319   llvm::SmallVector<std::string> featuresVec;
320   for (auto &implicitFeatureItem : implicitFeaturesMap) {
321     featuresVec.push_back((llvm::Twine(implicitFeatureItem.second ? "+" : "-") +
322                            implicitFeatureItem.first().str())
323                               .str());
324   }
325   llvm::sort(featuresVec);
326   return llvm::join(featuresVec, ",");
327 }
328 
329 std::string CompilerInstance::getTargetFeatures() {
330   const TargetOptions &targetOpts = getInvocation().getTargetOpts();
331   const llvm::Triple triple(targetOpts.triple);
332 
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(),
341                                                       targetOpts, triple);
342   } else if (triple.isNVPTX()) {
343     return getExplicitAndImplicitNVPTXTargetFeatures(getDiagnostics(),
344                                                      targetOpts, triple);
345   }
346   return llvm::join(targetOpts.featuresAsWritten.begin(),
347                     targetOpts.featuresAsWritten.end(), ",");
348 }
349 
350 bool CompilerInstance::setUpTargetMachine() {
351   const TargetOptions &targetOpts = getInvocation().getTargetOpts();
352   const std::string &theTriple = targetOpts.triple;
353 
354   // Create `Target`
355   std::string error;
356   const llvm::Target *theTarget =
357       llvm::TargetRegistry::lookupTarget(theTriple, error);
358   if (!theTarget) {
359     getDiagnostics().Report(clang::diag::err_fe_unable_to_create_target)
360         << error;
361     return false;
362   }
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);
371 
372   llvm::TargetOptions tOpts = llvm::TargetOptions();
373   tOpts.EnableAIXExtendedAltivecABI = targetOpts.EnableAIXExtendedAltivecABI;
374 
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);
386     }
387   }
388   return true;
389 }
390