asmjit_bench_regalloc.cpp (14126B)
1 // This file is part of AsmJit project <https://asmjit.com> 2 // 3 // See asmjit.h or LICENSE.md for license and copyright information 4 // SPDX-License-Identifier: Zlib 5 6 #include <asmjit/core.h> 7 8 #if !defined(ASMJIT_NO_X86) 9 #include <asmjit/x86.h> 10 #endif // !ASMJIT_NO_X86 11 12 #if !defined(ASMJIT_NO_AARCH64) 13 #include <asmjit/a64.h> 14 #endif // !ASMJIT_NO_AARCH64 15 16 #include <stdio.h> 17 #include <stdlib.h> 18 #include <string.h> 19 20 #include <memory> 21 #include <vector> 22 23 #include "../commons/asmjitutils.h" 24 25 #if !defined(ASMJIT_NO_COMPILER) 26 #include "../commons/cmdline.h" 27 #include "../commons/performancetimer.h" 28 #include "../commons/random.h" 29 #endif 30 31 using namespace asmjit; 32 33 static void print_app_info() { 34 printf("AsmJit Benchmark RegAlloc v%u.%u.%u [Arch=%s] [Mode=%s]\n\n", 35 unsigned((ASMJIT_LIBRARY_VERSION >> 16) ), 36 unsigned((ASMJIT_LIBRARY_VERSION >> 8) & 0xFF), 37 unsigned((ASMJIT_LIBRARY_VERSION ) & 0xFF), 38 asmjit_arch_as_string(Arch::kHost), 39 asmjit_build_type() 40 ); 41 } 42 43 #if !defined(ASMJIT_NO_COMPILER) 44 45 class BenchRegAllocApp { 46 public: 47 const char* _arch = nullptr; 48 bool _help_only = false; 49 bool _verbose = false; 50 uint32_t _maximum_complexity = 65536; 51 52 BenchRegAllocApp() noexcept 53 : _arch("all") {} 54 ~BenchRegAllocApp() noexcept {} 55 56 template<class T> 57 inline void add_t() { T::add(*this); } 58 59 int handle_args(int argc, const char* const* argv); 60 void show_info(); 61 62 bool should_run_arch(Arch arch) const noexcept; 63 void emit_code(BaseCompiler* cc, uint32_t complexity, uint32_t reg_count); 64 65 #if !defined(ASMJIT_NO_X86) 66 void emit_code_x86(x86::Compiler* cc, uint32_t complexity, uint32_t reg_count); 67 #endif // !ASMJIT_NO_X86 68 69 #if !defined(ASMJIT_NO_AARCH64) 70 void emit_code_aarch64(a64::Compiler* cc, uint32_t complexity, uint32_t reg_count); 71 #endif // !ASMJIT_NO_AARCH64 72 73 int run(); 74 bool run_arch(Arch arch); 75 }; 76 77 int BenchRegAllocApp::handle_args(int argc, const char* const* argv) { 78 CmdLine cmd(argc, argv); 79 _arch = cmd.value_of("--arch", "all"); 80 _maximum_complexity = cmd.value_as_uint("--complexity", _maximum_complexity); 81 82 if (cmd.has_arg("--help")) _help_only = true; 83 if (cmd.has_arg("--verbose")) _verbose = true; 84 85 return 0; 86 } 87 88 void BenchRegAllocApp::show_info() { 89 print_app_info(); 90 91 printf("Usage:\n"); 92 printf(" asmjit_bench_regalloc [arguments]\n"); 93 printf("\n"); 94 95 printf("Arguments:\n"); 96 printf(" --help Show usage only\n"); 97 printf(" --arch=<NAME> Select architecture to run ('all' by default)\n"); 98 printf(" --verbose Verbose output\n"); 99 printf(" --complexity=<n> Maximum complexity to test (%u)\n", _maximum_complexity); 100 printf("\n"); 101 102 printf("Architectures:\n"); 103 #if !defined(ASMJIT_NO_X86) 104 printf(" --arch=x86 32-bit X86 architecture (X86)\n"); 105 printf(" --arch=x64 64-bit X86 architecture (X86_64)\n"); 106 #endif 107 #if !defined(ASMJIT_NO_AARCH64) 108 printf(" --arch=aarch64 64-bit ARM architecture (AArch64)\n"); 109 #endif 110 printf("\n"); 111 } 112 113 bool BenchRegAllocApp::should_run_arch(Arch arch) const noexcept { 114 if (strcmp(_arch, "all") == 0) { 115 return true; 116 } 117 118 if (strcmp(_arch, "x86") == 0 && arch == Arch::kX86) { 119 return true; 120 } 121 122 if (strcmp(_arch, "x64") == 0 && arch == Arch::kX64) { 123 return true; 124 } 125 126 if (strcmp(_arch, "aarch64") == 0 && arch == Arch::kAArch64) { 127 return true; 128 } 129 130 return false; 131 } 132 133 void BenchRegAllocApp::emit_code(BaseCompiler* cc, uint32_t complexity, uint32_t reg_count) { 134 #if !defined(ASMJIT_NO_X86) 135 if (cc->arch() == Arch::kX86 || cc->arch() == Arch::kX64) { 136 emit_code_x86(cc->as<x86::Compiler>(), complexity, reg_count); 137 } 138 #endif 139 140 #if !defined(ASMJIT_NO_AARCH64) 141 if (cc->arch() == Arch::kAArch64) { 142 emit_code_aarch64(cc->as<a64::Compiler>(), complexity, reg_count); 143 } 144 #endif 145 } 146 147 constexpr size_t kLocalRegCount = 3; 148 constexpr size_t kLocalOpCount = 15; 149 150 #if !defined(ASMJIT_NO_X86) 151 void BenchRegAllocApp::emit_code_x86(x86::Compiler* cc, uint32_t complexity, uint32_t reg_count) { 152 TestUtils::Random rnd(0x1234); 153 154 std::vector<Label> labels; 155 std::vector<uint32_t> used_labels; 156 std::vector<x86::Vec> virt_regs; 157 158 x86::Gp arg_ptr = cc->new_gp_ptr("arg_ptr"); 159 x86::Gp counter = cc->new_gp_ptr("counter"); 160 161 for (size_t i = 0; i < complexity; i++) { 162 labels.push_back(cc->new_label()); 163 used_labels.push_back(0u); 164 } 165 166 for (size_t i = 0; i < reg_count; i++) { 167 virt_regs.push_back(cc->new_xmm_sd("v%u", unsigned(i))); 168 } 169 170 FuncNode* func = cc->add_func(FuncSignature::build<void, size_t, void*>()); 171 func->add_attributes(FuncAttributes::kX86_AVXEnabled); 172 func->set_arg(0, counter); 173 func->set_arg(1, arg_ptr); 174 175 for (size_t i = 0; i < reg_count; i++) { 176 cc->vmovsd(virt_regs[i], x86::ptr_64(arg_ptr, int32_t(i * 8))); 177 } 178 179 auto next_label = [&]() { 180 uint32_t id = rnd.next_uint32() % complexity; 181 if (used_labels[id] > 1) { 182 id = 0; 183 do { 184 if (++id >= complexity) { 185 id = 0; 186 } 187 } while (used_labels[id] != 0); 188 } 189 190 used_labels[id]++; 191 return labels[id]; 192 }; 193 194 for (size_t i = 0; i < labels.size(); i++) { 195 cc->bind(labels[i]); 196 197 x86::Vec locals[kLocalRegCount]; 198 for (size_t j = 0; j < kLocalRegCount; j++) { 199 locals[j] = cc->new_xmm_sd("local%u", unsigned(j)); 200 } 201 202 size_t local_op_threshold = kLocalOpCount - kLocalRegCount; 203 204 for (size_t j = 0; j < 15; j++) { 205 uint32_t op = rnd.next_uint32() % 6u; 206 uint32_t id1 = rnd.next_uint32() % reg_count; 207 uint32_t id2 = rnd.next_uint32() % reg_count; 208 209 x86::Vec v0 = virt_regs[id1]; 210 x86::Vec v1 = virt_regs[id1]; 211 x86::Vec v2 = virt_regs[id2]; 212 213 if (j < kLocalRegCount) { 214 v0 = locals[j]; 215 } 216 217 if (j >= local_op_threshold) { 218 v2 = locals[j - local_op_threshold]; 219 } 220 221 switch (op) { 222 case 0: cc->vaddsd(v0, v1, v2); break; 223 case 1: cc->vsubsd(v0, v1, v2); break; 224 case 2: cc->vmulsd(v0, v1, v2); break; 225 case 3: cc->vdivsd(v0, v1, v2); break; 226 case 4: cc->vminsd(v0, v1, v2); break; 227 case 5: cc->vmaxsd(v0, v1, v2); break; 228 } 229 } 230 231 cc->sub(counter, 1); 232 cc->jns(next_label()); 233 } 234 235 for (size_t i = 0; i < reg_count; i++) { 236 cc->vmovsd(x86::ptr_64(arg_ptr, int32_t(i * 8)), virt_regs[i]); 237 } 238 239 cc->end_func(); 240 } 241 #endif // !ASMJIT_NO_X86 242 243 #if !defined(ASMJIT_NO_AARCH64) 244 void BenchRegAllocApp::emit_code_aarch64(a64::Compiler* cc, uint32_t complexity, uint32_t reg_count) { 245 TestUtils::Random rnd(0x1234); 246 247 std::vector<Label> labels; 248 std::vector<uint32_t> used_labels; 249 std::vector<a64::Vec> virt_regs; 250 251 a64::Gp arg_ptr = cc->new_gp_ptr("arg_ptr"); 252 a64::Gp counter = cc->new_gp_ptr("counter"); 253 254 for (size_t i = 0; i < complexity; i++) { 255 labels.push_back(cc->new_label()); 256 used_labels.push_back(0u); 257 } 258 259 for (size_t i = 0; i < reg_count; i++) { 260 virt_regs.push_back(cc->new_vec_d("v%u", unsigned(i))); 261 } 262 263 FuncNode* func = cc->add_func(FuncSignature::build<void, size_t, void*>()); 264 func->add_attributes(FuncAttributes::kX86_AVXEnabled); 265 func->set_arg(0, counter); 266 func->set_arg(1, arg_ptr); 267 268 for (size_t i = 0; i < reg_count; i++) { 269 cc->ldr(virt_regs[i].d(), a64::ptr(arg_ptr, int32_t(i * 8) & 1023)); 270 } 271 272 auto next_label = [&]() { 273 uint32_t id = rnd.next_uint32() % complexity; 274 if (used_labels[id] > 1) { 275 id = 0; 276 do { 277 if (++id >= complexity) { 278 id = 0; 279 } 280 } while (used_labels[id] != 0); 281 } 282 283 used_labels[id]++; 284 return labels[id]; 285 }; 286 287 for (size_t i = 0; i < labels.size(); i++) { 288 cc->bind(labels[i]); 289 290 a64::Vec locals[kLocalRegCount]; 291 for (size_t j = 0; j < kLocalRegCount; j++) { 292 locals[j] = cc->new_vec_d("local%u", unsigned(j)); 293 } 294 295 size_t local_op_threshold = kLocalOpCount - kLocalRegCount; 296 297 for (size_t j = 0; j < 15; j++) { 298 uint32_t op = rnd.next_uint32() % 6; 299 uint32_t id1 = rnd.next_uint32() % reg_count; 300 uint32_t id2 = rnd.next_uint32() % reg_count; 301 302 a64::Vec v0 = virt_regs[id1]; 303 a64::Vec v1 = virt_regs[id1]; 304 a64::Vec v2 = virt_regs[id2]; 305 306 if (j < kLocalRegCount) { 307 v0 = locals[j]; 308 } 309 310 if (j >= local_op_threshold) { 311 v2 = locals[j - local_op_threshold]; 312 } 313 314 switch (op) { 315 case 0: cc->fadd(v0.d(), v1.d(), v2.d()); break; 316 case 1: cc->fsub(v0.d(), v1.d(), v2.d()); break; 317 case 2: cc->fmul(v0.d(), v1.d(), v2.d()); break; 318 case 3: cc->fdiv(v0.d(), v1.d(), v2.d()); break; 319 case 4: cc->fmin(v0.d(), v1.d(), v2.d()); break; 320 case 5: cc->fmax(v0.d(), v1.d(), v2.d()); break; 321 } 322 } 323 324 cc->subs(counter, counter, 1); 325 cc->b_hi(next_label()); 326 } 327 328 for (size_t i = 0; i < reg_count; i++) { 329 cc->str(virt_regs[i].d(), a64::ptr(arg_ptr, int32_t(i * 8) & 1023)); 330 } 331 332 cc->end_func(); 333 } 334 #endif // !ASMJIT_NO_AARCH64 335 336 int BenchRegAllocApp::run() { 337 if (should_run_arch(Arch::kX64) && !run_arch(Arch::kX64)) { 338 return 1; 339 } 340 341 if (should_run_arch(Arch::kAArch64) && !run_arch(Arch::kAArch64)) { 342 return 1; 343 } 344 345 return 0; 346 } 347 348 bool BenchRegAllocApp::run_arch(Arch arch) { 349 Environment custom_env; 350 CpuFeatures features; 351 352 switch (arch) { 353 case Arch::kX86: 354 case Arch::kX64: 355 features.add(CpuFeatures::X86::kADX, 356 CpuFeatures::X86::kAVX, 357 CpuFeatures::X86::kAVX2, 358 CpuFeatures::X86::kBMI, 359 CpuFeatures::X86::kBMI2, 360 CpuFeatures::X86::kCMOV, 361 CpuFeatures::X86::kF16C, 362 CpuFeatures::X86::kFMA, 363 CpuFeatures::X86::kFPU, 364 CpuFeatures::X86::kI486, 365 CpuFeatures::X86::kLZCNT, 366 CpuFeatures::X86::kMMX, 367 CpuFeatures::X86::kMMX2, 368 CpuFeatures::X86::kPOPCNT, 369 CpuFeatures::X86::kSSE, 370 CpuFeatures::X86::kSSE2, 371 CpuFeatures::X86::kSSE3, 372 CpuFeatures::X86::kSSSE3, 373 CpuFeatures::X86::kSSE4_1, 374 CpuFeatures::X86::kSSE4_2, 375 CpuFeatures::X86::kAVX, 376 CpuFeatures::X86::kAVX2); 377 break; 378 379 case Arch::kAArch64: 380 features.add(CpuFeatures::ARM::kAES, 381 CpuFeatures::ARM::kASIMD, 382 CpuFeatures::ARM::kIDIVA, 383 CpuFeatures::ARM::kIDIVT, 384 CpuFeatures::ARM::kPMULL); 385 break; 386 387 default: 388 return false; 389 } 390 391 CodeHolder code; 392 393 custom_env.init(arch); 394 code.init(custom_env, features); 395 396 std::unique_ptr<BaseCompiler> cc; 397 398 #ifndef ASMJIT_NO_X86 399 if (code.arch() == Arch::kX86 || code.arch() == Arch::kX64) { 400 cc = std::make_unique<x86::Compiler>(); 401 } 402 #endif // !ASMJIT_NO_X86 403 404 #ifndef ASMJIT_NO_AARCH64 405 if (code.arch() == Arch::kAArch64) { 406 cc = std::make_unique<a64::Compiler>(); 407 } 408 #endif // !ASMJIT_NO_AARCH64 409 410 if (!cc) 411 return false; 412 413 PerformanceTimer emit_timer; 414 PerformanceTimer finalize_timer; 415 416 uint32_t reg_count = 35; 417 418 code.reinit(); 419 code.attach(cc.get()); 420 421 // Dry run to not benchmark allocs on the first run. 422 emit_code(cc.get(), 0, reg_count); 423 cc->finalize(); 424 code.reinit(); 425 426 #if !defined(ASMJIT_NO_LOGGING) 427 StringLogger logger; 428 if (_verbose) { 429 code.set_logger(&logger); 430 cc->add_diagnostic_options(DiagnosticOptions::kRAAnnotate | DiagnosticOptions::kRADebugAll); 431 } 432 #endif // !ASMJIT_NO_LOGGING 433 434 printf("+-----------------------------------------+-----------+-----------------------------------+--------------+--------------+\n"); 435 printf("| Input Configuration | Output | Reserved Memory [KiB] | Time Elapsed [ms] |\n"); 436 printf("+--------+------------+--------+----------+-----------+-----------+-----------+-----------+--------------+--------------+\n"); 437 printf("| Arch | Complexity | Labels | RegCount | CodeSize | Code Hold.| Compiler | Pass Temp.| Emit Time | Reg. Alloc |\n"); 438 printf("+--------+------------+--------+----------+-----------+-----------+-----------+-----------+--------------+--------------+\n"); 439 440 for (uint32_t complexity = 1u; complexity <= _maximum_complexity; complexity *= 2u) { 441 emit_timer.start(); 442 emit_code(cc.get(), complexity + 1, reg_count); 443 emit_timer.stop(); 444 445 finalize_timer.start(); 446 Error err = cc->finalize(); 447 finalize_timer.stop(); 448 449 #if !defined(ASMJIT_NO_LOGGING) 450 if (_verbose) { 451 printf("%s\n", logger.data()); 452 logger.clear(); 453 } 454 #endif 455 456 code.flatten(); 457 458 double emit_time = emit_timer.duration(); 459 double finalize_time = finalize_timer.duration(); 460 size_t code_size = code.code_size(); 461 size_t label_count = code.label_count(); 462 size_t virt_reg_count = cc->virt_regs().size(); 463 464 ArenaStatistics code_holder_stats = code._arena.statistics(); 465 ArenaStatistics compiler_stats = cc->_builder_arena.statistics(); 466 ArenaStatistics pass_stats = cc->_pass_arena.statistics(); 467 468 printf( 469 "| %-7s| %10u | %6zu | %8zu | %9zu | %9zu | %9zu | %9zu | %12.3f | %12.3f |", 470 asmjit_arch_as_string(arch), 471 complexity, 472 label_count, 473 virt_reg_count, 474 code_size, 475 (code_holder_stats.reserved_size() + 1023) / 1024, 476 (compiler_stats.reserved_size() + 1023) / 1024, 477 (pass_stats.reserved_size() + 1023) / 1024, 478 emit_time, 479 finalize_time 480 ); 481 482 if (err != Error::kOk) { 483 printf(" (err: %s)", DebugUtils::error_as_string(err)); 484 } 485 486 printf("\n"); 487 488 code.reinit(); 489 } 490 491 printf("+--------+------------+--------+----------+-----------+-----------+-----------+-----------+--------------+--------------+\n"); 492 printf("\n"); 493 494 return true; 495 } 496 497 int main(int argc, char* argv[]) { 498 BenchRegAllocApp app; 499 500 app.handle_args(argc, argv); 501 app.show_info(); 502 503 if (app._help_only) 504 return 0; 505 506 return app.run(); 507 } 508 509 #else 510 511 int main() { 512 print_app_info(); 513 printf("!! This Benchmark is disabled: <ASMJIT_NO_JIT> or unsuitable target architecture !!\n"); 514 return 0; 515 } 516 517 #endif // !ASMJIT_NO_COMPILER