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