382 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8//9// Coding style: https://mlir.llvm.org/getting_started/DeveloperGuide/10//11//===----------------------------------------------------------------------===//12 13#include "flang/Frontend/CompilerInstance.h"14#include "flang/Frontend/CompilerInvocation.h"15#include "flang/Frontend/TextDiagnosticPrinter.h"16#include "flang/Parser/parsing.h"17#include "flang/Parser/provenance.h"18#include "flang/Semantics/semantics.h"19#include "flang/Support/Fortran-features.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 34using namespace Fortran::frontend;35 36CompilerInstance::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 should42 // give the user the opportunity to specify this.43 allSources->set_encoding(Fortran::parser::Encoding::UTF_8);44}45 46CompilerInstance::~CompilerInstance() {47 assert(outputFiles.empty() && "Still output files in flight?");48}49 50void CompilerInstance::setInvocation(51 std::shared_ptr<CompilerInvocation> value) {52 invocation = std::move(value);53}54 55void CompilerInstance::setSemaOutputStream(raw_ostream &value) {56 ownedSemaOutputStream.release();57 semaOutputStream = &value;58}59 60void 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 logic66// 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 from70// the input file (i.e. inputFilename + extension)71// 3. If the output file is not specified and the input file is `-`, then set72// the output file to `-` as well.73static 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 92std::unique_ptr<llvm::raw_pwrite_stream>93CompilerInstance::createDefaultOutputFile(bool binary, llvm::StringRef baseName,94 llvm::StringRef extension) {95 96 // Get the path of the output file97 std::string outputFilePath =98 getOutputFilePath(getFrontendOpts().outputFile, baseName, extension);99 100 // Create the output file101 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 and105 // 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 Null112 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 119llvm::Expected<std::unique_ptr<llvm::raw_pwrite_stream>>120CompilerInstance::createOutputFileImpl(llvm::StringRef outputFilePath,121 bool binary) {122 123 // Creates the file descriptor for the output file124 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 output135 // 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 something140 // 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 144void 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 152bool 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 // Set options controlling lowering to FIR.166 invoc.setLoweringOptions();167 168 if (invoc.getEnableTimers()) {169 llvm::TimePassesIsEnabled = true;170 171 timingStreamMLIR = std::make_unique<Fortran::support::string_ostream>();172 timingStreamLLVM = std::make_unique<Fortran::support::string_ostream>();173 timingStreamCodeGen = std::make_unique<Fortran::support::string_ostream>();174 175 timingMgr.setEnabled(true);176 timingMgr.setDisplayMode(mlir::DefaultTimingManager::DisplayMode::Tree);177 timingMgr.setOutput(178 Fortran::support::createTimingFormatterText(*timingStreamMLIR));179 180 // Creating a new TimingScope will automatically start the timer. Since this181 // is the top-level timer, this is ok because it will end up capturing the182 // time for all the bookkeeping and other tasks that take place between183 // parsing, lowering etc. for which finer-grained timers will be created.184 timingScopeRoot = timingMgr.getRootScope();185 }186 187 // Run the frontend action `act` for every input file.188 for (const FrontendInputFile &fif : getFrontendOpts().inputs) {189 if (act.beginSourceFile(*this, fif)) {190 if (llvm::Error err = act.execute()) {191 consumeError(std::move(err));192 }193 act.endSourceFile();194 }195 }196 197 if (timingMgr.isEnabled()) {198 timingScopeRoot.stop();199 200 // Write the timings to the associated output stream and clear all timers.201 // We need to provide another stream because the TimingManager will attempt202 // to print in its destructor even if it has been cleared. By the time that203 // destructor runs, the output streams will have been destroyed, so give it204 // a null stream.205 timingMgr.print();206 timingMgr.setOutput(207 Fortran::support::createTimingFormatterText(mlir::thread_safe_nulls()));208 209 // This prints the timings in "reverse" order, starting from code210 // generation, followed by LLVM-IR optimizations, then MLIR optimizations211 // and transformations and the frontend. If any of the steps are disabled,212 // for instance because code generation was not performed, the strings213 // will be empty.214 if (!timingStreamCodeGen->str().empty())215 llvm::errs() << timingStreamCodeGen->str() << "\n";216 217 if (!timingStreamLLVM->str().empty())218 llvm::errs() << timingStreamLLVM->str() << "\n";219 220 if (!timingStreamMLIR->str().empty())221 llvm::errs() << timingStreamMLIR->str() << "\n";222 }223 224 return !getDiagnostics().getClient()->getNumErrors();225}226 227void CompilerInstance::createDiagnostics(clang::DiagnosticConsumer *client,228 bool shouldOwnClient) {229 diagnostics = createDiagnostics(getDiagnosticOpts(), client, shouldOwnClient);230}231 232clang::IntrusiveRefCntPtr<clang::DiagnosticsEngine>233CompilerInstance::createDiagnostics(clang::DiagnosticOptions &opts,234 clang::DiagnosticConsumer *client,235 bool shouldOwnClient) {236 auto diags = llvm::makeIntrusiveRefCnt<clang::DiagnosticsEngine>(237 clang::DiagnosticIDs::create(), opts);238 239 // Create the diagnostic client for reporting errors or for240 // implementing -verify.241 if (client) {242 diags->setClient(client, shouldOwnClient);243 } else {244 diags->setClient(new TextDiagnosticPrinter(llvm::errs(), opts));245 }246 return diags;247}248 249// Get feature string which represents combined explicit target features250// for AMD GPU and the target features specified by the user251static std::string252getExplicitAndImplicitAMDGPUTargetFeatures(clang::DiagnosticsEngine &diags,253 const TargetOptions &targetOpts,254 const llvm::Triple triple) {255 llvm::StringRef cpu = targetOpts.cpu;256 llvm::StringMap<bool> FeaturesMap;257 258 // Add target features specified by the user259 for (auto &userFeature : targetOpts.featuresAsWritten) {260 std::string userKeyString = userFeature.substr(1);261 FeaturesMap[userKeyString] = (userFeature[0] == '+');262 }263 264 auto HasError = llvm::AMDGPU::fillAMDGPUFeatureMap(cpu, triple, FeaturesMap);265 if (HasError.first) {266 unsigned diagID = diags.getCustomDiagID(clang::DiagnosticsEngine::Error,267 "Unsupported feature ID: %0");268 diags.Report(diagID) << HasError.second;269 return std::string();270 }271 272 llvm::SmallVector<std::string> featuresVec;273 for (auto &FeatureItem : FeaturesMap) {274 featuresVec.push_back((llvm::Twine(FeatureItem.second ? "+" : "-") +275 FeatureItem.first().str())276 .str());277 }278 llvm::sort(featuresVec);279 return llvm::join(featuresVec, ",");280}281 282// Get feature string which represents combined explicit target features283// for NVPTX and the target features specified by the user/284// TODO: Have a more robust target conf like `clang/lib/Basic/Targets/NVPTX.cpp`285static std::string286getExplicitAndImplicitNVPTXTargetFeatures(clang::DiagnosticsEngine &diags,287 const TargetOptions &targetOpts,288 const llvm::Triple triple) {289 llvm::StringRef cpu = targetOpts.cpu;290 llvm::StringMap<bool> implicitFeaturesMap;291 std::string errorMsg;292 bool ptxVer = false;293 294 // Add target features specified by the user295 for (auto &userFeature : targetOpts.featuresAsWritten) {296 llvm::StringRef userKeyString(llvm::StringRef(userFeature).drop_front(1));297 implicitFeaturesMap[userKeyString.str()] = (userFeature[0] == '+');298 // Check if the user provided a PTX version299 if (userKeyString.starts_with("ptx"))300 ptxVer = true;301 }302 303 // Set the default PTX version to `ptx61` if none was provided.304 // TODO: set the default PTX version based on the chip.305 if (!ptxVer)306 implicitFeaturesMap["ptx61"] = true;307 308 // Set the compute capability.309 implicitFeaturesMap[cpu.str()] = true;310 311 llvm::SmallVector<std::string> featuresVec;312 for (auto &implicitFeatureItem : implicitFeaturesMap) {313 featuresVec.push_back((llvm::Twine(implicitFeatureItem.second ? "+" : "-") +314 implicitFeatureItem.first().str())315 .str());316 }317 llvm::sort(featuresVec);318 return llvm::join(featuresVec, ",");319}320 321std::string CompilerInstance::getTargetFeatures() {322 const TargetOptions &targetOpts = getInvocation().getTargetOpts();323 const llvm::Triple triple(targetOpts.triple);324 325 // Clang does not append all target features to the clang -cc1 invocation.326 // Some target features are parsed implicitly by clang::TargetInfo child327 // class. Clang::TargetInfo classes are the basic clang classes and328 // they cannot be reused by Flang.329 // That's why we need to extract implicit target features and add330 // them to the target features specified by the user331 if (triple.isAMDGPU()) {332 return getExplicitAndImplicitAMDGPUTargetFeatures(getDiagnostics(),333 targetOpts, triple);334 } else if (triple.isNVPTX()) {335 return getExplicitAndImplicitNVPTXTargetFeatures(getDiagnostics(),336 targetOpts, triple);337 }338 return llvm::join(targetOpts.featuresAsWritten.begin(),339 targetOpts.featuresAsWritten.end(), ",");340}341 342bool CompilerInstance::setUpTargetMachine() {343 const TargetOptions &targetOpts = getInvocation().getTargetOpts();344 const std::string &theTriple = targetOpts.triple;345 346 // Create `Target`347 const llvm::Triple triple(theTriple);348 std::string error;349 const llvm::Target *theTarget =350 llvm::TargetRegistry::lookupTarget(triple, error);351 if (!theTarget) {352 getDiagnostics().Report(clang::diag::err_fe_unable_to_create_target)353 << error;354 return false;355 }356 // Create `TargetMachine`357 const auto &CGOpts = getInvocation().getCodeGenOpts();358 std::optional<llvm::CodeGenOptLevel> OptLevelOrNone =359 llvm::CodeGenOpt::getLevel(CGOpts.OptimizationLevel);360 assert(OptLevelOrNone && "Invalid optimization level!");361 llvm::CodeGenOptLevel OptLevel = *OptLevelOrNone;362 std::string featuresStr = getTargetFeatures();363 std::optional<llvm::CodeModel::Model> cm = getCodeModel(CGOpts.CodeModel);364 365 llvm::TargetOptions tOpts = llvm::TargetOptions();366 tOpts.EnableAIXExtendedAltivecABI = targetOpts.EnableAIXExtendedAltivecABI;367 368 targetMachine.reset(theTarget->createTargetMachine(369 triple, /*CPU=*/targetOpts.cpu,370 /*Features=*/featuresStr, /*Options=*/tOpts,371 /*Reloc::Model=*/CGOpts.getRelocationModel(),372 /*CodeModel::Model=*/cm, OptLevel));373 assert(targetMachine && "Failed to create TargetMachine");374 if (cm.has_value()) {375 if ((cm == llvm::CodeModel::Medium || cm == llvm::CodeModel::Large) &&376 triple.getArch() == llvm::Triple::x86_64) {377 targetMachine->setLargeDataThreshold(CGOpts.LargeDataThreshold);378 }379 }380 return true;381}382