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
31using namespace Fortran::frontend;
32
33CompilerInstance::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
43CompilerInstance::~CompilerInstance() {
44 assert(outputFiles.empty() && "Still output files in flight?");
45}
46
47void CompilerInstance::setInvocation(
48 std::shared_ptr<CompilerInvocation> value) {
49 invocation = std::move(value);
50}
51
52void CompilerInstance::setSemaOutputStream(raw_ostream &value) {
53 ownedSemaOutputStream.release();
54 semaOutputStream = &value;
55}
56
57void 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.
70static 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
89std::unique_ptr<llvm::raw_pwrite_stream>
90CompilerInstance::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
116llvm::Expected<std::unique_ptr<llvm::raw_pwrite_stream>>
117CompilerInstance::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
141void 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
149bool 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
182void CompilerInstance::createDiagnostics(clang::DiagnosticConsumer *client,
183 bool shouldOwnClient) {
184 diagnostics =
185 createDiagnostics(&getDiagnosticOpts(), client, shouldOwnClient);
186}
187
188clang::IntrusiveRefCntPtr<clang::DiagnosticsEngine>
189CompilerInstance::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
209static std::string
210getExplicitAndImplicitAMDGPUTargetFeatures(clang::DiagnosticsEngine &diags,
211 const TargetOptions &targetOpts,
212 const llvm::Triple triple) {
213 llvm::StringRef cpu = targetOpts.cpu;
214 llvm::StringMap<bool> implicitFeaturesMap;
215 std::string errorMsg;
216 // Get the set of implicit target features
217 llvm::AMDGPU::fillAMDGPUFeatureMap(GPU: cpu, T: triple, Features&: implicitFeaturesMap);
218
219 // Add target features specified by the user
220 for (auto &userFeature : targetOpts.featuresAsWritten) {
221 std::string userKeyString = userFeature.substr(1);
222 implicitFeaturesMap[userKeyString] = (userFeature[0] == '+');
223 }
224
225 if (!llvm::AMDGPU::insertWaveSizeFeature(GPU: cpu, T: triple, Features&: implicitFeaturesMap,
226 ErrorMsg&: errorMsg)) {
227 unsigned diagID = diags.getCustomDiagID(L: clang::DiagnosticsEngine::Error,
228 FormatString: "Unsupported feature ID: %0");
229 diags.Report(DiagID: diagID) << errorMsg.data();
230 return std::string();
231 }
232
233 llvm::SmallVector<std::string> featuresVec;
234 for (auto &implicitFeatureItem : implicitFeaturesMap) {
235 featuresVec.push_back(Elt: (llvm::Twine(implicitFeatureItem.second ? "+" : "-") +
236 implicitFeatureItem.first().str())
237 .str());
238 }
239 llvm::sort(C&: featuresVec);
240 return llvm::join(R&: featuresVec, Separator: ",");
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`
246static std::string
247getExplicitAndImplicitNVPTXTargetFeatures(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(Elt: (llvm::Twine(implicitFeatureItem.second ? "+" : "-") +
275 implicitFeatureItem.first().str())
276 .str());
277 }
278 llvm::sort(C&: featuresVec);
279 return llvm::join(R&: featuresVec, Separator: ",");
280}
281
282std::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
303bool 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
317 // Create `TargetMachine`
318 const auto &CGOpts = getInvocation().getCodeGenOpts();
319 std::optional<llvm::CodeGenOptLevel> OptLevelOrNone =
320 llvm::CodeGenOpt::getLevel(CGOpts.OptimizationLevel);
321 assert(OptLevelOrNone && "Invalid optimization level!");
322 llvm::CodeGenOptLevel OptLevel = *OptLevelOrNone;
323 std::string featuresStr = getTargetFeatures();
324 targetMachine.reset(theTarget->createTargetMachine(
325 theTriple, /*CPU=*/targetOpts.cpu,
326 /*Features=*/featuresStr, llvm::TargetOptions(),
327 /*Reloc::Model=*/CGOpts.getRelocationModel(),
328 /*CodeModel::Model=*/std::nullopt, OptLevel));
329 assert(targetMachine && "Failed to create TargetMachine");
330 return true;
331}
332

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