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