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