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 "llvm/Support/Errc.h"
21 #include "llvm/Support/Error.h"
22 #include "llvm/Support/FileSystem.h"
23 #include "llvm/Support/Path.h"
24 #include "llvm/Support/raw_ostream.h"
25 
26 using namespace Fortran::frontend;
27 
CompilerInstance()28 CompilerInstance::CompilerInstance()
29     : invocation(new CompilerInvocation()),
30       allSources(new Fortran::parser::AllSources()),
31       allCookedSources(new Fortran::parser::AllCookedSources(*allSources)),
32       parsing(new Fortran::parser::Parsing(*allCookedSources)) {
33   // TODO: This is a good default during development, but ultimately we should
34   // give the user the opportunity to specify this.
35   allSources->set_encoding(Fortran::parser::Encoding::UTF_8);
36 }
37 
~CompilerInstance()38 CompilerInstance::~CompilerInstance() {
39   assert(outputFiles.empty() && "Still output files in flight?");
40 }
41 
setInvocation(std::shared_ptr<CompilerInvocation> value)42 void CompilerInstance::setInvocation(
43     std::shared_ptr<CompilerInvocation> value) {
44   invocation = std::move(value);
45 }
46 
setSemaOutputStream(raw_ostream & value)47 void CompilerInstance::setSemaOutputStream(raw_ostream &value) {
48   ownedSemaOutputStream.release();
49   semaOutputStream = &value;
50 }
51 
setSemaOutputStream(std::unique_ptr<raw_ostream> value)52 void CompilerInstance::setSemaOutputStream(std::unique_ptr<raw_ostream> value) {
53   ownedSemaOutputStream.swap(value);
54   semaOutputStream = ownedSemaOutputStream.get();
55 }
56 
57 // Helper method to generate the path of the output file. The following logic
58 // applies:
59 // 1. If the user specifies the output file via `-o`, then use that (i.e.
60 //    the outputFilename parameter).
61 // 2. If the user does not specify the name of the output file, derive it from
62 //    the input file (i.e. inputFilename + extension)
63 // 3. If the output file is not specified and the input file is `-`, then set
64 //    the output file to `-` as well.
getOutputFilePath(llvm::StringRef outputFilename,llvm::StringRef inputFilename,llvm::StringRef extension)65 static std::string getOutputFilePath(llvm::StringRef outputFilename,
66                                      llvm::StringRef inputFilename,
67                                      llvm::StringRef extension) {
68 
69   // Output filename _is_ specified. Just use that.
70   if (!outputFilename.empty())
71     return std::string(outputFilename);
72 
73   // Output filename _is not_ specified. Derive it from the input file name.
74   std::string outFile = "-";
75   if (!extension.empty() && (inputFilename != "-")) {
76     llvm::SmallString<128> path(inputFilename);
77     llvm::sys::path::replace_extension(path, extension);
78     outFile = std::string(path.str());
79   }
80 
81   return outFile;
82 }
83 
84 std::unique_ptr<llvm::raw_pwrite_stream>
createDefaultOutputFile(bool binary,llvm::StringRef baseName,llvm::StringRef extension)85 CompilerInstance::createDefaultOutputFile(bool binary, llvm::StringRef baseName,
86                                           llvm::StringRef extension) {
87 
88   // Get the path of the output file
89   std::string outputFilePath =
90       getOutputFilePath(getFrontendOpts().outputFile, baseName, extension);
91 
92   // Create the output file
93   llvm::Expected<std::unique_ptr<llvm::raw_pwrite_stream>> os =
94       createOutputFileImpl(outputFilePath, binary);
95 
96   // If successful, add the file to the list of tracked output files and
97   // return.
98   if (os) {
99     outputFiles.emplace_back(OutputFile(outputFilePath));
100     return std::move(*os);
101   }
102 
103   // If unsuccessful, issue an error and return Null
104   unsigned diagID = getDiagnostics().getCustomDiagID(
105       clang::DiagnosticsEngine::Error, "unable to open output file '%0': '%1'");
106   getDiagnostics().Report(diagID)
107       << outputFilePath << llvm::errorToErrorCode(os.takeError()).message();
108   return nullptr;
109 }
110 
111 llvm::Expected<std::unique_ptr<llvm::raw_pwrite_stream>>
createOutputFileImpl(llvm::StringRef outputFilePath,bool binary)112 CompilerInstance::createOutputFileImpl(llvm::StringRef outputFilePath,
113                                        bool binary) {
114 
115   // Creates the file descriptor for the output file
116   std::unique_ptr<llvm::raw_fd_ostream> os;
117 
118   std::error_code error;
119   os.reset(new llvm::raw_fd_ostream(outputFilePath, error,
120       (binary ? llvm::sys::fs::OF_None : llvm::sys::fs::OF_TextWithCRLF)));
121   if (error) {
122     return llvm::errorCodeToError(error);
123   }
124 
125   // For seekable streams, just return the stream corresponding to the output
126   // file.
127   if (!binary || os->supportsSeeking())
128     return std::move(os);
129 
130   // For non-seekable streams, we need to wrap the output stream into something
131   // that supports 'pwrite' and takes care of the ownership for us.
132   return std::make_unique<llvm::buffer_unique_ostream>(std::move(os));
133 }
134 
clearOutputFiles(bool eraseFiles)135 void CompilerInstance::clearOutputFiles(bool eraseFiles) {
136   for (OutputFile &of : outputFiles)
137     if (!of.filename.empty() && eraseFiles)
138       llvm::sys::fs::remove(of.filename);
139 
140   outputFiles.clear();
141 }
142 
executeAction(FrontendAction & act)143 bool CompilerInstance::executeAction(FrontendAction &act) {
144   auto &invoc = this->getInvocation();
145 
146   // Set some sane defaults for the frontend.
147   invoc.setDefaultFortranOpts();
148   // Update the fortran options based on user-based input.
149   invoc.setFortranOpts();
150   // Set the encoding to read all input files in based on user input.
151   allSources->set_encoding(invoc.getFortranOpts().encoding);
152   // Create the semantics context and set semantic options.
153   invoc.setSemanticsOpts(*this->allCookedSources);
154 
155   // Run the frontend action `act` for every input file.
156   for (const FrontendInputFile &fif : getFrontendOpts().inputs) {
157     if (act.beginSourceFile(*this, fif)) {
158       if (llvm::Error err = act.execute()) {
159         consumeError(std::move(err));
160       }
161       act.endSourceFile();
162     }
163   }
164   return !getDiagnostics().getClient()->getNumErrors();
165 }
166 
createDiagnostics(clang::DiagnosticConsumer * client,bool shouldOwnClient)167 void CompilerInstance::createDiagnostics(clang::DiagnosticConsumer *client,
168                                          bool shouldOwnClient) {
169   diagnostics =
170       createDiagnostics(&getDiagnosticOpts(), client, shouldOwnClient);
171 }
172 
173 clang::IntrusiveRefCntPtr<clang::DiagnosticsEngine>
createDiagnostics(clang::DiagnosticOptions * opts,clang::DiagnosticConsumer * client,bool shouldOwnClient)174 CompilerInstance::createDiagnostics(clang::DiagnosticOptions *opts,
175                                     clang::DiagnosticConsumer *client,
176                                     bool shouldOwnClient) {
177   clang::IntrusiveRefCntPtr<clang::DiagnosticIDs> diagID(
178       new clang::DiagnosticIDs());
179   clang::IntrusiveRefCntPtr<clang::DiagnosticsEngine> diags(
180       new clang::DiagnosticsEngine(diagID, opts));
181 
182   // Create the diagnostic client for reporting errors or for
183   // implementing -verify.
184   if (client) {
185     diags->setClient(client, shouldOwnClient);
186   } else {
187     diags->setClient(new TextDiagnosticPrinter(llvm::errs(), opts));
188   }
189   return diags;
190 }
191