1 /*
2 * Copyright (c) 2023-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "compiler/code_info/code_info.h"
17 #include "compiler/optimizer/code_generator/relocations.h"
18 #include "events/events.h"
19 #include "optimizer/ir/graph.h"
20 #include "runtime/include/method.h"
21 #include "compiler_options.h"
22 #include "target_machine_builder.h"
23
24 #include "llvm_irtoc_compiler.h"
25 #include "llvm_logger.h"
26 #include "llvm_options.h"
27 #include "mir_compiler.h"
28
29 #include "lowering/llvm_ir_constructor.h"
30 #include "transforms/passes/check_tail_calls.h"
31 #include "transforms/passes/inline_ir/patch_return_handler_stack_adjustment.h"
32
33 #include <llvm/Bitcode/BitcodeReader.h>
34 #include <llvm/CodeGen/MachineFunctionPass.h>
35 #include <llvm/CodeGen/Passes.h>
36 // Suppress warning about forward Pass declaration defined in another namespace
37 #include <llvm/Pass.h>
38 #include <llvm/Support/FileSystem.h>
39
40 using ark::compiler::LLVMIrConstructor;
41
42 namespace ark::llvmbackend {
43
CreateLLVMIrtocCompiler(ark::compiler::RuntimeInterface * runtime,ark::ArenaAllocator * allocator,ark::Arch arch)44 std::unique_ptr<IrtocCompilerInterface> CreateLLVMIrtocCompiler(ark::compiler::RuntimeInterface *runtime,
45 ark::ArenaAllocator *allocator, ark::Arch arch)
46 {
47 return std::make_unique<LLVMIrtocCompiler>(runtime, allocator, arch, "irtoc_file_name.hack");
48 }
49
LLVMIrtocCompiler(ark::compiler::RuntimeInterface * runtime,ark::ArenaAllocator * allocator,ark::Arch arch,std::string filename)50 LLVMIrtocCompiler::LLVMIrtocCompiler(ark::compiler::RuntimeInterface *runtime, ark::ArenaAllocator *allocator,
51 ark::Arch arch, std::string filename)
52 : LLVMCompiler(arch),
53 methods_(allocator->Adapter()),
54 filename_(std::move(filename)),
55 arkInterface_(runtime, GetTripleForArch(arch), nullptr, nullptr)
56 {
57 InitializeSpecificLLVMOptions(arch);
58 auto llvmCompilerOptions = InitializeLLVMCompilerOptions();
59
60 // clang-format off
61 targetMachine_ = cantFail(ark::llvmbackend::TargetMachineBuilder {}
62 .SetCPU(GetCPUForArch(arch))
63 .SetOptLevel(static_cast<llvm::CodeGenOpt::Level>(llvmCompilerOptions.optlevel))
64 .SetFeatures(GetFeaturesForArch(GetArch()))
65 .SetTriple(GetTripleForArch(GetArch()))
66 .Build());
67 // clang-format on
68 mirCompiler_ =
69 std::make_unique<MIRCompiler>(targetMachine_, [this](ark::llvmbackend::InsertingPassManager *manager) -> void {
70 manager->InsertBefore(&llvm::EarlyTailDuplicateID, ark::llvmbackend::CreateCheckTailCallsPass());
71 manager->InsertBefore(&llvm::FEntryInserterID,
72 ark::llvmbackend::CreatePatchReturnHandlerStackAdjustmentPass(&arkInterface_));
73 });
74
75 optimizer_ = std::make_unique<ark::llvmbackend::LLVMOptimizer>(llvmCompilerOptions, &arkInterface_,
76 mirCompiler_->GetTargetMachine());
77 InitializeModule();
78
79 debugData_ = std::make_unique<DebugDataBuilder>(module_.get(), filename_);
80 }
81
GetFeaturesForArch(Arch arch)82 std::vector<std::string> LLVMIrtocCompiler::GetFeaturesForArch(Arch arch)
83 {
84 if (arch == Arch::X86_64 && ark::compiler::g_options.IsCpuFeatureEnabled(compiler::SSE42)) {
85 return {std::string("+sse4.2")};
86 }
87 return {};
88 }
89
TryAddGraph(compiler::Graph * graph)90 Expected<bool, std::string> LLVMIrtocCompiler::TryAddGraph(compiler::Graph *graph)
91 {
92 ASSERT(graph != nullptr);
93 ASSERT(graph->GetArch() == GetArch());
94 ASSERT(!graph->SupportManagedCode());
95
96 if (graph->IsDynamicMethod()) {
97 return false;
98 }
99
100 std::string graphError = LLVMIrConstructor::CheckGraph(graph);
101 if (!graphError.empty()) {
102 return Unexpected(graphError);
103 }
104
105 LLVMIrConstructor ctor(graph, module_.get(), GetLLVMContext(), &arkInterface_, debugData_);
106 auto llvmFunction = ctor.GetFunc();
107 if (graph->GetMode().IsFastPath()) {
108 llvmFunction->addFnAttr("target-features", GetFastPathFeatures());
109 }
110
111 bool noInline = IsInliningDisabled(graph);
112 bool builtIr = ctor.BuildIr(noInline);
113 if (!builtIr) {
114 LLVM_LOG(ERROR, INFRA) << "Failed to build LLVM IR";
115 llvmFunction->deleteBody();
116 return Unexpected(std::string("Failed to build LLVM IR"));
117 }
118
119 LLVM_LOG(DEBUG, INFRA) << "LLVM built LLVM IR for method " << arkInterface_.GetUniqMethodName(graph->GetMethod());
120 methods_.push_back(static_cast<Method *>(graph->GetMethod()));
121 return true;
122 }
123
FinishCompile()124 void LLVMIrtocCompiler::FinishCompile()
125 {
126 // Compile even if there are no methods because we have to produce an object file, even an empty one
127 ASSERT_PRINT(!HasCompiledCode(), "Cannot compile twice");
128
129 LLVM_LOG_IF(g_options.IsLlvmDumpObj(), FATAL, INFRA)
130 << "Do not use '--llvm-dump-obj' in irtoc mode. Instead, look at the object file from '--irtoc-output-llvm' "
131 "option value";
132
133 optimizer_->DumpModuleBefore(module_.get());
134 optimizer_->OptimizeModule(module_.get());
135 optimizer_->DumpModuleAfter(module_.get());
136 debugData_->Finalize();
137 objectFile_ = exitOnErr_(mirCompiler_->CompileModule(*module_));
138 }
139
GetFastPathFeatures() const140 std::string LLVMIrtocCompiler::GetFastPathFeatures() const
141 {
142 std::string features;
143 for (const auto &feature : GetFeaturesForArch(GetArch())) {
144 features.append(feature).append(",");
145 }
146 // FastPath may use FP register. We should be ready for this
147 switch (GetArch()) {
148 case Arch::AARCH64:
149 features.append("+reserve-").append(arkInterface_.GetFramePointerRegister());
150 features.append(",");
151 features.append("+reserve-").append(arkInterface_.GetThreadRegister());
152 break;
153 default:
154 UNREACHABLE();
155 }
156 return features;
157 }
158
InitializeSpecificLLVMOptions(Arch arch)159 void LLVMIrtocCompiler::InitializeSpecificLLVMOptions(Arch arch)
160 {
161 if (arch == Arch::X86_64) {
162 SetLLVMOption("x86-use-base-pointer", "false");
163 }
164 if (arch == Arch::AARCH64) {
165 SetLLVMOption("aarch64-enable-ptr32", "true");
166 }
167 SetLLVMOption("inline-remark-attribute", "true");
168 LLVMCompiler::InitializeLLVMOptions();
169 }
170
InitializeModule()171 void LLVMIrtocCompiler::InitializeModule()
172 {
173 auto moduleFile = llvmbackend::g_options.GetLlvmInlineModule();
174 auto layout = targetMachine_->createDataLayout();
175 if (moduleFile.empty()) {
176 module_ = std::make_unique<llvm::Module>("irtoc empty module", *GetLLVMContext());
177 module_->setDataLayout(layout);
178 module_->setTargetTriple(GetTripleForArch(GetArch()).getTriple());
179 return;
180 }
181 auto buffer = errorOrToExpected(llvm::MemoryBuffer::getFile(moduleFile));
182 LLVM_LOG_IF(!buffer, FATAL, INFRA) << "Could not read inline module from file = '" << moduleFile << "', error: '"
183 << toString(buffer.takeError()) << "'";
184
185 auto contents = llvm::getBitcodeFileContents(*buffer.get());
186 LLVM_LOG_IF(!contents, FATAL, INFRA) << "Could get bitcode file contents from file = '" << moduleFile
187 << "', error: '" << toString(contents.takeError()) << "'";
188
189 static constexpr int32_t EXPECTED_MODULES = 1;
190 LLVM_LOG_IF(contents->Mods.size() != EXPECTED_MODULES, FATAL, INFRA)
191 << "Inline module file '" << moduleFile << "' has unexpected number of modules = " << contents->Mods.size()
192 << ", expected " << EXPECTED_MODULES;
193 auto module = contents->Mods[0].parseModule(*GetLLVMContext());
194 LLVM_LOG_IF(!module, FATAL, INFRA) << "Could not parse inline module from file '" << moduleFile << "', error: '"
195 << toString(buffer.takeError()) << "'";
196 module_ = std::move(*module);
197 module_->setDataLayout(layout);
198 optimizer_->ProcessInlineModule(module_.get());
199 }
200
WriteObjectFile(std::string_view output)201 void LLVMIrtocCompiler::WriteObjectFile(std::string_view output)
202 {
203 ASSERT_PRINT(HasCompiledCode(), "Call FinishCompile first");
204 objectFile_->WriteTo(output);
205 }
206
GetObjectFileSize()207 size_t LLVMIrtocCompiler::GetObjectFileSize()
208 {
209 return objectFile_->Size();
210 }
211
GetCompiledCode(std::string_view functionName)212 CompiledCode LLVMIrtocCompiler::GetCompiledCode(std::string_view functionName)
213 {
214 ASSERT(objectFile_ != nullptr);
215 auto reference = objectFile_->GetSectionByFunctionName(std::string {functionName});
216 CompiledCode code {};
217 code.size = reference.GetSize();
218 code.code = reference.GetMemory();
219 return code;
220 }
221 } // namespace ark::llvmbackend
222