1 | //===- ModuleToObject.cpp - Module to object base class ---------*- C++ -*-===// |
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 | // This file implements the base class for transforming Operations into binary |
10 | // objects. |
11 | // |
12 | //===----------------------------------------------------------------------===// |
13 | |
14 | #include "mlir/Target/LLVM/ModuleToObject.h" |
15 | |
16 | #include "mlir/ExecutionEngine/OptUtils.h" |
17 | #include "mlir/IR/BuiltinOps.h" |
18 | #include "mlir/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.h" |
19 | #include "mlir/Target/LLVMIR/Export.h" |
20 | #include "mlir/Target/LLVMIR/ModuleTranslation.h" |
21 | |
22 | #include "llvm/Bitcode/BitcodeWriter.h" |
23 | #include "llvm/IR/LegacyPassManager.h" |
24 | #include "llvm/IRReader/IRReader.h" |
25 | #include "llvm/Linker/Linker.h" |
26 | #include "llvm/MC/TargetRegistry.h" |
27 | #include "llvm/Support/FileSystem.h" |
28 | #include "llvm/Support/Path.h" |
29 | #include "llvm/Support/SourceMgr.h" |
30 | #include "llvm/Support/raw_ostream.h" |
31 | #include "llvm/Target/TargetMachine.h" |
32 | #include "llvm/Transforms/IPO/Internalize.h" |
33 | |
34 | using namespace mlir; |
35 | using namespace mlir::LLVM; |
36 | |
37 | ModuleToObject::ModuleToObject(Operation &module, StringRef triple, |
38 | StringRef chip, StringRef features, int optLevel) |
39 | : module(module), triple(triple), chip(chip), features(features), |
40 | optLevel(optLevel) {} |
41 | |
42 | ModuleToObject::~ModuleToObject() = default; |
43 | |
44 | Operation &ModuleToObject::getOperation() { return module; } |
45 | |
46 | std::optional<llvm::TargetMachine *> |
47 | ModuleToObject::getOrCreateTargetMachine() { |
48 | if (targetMachine) |
49 | return targetMachine.get(); |
50 | // Load the target. |
51 | std::string error; |
52 | const llvm::Target *target = |
53 | llvm::TargetRegistry::lookupTarget(Triple: triple, Error&: error); |
54 | if (!target) { |
55 | getOperation().emitError() |
56 | << "Failed to lookup target for triple '" << triple << "' " << error; |
57 | return std::nullopt; |
58 | } |
59 | |
60 | // Create the target machine using the target. |
61 | targetMachine.reset( |
62 | p: target->createTargetMachine(TT: triple, CPU: chip, Features: features, Options: {}, RM: {})); |
63 | if (!targetMachine) |
64 | return std::nullopt; |
65 | return targetMachine.get(); |
66 | } |
67 | |
68 | std::unique_ptr<llvm::Module> |
69 | ModuleToObject::loadBitcodeFile(llvm::LLVMContext &context, StringRef path) { |
70 | llvm::SMDiagnostic error; |
71 | std::unique_ptr<llvm::Module> library = |
72 | llvm::getLazyIRFileModule(Filename: path, Err&: error, Context&: context); |
73 | if (!library) { |
74 | getOperation().emitError() << "Failed loading file from " << path |
75 | << ", error: " << error.getMessage(); |
76 | return nullptr; |
77 | } |
78 | if (failed(result: handleBitcodeFile(module&: *library))) { |
79 | return nullptr; |
80 | } |
81 | return library; |
82 | } |
83 | |
84 | LogicalResult ModuleToObject::loadBitcodeFilesFromList( |
85 | llvm::LLVMContext &context, ArrayRef<std::string> fileList, |
86 | SmallVector<std::unique_ptr<llvm::Module>> &llvmModules, |
87 | bool failureOnError) { |
88 | for (const std::string &str : fileList) { |
89 | // Test if the path exists, if it doesn't abort. |
90 | StringRef pathRef = StringRef(str.data(), str.size()); |
91 | if (!llvm::sys::fs::is_regular_file(Path: pathRef)) { |
92 | getOperation().emitError() |
93 | << "File path: " << pathRef << " does not exist or is not a file.\n" ; |
94 | return failure(); |
95 | } |
96 | // Load the file or abort on error. |
97 | if (auto bcFile = loadBitcodeFile(context, path: pathRef)) |
98 | llvmModules.push_back(Elt: std::move(bcFile)); |
99 | else if (failureOnError) |
100 | return failure(); |
101 | } |
102 | return success(); |
103 | } |
104 | |
105 | std::unique_ptr<llvm::Module> |
106 | ModuleToObject::translateToLLVMIR(llvm::LLVMContext &llvmContext) { |
107 | return translateModuleToLLVMIR(module: &getOperation(), llvmContext); |
108 | } |
109 | |
110 | LogicalResult |
111 | ModuleToObject::linkFiles(llvm::Module &module, |
112 | SmallVector<std::unique_ptr<llvm::Module>> &&libs) { |
113 | if (libs.empty()) |
114 | return success(); |
115 | llvm::Linker linker(module); |
116 | for (std::unique_ptr<llvm::Module> &libModule : libs) { |
117 | // This bitcode linking imports the library functions into the module, |
118 | // allowing LLVM optimization passes (which must run after linking) to |
119 | // optimize across the libraries and the module's code. We also only import |
120 | // symbols if they are referenced by the module or a previous library since |
121 | // there will be no other source of references to those symbols in this |
122 | // compilation and since we don't want to bloat the resulting code object. |
123 | bool err = linker.linkInModule( |
124 | Src: std::move(libModule), Flags: llvm::Linker::Flags::LinkOnlyNeeded, |
125 | InternalizeCallback: [](llvm::Module &m, const StringSet<> &gvs) { |
126 | llvm::internalizeModule(TheModule&: m, MustPreserveGV: [&gvs](const llvm::GlobalValue &gv) { |
127 | return !gv.hasName() || (gvs.count(Key: gv.getName()) == 0); |
128 | }); |
129 | }); |
130 | // True is linker failure |
131 | if (err) { |
132 | getOperation().emitError(message: "Unrecoverable failure during bitcode linking." ); |
133 | // We have no guaranties about the state of `ret`, so bail |
134 | return failure(); |
135 | } |
136 | } |
137 | return success(); |
138 | } |
139 | |
140 | LogicalResult ModuleToObject::optimizeModule(llvm::Module &module, |
141 | |
142 | int optLevel) { |
143 | if (optLevel < 0 || optLevel > 3) |
144 | return getOperation().emitError() |
145 | << "Invalid optimization level: " << optLevel << "." ; |
146 | |
147 | std::optional<llvm::TargetMachine *> targetMachine = |
148 | getOrCreateTargetMachine(); |
149 | if (!targetMachine) |
150 | return getOperation().emitError() |
151 | << "Target Machine unavailable for triple " << triple |
152 | << ", can't optimize with LLVM\n" ; |
153 | (*targetMachine)->setOptLevel(static_cast<llvm::CodeGenOptLevel>(optLevel)); |
154 | |
155 | auto transformer = |
156 | makeOptimizingTransformer(optLevel, /*sizeLevel=*/0, targetMachine: *targetMachine); |
157 | auto error = transformer(&module); |
158 | if (error) { |
159 | InFlightDiagnostic mlirError = getOperation().emitError(); |
160 | llvm::handleAllErrors( |
161 | E: std::move(error), Handlers: [&mlirError](const llvm::ErrorInfoBase &ei) { |
162 | mlirError << "Could not optimize LLVM IR: " << ei.message() << "\n" ; |
163 | }); |
164 | return mlirError; |
165 | } |
166 | return success(); |
167 | } |
168 | |
169 | std::optional<std::string> |
170 | ModuleToObject::translateToISA(llvm::Module &llvmModule, |
171 | llvm::TargetMachine &targetMachine) { |
172 | std::string targetISA; |
173 | llvm::raw_string_ostream stream(targetISA); |
174 | |
175 | { // Drop pstream after this to prevent the ISA from being stuck buffering |
176 | llvm::buffer_ostream pstream(stream); |
177 | llvm::legacy::PassManager codegenPasses; |
178 | |
179 | if (targetMachine.addPassesToEmitFile(codegenPasses, pstream, nullptr, |
180 | llvm::CodeGenFileType::AssemblyFile)) |
181 | return std::nullopt; |
182 | |
183 | codegenPasses.run(M&: llvmModule); |
184 | } |
185 | return stream.str(); |
186 | } |
187 | |
188 | void ModuleToObject::setDataLayoutAndTriple(llvm::Module &module) { |
189 | // Create the target machine. |
190 | std::optional<llvm::TargetMachine *> targetMachine = |
191 | getOrCreateTargetMachine(); |
192 | if (targetMachine) { |
193 | // Set the data layout and target triple of the module. |
194 | module.setDataLayout((*targetMachine)->createDataLayout()); |
195 | module.setTargetTriple((*targetMachine)->getTargetTriple().getTriple()); |
196 | } |
197 | } |
198 | |
199 | std::optional<SmallVector<char, 0>> |
200 | ModuleToObject::moduleToObject(llvm::Module &llvmModule) { |
201 | SmallVector<char, 0> binaryData; |
202 | // Write the LLVM module bitcode to a buffer. |
203 | llvm::raw_svector_ostream outputStream(binaryData); |
204 | llvm::WriteBitcodeToFile(M: llvmModule, Out&: outputStream); |
205 | return binaryData; |
206 | } |
207 | |
208 | std::optional<SmallVector<char, 0>> ModuleToObject::run() { |
209 | // Translate the module to LLVM IR. |
210 | llvm::LLVMContext llvmContext; |
211 | std::unique_ptr<llvm::Module> llvmModule = translateToLLVMIR(llvmContext); |
212 | if (!llvmModule) { |
213 | getOperation().emitError() << "Failed creating the llvm::Module." ; |
214 | return std::nullopt; |
215 | } |
216 | setDataLayoutAndTriple(*llvmModule); |
217 | |
218 | // Link bitcode files. |
219 | handleModulePreLink(module&: *llvmModule); |
220 | { |
221 | auto libs = loadBitcodeFiles(module&: *llvmModule); |
222 | if (!libs) |
223 | return std::nullopt; |
224 | if (!libs->empty()) |
225 | if (failed(result: linkFiles(module&: *llvmModule, libs: std::move(*libs)))) |
226 | return std::nullopt; |
227 | handleModulePostLink(module&: *llvmModule); |
228 | } |
229 | |
230 | // Optimize the module. |
231 | if (failed(result: optimizeModule(module&: *llvmModule, optLevel))) |
232 | return std::nullopt; |
233 | |
234 | // Return the serialized object. |
235 | return moduleToObject(llvmModule&: *llvmModule); |
236 | } |
237 | |