xref: /llvm-project/flang/lib/Frontend/CompilerInstance.cpp (revision 8e14c6c172b122203f46a9ad114d51c74535cbb7)
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 "clang/Basic/DiagnosticFrontend.h"
21 #include "llvm/ADT/StringExtras.h"
22 #include "llvm/MC/TargetRegistry.h"
23 #include "llvm/Support/Errc.h"
24 #include "llvm/Support/Error.h"
25 #include "llvm/Support/FileSystem.h"
26 #include "llvm/Support/Path.h"
27 #include "llvm/Support/raw_ostream.h"
28 #include "llvm/TargetParser/TargetParser.h"
29 #include "llvm/TargetParser/Triple.h"
30 
31 using namespace Fortran::frontend;
32 
33 CompilerInstance::CompilerInstance()
34     : invocation(new CompilerInvocation()),
35       allSources(new Fortran::parser::AllSources()),
36       allCookedSources(new Fortran::parser::AllCookedSources(*allSources)),
37       parsing(new Fortran::parser::Parsing(*allCookedSources)) {
38   // TODO: This is a good default during development, but ultimately we should
39   // give the user the opportunity to specify this.
40   allSources->set_encoding(Fortran::parser::Encoding::UTF_8);
41 }
42 
43 CompilerInstance::~CompilerInstance() {
44   assert(outputFiles.empty() && "Still output files in flight?");
45 }
46 
47 void CompilerInstance::setInvocation(
48     std::shared_ptr<CompilerInvocation> value) {
49   invocation = std::move(value);
50 }
51 
52 void CompilerInstance::setSemaOutputStream(raw_ostream &value) {
53   ownedSemaOutputStream.release();
54   semaOutputStream = &value;
55 }
56 
57 void CompilerInstance::setSemaOutputStream(std::unique_ptr<raw_ostream> value) {
58   ownedSemaOutputStream.swap(value);
59   semaOutputStream = ownedSemaOutputStream.get();
60 }
61 
62 // Helper method to generate the path of the output file. The following logic
63 // applies:
64 // 1. If the user specifies the output file via `-o`, then use that (i.e.
65 //    the outputFilename parameter).
66 // 2. If the user does not specify the name of the output file, derive it from
67 //    the input file (i.e. inputFilename + extension)
68 // 3. If the output file is not specified and the input file is `-`, then set
69 //    the output file to `-` as well.
70 static std::string getOutputFilePath(llvm::StringRef outputFilename,
71                                      llvm::StringRef inputFilename,
72                                      llvm::StringRef extension) {
73 
74   // Output filename _is_ specified. Just use that.
75   if (!outputFilename.empty())
76     return std::string(outputFilename);
77 
78   // Output filename _is not_ specified. Derive it from the input file name.
79   std::string outFile = "-";
80   if (!extension.empty() && (inputFilename != "-")) {
81     llvm::SmallString<128> path(inputFilename);
82     llvm::sys::path::replace_extension(path, extension);
83     outFile = std::string(path);
84   }
85 
86   return outFile;
87 }
88 
89 std::unique_ptr<llvm::raw_pwrite_stream>
90 CompilerInstance::createDefaultOutputFile(bool binary, llvm::StringRef baseName,
91                                           llvm::StringRef extension) {
92 
93   // Get the path of the output file
94   std::string outputFilePath =
95       getOutputFilePath(getFrontendOpts().outputFile, baseName, extension);
96 
97   // Create the output file
98   llvm::Expected<std::unique_ptr<llvm::raw_pwrite_stream>> os =
99       createOutputFileImpl(outputFilePath, binary);
100 
101   // If successful, add the file to the list of tracked output files and
102   // return.
103   if (os) {
104     outputFiles.emplace_back(OutputFile(outputFilePath));
105     return std::move(*os);
106   }
107 
108   // If unsuccessful, issue an error and return Null
109   unsigned diagID = getDiagnostics().getCustomDiagID(
110       clang::DiagnosticsEngine::Error, "unable to open output file '%0': '%1'");
111   getDiagnostics().Report(diagID)
112       << outputFilePath << llvm::errorToErrorCode(os.takeError()).message();
113   return nullptr;
114 }
115 
116 llvm::Expected<std::unique_ptr<llvm::raw_pwrite_stream>>
117 CompilerInstance::createOutputFileImpl(llvm::StringRef outputFilePath,
118                                        bool binary) {
119 
120   // Creates the file descriptor for the output file
121   std::unique_ptr<llvm::raw_fd_ostream> os;
122 
123   std::error_code error;
124   os.reset(new llvm::raw_fd_ostream(
125       outputFilePath, error,
126       (binary ? llvm::sys::fs::OF_None : llvm::sys::fs::OF_TextWithCRLF)));
127   if (error) {
128     return llvm::errorCodeToError(error);
129   }
130 
131   // For seekable streams, just return the stream corresponding to the output
132   // file.
133   if (!binary || os->supportsSeeking())
134     return std::move(os);
135 
136   // For non-seekable streams, we need to wrap the output stream into something
137   // that supports 'pwrite' and takes care of the ownership for us.
138   return std::make_unique<llvm::buffer_unique_ostream>(std::move(os));
139 }
140 
141 void CompilerInstance::clearOutputFiles(bool eraseFiles) {
142   for (OutputFile &of : outputFiles)
143     if (!of.filename.empty() && eraseFiles)
144       llvm::sys::fs::remove(of.filename);
145 
146   outputFiles.clear();
147 }
148 
149 bool CompilerInstance::executeAction(FrontendAction &act) {
150   auto &invoc = this->getInvocation();
151 
152   llvm::Triple targetTriple{llvm::Triple(invoc.getTargetOpts().triple)};
153   if (targetTriple.getArch() == llvm::Triple::ArchType::x86_64) {
154     invoc.getDefaultKinds().set_quadPrecisionKind(10);
155   }
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   // Run the frontend action `act` for every input file.
171   for (const FrontendInputFile &fif : getFrontendOpts().inputs) {
172     if (act.beginSourceFile(*this, fif)) {
173       if (llvm::Error err = act.execute()) {
174         consumeError(std::move(err));
175       }
176       act.endSourceFile();
177     }
178   }
179   return !getDiagnostics().getClient()->getNumErrors();
180 }
181 
182 void CompilerInstance::createDiagnostics(clang::DiagnosticConsumer *client,
183                                          bool shouldOwnClient) {
184   diagnostics =
185       createDiagnostics(&getDiagnosticOpts(), client, shouldOwnClient);
186 }
187 
188 clang::IntrusiveRefCntPtr<clang::DiagnosticsEngine>
189 CompilerInstance::createDiagnostics(clang::DiagnosticOptions *opts,
190                                     clang::DiagnosticConsumer *client,
191                                     bool shouldOwnClient) {
192   clang::IntrusiveRefCntPtr<clang::DiagnosticIDs> diagID(
193       new clang::DiagnosticIDs());
194   clang::IntrusiveRefCntPtr<clang::DiagnosticsEngine> diags(
195       new clang::DiagnosticsEngine(diagID, opts));
196 
197   // Create the diagnostic client for reporting errors or for
198   // implementing -verify.
199   if (client) {
200     diags->setClient(client, shouldOwnClient);
201   } else {
202     diags->setClient(new TextDiagnosticPrinter(llvm::errs(), opts));
203   }
204   return diags;
205 }
206 
207 // Get feature string which represents combined explicit target features
208 // for AMD GPU and the target features specified by the user
209 static std::string
210 getExplicitAndImplicitAMDGPUTargetFeatures(clang::DiagnosticsEngine &diags,
211                                            const TargetOptions &targetOpts,
212                                            const llvm::Triple triple) {
213   llvm::StringRef cpu = targetOpts.cpu;
214   llvm::StringMap<bool> implicitFeaturesMap;
215   // Get the set of implicit target features
216   llvm::AMDGPU::fillAMDGPUFeatureMap(cpu, triple, implicitFeaturesMap);
217 
218   // Add target features specified by the user
219   for (auto &userFeature : targetOpts.featuresAsWritten) {
220     std::string userKeyString = userFeature.substr(1);
221     implicitFeaturesMap[userKeyString] = (userFeature[0] == '+');
222   }
223 
224   auto HasError =
225       llvm::AMDGPU::insertWaveSizeFeature(cpu, triple, implicitFeaturesMap);
226   if (HasError.first) {
227     unsigned diagID = diags.getCustomDiagID(clang::DiagnosticsEngine::Error,
228                                             "Unsupported feature ID: %0");
229     diags.Report(diagID) << HasError.second;
230     return std::string();
231   }
232 
233   llvm::SmallVector<std::string> featuresVec;
234   for (auto &implicitFeatureItem : implicitFeaturesMap) {
235     featuresVec.push_back((llvm::Twine(implicitFeatureItem.second ? "+" : "-") +
236                            implicitFeatureItem.first().str())
237                               .str());
238   }
239   llvm::sort(featuresVec);
240   return llvm::join(featuresVec, ",");
241 }
242 
243 // Get feature string which represents combined explicit target features
244 // for NVPTX and the target features specified by the user/
245 // TODO: Have a more robust target conf like `clang/lib/Basic/Targets/NVPTX.cpp`
246 static std::string
247 getExplicitAndImplicitNVPTXTargetFeatures(clang::DiagnosticsEngine &diags,
248                                           const TargetOptions &targetOpts,
249                                           const llvm::Triple triple) {
250   llvm::StringRef cpu = targetOpts.cpu;
251   llvm::StringMap<bool> implicitFeaturesMap;
252   std::string errorMsg;
253   bool ptxVer = false;
254 
255   // Add target features specified by the user
256   for (auto &userFeature : targetOpts.featuresAsWritten) {
257     llvm::StringRef userKeyString(llvm::StringRef(userFeature).drop_front(1));
258     implicitFeaturesMap[userKeyString.str()] = (userFeature[0] == '+');
259     // Check if the user provided a PTX version
260     if (userKeyString.starts_with("ptx"))
261       ptxVer = true;
262   }
263 
264   // Set the default PTX version to `ptx61` if none was provided.
265   // TODO: set the default PTX version based on the chip.
266   if (!ptxVer)
267     implicitFeaturesMap["ptx61"] = true;
268 
269   // Set the compute capability.
270   implicitFeaturesMap[cpu.str()] = true;
271 
272   llvm::SmallVector<std::string> featuresVec;
273   for (auto &implicitFeatureItem : implicitFeaturesMap) {
274     featuresVec.push_back((llvm::Twine(implicitFeatureItem.second ? "+" : "-") +
275                            implicitFeatureItem.first().str())
276                               .str());
277   }
278   llvm::sort(featuresVec);
279   return llvm::join(featuresVec, ",");
280 }
281 
282 std::string CompilerInstance::getTargetFeatures() {
283   const TargetOptions &targetOpts = getInvocation().getTargetOpts();
284   const llvm::Triple triple(targetOpts.triple);
285 
286   // Clang does not append all target features to the clang -cc1 invocation.
287   // Some target features are parsed implicitly by clang::TargetInfo child
288   // class. Clang::TargetInfo classes are the basic clang classes and
289   // they cannot be reused by Flang.
290   // That's why we need to extract implicit target features and add
291   // them to the target features specified by the user
292   if (triple.isAMDGPU()) {
293     return getExplicitAndImplicitAMDGPUTargetFeatures(getDiagnostics(),
294                                                       targetOpts, triple);
295   } else if (triple.isNVPTX()) {
296     return getExplicitAndImplicitNVPTXTargetFeatures(getDiagnostics(),
297                                                      targetOpts, triple);
298   }
299   return llvm::join(targetOpts.featuresAsWritten.begin(),
300                     targetOpts.featuresAsWritten.end(), ",");
301 }
302 
303 bool CompilerInstance::setUpTargetMachine() {
304   const TargetOptions &targetOpts = getInvocation().getTargetOpts();
305   const std::string &theTriple = targetOpts.triple;
306 
307   // Create `Target`
308   std::string error;
309   const llvm::Target *theTarget =
310       llvm::TargetRegistry::lookupTarget(theTriple, error);
311   if (!theTarget) {
312     getDiagnostics().Report(clang::diag::err_fe_unable_to_create_target)
313         << error;
314     return false;
315   }
316   // Create `TargetMachine`
317   const auto &CGOpts = getInvocation().getCodeGenOpts();
318   std::optional<llvm::CodeGenOptLevel> OptLevelOrNone =
319       llvm::CodeGenOpt::getLevel(CGOpts.OptimizationLevel);
320   assert(OptLevelOrNone && "Invalid optimization level!");
321   llvm::CodeGenOptLevel OptLevel = *OptLevelOrNone;
322   std::string featuresStr = getTargetFeatures();
323   std::optional<llvm::CodeModel::Model> cm = getCodeModel(CGOpts.CodeModel);
324 
325   llvm::TargetOptions tOpts = llvm::TargetOptions();
326   tOpts.EnableAIXExtendedAltivecABI = targetOpts.EnableAIXExtendedAltivecABI;
327 
328   targetMachine.reset(theTarget->createTargetMachine(
329       theTriple, /*CPU=*/targetOpts.cpu,
330       /*Features=*/featuresStr, /*Options=*/tOpts,
331       /*Reloc::Model=*/CGOpts.getRelocationModel(),
332       /*CodeModel::Model=*/cm, OptLevel));
333   assert(targetMachine && "Failed to create TargetMachine");
334   if (cm.has_value()) {
335     const llvm::Triple triple(theTriple);
336     if ((cm == llvm::CodeModel::Medium || cm == llvm::CodeModel::Large) &&
337         triple.getArch() == llvm::Triple::x86_64) {
338       targetMachine->setLargeDataThreshold(CGOpts.LargeDataThreshold);
339     }
340   }
341   return true;
342 }
343