1 | //===--- FrontendAction.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/FrontendAction.h" |
14 | #include "flang/Frontend/CompilerInstance.h" |
15 | #include "flang/Frontend/FrontendActions.h" |
16 | #include "flang/Frontend/FrontendOptions.h" |
17 | #include "flang/Frontend/FrontendPluginRegistry.h" |
18 | #include "clang/Basic/DiagnosticFrontend.h" |
19 | #include "llvm/Support/Errc.h" |
20 | #include "llvm/Support/VirtualFileSystem.h" |
21 | |
22 | using namespace Fortran::frontend; |
23 | |
24 | LLVM_INSTANTIATE_REGISTRY(FrontendPluginRegistry) |
25 | |
26 | void FrontendAction::setCurrentInput(const FrontendInputFile &input) { |
27 | this->currentInput = input; |
28 | } |
29 | |
30 | // Call this method if BeginSourceFile fails. |
31 | // Deallocate compiler instance, input and output descriptors |
32 | static void beginSourceFileCleanUp(FrontendAction &fa, CompilerInstance &ci) { |
33 | ci.clearOutputFiles(/*EraseFiles=*/true); |
34 | fa.setCurrentInput(FrontendInputFile()); |
35 | fa.setInstance(nullptr); |
36 | } |
37 | |
38 | bool FrontendAction::beginSourceFile(CompilerInstance &ci, |
39 | const FrontendInputFile &realInput) { |
40 | |
41 | FrontendInputFile input(realInput); |
42 | |
43 | // Return immediately if the input file does not exist or is not a file. Note |
44 | // that we cannot check this for input from stdin. |
45 | if (input.getFile() != "-" ) { |
46 | if (!llvm::sys::fs::is_regular_file(input.getFile())) { |
47 | // Create an diagnostic ID to report |
48 | unsigned diagID; |
49 | if (llvm::vfs::getRealFileSystem()->exists(input.getFile())) { |
50 | ci.getDiagnostics().Report(clang::diag::err_fe_error_reading) |
51 | << input.getFile() << "not a regular file" ; |
52 | diagID = ci.getDiagnostics().getCustomDiagID( |
53 | clang::DiagnosticsEngine::Error, "%0 is not a regular file" ); |
54 | } else { |
55 | diagID = ci.getDiagnostics().getCustomDiagID( |
56 | clang::DiagnosticsEngine::Error, "%0 does not exist" ); |
57 | } |
58 | |
59 | // Report the diagnostic and return |
60 | ci.getDiagnostics().Report(diagID) << input.getFile(); |
61 | beginSourceFileCleanUp(*this, ci); |
62 | return false; |
63 | } |
64 | } |
65 | |
66 | assert(!instance && "Already processing a source file!" ); |
67 | assert(!realInput.isEmpty() && "Unexpected empty filename!" ); |
68 | setCurrentInput(realInput); |
69 | setInstance(&ci); |
70 | |
71 | if (!ci.hasAllSources()) { |
72 | beginSourceFileCleanUp(*this, ci); |
73 | return false; |
74 | } |
75 | |
76 | auto &invoc = ci.getInvocation(); |
77 | |
78 | // Include command-line and predefined preprocessor macros. Use either: |
79 | // * `-cpp/-nocpp`, or |
80 | // * the file extension (if the user didn't express any preference) |
81 | // to decide whether to include them or not. |
82 | if ((invoc.getPreprocessorOpts().macrosFlag == PPMacrosFlag::Include) || |
83 | (invoc.getPreprocessorOpts().macrosFlag == PPMacrosFlag::Unknown && |
84 | getCurrentInput().getMustBePreprocessed())) { |
85 | invoc.setDefaultPredefinitions(); |
86 | invoc.collectMacroDefinitions(); |
87 | } |
88 | |
89 | if (!invoc.getFortranOpts().features.IsEnabled( |
90 | Fortran::common::LanguageFeature::CUDA)) { |
91 | // Enable CUDA Fortran if source file is *.cuf/*.CUF and not already |
92 | // enabled. |
93 | invoc.getFortranOpts().features.Enable( |
94 | Fortran::common::LanguageFeature::CUDA, |
95 | getCurrentInput().getIsCUDAFortran()); |
96 | } |
97 | |
98 | // Decide between fixed and free form (if the user didn't express any |
99 | // preference, use the file extension to decide) |
100 | if (invoc.getFrontendOpts().fortranForm == FortranForm::Unknown) { |
101 | invoc.getFortranOpts().isFixedForm = getCurrentInput().getIsFixedForm(); |
102 | } |
103 | |
104 | if (!beginSourceFileAction()) { |
105 | beginSourceFileCleanUp(*this, ci); |
106 | return false; |
107 | } |
108 | |
109 | return true; |
110 | } |
111 | |
112 | bool FrontendAction::shouldEraseOutputFiles() { |
113 | return getInstance().getDiagnostics().hasErrorOccurred(); |
114 | } |
115 | |
116 | llvm::Error FrontendAction::execute() { |
117 | executeAction(); |
118 | |
119 | return llvm::Error::success(); |
120 | } |
121 | |
122 | void FrontendAction::endSourceFile() { |
123 | CompilerInstance &ci = getInstance(); |
124 | |
125 | // Cleanup the output streams, and erase the output files if instructed by the |
126 | // FrontendAction. |
127 | ci.clearOutputFiles(/*EraseFiles=*/shouldEraseOutputFiles()); |
128 | |
129 | setInstance(nullptr); |
130 | setCurrentInput(FrontendInputFile()); |
131 | } |
132 | |
133 | bool FrontendAction::runPrescan() { |
134 | CompilerInstance &ci = this->getInstance(); |
135 | std::string currentInputPath{getCurrentFileOrBufferName()}; |
136 | Fortran::parser::Options parserOptions = ci.getInvocation().getFortranOpts(); |
137 | |
138 | if (ci.getInvocation().getFrontendOpts().fortranForm == |
139 | FortranForm::Unknown) { |
140 | // Switch between fixed and free form format based on the input file |
141 | // extension. |
142 | // |
143 | // Ideally we should have all Fortran options set before entering this |
144 | // method (i.e. before processing any specific input files). However, we |
145 | // can't decide between fixed and free form based on the file extension |
146 | // earlier than this. |
147 | parserOptions.isFixedForm = getCurrentInput().getIsFixedForm(); |
148 | } |
149 | |
150 | // Prescan. In case of failure, report and return. |
151 | ci.getParsing().Prescan(currentInputPath, parserOptions); |
152 | |
153 | return !reportFatalScanningErrors(); |
154 | } |
155 | |
156 | bool FrontendAction::runParse() { |
157 | CompilerInstance &ci = this->getInstance(); |
158 | |
159 | // Parse. In case of failure, report and return. |
160 | ci.getParsing().Parse(llvm::outs()); |
161 | |
162 | if (reportFatalParsingErrors()) { |
163 | return false; |
164 | } |
165 | |
166 | // Report the diagnostics from getParsing |
167 | ci.getParsing().messages().Emit(llvm::errs(), ci.getAllCookedSources()); |
168 | |
169 | return true; |
170 | } |
171 | |
172 | bool FrontendAction::runSemanticChecks() { |
173 | CompilerInstance &ci = this->getInstance(); |
174 | std::optional<parser::Program> &parseTree{ci.getParsing().parseTree()}; |
175 | assert(parseTree && "Cannot run semantic checks without a parse tree!" ); |
176 | |
177 | // Prepare semantics |
178 | ci.setSemantics(std::make_unique<Fortran::semantics::Semantics>( |
179 | ci.getSemanticsContext(), *parseTree, |
180 | ci.getInvocation().getDebugModuleDir())); |
181 | auto &semantics = ci.getSemantics(); |
182 | |
183 | // Run semantic checks |
184 | semantics.Perform(); |
185 | |
186 | if (reportFatalSemanticErrors()) { |
187 | return false; |
188 | } |
189 | |
190 | // Report the diagnostics from the semantic checks |
191 | semantics.EmitMessages(ci.getSemaOutputStream()); |
192 | |
193 | return true; |
194 | } |
195 | |
196 | bool FrontendAction::generateRtTypeTables() { |
197 | getInstance().setRtTyTables( |
198 | std::make_unique<Fortran::semantics::RuntimeDerivedTypeTables>( |
199 | BuildRuntimeDerivedTypeTables(getInstance().getSemanticsContext()))); |
200 | |
201 | // The runtime derived type information table builder may find additional |
202 | // semantic errors. Report them. |
203 | if (reportFatalSemanticErrors()) { |
204 | return false; |
205 | } |
206 | |
207 | return true; |
208 | } |
209 | |
210 | template <unsigned N> |
211 | bool FrontendAction::reportFatalErrors(const char (&message)[N]) { |
212 | if (!instance->getParsing().messages().empty() && |
213 | (instance->getInvocation().getWarnAsErr() || |
214 | instance->getParsing().messages().AnyFatalError())) { |
215 | const unsigned diagID = instance->getDiagnostics().getCustomDiagID( |
216 | clang::DiagnosticsEngine::Error, message); |
217 | instance->getDiagnostics().Report(diagID) << getCurrentFileOrBufferName(); |
218 | instance->getParsing().messages().Emit(llvm::errs(), |
219 | instance->getAllCookedSources()); |
220 | return true; |
221 | } |
222 | return false; |
223 | } |
224 | |
225 | bool FrontendAction::reportFatalSemanticErrors() { |
226 | auto &diags = instance->getDiagnostics(); |
227 | auto &sema = instance->getSemantics(); |
228 | |
229 | if (instance->getSemantics().AnyFatalError()) { |
230 | unsigned diagID = diags.getCustomDiagID(clang::DiagnosticsEngine::Error, |
231 | "Semantic errors in %0" ); |
232 | diags.Report(diagID) << getCurrentFileOrBufferName(); |
233 | sema.EmitMessages(instance->getSemaOutputStream()); |
234 | |
235 | return true; |
236 | } |
237 | |
238 | return false; |
239 | } |
240 | |