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 "optimizer/ir/basicblock.h"
17 #include "optimizer/ir/graph.h"
18 #include "compiler_options.h"
19
20 #include "llvm_compiler.h"
21 #include "llvm_ark_interface.h"
22 #include "llvm_logger.h"
23 #include "llvm_options.h"
24
25 #include <llvm/Config/llvm-config.h>
26 #include <llvm/ExecutionEngine/Interpreter.h>
27 #include <llvm/InitializePasses.h>
28 #include <llvm/MC/TargetRegistry.h>
29 #include <llvm/PassRegistry.h>
30 #include <llvm/Support/CommandLine.h>
31 #include <llvm/Support/StringSaver.h>
32
33 namespace ark::llvmbackend {
34
35 static llvm::llvm_shutdown_obj g_shutdown {};
36
LLVMCompiler(Arch arch)37 LLVMCompiler::LLVMCompiler(Arch arch) : arch_(arch)
38 {
39 #ifndef REQUIRED_LLVM_VERSION
40 #error Internal build error, missing cmake variable
41 #else
42 #define STRINGIFY(s) STR(s) // NOLINT(cppcoreguidelines-macro-usage)
43 #define STR(s) #s // NOLINT(cppcoreguidelines-macro-usage)
44 // LLVM_VERSION_STRING is defined in llvm-config.h
45 // REQUIRED_LLVM_VERSION is defined in libllvmbackend/CMakeLists.txt
46 static_assert(std::string_view {LLVM_VERSION_STRING} == STRINGIFY(REQUIRED_LLVM_VERSION));
47
48 const std::string currentLlvmLibVersion = llvm::LLVMContext::getLLVMVersion();
49 if (currentLlvmLibVersion != STRINGIFY(REQUIRED_LLVM_VERSION)) {
50 llvm::report_fatal_error(llvm::Twine("Incompatible LLVM version " + currentLlvmLibVersion + ". " +
51 std::string(STRINGIFY(REQUIRED_LLVM_VERSION)) + " is required."),
52 false); /* gen_crash_diag = false */
53 }
54 #undef STR
55 #undef STRINGIFY
56 #endif
57 InitializeLLVMTarget(arch);
58 InitializeLLVMPasses();
59 #ifdef NDEBUG
60 context_.setDiscardValueNames(true);
61 #endif
62 context_.setOpaquePointers(true);
63
64 LLVMLogger::Init(g_options.GetLlvmLog());
65 }
66
IsInliningDisabled()67 bool LLVMCompiler::IsInliningDisabled()
68 {
69 if (ark::compiler::g_options.WasSetCompilerInlining() && !g_options.WasSetLlvmInlining()) {
70 return !ark::compiler::g_options.IsCompilerInlining();
71 }
72 return !g_options.IsLlvmInlining();
73 }
74
IsInliningDisabled(compiler::Graph * graph)75 bool LLVMCompiler::IsInliningDisabled(compiler::Graph *graph)
76 {
77 ASSERT(graph != nullptr);
78
79 // Erased end block means infinite loop in the 'Graph', such method should not be inlined.
80 if (graph->GetEndBlock() == nullptr) {
81 return true;
82 }
83
84 // Since we do not generate code for 'Catch' blocks, LLVM can not inline function with
85 // 'Try' blocks. Otherwise, if the inlined code throws, execution is on another frame and
86 // it is impossible to find the 'Catch' block. The approach here is same with Ark Compiler.
87 // Also, we can not just check 'graph->GetTryBeginBlocks().empty()', because 'TryCatchResolving'
88 // pass may remove 'TryBegin' blocks, but keep some 'Try' blocks.
89 for (auto block : graph->GetBlocksRPO()) {
90 if (block->IsTry()) {
91 return true;
92 }
93 }
94
95 // NB! LLVM does not follow '--compiler-inlining-skip-always-throw-methods=false' option,
96 // as it may lead to incorrect 'NoReturn' attribute propagation.
97 // Loop below checks if the function exits only using 'Throw' or 'Deoptimize' opcode
98 bool alwaysThrowOrDeopt = true;
99 for (auto pred : graph->GetEndBlock()->GetPredsBlocks()) {
100 if (pred->GetLastInst() == nullptr || !(pred->GetLastInst()->GetOpcode() == compiler::Opcode::Throw ||
101 pred->GetLastInst()->GetOpcode() == compiler::Opcode::Deoptimize)) {
102 alwaysThrowOrDeopt = false;
103 break;
104 }
105 }
106 return alwaysThrowOrDeopt || IsInliningDisabled(graph->GetRuntime(), graph->GetMethod());
107 }
108
IsInliningDisabled(compiler::RuntimeInterface * runtime,compiler::RuntimeInterface::MethodPtr method)109 bool LLVMCompiler::IsInliningDisabled(compiler::RuntimeInterface *runtime, compiler::RuntimeInterface::MethodPtr method)
110 {
111 ASSERT(runtime != nullptr);
112 ASSERT(method != nullptr);
113
114 if (IsInliningDisabled()) {
115 return true;
116 }
117
118 auto skipList = ark::compiler::g_options.GetCompilerInliningBlacklist();
119 if (!skipList.empty()) {
120 std::string methodName = runtime->GetMethodFullName(method);
121 if (std::find(skipList.begin(), skipList.end(), methodName) != skipList.end()) {
122 return true;
123 }
124 }
125
126 return (runtime->GetMethodName(method).find("__noinline__") != std::string::npos);
127 }
128
InitializeLLVMCompilerOptions()129 ark::llvmbackend::LLVMCompilerOptions LLVMCompiler::InitializeLLVMCompilerOptions()
130 {
131 ark::llvmbackend::LLVMCompilerOptions llvmCompilerOptions {};
132 llvmCompilerOptions.optimize = !ark::compiler::g_options.IsCompilerNonOptimizing();
133 llvmCompilerOptions.optlevel = llvmCompilerOptions.optimize ? 2U : 0U;
134 llvmCompilerOptions.gcIntrusionChecks = g_options.IsLlvmGcCheck();
135 llvmCompilerOptions.useSafepoint = ark::compiler::g_options.IsCompilerUseSafepoint();
136 llvmCompilerOptions.dumpModuleAfterOptimizations = g_options.IsLlvmDumpAfter();
137 llvmCompilerOptions.dumpModuleBeforeOptimizations = g_options.IsLlvmDumpBefore();
138 llvmCompilerOptions.inlineModuleFile = g_options.GetLlvmInlineModule();
139 llvmCompilerOptions.pipelineFile = g_options.GetLlvmPipeline();
140
141 llvmCompilerOptions.inlining = !IsInliningDisabled();
142 llvmCompilerOptions.recursiveInlining = g_options.IsLlvmRecursiveInlining();
143 llvmCompilerOptions.doIrtocInline = !llvmCompilerOptions.inlineModuleFile.empty();
144 llvmCompilerOptions.doVirtualInline = !ark::compiler::g_options.IsCompilerNoVirtualInlining();
145
146 return llvmCompilerOptions;
147 }
148
InitializeDefaultLLVMOptions()149 void LLVMCompiler::InitializeDefaultLLVMOptions()
150 {
151 if (ark::compiler::g_options.IsCompilerNonOptimizing()) {
152 SetLLVMOption("fast-isel", "false");
153 SetLLVMOption("global-isel", "false");
154 } else {
155 SetLLVMOption("fixup-allow-gcptr-in-csr", "true");
156 SetLLVMOption("max-registers-for-gc-values", std::to_string(std::numeric_limits<int>::max()));
157 }
158 }
159
InitializeLLVMOptions()160 void LLVMCompiler::InitializeLLVMOptions()
161 {
162 llvm::cl::ResetAllOptionOccurrences();
163 InitializeDefaultLLVMOptions();
164 auto llvmOptionsStr = llvmPreOptions_ + " " + g_options.GetLlvmOptions();
165 LLVM_LOG(DEBUG, INFRA) << "Using the following llvm options: '" << llvmPreOptions_ << "' (set internally), and '"
166 << g_options.GetLlvmOptions() << "' (from the option to aot compiler)";
167 llvm::BumpPtrAllocator alloc;
168 llvm::StringSaver stringSaver(alloc);
169 llvm::SmallVector<const char *, 0> parsedArgv;
170 parsedArgv.emplace_back(""); // First argument is an executable
171 llvm::cl::TokenizeGNUCommandLine(llvmOptionsStr, stringSaver, parsedArgv);
172 llvm::cl::ParseCommandLineOptions(parsedArgv.size(), parsedArgv.data());
173 #ifndef NDEBUG
174 parsedOptions_ = true;
175 #endif
176 }
177
SetLLVMOption(const char * option,const std::string & value)178 void LLVMCompiler::SetLLVMOption(const char *option, const std::string &value)
179 {
180 #ifndef NDEBUG
181 ASSERT_PRINT(!parsedOptions_, "Should call SetLLVMOptions earlier");
182 #endif
183 std::string prefix {llvmPreOptions_.empty() ? "" : " "};
184 llvmPreOptions_ += prefix + "--" + option + (value.empty() ? "" : "=") + value;
185 }
186
GetTripleForArch(Arch arch)187 llvm::Triple LLVMCompiler::GetTripleForArch(Arch arch)
188 {
189 std::string error;
190 std::string tripleName;
191 switch (arch) {
192 case Arch::AARCH64:
193 tripleName = g_options.GetLlvmTriple();
194 break;
195 case Arch::X86_64:
196 #ifdef PANDA_TARGET_LINUX
197 tripleName = g_options.WasSetLlvmTriple() ? g_options.GetLlvmTriple() : "x86_64-unknown-linux-gnu";
198 #elif defined(PANDA_TARGET_MACOS)
199 tripleName = g_options.WasSetLlvmTriple() ? g_options.GetLlvmTriple() : "x86_64-apple-darwin-gnu";
200 #elif defined(PANDA_TARGET_WINDOWS)
201 tripleName = g_options.WasSetLlvmTriple() ? g_options.GetLlvmTriple() : "x86_64-unknown-windows-unknown";
202 #else
203 tripleName = g_options.WasSetLlvmTriple() ? g_options.GetLlvmTriple() : "x86_64-unknown-unknown-unknown";
204 #endif
205 break;
206
207 default:
208 UNREACHABLE();
209 }
210 llvm::Triple triple(llvm::Triple::normalize(tripleName));
211 [[maybe_unused]] auto target = llvm::TargetRegistry::lookupTarget("", triple, error);
212 ASSERT_PRINT(target != nullptr, error);
213 return triple;
214 }
215
GetCPUForArch(Arch arch)216 std::string LLVMCompiler::GetCPUForArch(Arch arch)
217 {
218 std::string cpu;
219 switch (arch) {
220 case Arch::AARCH64:
221 #if defined(PANDA_TARGET_ARM64) && defined(PANDA_TARGET_LINUX)
222 // Avoid specifying default cortex for arm64-linux
223 cpu = g_options.WasSetLlvmCpu() ? g_options.GetLlvmCpu() : "";
224 #else
225 cpu = g_options.GetLlvmCpu();
226 #endif
227 break;
228 case Arch::X86_64:
229 cpu = g_options.WasSetLlvmCpu() ? g_options.GetLlvmCpu() : "";
230 break;
231 default:
232 UNREACHABLE();
233 }
234 return cpu;
235 }
236
InitializeLLVMTarget(Arch arch)237 void LLVMCompiler::InitializeLLVMTarget(Arch arch)
238 {
239 switch (arch) {
240 case Arch::AARCH64: {
241 LLVMInitializeAArch64TargetInfo();
242 LLVMInitializeAArch64Target();
243 LLVMInitializeAArch64TargetMC();
244 LLVMInitializeAArch64AsmPrinter();
245 LLVMInitializeAArch64AsmParser();
246 break;
247 }
248 #ifdef PANDA_TARGET_AMD64
249 case Arch::X86_64: {
250 LLVMInitializeX86TargetInfo();
251 LLVMInitializeX86Target();
252 LLVMInitializeX86TargetMC();
253 LLVMInitializeX86AsmPrinter();
254 LLVMInitializeX86AsmParser();
255 break;
256 }
257 #endif
258 default:
259 LLVM_LOG(FATAL, INFRA) << GetArchString(arch) << std::string(" is unsupported. ");
260 }
261 LLVM_LOG(DEBUG, INFRA) << "Available targets";
262 for (auto target : llvm::TargetRegistry::targets()) {
263 LLVM_LOG(DEBUG, INFRA) << "\t" << target.getName();
264 }
265 }
266
InitializeLLVMPasses()267 void LLVMCompiler::InitializeLLVMPasses()
268 {
269 llvm::PassRegistry ®istry = *llvm::PassRegistry::getPassRegistry();
270 initializeCore(registry);
271 initializeScalarOpts(registry);
272 initializeObjCARCOpts(registry);
273 initializeVectorization(registry);
274 initializeIPO(registry);
275 initializeAnalysis(registry);
276 initializeTransformUtils(registry);
277 initializeInstCombine(registry);
278 initializeAggressiveInstCombine(registry);
279 initializeInstrumentation(registry);
280 initializeTarget(registry);
281 initializeCodeGen(registry);
282 initializeGlobalISel(registry);
283 }
284
285 } // namespace ark::llvmbackend
286