xref: /llvm-project/flang/lib/Frontend/CompilerInstance.cpp (revision fd21d1e198e381a2b9e7af1701044462b2d386cd)
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   // For seekable streams, just return the stream corresponding to the output
121   // file.
122   if (!binary || os->supportsSeeking())
123     return std::move(os);
124 
125   // For non-seekable streams, we need to wrap the output stream into something
126   // that supports 'pwrite' and takes care of the ownership for us.
127   return std::make_unique<llvm::buffer_unique_ostream>(std::move(os));
128 }
129 
130 void CompilerInstance::ClearOutputFiles(bool eraseFiles) {
131   for (OutputFile &of : outputFiles_)
132     if (!of.filename_.empty() && eraseFiles)
133       llvm::sys::fs::remove(of.filename_);
134 
135   outputFiles_.clear();
136 }
137 
138 bool CompilerInstance::ExecuteAction(FrontendAction &act) {
139   auto &invoc = this->invocation();
140 
141   // Set some sane defaults for the frontend.
142   invoc.SetDefaultFortranOpts();
143   // Update the fortran options based on user-based input.
144   invoc.setFortranOpts();
145   // Set the encoding to read all input files in based on user input.
146   allSources_->set_encoding(invoc.fortranOpts().encoding);
147   // Create the semantics context and set semantic options.
148   invoc.setSemanticsOpts(*this->allCookedSources_);
149 
150   // Run the frontend action `act` for every input file.
151   for (const FrontendInputFile &fif : frontendOpts().inputs) {
152     if (act.BeginSourceFile(*this, fif)) {
153       if (llvm::Error err = act.Execute()) {
154         consumeError(std::move(err));
155       }
156       act.EndSourceFile();
157     }
158   }
159   return !diagnostics().getClient()->getNumErrors();
160 }
161 
162 void CompilerInstance::CreateDiagnostics(
163     clang::DiagnosticConsumer *client, bool shouldOwnClient) {
164   diagnostics_ =
165       CreateDiagnostics(&GetDiagnosticOpts(), client, shouldOwnClient);
166 }
167 
168 clang::IntrusiveRefCntPtr<clang::DiagnosticsEngine>
169 CompilerInstance::CreateDiagnostics(clang::DiagnosticOptions *opts,
170     clang::DiagnosticConsumer *client, bool shouldOwnClient) {
171   clang::IntrusiveRefCntPtr<clang::DiagnosticIDs> diagID(
172       new clang::DiagnosticIDs());
173   clang::IntrusiveRefCntPtr<clang::DiagnosticsEngine> diags(
174       new clang::DiagnosticsEngine(diagID, opts));
175 
176   // Create the diagnostic client for reporting errors or for
177   // implementing -verify.
178   if (client) {
179     diags->setClient(client, shouldOwnClient);
180   } else {
181     diags->setClient(new TextDiagnosticPrinter(llvm::errs(), opts));
182   }
183   return diags;
184 }
185