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 "flang/Support/Timing.h" 21 #include "mlir/Support/RawOstreamExtras.h" 22 #include "clang/Basic/DiagnosticFrontend.h" 23 #include "llvm/ADT/StringExtras.h" 24 #include "llvm/MC/TargetRegistry.h" 25 #include "llvm/Pass.h" 26 #include "llvm/Support/Errc.h" 27 #include "llvm/Support/Error.h" 28 #include "llvm/Support/FileSystem.h" 29 #include "llvm/Support/Path.h" 30 #include "llvm/Support/raw_ostream.h" 31 #include "llvm/TargetParser/TargetParser.h" 32 #include "llvm/TargetParser/Triple.h" 33 34 using namespace Fortran::frontend; 35 36 CompilerInstance::CompilerInstance() 37 : invocation(new CompilerInvocation()), 38 allSources(new Fortran::parser::AllSources()), 39 allCookedSources(new Fortran::parser::AllCookedSources(*allSources)), 40 parsing(new Fortran::parser::Parsing(*allCookedSources)) { 41 // TODO: This is a good default during development, but ultimately we should 42 // give the user the opportunity to specify this. 43 allSources->set_encoding(Fortran::parser::Encoding::UTF_8); 44 } 45 46 CompilerInstance::~CompilerInstance() { 47 assert(outputFiles.empty() && "Still output files in flight?"); 48 } 49 50 void CompilerInstance::setInvocation( 51 std::shared_ptr<CompilerInvocation> value) { 52 invocation = std::move(value); 53 } 54 55 void CompilerInstance::setSemaOutputStream(raw_ostream &value) { 56 ownedSemaOutputStream.release(); 57 semaOutputStream = &value; 58 } 59 60 void CompilerInstance::setSemaOutputStream(std::unique_ptr<raw_ostream> value) { 61 ownedSemaOutputStream.swap(value); 62 semaOutputStream = ownedSemaOutputStream.get(); 63 } 64 65 // Helper method to generate the path of the output file. The following logic 66 // applies: 67 // 1. If the user specifies the output file via `-o`, then use that (i.e. 68 // the outputFilename parameter). 69 // 2. If the user does not specify the name of the output file, derive it from 70 // the input file (i.e. inputFilename + extension) 71 // 3. If the output file is not specified and the input file is `-`, then set 72 // the output file to `-` as well. 73 static std::string getOutputFilePath(llvm::StringRef outputFilename, 74 llvm::StringRef inputFilename, 75 llvm::StringRef extension) { 76 77 // Output filename _is_ specified. Just use that. 78 if (!outputFilename.empty()) 79 return std::string(outputFilename); 80 81 // Output filename _is not_ specified. Derive it from the input file name. 82 std::string outFile = "-"; 83 if (!extension.empty() && (inputFilename != "-")) { 84 llvm::SmallString<128> path(inputFilename); 85 llvm::sys::path::replace_extension(path, extension); 86 outFile = std::string(path); 87 } 88 89 return outFile; 90 } 91 92 std::unique_ptr<llvm::raw_pwrite_stream> 93 CompilerInstance::createDefaultOutputFile(bool binary, llvm::StringRef baseName, 94 llvm::StringRef extension) { 95 96 // Get the path of the output file 97 std::string outputFilePath = 98 getOutputFilePath(getFrontendOpts().outputFile, baseName, extension); 99 100 // Create the output file 101 llvm::Expected<std::unique_ptr<llvm::raw_pwrite_stream>> os = 102 createOutputFileImpl(outputFilePath, binary); 103 104 // If successful, add the file to the list of tracked output files and 105 // return. 106 if (os) { 107 outputFiles.emplace_back(OutputFile(outputFilePath)); 108 return std::move(*os); 109 } 110 111 // If unsuccessful, issue an error and return Null 112 unsigned diagID = getDiagnostics().getCustomDiagID( 113 clang::DiagnosticsEngine::Error, "unable to open output file '%0': '%1'"); 114 getDiagnostics().Report(diagID) 115 << outputFilePath << llvm::errorToErrorCode(os.takeError()).message(); 116 return nullptr; 117 } 118 119 llvm::Expected<std::unique_ptr<llvm::raw_pwrite_stream>> 120 CompilerInstance::createOutputFileImpl(llvm::StringRef outputFilePath, 121 bool binary) { 122 123 // Creates the file descriptor for the output file 124 std::unique_ptr<llvm::raw_fd_ostream> os; 125 126 std::error_code error; 127 os.reset(new llvm::raw_fd_ostream( 128 outputFilePath, error, 129 (binary ? llvm::sys::fs::OF_None : llvm::sys::fs::OF_TextWithCRLF))); 130 if (error) { 131 return llvm::errorCodeToError(error); 132 } 133 134 // For seekable streams, just return the stream corresponding to the output 135 // file. 136 if (!binary || os->supportsSeeking()) 137 return std::move(os); 138 139 // For non-seekable streams, we need to wrap the output stream into something 140 // that supports 'pwrite' and takes care of the ownership for us. 141 return std::make_unique<llvm::buffer_unique_ostream>(std::move(os)); 142 } 143 144 void CompilerInstance::clearOutputFiles(bool eraseFiles) { 145 for (OutputFile &of : outputFiles) 146 if (!of.filename.empty() && eraseFiles) 147 llvm::sys::fs::remove(of.filename); 148 149 outputFiles.clear(); 150 } 151 152 bool CompilerInstance::executeAction(FrontendAction &act) { 153 CompilerInvocation &invoc = this->getInvocation(); 154 155 llvm::Triple targetTriple{llvm::Triple(invoc.getTargetOpts().triple)}; 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 if (invoc.getEnableTimers()) { 171 llvm::TimePassesIsEnabled = true; 172 173 timingStreamMLIR = std::make_unique<Fortran::support::string_ostream>(); 174 timingStreamLLVM = std::make_unique<Fortran::support::string_ostream>(); 175 timingStreamCodeGen = std::make_unique<Fortran::support::string_ostream>(); 176 177 timingMgr.setEnabled(true); 178 timingMgr.setDisplayMode(mlir::DefaultTimingManager::DisplayMode::Tree); 179 timingMgr.setOutput( 180 Fortran::support::createTimingFormatterText(*timingStreamMLIR)); 181 182 // Creating a new TimingScope will automatically start the timer. Since this 183 // is the top-level timer, this is ok because it will end up capturing the 184 // time for all the bookkeeping and other tasks that take place between 185 // parsing, lowering etc. for which finer-grained timers will be created. 186 timingScopeRoot = timingMgr.getRootScope(); 187 } 188 189 // Run the frontend action `act` for every input file. 190 for (const FrontendInputFile &fif : getFrontendOpts().inputs) { 191 if (act.beginSourceFile(*this, fif)) { 192 if (llvm::Error err = act.execute()) { 193 consumeError(std::move(err)); 194 } 195 act.endSourceFile(); 196 } 197 } 198 199 if (timingMgr.isEnabled()) { 200 timingScopeRoot.stop(); 201 202 // Write the timings to the associated output stream and clear all timers. 203 // We need to provide another stream because the TimingManager will attempt 204 // to print in its destructor even if it has been cleared. By the time that 205 // destructor runs, the output streams will have been destroyed, so give it 206 // a null stream. 207 timingMgr.print(); 208 timingMgr.setOutput( 209 Fortran::support::createTimingFormatterText(mlir::thread_safe_nulls())); 210 211 // This prints the timings in "reverse" order, starting from code 212 // generation, followed by LLVM-IR optimizations, then MLIR optimizations 213 // and transformations and the frontend. If any of the steps are disabled, 214 // for instance because code generation was not performed, the strings 215 // will be empty. 216 if (!timingStreamCodeGen->str().empty()) 217 llvm::errs() << timingStreamCodeGen->str() << "\n"; 218 219 if (!timingStreamLLVM->str().empty()) 220 llvm::errs() << timingStreamLLVM->str() << "\n"; 221 222 if (!timingStreamMLIR->str().empty()) 223 llvm::errs() << timingStreamMLIR->str() << "\n"; 224 } 225 226 return !getDiagnostics().getClient()->getNumErrors(); 227 } 228 229 void CompilerInstance::createDiagnostics(clang::DiagnosticConsumer *client, 230 bool shouldOwnClient) { 231 diagnostics = 232 createDiagnostics(&getDiagnosticOpts(), client, shouldOwnClient); 233 } 234 235 clang::IntrusiveRefCntPtr<clang::DiagnosticsEngine> 236 CompilerInstance::createDiagnostics(clang::DiagnosticOptions *opts, 237 clang::DiagnosticConsumer *client, 238 bool shouldOwnClient) { 239 clang::IntrusiveRefCntPtr<clang::DiagnosticIDs> diagID( 240 new clang::DiagnosticIDs()); 241 clang::IntrusiveRefCntPtr<clang::DiagnosticsEngine> diags( 242 new clang::DiagnosticsEngine(diagID, opts)); 243 244 // Create the diagnostic client for reporting errors or for 245 // implementing -verify. 246 if (client) { 247 diags->setClient(client, shouldOwnClient); 248 } else { 249 diags->setClient(new TextDiagnosticPrinter(llvm::errs(), opts)); 250 } 251 return diags; 252 } 253 254 // Get feature string which represents combined explicit target features 255 // for AMD GPU and the target features specified by the user 256 static std::string 257 getExplicitAndImplicitAMDGPUTargetFeatures(clang::DiagnosticsEngine &diags, 258 const TargetOptions &targetOpts, 259 const llvm::Triple triple) { 260 llvm::StringRef cpu = targetOpts.cpu; 261 llvm::StringMap<bool> implicitFeaturesMap; 262 // Get the set of implicit target features 263 llvm::AMDGPU::fillAMDGPUFeatureMap(cpu, triple, implicitFeaturesMap); 264 265 // Add target features specified by the user 266 for (auto &userFeature : targetOpts.featuresAsWritten) { 267 std::string userKeyString = userFeature.substr(1); 268 implicitFeaturesMap[userKeyString] = (userFeature[0] == '+'); 269 } 270 271 auto HasError = 272 llvm::AMDGPU::insertWaveSizeFeature(cpu, triple, implicitFeaturesMap); 273 if (HasError.first) { 274 unsigned diagID = diags.getCustomDiagID(clang::DiagnosticsEngine::Error, 275 "Unsupported feature ID: %0"); 276 diags.Report(diagID) << HasError.second; 277 return std::string(); 278 } 279 280 llvm::SmallVector<std::string> featuresVec; 281 for (auto &implicitFeatureItem : implicitFeaturesMap) { 282 featuresVec.push_back((llvm::Twine(implicitFeatureItem.second ? "+" : "-") + 283 implicitFeatureItem.first().str()) 284 .str()); 285 } 286 llvm::sort(featuresVec); 287 return llvm::join(featuresVec, ","); 288 } 289 290 // Get feature string which represents combined explicit target features 291 // for NVPTX and the target features specified by the user/ 292 // TODO: Have a more robust target conf like `clang/lib/Basic/Targets/NVPTX.cpp` 293 static std::string 294 getExplicitAndImplicitNVPTXTargetFeatures(clang::DiagnosticsEngine &diags, 295 const TargetOptions &targetOpts, 296 const llvm::Triple triple) { 297 llvm::StringRef cpu = targetOpts.cpu; 298 llvm::StringMap<bool> implicitFeaturesMap; 299 std::string errorMsg; 300 bool ptxVer = false; 301 302 // Add target features specified by the user 303 for (auto &userFeature : targetOpts.featuresAsWritten) { 304 llvm::StringRef userKeyString(llvm::StringRef(userFeature).drop_front(1)); 305 implicitFeaturesMap[userKeyString.str()] = (userFeature[0] == '+'); 306 // Check if the user provided a PTX version 307 if (userKeyString.starts_with("ptx")) 308 ptxVer = true; 309 } 310 311 // Set the default PTX version to `ptx61` if none was provided. 312 // TODO: set the default PTX version based on the chip. 313 if (!ptxVer) 314 implicitFeaturesMap["ptx61"] = true; 315 316 // Set the compute capability. 317 implicitFeaturesMap[cpu.str()] = true; 318 319 llvm::SmallVector<std::string> featuresVec; 320 for (auto &implicitFeatureItem : implicitFeaturesMap) { 321 featuresVec.push_back((llvm::Twine(implicitFeatureItem.second ? "+" : "-") + 322 implicitFeatureItem.first().str()) 323 .str()); 324 } 325 llvm::sort(featuresVec); 326 return llvm::join(featuresVec, ","); 327 } 328 329 std::string CompilerInstance::getTargetFeatures() { 330 const TargetOptions &targetOpts = getInvocation().getTargetOpts(); 331 const llvm::Triple triple(targetOpts.triple); 332 333 // Clang does not append all target features to the clang -cc1 invocation. 334 // Some target features are parsed implicitly by clang::TargetInfo child 335 // class. Clang::TargetInfo classes are the basic clang classes and 336 // they cannot be reused by Flang. 337 // That's why we need to extract implicit target features and add 338 // them to the target features specified by the user 339 if (triple.isAMDGPU()) { 340 return getExplicitAndImplicitAMDGPUTargetFeatures(getDiagnostics(), 341 targetOpts, triple); 342 } else if (triple.isNVPTX()) { 343 return getExplicitAndImplicitNVPTXTargetFeatures(getDiagnostics(), 344 targetOpts, triple); 345 } 346 return llvm::join(targetOpts.featuresAsWritten.begin(), 347 targetOpts.featuresAsWritten.end(), ","); 348 } 349 350 bool CompilerInstance::setUpTargetMachine() { 351 const TargetOptions &targetOpts = getInvocation().getTargetOpts(); 352 const std::string &theTriple = targetOpts.triple; 353 354 // Create `Target` 355 std::string error; 356 const llvm::Target *theTarget = 357 llvm::TargetRegistry::lookupTarget(theTriple, error); 358 if (!theTarget) { 359 getDiagnostics().Report(clang::diag::err_fe_unable_to_create_target) 360 << error; 361 return false; 362 } 363 // Create `TargetMachine` 364 const auto &CGOpts = getInvocation().getCodeGenOpts(); 365 std::optional<llvm::CodeGenOptLevel> OptLevelOrNone = 366 llvm::CodeGenOpt::getLevel(CGOpts.OptimizationLevel); 367 assert(OptLevelOrNone && "Invalid optimization level!"); 368 llvm::CodeGenOptLevel OptLevel = *OptLevelOrNone; 369 std::string featuresStr = getTargetFeatures(); 370 std::optional<llvm::CodeModel::Model> cm = getCodeModel(CGOpts.CodeModel); 371 372 llvm::TargetOptions tOpts = llvm::TargetOptions(); 373 tOpts.EnableAIXExtendedAltivecABI = targetOpts.EnableAIXExtendedAltivecABI; 374 375 targetMachine.reset(theTarget->createTargetMachine( 376 theTriple, /*CPU=*/targetOpts.cpu, 377 /*Features=*/featuresStr, /*Options=*/tOpts, 378 /*Reloc::Model=*/CGOpts.getRelocationModel(), 379 /*CodeModel::Model=*/cm, OptLevel)); 380 assert(targetMachine && "Failed to create TargetMachine"); 381 if (cm.has_value()) { 382 const llvm::Triple triple(theTriple); 383 if ((cm == llvm::CodeModel::Medium || cm == llvm::CodeModel::Large) && 384 triple.getArch() == llvm::Triple::x86_64) { 385 targetMachine->setLargeDataThreshold(CGOpts.LargeDataThreshold); 386 } 387 } 388 return true; 389 } 390