mirror of
https://github.com/asmjit/asmjit.git
synced 2025-12-18 04:54:36 +03:00
Codebase update and improvements, instruction DB update
* Denested src folder to root, renamed testing to asmjit-testing
* Refactored how headers are included into <asmjit/...> form. This
is necessary as compilers would never simplify a path once a ..
appears in include directory - then paths such as ../core/../core
appeared in asserts, which was ugly
* Moved support utilities into asmjit/support/... (still included
by asmjit/core.h for convenience and compatibility)
* Added CMakePresets.json for making it easy to develop AsmJit
* Reworked CMakeLists to be shorter and use CMake option(),
etc... This simplifies it and makes it using more standard
features
* ASMJIT_EMBED now creates asmjit_embed INTERFACE library,
which is accessible via asmjit::asmjit target - this simplifies
embedding and makes it the same as library targets from a CMake
perspective
* Removed ASMJIT_DEPS - this is now provided by cmake target
aliases - 'asmjit::asmjit' so users should not need this variable
* Changed meaning of ASMJIT_LIBS - this now contains only AsmJit
dependencies without asmjit::asmjit target alias. Don't rely on
ASMJIT_LIBS anymore as it's only used internally
* Removed ASMJIT_NO_DEPRECATED option - AsmJit is not going
to provide controllable deprecations in the future
* Removed ASMJIT_NO_VALIDATION in favor of ASMJIT_NO_INTROSPECTION,
which now controls query, features, and validation API presence
* Removed ASMJIT_DIR option - it was never really needed
* Removed AMX_TRANSPOSE feature from instruction database (X86).
Intel has removed it as well, so it's a feature that won't
be siliconized
This commit is contained in:
83
asmjit-testing/bench/asmjit_bench_codegen.cpp
Normal file
83
asmjit-testing/bench/asmjit_bench_codegen.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
// This file is part of AsmJit project <https://asmjit.com>
|
||||
//
|
||||
// See <asmjit/core.h> or LICENSE.md for license and copyright information
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#include <asmjit/core.h>
|
||||
|
||||
#include <asmjit-testing/commons/asmjitutils.h>
|
||||
#include <asmjit-testing/commons/cmdline.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
using namespace asmjit;
|
||||
|
||||
static void print_app_info() noexcept {
|
||||
printf("AsmJit Benchmark CodeGen v%u.%u.%u [Arch=%s] [Mode=%s]\n\n",
|
||||
unsigned((ASMJIT_LIBRARY_VERSION >> 16) ),
|
||||
unsigned((ASMJIT_LIBRARY_VERSION >> 8) & 0xFF),
|
||||
unsigned((ASMJIT_LIBRARY_VERSION ) & 0xFF),
|
||||
asmjit_arch_as_string(Arch::kHost),
|
||||
asmjit_build_type()
|
||||
);
|
||||
}
|
||||
|
||||
#if !defined(ASMJIT_NO_X86)
|
||||
void benchmark_x86_emitters(uint32_t num_iterations, bool test_x86, bool test_x64) noexcept;
|
||||
#endif
|
||||
|
||||
#if !defined(ASMJIT_NO_AARCH64)
|
||||
void benchmark_aarch64_emitters(uint32_t num_iterations);
|
||||
#endif
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
CmdLine cmd_line(argc, argv);
|
||||
uint32_t num_iterations = 100000;
|
||||
|
||||
print_app_info();
|
||||
|
||||
printf("Usage:\n");
|
||||
printf(" --help Show usage only\n");
|
||||
printf(" --quick Decrease the number of iterations to make tests quicker\n");
|
||||
printf(" --arch=<ARCH> Select architecture(s) to run ('all' by default)\n");
|
||||
printf("\n");
|
||||
|
||||
printf("Architectures:\n");
|
||||
#if !defined(ASMJIT_NO_X86)
|
||||
printf(" --arch=x86 32-bit X86 architecture (X86)\n");
|
||||
printf(" --arch=x64 64-bit X86 architecture (X86_64)\n");
|
||||
#endif
|
||||
#if !defined(ASMJIT_NO_AARCH64)
|
||||
printf(" --arch=aarch64 64-bit ARM architecture (AArch64)\n");
|
||||
#endif
|
||||
printf("\n");
|
||||
|
||||
if (cmd_line.has_arg("--help"))
|
||||
return 0;
|
||||
|
||||
if (cmd_line.has_arg("--quick"))
|
||||
num_iterations = 1000;
|
||||
|
||||
const char* arch = cmd_line.value_of("--arch", "all");
|
||||
|
||||
#if !defined(ASMJIT_NO_X86)
|
||||
bool test_x86 = strcmp(arch, "all") == 0 || strcmp(arch, "x86") == 0;
|
||||
bool test_x64 = strcmp(arch, "all") == 0 || strcmp(arch, "x64") == 0;
|
||||
|
||||
if (test_x86 || test_x64) {
|
||||
benchmark_x86_emitters(num_iterations, test_x86, test_x64);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !defined(ASMJIT_NO_AARCH64)
|
||||
bool test_aarch64 = strcmp(arch, "all") == 0 || strcmp(arch, "aarch64") == 0;
|
||||
|
||||
if (test_aarch64) {
|
||||
benchmark_aarch64_emitters(num_iterations);
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
117
asmjit-testing/bench/asmjit_bench_codegen.h
Normal file
117
asmjit-testing/bench/asmjit_bench_codegen.h
Normal file
@@ -0,0 +1,117 @@
|
||||
// This file is part of AsmJit project <https://asmjit.com>
|
||||
//
|
||||
// See <asmjit/core.h> or LICENSE.md for license and copyright information
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef ASMJIT_TEST_PERF_H_INCLUDED
|
||||
#define ASMJIT_TEST_PERF_H_INCLUDED
|
||||
|
||||
#include <asmjit/core.h>
|
||||
|
||||
#include <asmjit-testing/commons/asmjitutils.h>
|
||||
#include <asmjit-testing/commons/performancetimer.h>
|
||||
|
||||
namespace asmjit_perf_utils {
|
||||
|
||||
class TestErrorHandler : public asmjit::ErrorHandler {
|
||||
void handle_error(asmjit::Error err, const char* message, asmjit::BaseEmitter* origin) override {
|
||||
(void)err;
|
||||
(void)origin;
|
||||
printf("ERROR: %s\n", message);
|
||||
abort();
|
||||
}
|
||||
};
|
||||
|
||||
#ifndef ASMJIT_NO_BUILDER
|
||||
template<typename BuilderT, typename FuncT>
|
||||
static uint32_t calculate_instruction_count(asmjit::CodeHolder& code, asmjit::Arch arch, const FuncT& func) noexcept {
|
||||
BuilderT builder;
|
||||
TestErrorHandler eh;
|
||||
|
||||
asmjit::Environment env(arch);
|
||||
code.init(env);
|
||||
code.set_error_handler(&eh);
|
||||
code.attach(&builder);
|
||||
func(builder);
|
||||
|
||||
uint32_t count = 0;
|
||||
asmjit::BaseNode* node = builder.first_node();
|
||||
|
||||
while (node) {
|
||||
count += uint32_t(node->is_inst());
|
||||
node = node->next();
|
||||
}
|
||||
|
||||
code.reset();
|
||||
return count;
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline double calculate_mbps(double duration_us, uint64_t output_size) noexcept {
|
||||
if (duration_us == 0)
|
||||
return 0.0;
|
||||
|
||||
double bytes_total = double(output_size);
|
||||
return (bytes_total * 1000000) / (duration_us * 1024 * 1024);
|
||||
}
|
||||
|
||||
static inline double calculate_mips(double duration, uint64_t instruction_count) noexcept {
|
||||
if (duration == 0)
|
||||
return 0.0;
|
||||
|
||||
return double(instruction_count) * 1000000.0 / (duration * 1e6);
|
||||
}
|
||||
|
||||
template<typename EmitterT, typename FuncT>
|
||||
static void bench(asmjit::CodeHolder& code, asmjit::Arch arch, uint32_t num_iterations, const char* test_name, uint32_t instruction_count, const FuncT& func) noexcept {
|
||||
EmitterT emitter;
|
||||
TestErrorHandler eh;
|
||||
|
||||
const char* arch_name = asmjit_arch_as_string(arch);
|
||||
const char* emitter_name =
|
||||
emitter.is_assembler() ? "Assembler" :
|
||||
emitter.is_compiler() ? "Compiler" :
|
||||
emitter.is_builder() ? "Builder" : "Unknown";
|
||||
|
||||
uint64_t code_size = 0;
|
||||
asmjit::Environment env(arch);
|
||||
|
||||
PerformanceTimer timer;
|
||||
double duration = std::numeric_limits<double>::infinity();
|
||||
|
||||
code.init(env);
|
||||
code.set_error_handler(&eh);
|
||||
code.attach(&emitter);
|
||||
|
||||
for (uint32_t r = 0; r < num_iterations; r++) {
|
||||
code_size = 0;
|
||||
|
||||
timer.start();
|
||||
func(emitter);
|
||||
code_size += code.code_size();
|
||||
|
||||
code.reinit();
|
||||
timer.stop();
|
||||
|
||||
duration = asmjit::Support::min(duration, timer.duration() * 1000);
|
||||
}
|
||||
|
||||
printf(" [%-7s] %-9s %-16s | CodeSize:%5llu [B] | Time:%7.3f [us]", arch_name, emitter_name, test_name, (unsigned long long)code_size, duration);
|
||||
if (code_size) {
|
||||
printf(" | Speed:%7.1f [MiB/s]", calculate_mbps(duration, code_size));
|
||||
}
|
||||
else {
|
||||
printf(" | Speed: N/A ");
|
||||
}
|
||||
|
||||
if (instruction_count) {
|
||||
printf(", %8.1f [MInst/s]", calculate_mips(duration, instruction_count));
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
code.reset();
|
||||
}
|
||||
|
||||
} // {asmjit_perf_utils}
|
||||
|
||||
#endif // ASMJIT_TEST_PERF_H_INCLUDED
|
||||
707
asmjit-testing/bench/asmjit_bench_codegen_a64.cpp
Normal file
707
asmjit-testing/bench/asmjit_bench_codegen_a64.cpp
Normal file
@@ -0,0 +1,707 @@
|
||||
// This file is part of AsmJit project <https://asmjit.com>
|
||||
//
|
||||
// See <asmjit/core.h> or LICENSE.md for license and copyright information
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#include <asmjit/core.h>
|
||||
|
||||
#if !defined(ASMJIT_NO_AARCH64)
|
||||
#include <asmjit/a64.h>
|
||||
|
||||
#include <asmjit-testing/bench/asmjit_bench_codegen.h>
|
||||
|
||||
#include <limits>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
using namespace asmjit;
|
||||
|
||||
// Generates a long sequence of GP instructions.
|
||||
template<typename Emitter>
|
||||
static void generate_gp_sequence_internal(
|
||||
Emitter& cc,
|
||||
const a64::Gp& a, const a64::Gp& b, const a64::Gp& c, const a64::Gp& d) {
|
||||
|
||||
using namespace asmjit::a64;
|
||||
|
||||
Gp wa = a.w();
|
||||
Gp wb = b.w();
|
||||
Gp wc = c.w();
|
||||
Gp wd = d.w();
|
||||
|
||||
Gp xa = a.x();
|
||||
Gp xb = b.x();
|
||||
Gp xc = c.x();
|
||||
Gp xd = d.x();
|
||||
|
||||
Mem m = ptr(xd);
|
||||
|
||||
cc.mov(wa, 0);
|
||||
cc.mov(wb, 1);
|
||||
cc.mov(wc, 2);
|
||||
cc.mov(wd, 3);
|
||||
|
||||
cc.adc(wa, wb, wc);
|
||||
cc.adc(xa, xb, xc);
|
||||
cc.adc(wa, wzr, wc);
|
||||
cc.adc(xa, xzr, xc);
|
||||
cc.adc(wzr, wb, wc);
|
||||
cc.adc(xzr, xb, xc);
|
||||
cc.adcs(wa, wb, wc);
|
||||
cc.adcs(xa, xb, xc);
|
||||
cc.add(wa, wb, wc);
|
||||
cc.add(xa, xb, xc);
|
||||
cc.add(wa, wb, wc, lsl(3));
|
||||
cc.add(xa, xb, xc, lsl(3));
|
||||
cc.add(wa, wzr, wc);
|
||||
cc.add(xa, xzr, xc);
|
||||
cc.add(wzr, wb, wc);
|
||||
cc.add(xzr, xb, xc);
|
||||
cc.add(wc, wd, 0, lsl(12));
|
||||
cc.add(xc, xd, 0, lsl(12));
|
||||
cc.add(wc, wd, 1024, lsl(12));
|
||||
cc.add(xc, xd, 1024, lsl(12));
|
||||
cc.add(wc, wd, 1024, lsl(12));
|
||||
cc.add(xc, xd, 1024, lsl(12));
|
||||
cc.adds(wa, wb, wc);
|
||||
cc.adds(xa, xb, xc);
|
||||
cc.adr(xa, 0);
|
||||
cc.adr(xa, 256);
|
||||
cc.adrp(xa, 4096);
|
||||
cc.and_(wa, wb, wc);
|
||||
cc.and_(xa, xb, xc);
|
||||
cc.and_(wa, wb, 1);
|
||||
cc.and_(xa, xb, 1);
|
||||
cc.and_(wa, wb, 15);
|
||||
cc.and_(xa, xb, 15);
|
||||
cc.and_(wa, wzr, wc);
|
||||
cc.and_(xa, xzr, xc);
|
||||
cc.and_(wzr, wb, wc);
|
||||
cc.and_(xzr, xb, xc);
|
||||
cc.and_(wa, wb, 0x1);
|
||||
cc.and_(xa, xb, 0x1);
|
||||
cc.and_(wa, wb, 0xf);
|
||||
cc.and_(xa, xb, 0xf);
|
||||
cc.ands(wa, wb, wc);
|
||||
cc.ands(xa, xb, xc);
|
||||
cc.ands(wa, wzr, wc);
|
||||
cc.ands(xa, xzr, xc);
|
||||
cc.ands(wzr, wb, wc);
|
||||
cc.ands(xzr, xb, xc);
|
||||
cc.ands(wa, wb, 0x1);
|
||||
cc.ands(xa, xb, 0x1);
|
||||
cc.ands(wa, wb, 0xf);
|
||||
cc.ands(xa, xb, 0xf);
|
||||
cc.asr(wa, wb, 15);
|
||||
cc.asr(xa, xb, 15);
|
||||
cc.asrv(wa, wb, wc);
|
||||
cc.asrv(xa, xb, xc);
|
||||
cc.bfc(wa, 8, 16);
|
||||
cc.bfc(xa, 8, 16);
|
||||
cc.bfi(wa, wb, 8, 16);
|
||||
cc.bfi(xa, xb, 8, 16);
|
||||
cc.bfm(wa, wb, 8, 16);
|
||||
cc.bfm(xa, xb, 8, 16);
|
||||
cc.bfxil(wa, wb, 8, 16);
|
||||
cc.bfxil(xa, xb, 8, 16);
|
||||
cc.bic(wa, wb, wc, lsl(4));
|
||||
cc.bic(xa, xb, xc, lsl(4));
|
||||
cc.bic(wa, wzr, wc);
|
||||
cc.bic(xa, xzr, xc);
|
||||
cc.bics(wa, wb, wc, lsl(4));
|
||||
cc.bics(xa, xb, xc, lsl(4));
|
||||
cc.bics(wa, wzr, wc);
|
||||
cc.bics(xa, xzr, xc);
|
||||
cc.cas(wa, wb, m);
|
||||
cc.cas(xa, xb, m);
|
||||
cc.casa(wa, wb, m);
|
||||
cc.casa(xa, xb, m);
|
||||
cc.casab(wa, wb, m);
|
||||
cc.casah(wa, wb, m);
|
||||
cc.casal(wa, wb, m);
|
||||
cc.casal(xa, xb, m);
|
||||
cc.casalb(wa, wb, m);
|
||||
cc.casalh(wa, wb, m);
|
||||
cc.casb(wa, wb, m);
|
||||
cc.cash(wa, wb, m);
|
||||
cc.casl(wa, wb, m);
|
||||
cc.casl(xa, xb, m);
|
||||
cc.caslb(wa, wb, m);
|
||||
cc.caslh(wa, wb, m);
|
||||
cc.ccmn(wa, wb, 3, CondCode::kEQ);
|
||||
cc.ccmn(xa, xb, 3, CondCode::kEQ);
|
||||
cc.ccmn(wa, 2, 3, CondCode::kEQ);
|
||||
cc.ccmn(xa, 2, 3, CondCode::kEQ);
|
||||
cc.ccmn(wa, wzr, 3, CondCode::kEQ);
|
||||
cc.ccmn(xa, xzr, 3, CondCode::kEQ);
|
||||
cc.ccmp(wa, wb, 3, CondCode::kEQ);
|
||||
cc.ccmp(xa, xb, 3, CondCode::kEQ);
|
||||
cc.ccmp(wa, 2, 3, CondCode::kEQ);
|
||||
cc.ccmp(xa, 2, 3, CondCode::kEQ);
|
||||
cc.ccmp(wa, wzr, 3, CondCode::kEQ);
|
||||
cc.ccmp(xa, xzr, 3, CondCode::kEQ);
|
||||
cc.cinc(wa, wb, CondCode::kEQ);
|
||||
cc.cinc(xa, xb, CondCode::kEQ);
|
||||
cc.cinc(wzr, wb, CondCode::kEQ);
|
||||
cc.cinc(wa, wzr, CondCode::kEQ);
|
||||
cc.cinc(xzr, xb, CondCode::kEQ);
|
||||
cc.cinc(xa, xzr, CondCode::kEQ);
|
||||
cc.cinv(wa, wb, CondCode::kEQ);
|
||||
cc.cinv(xa, xb, CondCode::kEQ);
|
||||
cc.cinv(wzr, wb, CondCode::kEQ);
|
||||
cc.cinv(wa, wzr, CondCode::kEQ);
|
||||
cc.cinv(xzr, xb, CondCode::kEQ);
|
||||
cc.cinv(xa, xzr, CondCode::kEQ);
|
||||
cc.cls(wa, wb);
|
||||
cc.cls(xa, xb);
|
||||
cc.cls(wa, wzr);
|
||||
cc.cls(xa, xzr);
|
||||
cc.cls(wzr, wb);
|
||||
cc.cls(xzr, xb);
|
||||
cc.clz(wa, wb);
|
||||
cc.clz(xa, xb);
|
||||
cc.clz(wa, wzr);
|
||||
cc.clz(xa, xzr);
|
||||
cc.clz(wzr, wb);
|
||||
cc.clz(xzr, xb);
|
||||
cc.cmn(wa, 33);
|
||||
cc.cmn(xa, 33);
|
||||
cc.cmn(wa, wb);
|
||||
cc.cmn(xa, xb);
|
||||
cc.cmn(wa, wb, uxtb(2));
|
||||
cc.cmn(xa, wb, uxtb(2));
|
||||
cc.cmp(wa, 33);
|
||||
cc.cmp(xa, 33);
|
||||
cc.cmp(wa, wb);
|
||||
cc.cmp(xa, xb);
|
||||
cc.cmp(wa, wb, uxtb(2));
|
||||
cc.cmp(xa, wb, uxtb(2));
|
||||
cc.crc32b(wa, wb, wc);
|
||||
cc.crc32b(wzr, wb, wc);
|
||||
cc.crc32b(wa, wzr, wc);
|
||||
cc.crc32b(wa, wb, wzr);
|
||||
cc.crc32cb(wa, wb, wc);
|
||||
cc.crc32cb(wzr, wb, wc);
|
||||
cc.crc32cb(wa, wzr, wc);
|
||||
cc.crc32cb(wa, wb, wzr);
|
||||
cc.crc32ch(wa, wb, wc);
|
||||
cc.crc32ch(wzr, wb, wc);
|
||||
cc.crc32ch(wa, wzr, wc);
|
||||
cc.crc32ch(wa, wb, wzr);
|
||||
cc.crc32cw(wa, wb, wc);
|
||||
cc.crc32cw(wzr, wb, wc);
|
||||
cc.crc32cw(wa, wzr, wc);
|
||||
cc.crc32cw(wa, wb, wzr);
|
||||
cc.crc32cx(wa, wb, xc);
|
||||
cc.crc32cx(wzr, wb, xc);
|
||||
cc.crc32cx(wa, wzr, xc);
|
||||
cc.crc32cx(wa, wb, xzr);
|
||||
cc.crc32h(wa, wb, wc);
|
||||
cc.crc32h(wzr, wb, wc);
|
||||
cc.crc32h(wa, wzr, wc);
|
||||
cc.crc32h(wa, wb, wzr);
|
||||
cc.crc32w(wa, wb, wc);
|
||||
cc.crc32w(wzr, wb, wc);
|
||||
cc.crc32w(wa, wzr, wc);
|
||||
cc.crc32w(wa, wb, wzr);
|
||||
cc.crc32x(wa, wb, xc);
|
||||
cc.crc32x(wzr, wb, xc);
|
||||
cc.crc32x(wa, wzr, xc);
|
||||
cc.crc32x(wa, wb, xzr);
|
||||
cc.csel(wa, wb, wc, CondCode::kEQ);
|
||||
cc.csel(xa, xb, xc, CondCode::kEQ);
|
||||
cc.cset(wa, CondCode::kEQ);
|
||||
cc.cset(xa, CondCode::kEQ);
|
||||
cc.cset(wa, CondCode::kEQ);
|
||||
cc.cset(xa, CondCode::kEQ);
|
||||
cc.csetm(wa, CondCode::kEQ);
|
||||
cc.csetm(xa, CondCode::kEQ);
|
||||
cc.csinc(wa, wb, wc, CondCode::kEQ);
|
||||
cc.csinc(xa, xb, xc, CondCode::kEQ);
|
||||
cc.csinv(wa, wb, wc, CondCode::kEQ);
|
||||
cc.csinv(xa, xb, xc, CondCode::kEQ);
|
||||
cc.csneg(wa, wb, wc, CondCode::kEQ);
|
||||
cc.csneg(xa, xb, xc, CondCode::kEQ);
|
||||
cc.eon(wa, wb, wc);
|
||||
cc.eon(wzr, wb, wc);
|
||||
cc.eon(wa, wzr, wc);
|
||||
cc.eon(wa, wb, wzr);
|
||||
cc.eon(wa, wb, wc, lsl(4));
|
||||
cc.eon(xa, xb, xc);
|
||||
cc.eon(xzr, xb, xc);
|
||||
cc.eon(xa, xzr, xc);
|
||||
cc.eon(xa, xb, xzr);
|
||||
cc.eon(xa, xb, xc, lsl(4));
|
||||
cc.eor(wa, wb, wc);
|
||||
cc.eor(wzr, wb, wc);
|
||||
cc.eor(wa, wzr, wc);
|
||||
cc.eor(wa, wb, wzr);
|
||||
cc.eor(xa, xb, xc);
|
||||
cc.eor(xzr, xb, xc);
|
||||
cc.eor(xa, xzr, xc);
|
||||
cc.eor(xa, xb, xzr);
|
||||
cc.eor(wa, wb, wc, lsl(4));
|
||||
cc.eor(xa, xb, xc, lsl(4));
|
||||
cc.eor(wa, wb, 0x4000);
|
||||
cc.eor(xa, xb, 0x8000);
|
||||
cc.extr(wa, wb, wc, 15);
|
||||
cc.extr(wzr, wb, wc, 15);
|
||||
cc.extr(wa, wzr, wc, 15);
|
||||
cc.extr(wa, wb, wzr, 15);
|
||||
cc.extr(xa, xb, xc, 15);
|
||||
cc.extr(xzr, xb, xc, 15);
|
||||
cc.extr(xa, xzr, xc, 15);
|
||||
cc.extr(xa, xb, xzr, 15);
|
||||
cc.ldadd(wa, wb, m);
|
||||
cc.ldadd(xa, xb, m);
|
||||
cc.ldadda(wa, wb, m);
|
||||
cc.ldadda(xa, xb, m);
|
||||
cc.ldaddab(wa, wb, m);
|
||||
cc.ldaddah(wa, wb, m);
|
||||
cc.ldaddal(wa, wb, m);
|
||||
cc.ldaddal(xa, xb, m);
|
||||
cc.ldaddalb(wa, wb, m);
|
||||
cc.ldaddalh(wa, wb, m);
|
||||
cc.ldaddb(wa, wb, m);
|
||||
cc.ldaddh(wa, wb, m);
|
||||
cc.ldaddl(wa, wb, m);
|
||||
cc.ldaddl(xa, xb, m);
|
||||
cc.ldaddlb(wa, wb, m);
|
||||
cc.ldaddlh(wa, wb, m);
|
||||
cc.ldclr(wa, wb, m);
|
||||
cc.ldclr(xa, xb, m);
|
||||
cc.ldclra(wa, wb, m);
|
||||
cc.ldclra(xa, xb, m);
|
||||
cc.ldclrab(wa, wb, m);
|
||||
cc.ldclrah(wa, wb, m);
|
||||
cc.ldclral(wa, wb, m);
|
||||
cc.ldclral(xa, xb, m);
|
||||
cc.ldclralb(wa, wb, m);
|
||||
cc.ldclralh(wa, wb, m);
|
||||
cc.ldclrb(wa, wb, m);
|
||||
cc.ldclrh(wa, wb, m);
|
||||
cc.ldclrl(wa, wb, m);
|
||||
cc.ldclrl(xa, xb, m);
|
||||
cc.ldclrlb(wa, wb, m);
|
||||
cc.ldclrlh(wa, wb, m);
|
||||
cc.ldeor(wa, wb, m);
|
||||
cc.ldeor(xa, xb, m);
|
||||
cc.ldeora(wa, wb, m);
|
||||
cc.ldeora(xa, xb, m);
|
||||
cc.ldeorab(wa, wb, m);
|
||||
cc.ldeorah(wa, wb, m);
|
||||
cc.ldeoral(wa, wb, m);
|
||||
cc.ldeoral(xa, xb, m);
|
||||
cc.ldeoralb(wa, wb, m);
|
||||
cc.ldeoralh(wa, wb, m);
|
||||
cc.ldeorb(wa, wb, m);
|
||||
cc.ldeorh(wa, wb, m);
|
||||
cc.ldeorl(wa, wb, m);
|
||||
cc.ldeorl(xa, xb, m);
|
||||
cc.ldeorlb(wa, wb, m);
|
||||
cc.ldeorlh(wa, wb, m);
|
||||
cc.ldlar(wa, m);
|
||||
cc.ldlar(xa, m);
|
||||
cc.ldlarb(wa, m);
|
||||
cc.ldlarh(wa, m);
|
||||
cc.ldnp(wa, wb, m);
|
||||
cc.ldnp(xa, xb, m);
|
||||
cc.ldp(wa, wb, m);
|
||||
cc.ldp(xa, xb, m);
|
||||
cc.ldpsw(xa, xb, m);
|
||||
cc.ldr(wa, m);
|
||||
cc.ldr(xa, m);
|
||||
cc.ldrb(wa, m);
|
||||
cc.ldrh(wa, m);
|
||||
cc.ldrsw(xa, m);
|
||||
cc.ldraa(xa, m);
|
||||
cc.ldrab(xa, m);
|
||||
cc.ldset(wa, wb, m);
|
||||
cc.ldset(xa, xb, m);
|
||||
cc.ldseta(wa, wb, m);
|
||||
cc.ldseta(xa, xb, m);
|
||||
cc.ldsetab(wa, wb, m);
|
||||
cc.ldsetah(wa, wb, m);
|
||||
cc.ldsetal(wa, wb, m);
|
||||
cc.ldsetal(xa, xb, m);
|
||||
cc.ldsetalh(wa, wb, m);
|
||||
cc.ldsetalb(wa, wb, m);
|
||||
cc.ldsetb(wa, wb, m);
|
||||
cc.ldseth(wa, wb, m);
|
||||
cc.ldsetl(wa, wb, m);
|
||||
cc.ldsetl(xa, xb, m);
|
||||
cc.ldsetlb(wa, wb, m);
|
||||
cc.ldsetlh(wa, wb, m);
|
||||
cc.ldsmax(wa, wb, m);
|
||||
cc.ldsmax(xa, xb, m);
|
||||
cc.ldsmaxa(wa, wb, m);
|
||||
cc.ldsmaxa(xa, xb, m);
|
||||
cc.ldsmaxab(wa, wb, m);
|
||||
cc.ldsmaxah(wa, wb, m);
|
||||
cc.ldsmaxal(wa, wb, m);
|
||||
cc.ldsmaxal(xa, xb, m);
|
||||
cc.ldsmaxalb(wa, wb, m);
|
||||
cc.ldsmaxalh(wa, wb, m);
|
||||
cc.ldsmaxb(wa, wb, m);
|
||||
cc.ldsmaxh(wa, wb, m);
|
||||
cc.ldsmaxl(wa, wb, m);
|
||||
cc.ldsmaxl(xa, xb, m);
|
||||
cc.ldsmaxlb(wa, wb, m);
|
||||
cc.ldsmaxlh(wa, wb, m);
|
||||
cc.ldsmin(wa, wb, m);
|
||||
cc.ldsmin(xa, xb, m);
|
||||
cc.ldsmina(wa, wb, m);
|
||||
cc.ldsmina(xa, xb, m);
|
||||
cc.ldsminab(wa, wb, m);
|
||||
cc.ldsminah(wa, wb, m);
|
||||
cc.ldsminal(wa, wb, m);
|
||||
cc.ldsminal(xa, xb, m);
|
||||
cc.ldsminalb(wa, wb, m);
|
||||
cc.ldsminalh(wa, wb, m);
|
||||
cc.ldsminb(wa, wb, m);
|
||||
cc.ldsminh(wa, wb, m);
|
||||
cc.ldsminl(wa, wb, m);
|
||||
cc.ldsminl(xa, xb, m);
|
||||
cc.ldsminlb(wa, wb, m);
|
||||
cc.ldsminlh(wa, wb, m);
|
||||
cc.ldtr(wa, m);
|
||||
cc.ldtr(xa, m);
|
||||
cc.ldtrb(wa, m);
|
||||
cc.ldtrh(wa, m);
|
||||
cc.ldtrsb(wa, m);
|
||||
cc.ldtrsh(wa, m);
|
||||
cc.ldtrsw(xa, m);
|
||||
cc.ldumax(wa, wb, m);
|
||||
cc.ldumax(xa, xb, m);
|
||||
cc.ldumaxa(wa, wb, m);
|
||||
cc.ldumaxa(xa, xb, m);
|
||||
cc.ldumaxab(wa, wb, m);
|
||||
cc.ldumaxah(wa, wb, m);
|
||||
cc.ldumaxal(wa, wb, m);
|
||||
cc.ldumaxal(xa, xb, m);
|
||||
cc.ldumaxalb(wa, wb, m);
|
||||
cc.ldumaxalh(wa, wb, m);
|
||||
cc.ldumaxb(wa, wb, m);
|
||||
cc.ldumaxh(wa, wb, m);
|
||||
cc.ldumaxl(wa, wb, m);
|
||||
cc.ldumaxl(xa, xb, m);
|
||||
cc.ldumaxlb(wa, wb, m);
|
||||
cc.ldumaxlh(wa, wb, m);
|
||||
cc.ldumin(wa, wb, m);
|
||||
cc.ldumin(xa, xb, m);
|
||||
cc.ldumina(wa, wb, m);
|
||||
cc.ldumina(xa, xb, m);
|
||||
cc.lduminab(wa, wb, m);
|
||||
cc.lduminah(wa, wb, m);
|
||||
cc.lduminal(wa, wb, m);
|
||||
cc.lduminal(xa, xb, m);
|
||||
cc.lduminalb(wa, wb, m);
|
||||
cc.lduminalh(wa, wb, m);
|
||||
cc.lduminb(wa, wb, m);
|
||||
cc.lduminh(wa, wb, m);
|
||||
cc.lduminl(wa, wb, m);
|
||||
cc.lduminl(xa, xb, m);
|
||||
cc.lduminlb(wa, wb, m);
|
||||
cc.lduminlh(wa, wb, m);
|
||||
cc.ldur(wa, m);
|
||||
cc.ldur(xa, m);
|
||||
cc.ldurb(wa, m);
|
||||
cc.ldurh(wa, m);
|
||||
cc.ldursb(wa, m);
|
||||
cc.ldursh(wa, m);
|
||||
cc.ldursw(xa, m);
|
||||
cc.ldxp(wa, wb, m);
|
||||
cc.ldxp(xa, xb, m);
|
||||
cc.ldxr(wa, m);
|
||||
cc.ldxr(xa, m);
|
||||
cc.ldxrb(wa, m);
|
||||
cc.ldxrh(wa, m);
|
||||
cc.lsl(wa, wb, wc);
|
||||
cc.lsl(xa, xb, xc);
|
||||
cc.lsl(wa, wb, 15);
|
||||
cc.lsl(xa, xb, 15);
|
||||
cc.lslv(wa, wb, wc);
|
||||
cc.lslv(xa, xb, xc);
|
||||
cc.lsr(wa, wb, wc);
|
||||
cc.lsr(xa, xb, xc);
|
||||
cc.lsr(wa, wb, 15);
|
||||
cc.lsr(xa, xb, 15);
|
||||
cc.lsrv(wa, wb, wc);
|
||||
cc.lsrv(xa, xb, xc);
|
||||
cc.madd(wa, wb, wc, wd);
|
||||
cc.madd(xa, xb, xc, xd);
|
||||
cc.mneg(wa, wb, wc);
|
||||
cc.mneg(xa, xb, xc);
|
||||
cc.mov(wa, wb);
|
||||
cc.mov(xa, xb);
|
||||
cc.mov(wa, 0);
|
||||
cc.mov(wa, 1);
|
||||
cc.mov(wa, 2);
|
||||
cc.mov(wa, 3);
|
||||
cc.mov(wa, 4);
|
||||
cc.mov(wa, 5);
|
||||
cc.mov(wa, 6);
|
||||
cc.mov(wa, 7);
|
||||
cc.mov(wa, 8);
|
||||
cc.mov(wa, 9);
|
||||
cc.mov(wa, 10);
|
||||
cc.mov(wa, 0xA234);
|
||||
cc.mov(xa, 0xA23400000000);
|
||||
cc.msub(wa, wb, wc, wd);
|
||||
cc.msub(xa, xb, xc, xd);
|
||||
cc.mul(wa, wb, wc);
|
||||
cc.mul(xa, xb, xc);
|
||||
cc.mvn_(wa, wb);
|
||||
cc.mvn_(xa, xb);
|
||||
cc.mvn_(wa, wb, lsl(4));
|
||||
cc.mvn_(xa, xb, lsl(4));
|
||||
cc.neg(wa, wb);
|
||||
cc.neg(xa, xb);
|
||||
cc.neg(wa, wb, lsl(4));
|
||||
cc.neg(xa, xb, lsl(4));
|
||||
cc.negs(wa, wb);
|
||||
cc.negs(xa, xb);
|
||||
cc.negs(wa, wb, lsl(4));
|
||||
cc.negs(xa, xb, lsl(4));
|
||||
cc.ngc(wa, wb);
|
||||
cc.ngc(xa, xb);
|
||||
cc.ngcs(wa, wb);
|
||||
cc.ngcs(xa, xb);
|
||||
cc.orn(wa, wb, wc);
|
||||
cc.orn(xa, xb, xc);
|
||||
cc.orn(wa, wb, wc, lsl(4));
|
||||
cc.orn(xa, xb, xc, lsl(4));
|
||||
cc.orr(wa, wb, wc);
|
||||
cc.orr(xa, xb, xc);
|
||||
cc.orr(wa, wb, wc, lsl(4));
|
||||
cc.orr(xa, xb, xc, lsl(4));
|
||||
cc.orr(wa, wb, 0x4000);
|
||||
cc.orr(xa, xb, 0x8000);
|
||||
cc.rbit(wa, wb);
|
||||
cc.rbit(xa, xb);
|
||||
cc.rev(wa, wb);
|
||||
cc.rev(xa, xb);
|
||||
cc.rev16(wa, wb);
|
||||
cc.rev16(xa, xb);
|
||||
cc.rev32(xa, xb);
|
||||
cc.rev64(xa, xb);
|
||||
cc.ror(wa, wb, wc);
|
||||
cc.ror(xa, xb, xc);
|
||||
cc.ror(wa, wb, 15);
|
||||
cc.ror(xa, xb, 15);
|
||||
cc.rorv(wa, wb, wc);
|
||||
cc.rorv(xa, xb, xc);
|
||||
cc.sbc(wa, wb, wc);
|
||||
cc.sbc(xa, xb, xc);
|
||||
cc.sbcs(wa, wb, wc);
|
||||
cc.sbcs(xa, xb, xc);
|
||||
cc.sbfiz(wa, wb, 5, 10);
|
||||
cc.sbfiz(xa, xb, 5, 10);
|
||||
cc.sbfm(wa, wb, 5, 10);
|
||||
cc.sbfm(xa, xb, 5, 10);
|
||||
cc.sbfx(wa, wb, 5, 10);
|
||||
cc.sbfx(xa, xb, 5, 10);
|
||||
cc.sdiv(wa, wb, wc);
|
||||
cc.sdiv(xa, xb, xc);
|
||||
cc.smaddl(xa, wb, wc, xd);
|
||||
cc.smnegl(xa, wb, wc);
|
||||
cc.smsubl(xa, wb, wc, xd);
|
||||
cc.smulh(xa, xb, xc);
|
||||
cc.smull(xa, wb, wc);
|
||||
cc.stp(wa, wb, m);
|
||||
cc.stp(xa, xb, m);
|
||||
cc.sttr(wa, m);
|
||||
cc.sttr(xa, m);
|
||||
cc.sttrb(wa, m);
|
||||
cc.sttrh(wa, m);
|
||||
cc.stur(wa, m);
|
||||
cc.stur(xa, m);
|
||||
cc.sturb(wa, m);
|
||||
cc.sturh(wa, m);
|
||||
cc.stxp(wa, wb, wc, m);
|
||||
cc.stxp(wa, xb, xc, m);
|
||||
cc.stxr(wa, wb, m);
|
||||
cc.stxr(wa, xb, m);
|
||||
cc.stxrb(wa, wb, m);
|
||||
cc.stxrh(wa, wb, m);
|
||||
cc.sub(wa, wb, wc);
|
||||
cc.sub(xa, xb, xc);
|
||||
cc.sub(wa, wb, wc, lsl(3));
|
||||
cc.sub(xa, xb, xc, lsl(3));
|
||||
cc.subg(xa, xb, 32, 11);
|
||||
cc.subp(xa, xb, xc);
|
||||
cc.subps(xa, xb, xc);
|
||||
cc.subs(wa, wb, wc);
|
||||
cc.subs(xa, xb, xc);
|
||||
cc.subs(wa, wb, wc, lsl(3));
|
||||
cc.subs(xa, xb, xc, lsl(3));
|
||||
cc.sxtb(wa, wb);
|
||||
cc.sxtb(xa, wb);
|
||||
cc.sxth(wa, wb);
|
||||
cc.sxth(xa, wb);
|
||||
cc.sxtw(xa, wb);
|
||||
cc.tst(wa, 1);
|
||||
cc.tst(xa, 1);
|
||||
cc.tst(wa, wb);
|
||||
cc.tst(xa, xb);
|
||||
cc.tst(wa, wb, lsl(4));
|
||||
cc.tst(xa, xb, lsl(4));
|
||||
cc.udiv(wa, wb, wc);
|
||||
cc.udiv(xa, xb, xc);
|
||||
cc.ubfiz(wa, wb, 5, 10);
|
||||
cc.ubfiz(xa, xb, 5, 10);
|
||||
cc.ubfm(wa, wb, 5, 10);
|
||||
cc.ubfm(xa, xb, 5, 10);
|
||||
cc.ubfx(wa, wb, 5, 10);
|
||||
cc.ubfx(xa, xb, 5, 10);
|
||||
cc.umaddl(xa, wb, wc, xd);
|
||||
cc.umnegl(xa, wb, wc);
|
||||
cc.umsubl(xa, wb, wc, xd);
|
||||
cc.umulh(xa, xb, xc);
|
||||
cc.umull(xa, wb, wc);
|
||||
cc.uxtb(wa, wb);
|
||||
cc.uxth(wa, wb);
|
||||
}
|
||||
|
||||
static void generate_gp_sequence(BaseEmitter& emitter, bool emit_prolog_epilog) {
|
||||
#ifndef ASMJIT_NO_COMPILER
|
||||
if (emitter.is_compiler()) {
|
||||
a64::Compiler& cc = *emitter.as<a64::Compiler>();
|
||||
|
||||
a64::Gp a = cc.new_gp_ptr("a");
|
||||
a64::Gp b = cc.new_gp_ptr("b");
|
||||
a64::Gp c = cc.new_gp_ptr("c");
|
||||
a64::Gp d = cc.new_gp_ptr("d");
|
||||
|
||||
cc.add_func(FuncSignature::build<void>());
|
||||
generate_gp_sequence_internal(cc, a, b, c, d);
|
||||
cc.end_func();
|
||||
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef ASMJIT_NO_BUILDER
|
||||
if (emitter.is_builder()) {
|
||||
a64::Builder& cc = *emitter.as<a64::Builder>();
|
||||
|
||||
a64::Gp a = a64::x0;
|
||||
a64::Gp b = a64::x1;
|
||||
a64::Gp c = a64::x2;
|
||||
a64::Gp d = a64::x3;
|
||||
|
||||
if (emit_prolog_epilog) {
|
||||
FuncDetail func;
|
||||
func.init(FuncSignature::build<void, void*, const void*, size_t>(), cc.environment());
|
||||
|
||||
FuncFrame frame;
|
||||
frame.init(func);
|
||||
frame.add_dirty_regs(a, b, c, d);
|
||||
frame.finalize();
|
||||
|
||||
cc.emit_prolog(frame);
|
||||
generate_gp_sequence_internal(cc, a, b, c, d);
|
||||
cc.emit_epilog(frame);
|
||||
}
|
||||
else {
|
||||
generate_gp_sequence_internal(cc, a, b, c, d);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (emitter.is_assembler()) {
|
||||
a64::Assembler& cc = *emitter.as<a64::Assembler>();
|
||||
|
||||
a64::Gp a = a64::x0;
|
||||
a64::Gp b = a64::x1;
|
||||
a64::Gp c = a64::x2;
|
||||
a64::Gp d = a64::x3;
|
||||
|
||||
if (emit_prolog_epilog) {
|
||||
FuncDetail func;
|
||||
func.init(FuncSignature::build<void, void*, const void*, size_t>(), cc.environment());
|
||||
|
||||
FuncFrame frame;
|
||||
frame.init(func);
|
||||
frame.add_dirty_regs(a, b, c, d);
|
||||
frame.finalize();
|
||||
|
||||
cc.emit_prolog(frame);
|
||||
generate_gp_sequence_internal(cc, a, b, c, d);
|
||||
cc.emit_epilog(frame);
|
||||
}
|
||||
else {
|
||||
generate_gp_sequence_internal(cc, a, b, c, d);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename EmitterFn>
|
||||
static void benchmark_a64_function(Arch arch, uint32_t num_iterations, const char* description, const EmitterFn& emitter_fn) noexcept {
|
||||
CodeHolder code;
|
||||
printf("%s:\n", description);
|
||||
|
||||
uint32_t instruction_count = 0;
|
||||
|
||||
#ifndef ASMJIT_NO_BUILDER
|
||||
instruction_count = asmjit_perf_utils::calculate_instruction_count<a64::Builder>(code, arch, [&](a64::Builder& cc) {
|
||||
emitter_fn(cc, false);
|
||||
});
|
||||
#endif
|
||||
|
||||
asmjit_perf_utils::bench<a64::Assembler>(code, arch, num_iterations, "[raw]", instruction_count, [&](a64::Assembler& cc) {
|
||||
emitter_fn(cc, false);
|
||||
});
|
||||
|
||||
asmjit_perf_utils::bench<a64::Assembler>(code, arch, num_iterations, "[validated]", instruction_count, [&](a64::Assembler& cc) {
|
||||
cc.add_diagnostic_options(DiagnosticOptions::kValidateAssembler);
|
||||
emitter_fn(cc, false);
|
||||
});
|
||||
|
||||
asmjit_perf_utils::bench<a64::Assembler>(code, arch, num_iterations, "[prolog/epilog]", instruction_count, [&](a64::Assembler& cc) {
|
||||
cc.add_diagnostic_options(DiagnosticOptions::kValidateAssembler);
|
||||
emitter_fn(cc, true);
|
||||
});
|
||||
|
||||
#ifndef ASMJIT_NO_BUILDER
|
||||
asmjit_perf_utils::bench<a64::Builder>(code, arch, num_iterations, "[no-asm]", instruction_count, [&](a64::Builder& cc) {
|
||||
emitter_fn(cc, false);
|
||||
});
|
||||
|
||||
asmjit_perf_utils::bench<a64::Builder>(code, arch, num_iterations, "[finalized]", instruction_count, [&](a64::Builder& cc) {
|
||||
emitter_fn(cc, false);
|
||||
cc.finalize();
|
||||
});
|
||||
|
||||
asmjit_perf_utils::bench<a64::Builder>(code, arch, num_iterations, "[prolog/epilog]", instruction_count, [&](a64::Builder& cc) {
|
||||
emitter_fn(cc, true);
|
||||
cc.finalize();
|
||||
});
|
||||
#endif
|
||||
|
||||
#ifndef ASMJIT_NO_COMPILER
|
||||
asmjit_perf_utils::bench<a64::Compiler>(code, arch, num_iterations, "[no-asm]", instruction_count, [&](a64::Compiler& cc) {
|
||||
emitter_fn(cc, true);
|
||||
});
|
||||
|
||||
asmjit_perf_utils::bench<a64::Compiler>(code, arch, num_iterations, "[finalized]", instruction_count, [&](a64::Compiler& cc) {
|
||||
emitter_fn(cc, true);
|
||||
cc.finalize();
|
||||
});
|
||||
#endif
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
void benchmark_aarch64_emitters(uint32_t num_iterations) {
|
||||
static const char description[] = "GpSequence (Sequence of GP instructions - reg/mem)";
|
||||
benchmark_a64_function(Arch::kAArch64, num_iterations, description, [](BaseEmitter& emitter, bool emit_prolog_epilog) {
|
||||
generate_gp_sequence(emitter, emit_prolog_epilog);
|
||||
});
|
||||
}
|
||||
|
||||
#endif // !ASMJIT_NO_AARCH64
|
||||
5336
asmjit-testing/bench/asmjit_bench_codegen_x86.cpp
Normal file
5336
asmjit-testing/bench/asmjit_bench_codegen_x86.cpp
Normal file
File diff suppressed because it is too large
Load Diff
482
asmjit-testing/bench/asmjit_bench_overhead.cpp
Normal file
482
asmjit-testing/bench/asmjit_bench_overhead.cpp
Normal file
@@ -0,0 +1,482 @@
|
||||
#include <asmjit/host.h>
|
||||
|
||||
#include <asmjit-testing/commons/asmjitutils.h>
|
||||
#include <asmjit-testing/commons/cmdline.h>
|
||||
#include <asmjit-testing/commons/performancetimer.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
using namespace asmjit;
|
||||
|
||||
static void print_app_info(size_t n) noexcept {
|
||||
printf("AsmJit Benchmark Overhead v%u.%u.%u [Arch=%s] [Mode=%s]\n\n",
|
||||
unsigned((ASMJIT_LIBRARY_VERSION >> 16) ),
|
||||
unsigned((ASMJIT_LIBRARY_VERSION >> 8) & 0xFF),
|
||||
unsigned((ASMJIT_LIBRARY_VERSION ) & 0xFF),
|
||||
asmjit_arch_as_string(Arch::kHost),
|
||||
asmjit_build_type()
|
||||
);
|
||||
|
||||
printf("This benchmark was designed to benchmark the cost of initialization and\n"
|
||||
"reset (or reinitialization) of CodeHolder and Emitters; and the cost of\n"
|
||||
"moving a minimal assembled function to executable memory. Each output line\n"
|
||||
"uses \"<Test> [Func] [Finalize] [RT]\" format, with the following meaning:\n"
|
||||
"\n"
|
||||
" - <Test> - test case name - either 'CodeHolder' only or an emitter\n"
|
||||
" - [Func] - function was assembled\n"
|
||||
" - [Finalize] - function was finalized (Builder/Compiler)\n"
|
||||
" - [RT] - function was added to JitRuntime and then removed from it\n"
|
||||
"\n"
|
||||
"Essentially the output provides an insight into the cost of reusing\n"
|
||||
"CodeHolder and other emitters, and the cost of assembling, finalizing,\n"
|
||||
"and moving the assembled code into executable memory by separating each\n"
|
||||
"phase.\n\n"
|
||||
);
|
||||
|
||||
printf("The number of iterations benchmarked: %zu (override by --count=n)\n", n);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
#if !defined(ASMJIT_NO_JIT)
|
||||
|
||||
class MyErrorHandler : public ErrorHandler {
|
||||
public:
|
||||
void handle_error(asmjit::Error err, const char* message, asmjit::BaseEmitter* origin) override {
|
||||
Support::maybe_unused(err, origin);
|
||||
fprintf(stderr, "AsmJit error: %s\n", message);
|
||||
}
|
||||
};
|
||||
|
||||
enum class InitStrategy : uint32_t {
|
||||
kInitReset,
|
||||
kReinit
|
||||
};
|
||||
|
||||
static inline void bench_codeholder(InitStrategy strategy, size_t count) {
|
||||
JitRuntime rt;
|
||||
CodeHolder code;
|
||||
|
||||
if (strategy == InitStrategy::kInitReset) {
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
code.init(rt.environment());
|
||||
code.reset();
|
||||
}
|
||||
}
|
||||
else {
|
||||
code.init(rt.environment());
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
code.reinit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if ASMJIT_ARCH_X86 != 0 && !defined(ASMJIT_NO_X86)
|
||||
template<typename EmitterT>
|
||||
static ASMJIT_INLINE void emit_raw_func(EmitterT& emitter) {
|
||||
emitter.mov(x86::eax, 0);
|
||||
emitter.ret();
|
||||
}
|
||||
|
||||
template<typename CompilerT>
|
||||
static ASMJIT_INLINE void compile_raw_func(CompilerT& cc) {
|
||||
x86::Gp r = cc.new_gp32();
|
||||
cc.mov(r, 0);
|
||||
cc.ret(r);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ASMJIT_ARCH_ARM == 64 && !defined(ASMJIT_NO_AARCH64)
|
||||
template<typename EmitterT>
|
||||
static ASMJIT_INLINE void emit_raw_func(EmitterT& emitter) {
|
||||
emitter.mov(a64::w0, 0);
|
||||
emitter.ret(a64::x30);
|
||||
}
|
||||
|
||||
template<typename CompilerT>
|
||||
static ASMJIT_INLINE void compile_raw_func(CompilerT& cc) {
|
||||
a64::Gp gp = cc.new_gp32();
|
||||
cc.mov(gp, 0);
|
||||
cc.ret(gp);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(ASMJIT_HAS_HOST_BACKEND)
|
||||
template<typename AssemblerT>
|
||||
static inline void bench_assembler(InitStrategy strategy, size_t count) {
|
||||
JitRuntime rt;
|
||||
CodeHolder code;
|
||||
AssemblerT a;
|
||||
MyErrorHandler eh;
|
||||
|
||||
if (strategy == InitStrategy::kInitReset) {
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
code.init(rt.environment());
|
||||
code.set_error_handler(&eh);
|
||||
code.attach(&a);
|
||||
code.reset();
|
||||
}
|
||||
}
|
||||
else {
|
||||
code.init(rt.environment());
|
||||
code.set_error_handler(&eh);
|
||||
code.attach(&a);
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
code.reinit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename AssemblerT>
|
||||
static inline void bench_assembler_func(InitStrategy strategy, size_t count) {
|
||||
JitRuntime rt;
|
||||
CodeHolder code;
|
||||
AssemblerT a;
|
||||
MyErrorHandler eh;
|
||||
|
||||
if (strategy == InitStrategy::kInitReset) {
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
code.init(rt.environment());
|
||||
code.set_error_handler(&eh);
|
||||
code.attach(&a);
|
||||
emit_raw_func(a);
|
||||
code.reset();
|
||||
}
|
||||
}
|
||||
else {
|
||||
code.init(rt.environment());
|
||||
code.set_error_handler(&eh);
|
||||
code.attach(&a);
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
code.reinit();
|
||||
emit_raw_func(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename AssemblerT>
|
||||
static inline void bench_assembler_func_rt(InitStrategy strategy, size_t count) {
|
||||
JitRuntime rt;
|
||||
CodeHolder code;
|
||||
AssemblerT a;
|
||||
MyErrorHandler eh;
|
||||
|
||||
using Func = uint32_t(*)(void);
|
||||
|
||||
if (strategy == InitStrategy::kInitReset) {
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
code.init(rt.environment());
|
||||
code.set_error_handler(&eh);
|
||||
code.attach(&a);
|
||||
emit_raw_func(a);
|
||||
|
||||
Func fn;
|
||||
rt.add(&fn, &code);
|
||||
rt.release(fn);
|
||||
|
||||
code.reset();
|
||||
}
|
||||
}
|
||||
else {
|
||||
code.init(rt.environment());
|
||||
code.set_error_handler(&eh);
|
||||
code.attach(&a);
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
code.reinit();
|
||||
emit_raw_func(a);
|
||||
|
||||
Func fn;
|
||||
rt.add(&fn, &code);
|
||||
rt.release(fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(ASMJIT_HAS_HOST_BACKEND) && !defined(ASMJIT_NO_BUILDER)
|
||||
template<typename BuilderT>
|
||||
static inline void bench_builder(InitStrategy strategy, size_t count) {
|
||||
JitRuntime rt;
|
||||
CodeHolder code;
|
||||
BuilderT b;
|
||||
MyErrorHandler eh;
|
||||
|
||||
if (strategy == InitStrategy::kInitReset) {
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
code.init(rt.environment());
|
||||
code.set_error_handler(&eh);
|
||||
code.attach(&b);
|
||||
code.reset();
|
||||
}
|
||||
}
|
||||
else {
|
||||
code.init(rt.environment());
|
||||
code.set_error_handler(&eh);
|
||||
code.attach(&b);
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
code.reinit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename BuilderT>
|
||||
static inline void bench_builder_func(InitStrategy strategy, size_t count, bool finalize) {
|
||||
JitRuntime rt;
|
||||
CodeHolder code;
|
||||
BuilderT b;
|
||||
MyErrorHandler eh;
|
||||
|
||||
if (strategy == InitStrategy::kInitReset) {
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
code.init(rt.environment());
|
||||
code.set_error_handler(&eh);
|
||||
code.attach(&b);
|
||||
emit_raw_func(b);
|
||||
|
||||
if (finalize) {
|
||||
b.finalize();
|
||||
}
|
||||
code.reset();
|
||||
}
|
||||
}
|
||||
else {
|
||||
code.init(rt.environment());
|
||||
code.set_error_handler(&eh);
|
||||
code.attach(&b);
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
code.reinit();
|
||||
emit_raw_func(b);
|
||||
|
||||
if (finalize) {
|
||||
b.finalize();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename BuilderT>
|
||||
static inline void bench_builder_func_finalize_rt(InitStrategy strategy, size_t count) {
|
||||
JitRuntime rt;
|
||||
CodeHolder code;
|
||||
BuilderT b;
|
||||
MyErrorHandler eh;
|
||||
|
||||
using Func = uint32_t(*)(void);
|
||||
|
||||
if (strategy == InitStrategy::kInitReset) {
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
code.init(rt.environment());
|
||||
code.set_error_handler(&eh);
|
||||
code.attach(&b);
|
||||
emit_raw_func(b);
|
||||
b.finalize();
|
||||
|
||||
Func fn;
|
||||
rt.add(&fn, &code);
|
||||
rt.release(fn);
|
||||
|
||||
code.reset();
|
||||
}
|
||||
}
|
||||
else {
|
||||
code.init(rt.environment());
|
||||
code.set_error_handler(&eh);
|
||||
code.attach(&b);
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
code.reinit();
|
||||
emit_raw_func(b);
|
||||
b.finalize();
|
||||
|
||||
Func fn;
|
||||
rt.add(&fn, &code);
|
||||
rt.release(fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // ASMJIT_HAS_HOST_BACKEND && !ASMJIT_NO_BUILDER
|
||||
|
||||
#if defined(ASMJIT_HAS_HOST_BACKEND) && !defined(ASMJIT_NO_COMPILER)
|
||||
template<typename CompilerT>
|
||||
static inline void bench_compiler(InitStrategy strategy, size_t count) {
|
||||
JitRuntime rt;
|
||||
CodeHolder code;
|
||||
CompilerT cc;
|
||||
MyErrorHandler eh;
|
||||
|
||||
if (strategy == InitStrategy::kInitReset) {
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
code.init(rt.environment());
|
||||
code.set_error_handler(&eh);
|
||||
code.attach(&cc);
|
||||
code.reset();
|
||||
}
|
||||
}
|
||||
else {
|
||||
code.init(rt.environment());
|
||||
code.set_error_handler(&eh);
|
||||
code.attach(&cc);
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
code.reinit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename CompilerT>
|
||||
static inline void bench_compiler_func(InitStrategy strategy, size_t count, bool finalize) {
|
||||
JitRuntime rt;
|
||||
CodeHolder code;
|
||||
CompilerT cc;
|
||||
MyErrorHandler eh;
|
||||
|
||||
if (strategy == InitStrategy::kInitReset) {
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
code.init(rt.environment());
|
||||
code.set_error_handler(&eh);
|
||||
code.attach(&cc);
|
||||
|
||||
(void)cc.add_func(FuncSignature::build<uint32_t>());
|
||||
compile_raw_func(cc);
|
||||
cc.end_func();
|
||||
|
||||
if (finalize) {
|
||||
cc.finalize();
|
||||
}
|
||||
|
||||
code.reset();
|
||||
}
|
||||
}
|
||||
else {
|
||||
code.init(rt.environment());
|
||||
code.set_error_handler(&eh);
|
||||
code.attach(&cc);
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
code.reinit();
|
||||
|
||||
(void)cc.add_func(FuncSignature::build<uint32_t>());
|
||||
compile_raw_func(cc);
|
||||
cc.end_func();
|
||||
|
||||
if (finalize) {
|
||||
cc.finalize();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename CompilerT>
|
||||
static inline void bench_compiler_func_rt(InitStrategy strategy, size_t count) {
|
||||
JitRuntime rt;
|
||||
CodeHolder code;
|
||||
CompilerT cc;
|
||||
MyErrorHandler eh;
|
||||
|
||||
using Func = uint32_t(*)(void);
|
||||
|
||||
if (strategy == InitStrategy::kInitReset) {
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
code.init(rt.environment());
|
||||
code.set_error_handler(&eh);
|
||||
code.attach(&cc);
|
||||
|
||||
(void)cc.add_func(FuncSignature::build<uint32_t>());
|
||||
compile_raw_func(cc);
|
||||
cc.end_func();
|
||||
cc.finalize();
|
||||
|
||||
Func fn;
|
||||
rt.add(&fn, &code);
|
||||
rt.release(fn);
|
||||
|
||||
code.reset();
|
||||
}
|
||||
}
|
||||
else {
|
||||
code.init(rt.environment());
|
||||
code.set_error_handler(&eh);
|
||||
code.attach(&cc);
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
code.reinit();
|
||||
|
||||
(void)cc.add_func(FuncSignature::build<uint32_t>());
|
||||
compile_raw_func(cc);
|
||||
cc.end_func();
|
||||
cc.finalize();
|
||||
|
||||
Func fn;
|
||||
rt.add(&fn, &code);
|
||||
rt.release(fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // ASMJIT_HAS_HOST_BACKEND && !ASMJIT_NO_COMPILER
|
||||
|
||||
template<typename Lambda>
|
||||
static inline void test_perf(const char* bench_name, InitStrategy strategy, size_t n, Lambda&& fn) {
|
||||
PerformanceTimer timer;
|
||||
const char* strategy_name = strategy == InitStrategy::kInitReset ? "init/reset" : "reinit ";
|
||||
|
||||
timer.start();
|
||||
fn(strategy, n);
|
||||
timer.stop();
|
||||
|
||||
printf("%-31s [%s]: %8.3f [ms]\n", bench_name, strategy_name, timer.duration());
|
||||
}
|
||||
|
||||
static inline void test_perf_all(InitStrategy strategy, size_t n) {
|
||||
using IS = InitStrategy;
|
||||
|
||||
test_perf("CodeHolder (Only)" , strategy, n, [](IS s, size_t n) { bench_codeholder(s, n); });
|
||||
|
||||
#if defined(ASMJIT_HAS_HOST_BACKEND)
|
||||
test_perf("Assembler" , strategy, n, [](IS s, size_t n) { bench_assembler<host::Assembler>(s, n); });
|
||||
test_perf("Assembler + Func" , strategy, n, [](IS s, size_t n) { bench_assembler_func<host::Assembler>(s, n); });
|
||||
test_perf("Assembler + Func + RT" , strategy, n, [](IS s, size_t n) { bench_assembler_func_rt<host::Assembler>(s, n); });
|
||||
#endif
|
||||
|
||||
#if defined(ASMJIT_HAS_HOST_BACKEND) && !defined(ASMJIT_NO_BUILDER)
|
||||
test_perf("Builder" , strategy, n, [](IS s, size_t n) { bench_builder<host::Builder>(s, n); });
|
||||
test_perf("Builder + Func" , strategy, n, [](IS s, size_t n) { bench_builder_func<host::Builder>(s, n, false); });
|
||||
test_perf("Builder + Func + Finalize" , strategy, n, [](IS s, size_t n) { bench_builder_func<host::Builder>(s, n, true); });
|
||||
test_perf("Builder + Func + Finalize + RT" , strategy, n, [](IS s, size_t n) { bench_builder_func_finalize_rt<host::Builder>(s, n); });
|
||||
#endif
|
||||
|
||||
#if defined(ASMJIT_HAS_HOST_BACKEND) && !defined(ASMJIT_NO_COMPILER)
|
||||
|
||||
test_perf("Compiler" , strategy, n, [](IS s, size_t n) { bench_compiler<host::Compiler>(s, n); });
|
||||
test_perf("Compiler + Func" , strategy, n, [](IS s, size_t n) { bench_compiler_func<host::Compiler>(s, n, false); });
|
||||
test_perf("Compiler + Func + Finalize" , strategy, n, [](IS s, size_t n) { bench_compiler_func<host::Compiler>(s, n, true); });
|
||||
|
||||
test_perf("Compiler + Func + Finalize + RT", strategy, n, [](IS s, size_t n) { bench_compiler_func_rt<host::Compiler>(s, n); });
|
||||
#endif
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
CmdLine cmd_line(argc, argv);
|
||||
size_t n = cmd_line.value_as_uint("--count", 1000000);
|
||||
|
||||
print_app_info(n);
|
||||
|
||||
test_perf_all(InitStrategy::kInitReset, n);
|
||||
printf("\n");
|
||||
test_perf_all(InitStrategy::kReinit, n);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int main() {
|
||||
print_app_info(0);
|
||||
printf("!!AsmJit Benchmark Reuse is currently disabled: <ASMJIT_NO_JIT> or unsuitable target architecture !!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
517
asmjit-testing/bench/asmjit_bench_regalloc.cpp
Normal file
517
asmjit-testing/bench/asmjit_bench_regalloc.cpp
Normal file
@@ -0,0 +1,517 @@
|
||||
// This file is part of AsmJit project <https://asmjit.com>
|
||||
//
|
||||
// See asmjit.h or LICENSE.md for license and copyright information
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#include <asmjit/core.h>
|
||||
|
||||
#if !defined(ASMJIT_NO_X86)
|
||||
#include <asmjit/x86.h>
|
||||
#endif // !ASMJIT_NO_X86
|
||||
|
||||
#if !defined(ASMJIT_NO_AARCH64)
|
||||
#include <asmjit/a64.h>
|
||||
#endif // !ASMJIT_NO_AARCH64
|
||||
|
||||
#include <asmjit-testing/commons/asmjitutils.h>
|
||||
|
||||
#if !defined(ASMJIT_NO_COMPILER)
|
||||
#include <asmjit-testing/commons/cmdline.h>
|
||||
#include <asmjit-testing/commons/performancetimer.h>
|
||||
#include <asmjit-testing/commons/random.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
using namespace asmjit;
|
||||
|
||||
static void print_app_info() {
|
||||
printf("AsmJit Benchmark RegAlloc v%u.%u.%u [Arch=%s] [Mode=%s]\n\n",
|
||||
unsigned((ASMJIT_LIBRARY_VERSION >> 16) ),
|
||||
unsigned((ASMJIT_LIBRARY_VERSION >> 8) & 0xFF),
|
||||
unsigned((ASMJIT_LIBRARY_VERSION ) & 0xFF),
|
||||
asmjit_arch_as_string(Arch::kHost),
|
||||
asmjit_build_type()
|
||||
);
|
||||
}
|
||||
|
||||
#if !defined(ASMJIT_NO_COMPILER)
|
||||
|
||||
class BenchRegAllocApp {
|
||||
public:
|
||||
const char* _arch = nullptr;
|
||||
bool _help_only = false;
|
||||
bool _verbose = false;
|
||||
uint32_t _maximum_complexity = 65536;
|
||||
|
||||
BenchRegAllocApp() noexcept
|
||||
: _arch("all") {}
|
||||
~BenchRegAllocApp() noexcept {}
|
||||
|
||||
template<class T>
|
||||
inline void add_t() { T::add(*this); }
|
||||
|
||||
int handle_args(int argc, const char* const* argv);
|
||||
void show_info();
|
||||
|
||||
bool should_run_arch(Arch arch) const noexcept;
|
||||
void emit_code(BaseCompiler* cc, uint32_t complexity, uint32_t reg_count);
|
||||
|
||||
#if !defined(ASMJIT_NO_X86)
|
||||
void emit_code_x86(x86::Compiler* cc, uint32_t complexity, uint32_t reg_count);
|
||||
#endif // !ASMJIT_NO_X86
|
||||
|
||||
#if !defined(ASMJIT_NO_AARCH64)
|
||||
void emit_code_aarch64(a64::Compiler* cc, uint32_t complexity, uint32_t reg_count);
|
||||
#endif // !ASMJIT_NO_AARCH64
|
||||
|
||||
int run();
|
||||
bool run_arch(Arch arch);
|
||||
};
|
||||
|
||||
int BenchRegAllocApp::handle_args(int argc, const char* const* argv) {
|
||||
CmdLine cmd(argc, argv);
|
||||
_arch = cmd.value_of("--arch", "all");
|
||||
_maximum_complexity = cmd.value_as_uint("--complexity", _maximum_complexity);
|
||||
|
||||
if (cmd.has_arg("--help")) _help_only = true;
|
||||
if (cmd.has_arg("--verbose")) _verbose = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void BenchRegAllocApp::show_info() {
|
||||
print_app_info();
|
||||
|
||||
printf("Usage:\n");
|
||||
printf(" asmjit_bench_regalloc [arguments]\n");
|
||||
printf("\n");
|
||||
|
||||
printf("Arguments:\n");
|
||||
printf(" --help Show usage only\n");
|
||||
printf(" --arch=<NAME> Select architecture to run ('all' by default)\n");
|
||||
printf(" --verbose Verbose output\n");
|
||||
printf(" --complexity=<n> Maximum complexity to test (%u)\n", _maximum_complexity);
|
||||
printf("\n");
|
||||
|
||||
printf("Architectures:\n");
|
||||
#if !defined(ASMJIT_NO_X86)
|
||||
printf(" --arch=x86 32-bit X86 architecture (X86)\n");
|
||||
printf(" --arch=x64 64-bit X86 architecture (X86_64)\n");
|
||||
#endif
|
||||
#if !defined(ASMJIT_NO_AARCH64)
|
||||
printf(" --arch=aarch64 64-bit ARM architecture (AArch64)\n");
|
||||
#endif
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
bool BenchRegAllocApp::should_run_arch(Arch arch) const noexcept {
|
||||
if (strcmp(_arch, "all") == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strcmp(_arch, "x86") == 0 && arch == Arch::kX86) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strcmp(_arch, "x64") == 0 && arch == Arch::kX64) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strcmp(_arch, "aarch64") == 0 && arch == Arch::kAArch64) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void BenchRegAllocApp::emit_code(BaseCompiler* cc, uint32_t complexity, uint32_t reg_count) {
|
||||
#if !defined(ASMJIT_NO_X86)
|
||||
if (cc->arch() == Arch::kX86 || cc->arch() == Arch::kX64) {
|
||||
emit_code_x86(cc->as<x86::Compiler>(), complexity, reg_count);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !defined(ASMJIT_NO_AARCH64)
|
||||
if (cc->arch() == Arch::kAArch64) {
|
||||
emit_code_aarch64(cc->as<a64::Compiler>(), complexity, reg_count);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
constexpr size_t kLocalRegCount = 3;
|
||||
constexpr size_t kLocalOpCount = 15;
|
||||
|
||||
#if !defined(ASMJIT_NO_X86)
|
||||
void BenchRegAllocApp::emit_code_x86(x86::Compiler* cc, uint32_t complexity, uint32_t reg_count) {
|
||||
TestUtils::Random rnd(0x1234);
|
||||
|
||||
std::vector<Label> labels;
|
||||
std::vector<uint32_t> used_labels;
|
||||
std::vector<x86::Vec> virt_regs;
|
||||
|
||||
x86::Gp arg_ptr = cc->new_gp_ptr("arg_ptr");
|
||||
x86::Gp counter = cc->new_gp_ptr("counter");
|
||||
|
||||
for (size_t i = 0; i < complexity; i++) {
|
||||
labels.push_back(cc->new_label());
|
||||
used_labels.push_back(0u);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < reg_count; i++) {
|
||||
virt_regs.push_back(cc->new_xmm_sd("v%u", unsigned(i)));
|
||||
}
|
||||
|
||||
FuncNode* func = cc->add_func(FuncSignature::build<void, size_t, void*>());
|
||||
func->add_attributes(FuncAttributes::kX86_AVXEnabled);
|
||||
func->set_arg(0, counter);
|
||||
func->set_arg(1, arg_ptr);
|
||||
|
||||
for (size_t i = 0; i < reg_count; i++) {
|
||||
cc->vmovsd(virt_regs[i], x86::ptr_64(arg_ptr, int32_t(i * 8)));
|
||||
}
|
||||
|
||||
auto next_label = [&]() {
|
||||
uint32_t id = rnd.next_uint32() % complexity;
|
||||
if (used_labels[id] > 1) {
|
||||
id = 0;
|
||||
do {
|
||||
if (++id >= complexity) {
|
||||
id = 0;
|
||||
}
|
||||
} while (used_labels[id] != 0);
|
||||
}
|
||||
|
||||
used_labels[id]++;
|
||||
return labels[id];
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < labels.size(); i++) {
|
||||
cc->bind(labels[i]);
|
||||
|
||||
x86::Vec locals[kLocalRegCount];
|
||||
for (size_t j = 0; j < kLocalRegCount; j++) {
|
||||
locals[j] = cc->new_xmm_sd("local%u", unsigned(j));
|
||||
}
|
||||
|
||||
size_t local_op_threshold = kLocalOpCount - kLocalRegCount;
|
||||
|
||||
for (size_t j = 0; j < 15; j++) {
|
||||
uint32_t op = rnd.next_uint32() % 6u;
|
||||
uint32_t id1 = rnd.next_uint32() % reg_count;
|
||||
uint32_t id2 = rnd.next_uint32() % reg_count;
|
||||
|
||||
x86::Vec v0 = virt_regs[id1];
|
||||
x86::Vec v1 = virt_regs[id1];
|
||||
x86::Vec v2 = virt_regs[id2];
|
||||
|
||||
if (j < kLocalRegCount) {
|
||||
v0 = locals[j];
|
||||
}
|
||||
|
||||
if (j >= local_op_threshold) {
|
||||
v2 = locals[j - local_op_threshold];
|
||||
}
|
||||
|
||||
switch (op) {
|
||||
case 0: cc->vaddsd(v0, v1, v2); break;
|
||||
case 1: cc->vsubsd(v0, v1, v2); break;
|
||||
case 2: cc->vmulsd(v0, v1, v2); break;
|
||||
case 3: cc->vdivsd(v0, v1, v2); break;
|
||||
case 4: cc->vminsd(v0, v1, v2); break;
|
||||
case 5: cc->vmaxsd(v0, v1, v2); break;
|
||||
}
|
||||
}
|
||||
|
||||
cc->sub(counter, 1);
|
||||
cc->jns(next_label());
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < reg_count; i++) {
|
||||
cc->vmovsd(x86::ptr_64(arg_ptr, int32_t(i * 8)), virt_regs[i]);
|
||||
}
|
||||
|
||||
cc->end_func();
|
||||
}
|
||||
#endif // !ASMJIT_NO_X86
|
||||
|
||||
#if !defined(ASMJIT_NO_AARCH64)
|
||||
void BenchRegAllocApp::emit_code_aarch64(a64::Compiler* cc, uint32_t complexity, uint32_t reg_count) {
|
||||
TestUtils::Random rnd(0x1234);
|
||||
|
||||
std::vector<Label> labels;
|
||||
std::vector<uint32_t> used_labels;
|
||||
std::vector<a64::Vec> virt_regs;
|
||||
|
||||
a64::Gp arg_ptr = cc->new_gp_ptr("arg_ptr");
|
||||
a64::Gp counter = cc->new_gp_ptr("counter");
|
||||
|
||||
for (size_t i = 0; i < complexity; i++) {
|
||||
labels.push_back(cc->new_label());
|
||||
used_labels.push_back(0u);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < reg_count; i++) {
|
||||
virt_regs.push_back(cc->new_vec_d("v%u", unsigned(i)));
|
||||
}
|
||||
|
||||
FuncNode* func = cc->add_func(FuncSignature::build<void, size_t, void*>());
|
||||
func->add_attributes(FuncAttributes::kX86_AVXEnabled);
|
||||
func->set_arg(0, counter);
|
||||
func->set_arg(1, arg_ptr);
|
||||
|
||||
for (size_t i = 0; i < reg_count; i++) {
|
||||
cc->ldr(virt_regs[i].d(), a64::ptr(arg_ptr, int32_t(i * 8) & 1023));
|
||||
}
|
||||
|
||||
auto next_label = [&]() {
|
||||
uint32_t id = rnd.next_uint32() % complexity;
|
||||
if (used_labels[id] > 1) {
|
||||
id = 0;
|
||||
do {
|
||||
if (++id >= complexity) {
|
||||
id = 0;
|
||||
}
|
||||
} while (used_labels[id] != 0);
|
||||
}
|
||||
|
||||
used_labels[id]++;
|
||||
return labels[id];
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < labels.size(); i++) {
|
||||
cc->bind(labels[i]);
|
||||
|
||||
a64::Vec locals[kLocalRegCount];
|
||||
for (size_t j = 0; j < kLocalRegCount; j++) {
|
||||
locals[j] = cc->new_vec_d("local%u", unsigned(j));
|
||||
}
|
||||
|
||||
size_t local_op_threshold = kLocalOpCount - kLocalRegCount;
|
||||
|
||||
for (size_t j = 0; j < 15; j++) {
|
||||
uint32_t op = rnd.next_uint32() % 6;
|
||||
uint32_t id1 = rnd.next_uint32() % reg_count;
|
||||
uint32_t id2 = rnd.next_uint32() % reg_count;
|
||||
|
||||
a64::Vec v0 = virt_regs[id1];
|
||||
a64::Vec v1 = virt_regs[id1];
|
||||
a64::Vec v2 = virt_regs[id2];
|
||||
|
||||
if (j < kLocalRegCount) {
|
||||
v0 = locals[j];
|
||||
}
|
||||
|
||||
if (j >= local_op_threshold) {
|
||||
v2 = locals[j - local_op_threshold];
|
||||
}
|
||||
|
||||
switch (op) {
|
||||
case 0: cc->fadd(v0.d(), v1.d(), v2.d()); break;
|
||||
case 1: cc->fsub(v0.d(), v1.d(), v2.d()); break;
|
||||
case 2: cc->fmul(v0.d(), v1.d(), v2.d()); break;
|
||||
case 3: cc->fdiv(v0.d(), v1.d(), v2.d()); break;
|
||||
case 4: cc->fmin(v0.d(), v1.d(), v2.d()); break;
|
||||
case 5: cc->fmax(v0.d(), v1.d(), v2.d()); break;
|
||||
}
|
||||
}
|
||||
|
||||
cc->subs(counter, counter, 1);
|
||||
cc->b_hi(next_label());
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < reg_count; i++) {
|
||||
cc->str(virt_regs[i].d(), a64::ptr(arg_ptr, int32_t(i * 8) & 1023));
|
||||
}
|
||||
|
||||
cc->end_func();
|
||||
}
|
||||
#endif // !ASMJIT_NO_AARCH64
|
||||
|
||||
int BenchRegAllocApp::run() {
|
||||
if (should_run_arch(Arch::kX64) && !run_arch(Arch::kX64)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (should_run_arch(Arch::kAArch64) && !run_arch(Arch::kAArch64)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool BenchRegAllocApp::run_arch(Arch arch) {
|
||||
Environment custom_env;
|
||||
CpuFeatures features;
|
||||
|
||||
switch (arch) {
|
||||
case Arch::kX86:
|
||||
case Arch::kX64:
|
||||
features.add(CpuFeatures::X86::kADX,
|
||||
CpuFeatures::X86::kAVX,
|
||||
CpuFeatures::X86::kAVX2,
|
||||
CpuFeatures::X86::kBMI,
|
||||
CpuFeatures::X86::kBMI2,
|
||||
CpuFeatures::X86::kCMOV,
|
||||
CpuFeatures::X86::kF16C,
|
||||
CpuFeatures::X86::kFMA,
|
||||
CpuFeatures::X86::kFPU,
|
||||
CpuFeatures::X86::kI486,
|
||||
CpuFeatures::X86::kLZCNT,
|
||||
CpuFeatures::X86::kMMX,
|
||||
CpuFeatures::X86::kMMX2,
|
||||
CpuFeatures::X86::kPOPCNT,
|
||||
CpuFeatures::X86::kSSE,
|
||||
CpuFeatures::X86::kSSE2,
|
||||
CpuFeatures::X86::kSSE3,
|
||||
CpuFeatures::X86::kSSSE3,
|
||||
CpuFeatures::X86::kSSE4_1,
|
||||
CpuFeatures::X86::kSSE4_2,
|
||||
CpuFeatures::X86::kAVX,
|
||||
CpuFeatures::X86::kAVX2);
|
||||
break;
|
||||
|
||||
case Arch::kAArch64:
|
||||
features.add(CpuFeatures::ARM::kAES,
|
||||
CpuFeatures::ARM::kASIMD,
|
||||
CpuFeatures::ARM::kIDIVA,
|
||||
CpuFeatures::ARM::kIDIVT,
|
||||
CpuFeatures::ARM::kPMULL);
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
CodeHolder code;
|
||||
|
||||
custom_env.init(arch);
|
||||
code.init(custom_env, features);
|
||||
|
||||
std::unique_ptr<BaseCompiler> cc;
|
||||
|
||||
#ifndef ASMJIT_NO_X86
|
||||
if (code.arch() == Arch::kX86 || code.arch() == Arch::kX64) {
|
||||
cc = std::make_unique<x86::Compiler>();
|
||||
}
|
||||
#endif // !ASMJIT_NO_X86
|
||||
|
||||
#ifndef ASMJIT_NO_AARCH64
|
||||
if (code.arch() == Arch::kAArch64) {
|
||||
cc = std::make_unique<a64::Compiler>();
|
||||
}
|
||||
#endif // !ASMJIT_NO_AARCH64
|
||||
|
||||
if (!cc)
|
||||
return false;
|
||||
|
||||
PerformanceTimer emit_timer;
|
||||
PerformanceTimer finalize_timer;
|
||||
|
||||
uint32_t reg_count = 35;
|
||||
|
||||
code.reinit();
|
||||
code.attach(cc.get());
|
||||
|
||||
// Dry run to not benchmark allocs on the first run.
|
||||
emit_code(cc.get(), 0, reg_count);
|
||||
cc->finalize();
|
||||
code.reinit();
|
||||
|
||||
#if !defined(ASMJIT_NO_LOGGING)
|
||||
StringLogger logger;
|
||||
if (_verbose) {
|
||||
code.set_logger(&logger);
|
||||
cc->add_diagnostic_options(DiagnosticOptions::kRAAnnotate | DiagnosticOptions::kRADebugAll);
|
||||
}
|
||||
#endif // !ASMJIT_NO_LOGGING
|
||||
|
||||
printf("+-----------------------------------------+-----------+-----------------------------------+--------------+--------------+\n");
|
||||
printf("| Input Configuration | Output | Reserved Memory [KiB] | Time Elapsed [ms] |\n");
|
||||
printf("+--------+------------+--------+----------+-----------+-----------+-----------+-----------+--------------+--------------+\n");
|
||||
printf("| Arch | Complexity | Labels | RegCount | CodeSize | Code Hold.| Compiler | Pass Temp.| Emit Time | Reg. Alloc |\n");
|
||||
printf("+--------+------------+--------+----------+-----------+-----------+-----------+-----------+--------------+--------------+\n");
|
||||
|
||||
for (uint32_t complexity = 1u; complexity <= _maximum_complexity; complexity *= 2u) {
|
||||
emit_timer.start();
|
||||
emit_code(cc.get(), complexity + 1, reg_count);
|
||||
emit_timer.stop();
|
||||
|
||||
finalize_timer.start();
|
||||
Error err = cc->finalize();
|
||||
finalize_timer.stop();
|
||||
|
||||
#if !defined(ASMJIT_NO_LOGGING)
|
||||
if (_verbose) {
|
||||
printf("%s\n", logger.data());
|
||||
logger.clear();
|
||||
}
|
||||
#endif
|
||||
|
||||
code.flatten();
|
||||
|
||||
double emit_time = emit_timer.duration();
|
||||
double finalize_time = finalize_timer.duration();
|
||||
size_t code_size = code.code_size();
|
||||
size_t label_count = code.label_count();
|
||||
size_t virt_reg_count = cc->virt_regs().size();
|
||||
|
||||
ArenaStatistics code_holder_stats = code._arena.statistics();
|
||||
ArenaStatistics compiler_stats = cc->_builder_arena.statistics();
|
||||
ArenaStatistics pass_stats = cc->_pass_arena.statistics();
|
||||
|
||||
printf(
|
||||
"| %-7s| %10u | %6zu | %8zu | %9zu | %9zu | %9zu | %9zu | %12.3f | %12.3f |",
|
||||
asmjit_arch_as_string(arch),
|
||||
complexity,
|
||||
label_count,
|
||||
virt_reg_count,
|
||||
code_size,
|
||||
(code_holder_stats.reserved_size() + 1023) / 1024,
|
||||
(compiler_stats.reserved_size() + 1023) / 1024,
|
||||
(pass_stats.reserved_size() + 1023) / 1024,
|
||||
emit_time,
|
||||
finalize_time
|
||||
);
|
||||
|
||||
if (err != Error::kOk) {
|
||||
printf(" (err: %s)", DebugUtils::error_as_string(err));
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
|
||||
code.reinit();
|
||||
}
|
||||
|
||||
printf("+--------+------------+--------+----------+-----------+-----------+-----------+-----------+--------------+--------------+\n");
|
||||
printf("\n");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
BenchRegAllocApp app;
|
||||
|
||||
app.handle_args(argc, argv);
|
||||
app.show_info();
|
||||
|
||||
if (app._help_only)
|
||||
return 0;
|
||||
|
||||
return app.run();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int main() {
|
||||
print_app_info();
|
||||
printf("!! This Benchmark is disabled: <ASMJIT_NO_JIT> or unsuitable target architecture !!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // !ASMJIT_NO_COMPILER
|
||||
228
asmjit-testing/commons/asmjitutils.h
Normal file
228
asmjit-testing/commons/asmjitutils.h
Normal file
@@ -0,0 +1,228 @@
|
||||
// This file is part of AsmJit project <https://asmjit.com>
|
||||
//
|
||||
// See <asmjit/core.h> or LICENSE.md for license and copyright information
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef ASMJITUTILS_H_INCLUDED
|
||||
#define ASMJITUTILS_H_INCLUDED
|
||||
|
||||
#include <asmjit/core.h>
|
||||
|
||||
namespace {
|
||||
|
||||
[[maybe_unused]]
|
||||
static const char* asmjit_build_type() noexcept {
|
||||
#if defined(ASMJIT_BUILD_DEBUG)
|
||||
static const char build_type[] = "Debug";
|
||||
#else
|
||||
static const char build_type[] = "Release";
|
||||
#endif
|
||||
return build_type;
|
||||
}
|
||||
|
||||
[[maybe_unused]]
|
||||
static const char* asmjit_arch_as_string(asmjit::Arch arch) noexcept {
|
||||
switch (arch) {
|
||||
case asmjit::Arch::kX86 : return "X86";
|
||||
case asmjit::Arch::kX64 : return "X64";
|
||||
|
||||
case asmjit::Arch::kRISCV32 : return "RISCV32";
|
||||
case asmjit::Arch::kRISCV64 : return "RISCV64";
|
||||
|
||||
case asmjit::Arch::kARM : return "ARM";
|
||||
case asmjit::Arch::kAArch64 : return "AArch64";
|
||||
case asmjit::Arch::kThumb : return "Thumb";
|
||||
|
||||
case asmjit::Arch::kMIPS32_LE : return "MIPS_LE";
|
||||
case asmjit::Arch::kMIPS64_LE : return "MIPS64_LE";
|
||||
|
||||
case asmjit::Arch::kARM_BE : return "ARM_BE";
|
||||
case asmjit::Arch::kThumb_BE : return "Thumb_BE";
|
||||
case asmjit::Arch::kAArch64_BE: return "AArch64_BE";
|
||||
|
||||
case asmjit::Arch::kMIPS32_BE : return "MIPS_BE";
|
||||
case asmjit::Arch::kMIPS64_BE : return "MIPS64_BE";
|
||||
|
||||
default:
|
||||
return "<Unknown>";
|
||||
}
|
||||
}
|
||||
|
||||
[[maybe_unused]]
|
||||
static void print_indented(const char* str, size_t indent) noexcept {
|
||||
const char* start = str;
|
||||
while (*str) {
|
||||
if (*str == '\n') {
|
||||
size_t size = (size_t)(str - start);
|
||||
printf("%*s%.*s\n", size ? int(indent) : 0, "", int(size), start);
|
||||
start = str + 1;
|
||||
}
|
||||
str++;
|
||||
}
|
||||
|
||||
size_t size = (size_t)(str - start);
|
||||
if (size)
|
||||
printf("%*s%.*s\n", int(indent), "", int(size), start);
|
||||
}
|
||||
|
||||
[[maybe_unused]]
|
||||
static void print_cpu_info() noexcept {
|
||||
const asmjit::CpuInfo& cpu = asmjit::CpuInfo::host();
|
||||
|
||||
// CPU Information
|
||||
// ---------------
|
||||
|
||||
printf("CPU Info:\n");
|
||||
printf(" Vendor : %s\n", cpu.vendor());
|
||||
printf(" Brand : %s\n", cpu.brand());
|
||||
printf(" Model ID : 0x%08X (%u)\n", cpu.model_id(), cpu.model_id());
|
||||
printf(" Brand ID : 0x%08X (%u)\n", cpu.brand_id(), cpu.brand_id());
|
||||
printf(" Family ID : 0x%08X (%u)\n", cpu.family_id(), cpu.family_id());
|
||||
printf(" Stepping : %u\n", cpu.stepping());
|
||||
printf(" Processor Type : %u\n", cpu.processor_type());
|
||||
printf(" Max logical Processors : %u\n", cpu.max_logical_processors());
|
||||
printf(" Cache-Line Size : %u\n", cpu.cache_line_size());
|
||||
printf(" HW-Thread Count : %u\n", cpu.hw_thread_count());
|
||||
printf("\n");
|
||||
|
||||
// CPU Features
|
||||
// ------------
|
||||
|
||||
using asmjit::CpuHints;
|
||||
|
||||
#ifndef ASMJIT_NO_LOGGING
|
||||
printf("CPU Features:\n");
|
||||
asmjit::CpuFeatures::Iterator it(cpu.features().iterator());
|
||||
while (it.has_next()) {
|
||||
uint32_t feature_id = uint32_t(it.next());
|
||||
asmjit::StringTmp<64> feature_string;
|
||||
asmjit::Formatter::format_feature(feature_string, cpu.arch(), feature_id);
|
||||
printf(" %s\n", feature_string.data());
|
||||
};
|
||||
printf("\n");
|
||||
#endif // !ASMJIT_NO_LOGGING
|
||||
|
||||
// CPU Hints
|
||||
// ---------
|
||||
|
||||
printf("CPU Hints:\n");
|
||||
auto print_hint = [&](CpuHints hint, const char* name) {
|
||||
if ((cpu.hints() & hint) != CpuHints::kNone) {
|
||||
printf(" %s\n", name);
|
||||
}
|
||||
};
|
||||
|
||||
print_hint(CpuHints::kVecMaskedOps8 , "VecMaskedOps8" );
|
||||
print_hint(CpuHints::kVecMaskedOps16 , "VecMaskedOps16" );
|
||||
print_hint(CpuHints::kVecMaskedOps32 , "VecMaskedOps32" );
|
||||
print_hint(CpuHints::kVecMaskedOps64 , "VecMaskedOps64" );
|
||||
print_hint(CpuHints::kVecFastIntMul32, "VecFastIntMul32");
|
||||
print_hint(CpuHints::kVecFastIntMul64, "VecFastIntMul64");
|
||||
print_hint(CpuHints::kVecFastGather , "VecFastGather" );
|
||||
print_hint(CpuHints::kVecMaskedStore , "VecMaskedStore" );
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
[[maybe_unused]]
|
||||
static void print_build_options() {
|
||||
auto stringify_build_definition = [](bool b) { return b ? "defined" : "(not defined)"; };
|
||||
|
||||
#if defined(ASMJIT_NO_X86)
|
||||
constexpr bool no_x86 = true;
|
||||
#else
|
||||
constexpr bool no_x86 = false;
|
||||
#endif
|
||||
|
||||
#if defined(ASMJIT_NO_AARCH64)
|
||||
constexpr bool no_aarch64 = true;
|
||||
#else
|
||||
constexpr bool no_aarch64 = false;
|
||||
#endif
|
||||
|
||||
#if defined(ASMJIT_NO_FOREIGN)
|
||||
constexpr bool no_foreign = true;
|
||||
#else
|
||||
constexpr bool no_foreign = false;
|
||||
#endif
|
||||
|
||||
#if defined(ASMJIT_NO_ABI_NAMESPACE)
|
||||
constexpr bool no_abi_namespace = true;
|
||||
#else
|
||||
constexpr bool no_abi_namespace = false;
|
||||
#endif
|
||||
|
||||
#if defined(ASMJIT_NO_SHM_OPEN)
|
||||
constexpr bool no_shm_open = true;
|
||||
#else
|
||||
constexpr bool no_shm_open = false;
|
||||
#endif
|
||||
|
||||
#if defined(ASMJIT_NO_JIT)
|
||||
constexpr bool no_jit = true;
|
||||
#else
|
||||
constexpr bool no_jit = false;
|
||||
#endif
|
||||
|
||||
#if defined(ASMJIT_NO_TEXT)
|
||||
constexpr bool no_text = true;
|
||||
#else
|
||||
constexpr bool no_text = false;
|
||||
#endif
|
||||
|
||||
#if defined(ASMJIT_NO_LOGGING)
|
||||
constexpr bool no_logging = true;
|
||||
#else
|
||||
constexpr bool no_logging = false;
|
||||
#endif
|
||||
|
||||
#if defined(ASMJIT_NO_INTROSPECTION)
|
||||
constexpr bool no_introspection = true;
|
||||
#else
|
||||
constexpr bool no_introspection = false;
|
||||
#endif
|
||||
|
||||
#if defined(ASMJIT_NO_BUILDER)
|
||||
constexpr bool no_builder = true;
|
||||
#else
|
||||
constexpr bool no_builder = false;
|
||||
#endif
|
||||
|
||||
#if defined(ASMJIT_NO_COMPILER)
|
||||
constexpr bool no_compiler = true;
|
||||
#else
|
||||
constexpr bool no_compiler = false;
|
||||
#endif
|
||||
|
||||
#if defined(ASMJIT_NO_UJIT)
|
||||
constexpr bool no_ujit = true;
|
||||
#else
|
||||
constexpr bool no_ujit = false;
|
||||
#endif
|
||||
|
||||
printf("Build Options:\n");
|
||||
printf(" BUILD_TYPE : %s\n", asmjit_build_type());
|
||||
printf(" ASMJIT_NO_ABI_NAMESPACE: %s\n", stringify_build_definition(no_abi_namespace));
|
||||
printf(" ASMJIT_NO_SHM_OPEN : %s\n", stringify_build_definition(no_shm_open));
|
||||
printf("\n");
|
||||
|
||||
printf("Build Backends:\n");
|
||||
printf(" ASMJIT_NO_X86 : %s\n", stringify_build_definition(no_x86));
|
||||
printf(" ASMJIT_NO_AARCH64 : %s\n", stringify_build_definition(no_aarch64));
|
||||
printf(" ASMJIT_NO_FOREIGN : %s\n", stringify_build_definition(no_foreign));
|
||||
printf("\n");
|
||||
|
||||
printf("Build Features:\n");
|
||||
printf(" ASMJIT_NO_JIT : %s\n", stringify_build_definition(no_jit));
|
||||
printf(" ASMJIT_NO_TEXT : %s\n", stringify_build_definition(no_text));
|
||||
printf(" ASMJIT_NO_LOGGING : %s\n", stringify_build_definition(no_logging));
|
||||
printf(" ASMJIT_NO_INTROSPECTION: %s\n", stringify_build_definition(no_introspection));
|
||||
printf(" ASMJIT_NO_BUILDER : %s\n", stringify_build_definition(no_builder));
|
||||
printf(" ASMJIT_NO_COMPILER : %s\n", stringify_build_definition(no_compiler));
|
||||
printf(" ASMJIT_NO_UJIT : %s\n", stringify_build_definition(no_ujit));
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
} // {anonymous}
|
||||
|
||||
#endif // ASMJITUTILS_H_INCLUDED
|
||||
63
asmjit-testing/commons/cmdline.h
Normal file
63
asmjit-testing/commons/cmdline.h
Normal file
@@ -0,0 +1,63 @@
|
||||
// This file is part of AsmJit project <https://asmjit.com>
|
||||
//
|
||||
// See <asmjit/core.h> or LICENSE.md for license and copyright information
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef CMDLINE_H_INCLUDED
|
||||
#define CMDLINE_H_INCLUDED
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
class CmdLine {
|
||||
public:
|
||||
int _argc;
|
||||
const char* const* _argv;
|
||||
|
||||
CmdLine(int argc, const char* const* argv)
|
||||
: _argc(argc),
|
||||
_argv(argv) {}
|
||||
|
||||
bool has_arg(const char* key) const {
|
||||
for (int i = 1; i < _argc; i++) {
|
||||
if (strcmp(key, _argv[i]) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* value_of(const char* key, const char* default_value) const {
|
||||
size_t keySize = strlen(key);
|
||||
for (int i = 1; i < _argc; i++) {
|
||||
const char* val = _argv[i];
|
||||
if (strlen(val) >= keySize + 1 && val[keySize] == '=' && memcmp(val, key, keySize) == 0)
|
||||
return val + keySize + 1;
|
||||
}
|
||||
|
||||
return default_value;
|
||||
}
|
||||
|
||||
int value_as_int(const char* key, int default_value) const {
|
||||
const char* val = value_of(key, nullptr);
|
||||
if (val == nullptr || val[0] == '\0')
|
||||
return default_value;
|
||||
|
||||
return atoi(val);
|
||||
}
|
||||
|
||||
unsigned value_as_uint(const char* key, unsigned default_value) const {
|
||||
const char* val = value_of(key, nullptr);
|
||||
if (val == nullptr || val[0] == '\0')
|
||||
return default_value;
|
||||
|
||||
int v = atoi(val);
|
||||
if (v < 0)
|
||||
return default_value;
|
||||
else
|
||||
return unsigned(v);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // CMDLINE_H_INCLUDED
|
||||
27
asmjit-testing/commons/performancetimer.h
Normal file
27
asmjit-testing/commons/performancetimer.h
Normal file
@@ -0,0 +1,27 @@
|
||||
// This file is part of AsmJit project <https://asmjit.com>
|
||||
//
|
||||
// See <asmjit/core.h> or LICENSE.md for license and copyright information
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef PERFORMANCETIMER_H_INCLUDED
|
||||
#define PERFORMANCETIMER_H_INCLUDED
|
||||
|
||||
#include <chrono>
|
||||
|
||||
class PerformanceTimer {
|
||||
public:
|
||||
using TimePoint = std::chrono::high_resolution_clock::time_point;
|
||||
|
||||
TimePoint _start_time {};
|
||||
TimePoint _end_time {};
|
||||
|
||||
inline void start() { _start_time = std::chrono::high_resolution_clock::now(); }
|
||||
inline void stop() { _end_time = std::chrono::high_resolution_clock::now(); }
|
||||
|
||||
inline double duration() const {
|
||||
std::chrono::duration<double> elapsed = _end_time - _start_time;
|
||||
return elapsed.count() * 1000;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // PERFORMANCETIMER_H_INCLUDED
|
||||
77
asmjit-testing/commons/random.h
Normal file
77
asmjit-testing/commons/random.h
Normal file
@@ -0,0 +1,77 @@
|
||||
// This file is part of AsmJit project <https://asmjit.com>
|
||||
//
|
||||
// See <asmjit/core.h> or LICENSE.md for license and copyright information
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef TESTING_COMMONS_RANDOM_H_INCLUDED
|
||||
#define TESTING_COMMONS_RANDOM_H_INCLUDED
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace TestUtils {
|
||||
namespace {
|
||||
|
||||
// A pseudo random number generator based on a paper by Sebastiano Vigna:
|
||||
// http://vigna.di.unimi.it/ftp/papers/xorshiftplus.pdf
|
||||
class Random {
|
||||
public:
|
||||
// Constants suggested as `23/18/5`.
|
||||
static inline constexpr uint32_t kStep1_SHL = 23;
|
||||
static inline constexpr uint32_t kStep2_SHR = 18;
|
||||
static inline constexpr uint32_t kStep3_SHR = 5;
|
||||
|
||||
uint64_t _state[2];
|
||||
|
||||
inline explicit Random(uint64_t seed = 0) noexcept { reset(seed); }
|
||||
inline Random(const Random& other) noexcept = default;
|
||||
|
||||
inline void reset(uint64_t seed = 0) noexcept {
|
||||
// The number is arbitrary, it means nothing.
|
||||
constexpr uint64_t kZeroSeed = 0x1F0A2BE71D163FA0u;
|
||||
|
||||
// Generate the state data by using splitmix64.
|
||||
for (uint32_t i = 0; i < 2; i++) {
|
||||
seed += 0x9E3779B97F4A7C15u;
|
||||
uint64_t x = seed;
|
||||
x = (x ^ (x >> 30)) * 0xBF58476D1CE4E5B9u;
|
||||
x = (x ^ (x >> 27)) * 0x94D049BB133111EBu;
|
||||
x = (x ^ (x >> 31));
|
||||
_state[i] = x != 0 ? x : kZeroSeed;
|
||||
}
|
||||
}
|
||||
|
||||
inline uint32_t next_uint32() noexcept {
|
||||
return uint32_t(next_uint64() >> 32);
|
||||
}
|
||||
|
||||
inline uint64_t next_uint64() noexcept {
|
||||
uint64_t x = _state[0];
|
||||
uint64_t y = _state[1];
|
||||
|
||||
x ^= x << kStep1_SHL;
|
||||
y ^= y >> kStep3_SHR;
|
||||
x ^= x >> kStep2_SHR;
|
||||
x ^= y;
|
||||
|
||||
_state[0] = y;
|
||||
_state[1] = x;
|
||||
return x + y;
|
||||
}
|
||||
|
||||
inline double next_double() noexcept {
|
||||
constexpr uint32_t kMantissaShift = 64 - 52;
|
||||
constexpr uint64_t kExpMsk = 0x3FF0000000000000u;
|
||||
|
||||
uint64_t u = (next_uint64() >> kMantissaShift) | kExpMsk;
|
||||
double d = 0.0;
|
||||
|
||||
memcpy(&d, &u, 8);
|
||||
return d - 1.0;
|
||||
}
|
||||
};
|
||||
|
||||
} // {anonymous}
|
||||
} // {TestUtils}
|
||||
|
||||
#endif // TESTING_COMMONS_RANDOM_H_INCLUDED
|
||||
107
asmjit-testing/tests/asmjit_test_assembler.cpp
Normal file
107
asmjit-testing/tests/asmjit_test_assembler.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
// This file is part of AsmJit project <https://asmjit.com>
|
||||
//
|
||||
// See <asmjit/core.h> or LICENSE.md for license and copyright information
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#include <asmjit/core.h>
|
||||
|
||||
#include <asmjit-testing/commons/asmjitutils.h>
|
||||
#include <asmjit-testing/commons/cmdline.h>
|
||||
#include <asmjit-testing/tests/asmjit_test_assembler.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
using namespace asmjit;
|
||||
|
||||
#if !defined(ASMJIT_NO_X86)
|
||||
bool test_x86_assembler(const TestSettings& settings) noexcept;
|
||||
bool test_x64_assembler(const TestSettings& settings) noexcept;
|
||||
#endif
|
||||
|
||||
#if !defined(ASMJIT_NO_AARCH64)
|
||||
bool test_aarch64_assembler(const TestSettings& settings) noexcept;
|
||||
#endif
|
||||
|
||||
static void print_app_info() noexcept {
|
||||
printf("AsmJit Assembler Test-Suite v%u.%u.%u [Arch=%s] [Mode=%s]\n\n",
|
||||
unsigned((ASMJIT_LIBRARY_VERSION >> 16) ),
|
||||
unsigned((ASMJIT_LIBRARY_VERSION >> 8) & 0xFF),
|
||||
unsigned((ASMJIT_LIBRARY_VERSION ) & 0xFF),
|
||||
asmjit_arch_as_string(Arch::kHost),
|
||||
asmjit_build_type()
|
||||
);
|
||||
}
|
||||
|
||||
static void print_app_usage(const TestSettings& settings) noexcept {
|
||||
printf("Usage:\n");
|
||||
printf(" --help Show usage only\n");
|
||||
printf(" --verbose Show only assembling errors [%s]\n", settings.verbose ? "x" : " ");
|
||||
printf(" --validate Use instruction validation [%s]\n", settings.validate ? "x" : " ");
|
||||
printf(" --arch=<ARCH> Select architecture to run ('all' by default)\n");
|
||||
printf("\n");
|
||||
|
||||
printf("Architectures:\n");
|
||||
#if !defined(ASMJIT_NO_X86)
|
||||
printf(" --arch=x86 32-bit X86 architecture (X86)\n");
|
||||
printf(" --arch=x64 64-bit X86 architecture (X86_64)\n");
|
||||
#endif
|
||||
#if !defined(ASMJIT_NO_AARCH64)
|
||||
printf(" --arch=aarch64 64-bit ARM architecture (AArch64)\n");
|
||||
#endif
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
CmdLine cmd_line(argc, argv);
|
||||
|
||||
TestSettings settings {};
|
||||
settings.verbose = cmd_line.has_arg("--verbose");
|
||||
settings.validate = cmd_line.has_arg("--validate");
|
||||
|
||||
print_app_info();
|
||||
print_app_usage(settings);
|
||||
|
||||
if (cmd_line.has_arg("--help")) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* arch = cmd_line.value_of("--arch", "all");
|
||||
bool x86_failed = false;
|
||||
bool x64_failed = false;
|
||||
bool aarch64_failed = false;
|
||||
|
||||
#if !defined(ASMJIT_NO_X86)
|
||||
if ((strcmp(arch, "all") == 0 || strcmp(arch, "x86") == 0))
|
||||
x86_failed = !test_x86_assembler(settings);
|
||||
|
||||
if ((strcmp(arch, "all") == 0 || strcmp(arch, "x64") == 0))
|
||||
x64_failed = !test_x64_assembler(settings);
|
||||
#endif
|
||||
|
||||
#if !defined(ASMJIT_NO_AARCH64)
|
||||
if ((strcmp(arch, "all") == 0 || strcmp(arch, "aarch64") == 0))
|
||||
aarch64_failed = !test_aarch64_assembler(settings);
|
||||
#endif
|
||||
|
||||
bool failed = x86_failed || x64_failed || aarch64_failed;
|
||||
|
||||
if (failed) {
|
||||
if (x86_failed)
|
||||
printf("** X86 test suite failed **\n");
|
||||
|
||||
if (x64_failed)
|
||||
printf("** X64 test suite failed **\n");
|
||||
|
||||
if (aarch64_failed)
|
||||
printf("** AArch64 test suite failed **\n");
|
||||
|
||||
printf("** FAILURE **\n");
|
||||
}
|
||||
else {
|
||||
printf("** SUCCESS **\n");
|
||||
}
|
||||
|
||||
return failed ? 1 : 0;
|
||||
}
|
||||
109
asmjit-testing/tests/asmjit_test_assembler.h
Normal file
109
asmjit-testing/tests/asmjit_test_assembler.h
Normal file
@@ -0,0 +1,109 @@
|
||||
// This file is part of AsmJit project <https://asmjit.com>
|
||||
//
|
||||
// See <asmjit/core.h> or LICENSE.md for license and copyright information
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef ASMJIT_TEST_ASSEMBLER_H_INCLUDED
|
||||
#define ASMJIT_TEST_ASSEMBLER_H_INCLUDED
|
||||
|
||||
#include <asmjit/core.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
struct TestSettings {
|
||||
bool verbose;
|
||||
bool validate;
|
||||
};
|
||||
|
||||
template<typename AssemblerType>
|
||||
class AssemblerTester {
|
||||
public:
|
||||
asmjit::Environment env {};
|
||||
asmjit::CodeHolder code {};
|
||||
AssemblerType assembler {};
|
||||
asmjit::Label L0 {};
|
||||
const TestSettings& settings;
|
||||
|
||||
size_t passed {};
|
||||
size_t count {};
|
||||
|
||||
AssemblerTester(asmjit::Arch arch, const TestSettings& settings) noexcept
|
||||
: env(arch),
|
||||
settings(settings) {
|
||||
prepare();
|
||||
}
|
||||
|
||||
void print_header(const char* arch_name) noexcept {
|
||||
printf("%s assembler tests:\n", arch_name);
|
||||
}
|
||||
|
||||
void print_summary() noexcept {
|
||||
printf(" Passed: %zu / %zu tests\n\n", passed, count);
|
||||
}
|
||||
|
||||
bool did_pass() const noexcept { return passed == count; }
|
||||
|
||||
void prepare() noexcept {
|
||||
code.reset();
|
||||
code.init(env, 0);
|
||||
code.attach(&assembler);
|
||||
L0 = assembler.new_label();
|
||||
|
||||
if (settings.validate)
|
||||
assembler.add_diagnostic_options(asmjit::DiagnosticOptions::kValidateAssembler);
|
||||
}
|
||||
|
||||
ASMJIT_NOINLINE bool test_valid_instruction(const char* s, const char* expected_opcode, asmjit::Error err = asmjit::Error::kOk) noexcept {
|
||||
count++;
|
||||
|
||||
if (err != asmjit::Error::kOk) {
|
||||
printf(" !! %s\n"
|
||||
" <%s>\n", s, asmjit::DebugUtils::error_as_string(err));
|
||||
prepare();
|
||||
return false;
|
||||
}
|
||||
|
||||
asmjit::String encoded_opcode;
|
||||
asmjit::Section* text = code.text_section();
|
||||
|
||||
encoded_opcode.append_hex(text->data(), text->buffer_size());
|
||||
if (encoded_opcode != expected_opcode) {
|
||||
printf(" !! [%s] <- %s\n"
|
||||
" [%s] (Expected)\n", encoded_opcode.data(), s, expected_opcode);
|
||||
prepare();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (settings.verbose)
|
||||
printf(" OK [%s] <- %s\n", encoded_opcode.data(), s);
|
||||
|
||||
passed++;
|
||||
prepare();
|
||||
return true;
|
||||
}
|
||||
|
||||
ASMJIT_NOINLINE bool test_invalid_instruction(const char* s, asmjit::Error expected_error, asmjit::Error err) noexcept {
|
||||
count++;
|
||||
|
||||
if (err == asmjit::Error::kOk) {
|
||||
printf(" !! %s passed, but should have failed with <%s> error\n", s, asmjit::DebugUtils::error_as_string(expected_error));
|
||||
prepare();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (err != asmjit::Error::kOk) {
|
||||
printf(" !! %s failed with <%s>, but should have failed with <%s>\n", s, asmjit::DebugUtils::error_as_string(err), asmjit::DebugUtils::error_as_string(expected_error));
|
||||
prepare();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (settings.verbose)
|
||||
printf(" OK [%s] <- %s\n", asmjit::DebugUtils::error_as_string(err), s);
|
||||
|
||||
passed++;
|
||||
prepare();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // ASMJIT_TEST_ASSEMBLER_H_INCLUDED
|
||||
4053
asmjit-testing/tests/asmjit_test_assembler_a64.cpp
Normal file
4053
asmjit-testing/tests/asmjit_test_assembler_a64.cpp
Normal file
File diff suppressed because it is too large
Load Diff
18073
asmjit-testing/tests/asmjit_test_assembler_x64.cpp
Normal file
18073
asmjit-testing/tests/asmjit_test_assembler_x64.cpp
Normal file
File diff suppressed because it is too large
Load Diff
8492
asmjit-testing/tests/asmjit_test_assembler_x86.cpp
Normal file
8492
asmjit-testing/tests/asmjit_test_assembler_x86.cpp
Normal file
File diff suppressed because it is too large
Load Diff
407
asmjit-testing/tests/asmjit_test_compiler.cpp
Normal file
407
asmjit-testing/tests/asmjit_test_compiler.cpp
Normal file
@@ -0,0 +1,407 @@
|
||||
// This file is part of AsmJit project <https://asmjit.com>
|
||||
//
|
||||
// See <asmjit/core.h> or LICENSE.md for license and copyright information
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#include <asmjit/core.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
|
||||
#if !defined(ASMJIT_NO_COMPILER)
|
||||
|
||||
#include <asmjit-testing/commons/asmjitutils.h>
|
||||
#include <asmjit-testing/commons/cmdline.h>
|
||||
#include <asmjit-testing/commons/performancetimer.h>
|
||||
#include <asmjit-testing/tests/asmjit_test_compiler.h>
|
||||
|
||||
#if !defined(ASMJIT_NO_X86)
|
||||
#include <asmjit/x86.h>
|
||||
void compiler_add_x86_tests(TestApp& app);
|
||||
#endif // !ASMJIT_NO_X86
|
||||
|
||||
#if !defined(ASMJIT_NO_AARCH64)
|
||||
#include <asmjit/a64.h>
|
||||
void compiler_add_a64_tests(TestApp& app);
|
||||
#endif // !ASMJIT_NO_AARCH64
|
||||
|
||||
using namespace asmjit;
|
||||
|
||||
int TestApp::handle_args(int argc, const char* const* argv) {
|
||||
CmdLine cmd(argc, argv);
|
||||
_arch = cmd.value_of("--arch", "all");
|
||||
_filter = cmd.value_of("--filter", nullptr);
|
||||
|
||||
if (cmd.has_arg("--help")) _help_only = true;
|
||||
if (cmd.has_arg("--verbose")) _verbose = true;
|
||||
|
||||
#ifndef ASMJIT_NO_LOGGING
|
||||
if (cmd.has_arg("--dump-asm")) _dump_asm = true;
|
||||
#endif // !ASMJIT_NO_LOGGING
|
||||
|
||||
if (cmd.has_arg("--dump-hex")) _dump_hex = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void TestApp::show_info() {
|
||||
printf("AsmJit Compiler Test-Suite v%u.%u.%u [Arch=%s] [Mode=%s]\n\n",
|
||||
unsigned((ASMJIT_LIBRARY_VERSION >> 16) ),
|
||||
unsigned((ASMJIT_LIBRARY_VERSION >> 8) & 0xFF),
|
||||
unsigned((ASMJIT_LIBRARY_VERSION ) & 0xFF),
|
||||
asmjit_arch_as_string(Arch::kHost),
|
||||
asmjit_build_type()
|
||||
);
|
||||
|
||||
printf("Usage:\n");
|
||||
printf(" --help Show usage only\n");
|
||||
printf(" --arch=<NAME> Select architecture to run ('all' by default)\n");
|
||||
printf(" --filter=<NAME> Use a filter to restrict which test is called\n");
|
||||
printf(" --verbose Verbose output\n");
|
||||
printf(" --dump-asm Assembler output\n");
|
||||
printf(" --dump-hex Hexadecimal output (relocated, only for host arch)\n");
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
#ifndef ASMJIT_NO_LOGGING
|
||||
class IndentedStdoutLogger : public Logger {
|
||||
public:
|
||||
ASMJIT_NONCOPYABLE(IndentedStdoutLogger)
|
||||
|
||||
size_t _indentation = 0;
|
||||
|
||||
explicit IndentedStdoutLogger(size_t indentation) noexcept
|
||||
: _indentation(indentation) {}
|
||||
|
||||
Error _log(const char* data, size_t size = SIZE_MAX) noexcept override {
|
||||
asmjit::Support::maybe_unused(size);
|
||||
print_indented(data, _indentation);
|
||||
return Error::kOk;
|
||||
}
|
||||
};
|
||||
#endif // !ASMJIT_NO_LOGGING
|
||||
|
||||
bool TestApp::should_run(const TestCase* tc) {
|
||||
if (!_filter)
|
||||
return true;
|
||||
|
||||
return strstr(tc->name(), _filter) != nullptr;
|
||||
}
|
||||
|
||||
int TestApp::run() {
|
||||
#ifndef ASMJIT_NO_LOGGING
|
||||
FormatOptions format_options;
|
||||
format_options.add_flags(
|
||||
FormatFlags::kMachineCode |
|
||||
FormatFlags::kShowAliases |
|
||||
FormatFlags::kExplainImms |
|
||||
FormatFlags::kRegCasts );
|
||||
format_options.set_indentation(FormatIndentationGroup::kCode, 2);
|
||||
|
||||
IndentedStdoutLogger print_logger(4);
|
||||
print_logger.set_options(format_options);
|
||||
|
||||
StringLogger string_logger;
|
||||
string_logger.set_options(format_options);
|
||||
|
||||
auto print_string_logger_content = [&]() {
|
||||
if (!_verbose) {
|
||||
printf("%s", string_logger.data());
|
||||
fflush(stdout);
|
||||
}
|
||||
};
|
||||
#else
|
||||
auto print_string_logger_content = [&]() {};
|
||||
#endif // !ASMJIT_NO_LOGGING
|
||||
|
||||
// maybe unused...
|
||||
Support::maybe_unused(print_string_logger_content);
|
||||
|
||||
#ifndef ASMJIT_NO_JIT
|
||||
JitRuntime runtime;
|
||||
#endif // !ASMJIT_NO_JIT
|
||||
|
||||
PerformanceTimer compile_timer;
|
||||
PerformanceTimer finalize_timer;
|
||||
|
||||
double compile_time = 0;
|
||||
double finalize_time = 0;
|
||||
|
||||
for (std::unique_ptr<TestCase>& test : _tests) {
|
||||
if (!should_run(test.get()))
|
||||
continue;
|
||||
|
||||
_num_tests++;
|
||||
|
||||
for (uint32_t pass = 0; pass < 2; pass++) {
|
||||
bool runnable = false;
|
||||
CodeHolder code;
|
||||
SimpleErrorHandler error_handler;
|
||||
|
||||
const char* status_separator = " ";
|
||||
|
||||
// Filter architecture to run.
|
||||
if (strcmp(_arch, "all") != 0) {
|
||||
switch (test->arch()) {
|
||||
case Arch::kX86:
|
||||
if (strcmp(_arch, "x86") == 0)
|
||||
break;
|
||||
continue;
|
||||
case Arch::kX64:
|
||||
if (strcmp(_arch, "x64") == 0)
|
||||
break;
|
||||
continue;
|
||||
case Arch::kAArch64:
|
||||
if (strcmp(_arch, "aarch64") == 0)
|
||||
break;
|
||||
continue;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Use platform environment and CPU features when the test can run on the arch.
|
||||
#ifndef ASMJIT_NO_JIT
|
||||
if (runtime.arch() == test->arch()) {
|
||||
code.init(runtime.environment(), runtime.cpu_features());
|
||||
runnable = true;
|
||||
}
|
||||
#endif // !ASMJIT_NO_JIT
|
||||
|
||||
if (!code.is_initialized()) {
|
||||
Environment custom_env;
|
||||
CpuFeatures features;
|
||||
|
||||
switch (test->arch()) {
|
||||
case Arch::kX86:
|
||||
case Arch::kX64:
|
||||
features.add(CpuFeatures::X86::kADX,
|
||||
CpuFeatures::X86::kAVX,
|
||||
CpuFeatures::X86::kAVX2,
|
||||
CpuFeatures::X86::kBMI,
|
||||
CpuFeatures::X86::kBMI2,
|
||||
CpuFeatures::X86::kCMOV,
|
||||
CpuFeatures::X86::kF16C,
|
||||
CpuFeatures::X86::kFMA,
|
||||
CpuFeatures::X86::kFPU,
|
||||
CpuFeatures::X86::kI486,
|
||||
CpuFeatures::X86::kLZCNT,
|
||||
CpuFeatures::X86::kMMX,
|
||||
CpuFeatures::X86::kMMX2,
|
||||
CpuFeatures::X86::kPOPCNT,
|
||||
CpuFeatures::X86::kSSE,
|
||||
CpuFeatures::X86::kSSE2,
|
||||
CpuFeatures::X86::kSSE3,
|
||||
CpuFeatures::X86::kSSSE3,
|
||||
CpuFeatures::X86::kSSE4_1,
|
||||
CpuFeatures::X86::kSSE4_2);
|
||||
break;
|
||||
|
||||
case Arch::kAArch64:
|
||||
features.add(CpuFeatures::ARM::kAES,
|
||||
CpuFeatures::ARM::kASIMD,
|
||||
CpuFeatures::ARM::kIDIVA,
|
||||
CpuFeatures::ARM::kIDIVT,
|
||||
CpuFeatures::ARM::kPMULL);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
custom_env.init(test->arch());
|
||||
code.init(custom_env, features);
|
||||
}
|
||||
|
||||
code.set_error_handler(&error_handler);
|
||||
|
||||
if (pass != 0) {
|
||||
printf("[Test:%s] %s", asmjit_arch_as_string(test->arch()), test->name());
|
||||
fflush(stdout);
|
||||
|
||||
#ifndef ASMJIT_NO_LOGGING
|
||||
if (_verbose || _dump_asm || _dump_hex) {
|
||||
printf("\n");
|
||||
status_separator = " ";
|
||||
}
|
||||
|
||||
if (_verbose) {
|
||||
printf(" [Log]\n");
|
||||
code.set_logger(&print_logger);
|
||||
}
|
||||
else {
|
||||
string_logger.clear();
|
||||
code.set_logger(&string_logger);
|
||||
}
|
||||
#endif // !ASMJIT_NO_LOGGING
|
||||
}
|
||||
|
||||
std::unique_ptr<BaseCompiler> cc;
|
||||
|
||||
#ifndef ASMJIT_NO_X86
|
||||
if (code.arch() == Arch::kX86 || code.arch() == Arch::kX64)
|
||||
cc = std::make_unique<x86::Compiler>(&code);
|
||||
#endif // !ASMJIT_NO_X86
|
||||
|
||||
#ifndef ASMJIT_NO_AARCH64
|
||||
if (code.arch() == Arch::kAArch64)
|
||||
cc = std::make_unique<a64::Compiler>(&code);
|
||||
#endif // !ASMJIT_NO_AARCH64
|
||||
|
||||
if (!cc)
|
||||
continue;
|
||||
|
||||
#ifndef ASMJIT_NO_LOGGING
|
||||
cc->add_diagnostic_options(DiagnosticOptions::kRAAnnotate | DiagnosticOptions::kRADebugAll);
|
||||
#endif // !ASMJIT_NO_LOGGING
|
||||
|
||||
compile_timer.start();
|
||||
test->compile(*cc);
|
||||
compile_timer.stop();
|
||||
|
||||
Error err = error_handler._err;
|
||||
if (err == Error::kOk) {
|
||||
finalize_timer.start();
|
||||
err = cc->finalize();
|
||||
finalize_timer.stop();
|
||||
}
|
||||
|
||||
// The first pass is only used for timing of serialization and compilation, because otherwise it would be
|
||||
// biased by logging, which takes much more time than finalize() does. We want to benchmark Compiler the
|
||||
// way it would be used in the production.
|
||||
if (pass == 0) {
|
||||
_output_size += code.code_size();
|
||||
compile_time += compile_timer.duration();
|
||||
finalize_time += finalize_timer.duration();
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifndef ASMJIT_NO_LOGGING
|
||||
if (_dump_asm) {
|
||||
String sb;
|
||||
Formatter::format_node_list(sb, format_options, cc.get());
|
||||
printf(" [Assembly]\n");
|
||||
print_indented(sb.data(), 4);
|
||||
}
|
||||
#endif // !ASMJIT_NO_LOGGING
|
||||
|
||||
#ifndef ASMJIT_NO_JIT
|
||||
if (runnable) {
|
||||
void* func = nullptr;
|
||||
if (err == Error::kOk)
|
||||
err = runtime.add(&func, &code);
|
||||
|
||||
if (err == Error::kOk && _dump_hex) {
|
||||
String sb;
|
||||
sb.append_hex((void*)func, code.code_size());
|
||||
printf(" [Hex Dump]:\n");
|
||||
for (size_t i = 0; i < sb.size(); i += 76) {
|
||||
printf(" %.60s\n", sb.data() + i);
|
||||
}
|
||||
}
|
||||
|
||||
if (_verbose)
|
||||
fflush(stdout);
|
||||
|
||||
if (err == Error::kOk) {
|
||||
StringTmp<128> result;
|
||||
StringTmp<128> expect;
|
||||
|
||||
if (test->run(func, result, expect)) {
|
||||
if (!_verbose)
|
||||
printf("%s[RUN OK]\n", status_separator);
|
||||
}
|
||||
else {
|
||||
if (!_verbose)
|
||||
printf("%s[RUN FAILED]\n", status_separator);
|
||||
|
||||
print_string_logger_content();
|
||||
printf(" [Output]\n");
|
||||
printf(" Returned: %s\n", result.data());
|
||||
printf(" Expected: %s\n", expect.data());
|
||||
_num_failed++;
|
||||
}
|
||||
|
||||
if (_dump_asm)
|
||||
printf("\n");
|
||||
|
||||
runtime.release(func);
|
||||
}
|
||||
else {
|
||||
if (!_verbose)
|
||||
printf("%s[COMPILE FAILED]\n", status_separator);
|
||||
|
||||
print_string_logger_content();
|
||||
printf(" [Status]\n");
|
||||
printf(" ERROR 0x%08X: %s\n", unsigned(err), error_handler._message.data());
|
||||
_num_failed++;
|
||||
}
|
||||
}
|
||||
#endif // !ASMJIT_NO_JIT
|
||||
|
||||
if (!runnable) {
|
||||
if (err != Error::kOk) {
|
||||
printf(" [Status]\n");
|
||||
printf(" ERROR 0x%08X: %s\n", unsigned(err), error_handler._message.data());
|
||||
_num_failed++;
|
||||
}
|
||||
else {
|
||||
printf("%s[COMPILE OK]\n", status_separator);
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef ASMJIT_NO_LOGGING
|
||||
if (_verbose || _dump_asm || _dump_hex) {
|
||||
printf("\n");
|
||||
}
|
||||
#endif // !ASMJIT_NO_LOGGING
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
printf("Summary:\n");
|
||||
printf(" OutputSize: %zu bytes\n", _output_size);
|
||||
printf(" CompileTime: %.2f ms\n", compile_time);
|
||||
printf(" FinalizeTime: %.2f ms\n", finalize_time);
|
||||
printf("\n");
|
||||
|
||||
if (_num_failed == 0)
|
||||
printf("** SUCCESS: All %u tests passed **\n", _num_tests);
|
||||
else
|
||||
printf("** FAILURE: %u of %u tests failed **\n", _num_failed, _num_tests);
|
||||
|
||||
return _num_failed == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
TestApp app;
|
||||
|
||||
app.handle_args(argc, argv);
|
||||
app.show_info();
|
||||
|
||||
#if !defined(ASMJIT_NO_X86)
|
||||
compiler_add_x86_tests(app);
|
||||
#endif // !ASMJIT_NO_X86
|
||||
|
||||
#if !defined(ASMJIT_NO_AARCH64)
|
||||
compiler_add_a64_tests(app);
|
||||
#endif // !ASMJIT_NO_AARCH64
|
||||
|
||||
return app.run();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
Support::maybe_unused(argc, argv);
|
||||
|
||||
printf("AsmJit Compiler Test suite is disabled when compiling with ASMJIT_NO_COMPILER\n\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // !ASMJIT_NO_COMPILER
|
||||
83
asmjit-testing/tests/asmjit_test_compiler.h
Normal file
83
asmjit-testing/tests/asmjit_test_compiler.h
Normal file
@@ -0,0 +1,83 @@
|
||||
// This file is part of AsmJit project <https://asmjit.com>
|
||||
//
|
||||
// See <asmjit/core.h> or LICENSE.md for license and copyright information
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef ASMJIT_TEST_COMPILER_H_INCLUDED
|
||||
#define ASMJIT_TEST_COMPILER_H_INCLUDED
|
||||
|
||||
#include <asmjit/core.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
class SimpleErrorHandler : public asmjit::ErrorHandler {
|
||||
public:
|
||||
SimpleErrorHandler()
|
||||
: _err(asmjit::Error::kOk) {}
|
||||
|
||||
void handle_error(asmjit::Error err, const char* message, asmjit::BaseEmitter* origin) override {
|
||||
asmjit::Support::maybe_unused(origin);
|
||||
_err = err;
|
||||
_message.assign(message);
|
||||
}
|
||||
|
||||
asmjit::Error _err;
|
||||
asmjit::String _message;
|
||||
};
|
||||
|
||||
//! A test case interface for testing AsmJit's Compiler.
|
||||
class TestCase {
|
||||
public:
|
||||
TestCase(const char* name, asmjit::Arch arch) {
|
||||
if (name)
|
||||
_name.assign(name);
|
||||
_arch = arch;
|
||||
}
|
||||
|
||||
virtual ~TestCase() {}
|
||||
|
||||
inline const char* name() const { return _name.data(); }
|
||||
inline asmjit::Arch arch() const { return _arch; }
|
||||
|
||||
virtual void compile(asmjit::BaseCompiler& cc) = 0;
|
||||
virtual bool run(void* func, asmjit::String& result, asmjit::String& expect) = 0;
|
||||
|
||||
asmjit::String _name;
|
||||
asmjit::Arch _arch;
|
||||
};
|
||||
|
||||
class TestApp {
|
||||
public:
|
||||
std::vector<std::unique_ptr<TestCase>> _tests;
|
||||
|
||||
const char* _arch = nullptr;
|
||||
const char* _filter = nullptr;
|
||||
bool _help_only = false;
|
||||
bool _verbose = false;
|
||||
bool _dump_asm = false;
|
||||
bool _dump_hex = false;
|
||||
|
||||
unsigned _num_tests = 0;
|
||||
unsigned _num_failed = 0;
|
||||
size_t _output_size = 0;
|
||||
|
||||
TestApp() noexcept
|
||||
: _arch("all") {}
|
||||
~TestApp() noexcept {}
|
||||
|
||||
void add(TestCase* test) noexcept {
|
||||
_tests.push_back(std::unique_ptr<TestCase>(test));
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline void add_t() { T::add(*this); }
|
||||
|
||||
int handle_args(int argc, const char* const* argv);
|
||||
void show_info();
|
||||
|
||||
bool should_run(const TestCase* tc);
|
||||
int run();
|
||||
};
|
||||
|
||||
#endif // ASMJIT_TEST_COMPILER_H_INCLUDED
|
||||
688
asmjit-testing/tests/asmjit_test_compiler_a64.cpp
Normal file
688
asmjit-testing/tests/asmjit_test_compiler_a64.cpp
Normal file
@@ -0,0 +1,688 @@
|
||||
// This file is part of AsmJit project <https://asmjit.com>
|
||||
//
|
||||
// See <asmjit/core.h> or LICENSE.md for license and copyright information
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#include <asmjit/core.h>
|
||||
#if !defined(ASMJIT_NO_COMPILER) && !defined(ASMJIT_NO_AARCH64)
|
||||
|
||||
#include <asmjit/a64.h>
|
||||
|
||||
#include <asmjit-testing/tests/asmjit_test_compiler.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
using namespace asmjit;
|
||||
|
||||
// a64::Compiler - A64TestCase
|
||||
// ===========================
|
||||
|
||||
class A64TestCase : public TestCase {
|
||||
public:
|
||||
A64TestCase(const char* name = nullptr)
|
||||
: TestCase(name, Arch::kAArch64) {}
|
||||
|
||||
void compile(BaseCompiler& cc) override {
|
||||
compile(static_cast<a64::Compiler&>(cc));
|
||||
}
|
||||
|
||||
virtual void compile(a64::Compiler& cc) = 0;
|
||||
};
|
||||
|
||||
// a64::Compiler - A64Test_GpArgs
|
||||
// ==============================
|
||||
|
||||
class A64Test_GpArgs : public A64TestCase {
|
||||
public:
|
||||
uint32_t _arg_count;
|
||||
bool _preserve_fp;
|
||||
|
||||
A64Test_GpArgs(uint32_t arg_count, bool preserve_fp)
|
||||
: _arg_count(arg_count),
|
||||
_preserve_fp(preserve_fp) {
|
||||
_name.assign_format("GpArgs {NumArgs=%u PreserveFP=%c}", arg_count, preserve_fp ? 'Y' : 'N');
|
||||
}
|
||||
|
||||
static void add(TestApp& app) {
|
||||
for (uint32_t i = 0; i <= 16; i++) {
|
||||
app.add(new A64Test_GpArgs(i, true));
|
||||
app.add(new A64Test_GpArgs(i, false));
|
||||
}
|
||||
}
|
||||
|
||||
void compile(a64::Compiler& cc) override {
|
||||
uint32_t arg_count = _arg_count;
|
||||
FuncSignature signature;
|
||||
|
||||
signature.set_ret_t<int>();
|
||||
for (uint32_t i = 0; i < arg_count; i++) {
|
||||
signature.add_arg_t<int>();
|
||||
}
|
||||
|
||||
FuncNode* func_node = cc.add_func(signature);
|
||||
if (_preserve_fp)
|
||||
func_node->frame().set_preserved_fp();
|
||||
|
||||
a64::Gp sum;
|
||||
|
||||
if (arg_count) {
|
||||
for (uint32_t i = 0; i < arg_count; i++) {
|
||||
a64::Gp i_reg = cc.new_gp32("i%u", i);
|
||||
func_node->set_arg(i, i_reg);
|
||||
|
||||
if (i == 0)
|
||||
sum = i_reg;
|
||||
else
|
||||
cc.add(sum, sum, i_reg);
|
||||
}
|
||||
}
|
||||
else {
|
||||
sum = cc.new_gp32("i");
|
||||
cc.mov(sum, 0);
|
||||
}
|
||||
|
||||
cc.ret(sum);
|
||||
cc.end_func();
|
||||
}
|
||||
|
||||
bool run(void* _func, String& result, String& expect) override {
|
||||
using U = unsigned int;
|
||||
|
||||
using Func0 = U (*)();
|
||||
using Func1 = U (*)(U);
|
||||
using Func2 = U (*)(U, U);
|
||||
using Func3 = U (*)(U, U, U);
|
||||
using Func4 = U (*)(U, U, U, U);
|
||||
using Func5 = U (*)(U, U, U, U, U);
|
||||
using Func6 = U (*)(U, U, U, U, U, U);
|
||||
using Func7 = U (*)(U, U, U, U, U, U, U);
|
||||
using Func8 = U (*)(U, U, U, U, U, U, U, U);
|
||||
using Func9 = U (*)(U, U, U, U, U, U, U, U, U);
|
||||
using Func10 = U (*)(U, U, U, U, U, U, U, U, U, U);
|
||||
using Func11 = U (*)(U, U, U, U, U, U, U, U, U, U, U);
|
||||
using Func12 = U (*)(U, U, U, U, U, U, U, U, U, U, U, U);
|
||||
using Func13 = U (*)(U, U, U, U, U, U, U, U, U, U, U, U, U);
|
||||
using Func14 = U (*)(U, U, U, U, U, U, U, U, U, U, U, U, U, U);
|
||||
using Func15 = U (*)(U, U, U, U, U, U, U, U, U, U, U, U, U, U, U);
|
||||
using Func16 = U (*)(U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U);
|
||||
|
||||
unsigned int result_ret = 0;
|
||||
unsigned int expect_ret = 0;
|
||||
|
||||
switch (_arg_count) {
|
||||
case 0:
|
||||
result_ret = ptr_as_func<Func0>(_func)();
|
||||
expect_ret = 0;
|
||||
break;
|
||||
case 1:
|
||||
result_ret = ptr_as_func<Func1>(_func)(1);
|
||||
expect_ret = 1;
|
||||
break;
|
||||
case 2:
|
||||
result_ret = ptr_as_func<Func2>(_func)(1, 2);
|
||||
expect_ret = 1 + 2;
|
||||
break;
|
||||
case 3:
|
||||
result_ret = ptr_as_func<Func3>(_func)(1, 2, 3);
|
||||
expect_ret = 1 + 2 + 3;
|
||||
break;
|
||||
case 4:
|
||||
result_ret = ptr_as_func<Func4>(_func)(1, 2, 3, 4);
|
||||
expect_ret = 1 + 2 + 3 + 4;
|
||||
break;
|
||||
case 5:
|
||||
result_ret = ptr_as_func<Func5>(_func)(1, 2, 3, 4, 5);
|
||||
expect_ret = 1 + 2 + 3 + 4 + 5;
|
||||
break;
|
||||
case 6:
|
||||
result_ret = ptr_as_func<Func6>(_func)(1, 2, 3, 4, 5, 6);
|
||||
expect_ret = 1 + 2 + 3 + 4 + 5 + 6;
|
||||
break;
|
||||
case 7:
|
||||
result_ret = ptr_as_func<Func7>(_func)(1, 2, 3, 4, 5, 6, 7);
|
||||
expect_ret = 1 + 2 + 3 + 4 + 5 + 6 + 7;
|
||||
break;
|
||||
case 8:
|
||||
result_ret = ptr_as_func<Func8>(_func)(1, 2, 3, 4, 5, 6, 7, 8);
|
||||
expect_ret = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8;
|
||||
break;
|
||||
case 9:
|
||||
result_ret = ptr_as_func<Func9>(_func)(1, 2, 3, 4, 5, 6, 7, 8, 9);
|
||||
expect_ret = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9;
|
||||
break;
|
||||
case 10:
|
||||
result_ret = ptr_as_func<Func10>(_func)(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
|
||||
expect_ret = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10;
|
||||
break;
|
||||
case 11:
|
||||
result_ret = ptr_as_func<Func11>(_func)(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
|
||||
expect_ret = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11;
|
||||
break;
|
||||
case 12:
|
||||
result_ret = ptr_as_func<Func12>(_func)(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
|
||||
expect_ret = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12;
|
||||
break;
|
||||
case 13:
|
||||
result_ret = ptr_as_func<Func13>(_func)(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13);
|
||||
expect_ret = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13;
|
||||
break;
|
||||
case 14:
|
||||
result_ret = ptr_as_func<Func14>(_func)(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14);
|
||||
expect_ret = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14;
|
||||
break;
|
||||
case 15:
|
||||
result_ret = ptr_as_func<Func15>(_func)(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
|
||||
expect_ret = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15;
|
||||
break;
|
||||
case 16:
|
||||
result_ret = ptr_as_func<Func16>(_func)(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
|
||||
expect_ret = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16;
|
||||
break;
|
||||
}
|
||||
|
||||
result.assign_format("ret={%u, %u}", result_ret >> 28, result_ret & 0x0FFFFFFFu);
|
||||
expect.assign_format("ret={%u, %u}", expect_ret >> 28, expect_ret & 0x0FFFFFFFu);
|
||||
|
||||
return result == expect;
|
||||
}
|
||||
};
|
||||
|
||||
// a64::Compiler - A64Test_Simd1
|
||||
// =============================
|
||||
|
||||
class A64Test_Simd1 : public A64TestCase {
|
||||
public:
|
||||
A64Test_Simd1()
|
||||
: A64TestCase("Simd1") {}
|
||||
|
||||
static void add(TestApp& app) {
|
||||
app.add(new A64Test_Simd1());
|
||||
}
|
||||
|
||||
void compile(a64::Compiler& cc) override {
|
||||
FuncNode* func_node = cc.add_func(FuncSignature::build<void, void*, const void*, const void*>());
|
||||
|
||||
a64::Gp dst = cc.new_gp_ptr("dst");
|
||||
a64::Gp src1 = cc.new_gp_ptr("src1");
|
||||
a64::Gp src2 = cc.new_gp_ptr("src2");
|
||||
|
||||
func_node->set_arg(0, dst);
|
||||
func_node->set_arg(1, src1);
|
||||
func_node->set_arg(2, src2);
|
||||
|
||||
a64::Vec v1 = cc.new_vec_q("vec1");
|
||||
a64::Vec v2 = cc.new_vec_q("vec2");
|
||||
a64::Vec v3 = cc.new_vec_q("vec3");
|
||||
|
||||
cc.ldr(v2, a64::ptr(src1));
|
||||
cc.ldr(v3, a64::ptr(src2));
|
||||
cc.add(v1.b16(), v2.b16(), v3.b16());
|
||||
cc.str(v1, a64::ptr(dst));
|
||||
|
||||
cc.end_func();
|
||||
}
|
||||
|
||||
bool run(void* _func, String& result, String& expect) override {
|
||||
using Func = void (*)(void*, const void*, const void*);
|
||||
|
||||
uint32_t dst[4];
|
||||
uint32_t a_src[4] = { 0 , 1 , 2 , 255 };
|
||||
uint32_t b_src[4] = { 99, 17, 33, 1 };
|
||||
|
||||
// NOTE: It's a byte-add, so uint8_t(255+1) == 0.
|
||||
uint32_t ref[4] = { 99, 18, 35, 0 };
|
||||
|
||||
ptr_as_func<Func>(_func)(dst, a_src, b_src);
|
||||
|
||||
result.assign_format("ret={%u, %u, %u, %u}", dst[0], dst[1], dst[2], dst[3]);
|
||||
expect.assign_format("ret={%u, %u, %u, %u}", ref[0], ref[1], ref[2], ref[3]);
|
||||
|
||||
return result == expect;
|
||||
}
|
||||
};
|
||||
|
||||
// a64::Compiler - A64Test_ManyRegs
|
||||
// ================================
|
||||
|
||||
class A64Test_ManyRegs : public A64TestCase {
|
||||
public:
|
||||
uint32_t _reg_count;
|
||||
|
||||
A64Test_ManyRegs(uint32_t n)
|
||||
: A64TestCase(),
|
||||
_reg_count(n) {
|
||||
_name.assign_format("GpRegs {NumRegs=%u}", n);
|
||||
}
|
||||
|
||||
static void add(TestApp& app) {
|
||||
for (uint32_t i = 2; i < 64; i++)
|
||||
app.add(new A64Test_ManyRegs(i));
|
||||
}
|
||||
|
||||
void compile(a64::Compiler& cc) override {
|
||||
cc.add_func(FuncSignature::build<int>());
|
||||
|
||||
a64::Gp* regs = static_cast<a64::Gp*>(malloc(_reg_count * sizeof(a64::Gp)));
|
||||
|
||||
for (uint32_t i = 0; i < _reg_count; i++) {
|
||||
regs[i] = cc.new_gp32("reg%u", i);
|
||||
cc.mov(regs[i], i + 1);
|
||||
}
|
||||
|
||||
a64::Gp sum = cc.new_gp32("sum");
|
||||
cc.mov(sum, 0);
|
||||
|
||||
for (uint32_t i = 0; i < _reg_count; i++) {
|
||||
cc.add(sum, sum, regs[i]);
|
||||
}
|
||||
|
||||
cc.ret(sum);
|
||||
cc.end_func();
|
||||
|
||||
free(regs);
|
||||
}
|
||||
|
||||
bool run(void* _func, String& result, String& expect) override {
|
||||
using Func = int (*)(void);
|
||||
Func func = ptr_as_func<Func>(_func);
|
||||
|
||||
result.assign_format("ret={%d}", func());
|
||||
expect.assign_format("ret={%d}", calc_sum());
|
||||
|
||||
return result == expect;
|
||||
}
|
||||
|
||||
uint32_t calc_sum() const {
|
||||
return (_reg_count | 1) * ((_reg_count + 1) / 2);
|
||||
}
|
||||
};
|
||||
|
||||
// a64::Compiler - A64Test_Adr
|
||||
// ===========================
|
||||
|
||||
class A64Test_Adr : public A64TestCase {
|
||||
public:
|
||||
A64Test_Adr()
|
||||
: A64TestCase("Adr") {}
|
||||
|
||||
static void add(TestApp& app) {
|
||||
app.add(new A64Test_Adr());
|
||||
}
|
||||
|
||||
void compile(a64::Compiler& cc) override {
|
||||
cc.add_func(FuncSignature::build<int>());
|
||||
|
||||
a64::Gp addr = cc.new_gp_ptr("addr");
|
||||
a64::Gp val = cc.new_gp_ptr("val");
|
||||
|
||||
Label L_Table = cc.new_label();
|
||||
|
||||
cc.adr(addr, L_Table);
|
||||
cc.ldrsw(val, a64::ptr(addr, 8));
|
||||
cc.ret(val);
|
||||
cc.end_func();
|
||||
|
||||
cc.bind(L_Table);
|
||||
cc.embed_int32(1);
|
||||
cc.embed_int32(2);
|
||||
cc.embed_int32(3);
|
||||
cc.embed_int32(4);
|
||||
cc.embed_int32(5);
|
||||
}
|
||||
|
||||
bool run(void* _func, String& result, String& expect) override {
|
||||
using Func = int (*)(void);
|
||||
Func func = ptr_as_func<Func>(_func);
|
||||
|
||||
result.assign_format("ret={%d}", func());
|
||||
expect.assign_format("ret={%d}", 3);
|
||||
|
||||
return result == expect;
|
||||
}
|
||||
};
|
||||
|
||||
// a64::Compiler - A64Test_Branch1
|
||||
// ===============================
|
||||
|
||||
class A64Test_Branch1 : public A64TestCase {
|
||||
public:
|
||||
A64Test_Branch1()
|
||||
: A64TestCase("Branch1") {}
|
||||
|
||||
static void add(TestApp& app) {
|
||||
app.add(new A64Test_Branch1());
|
||||
}
|
||||
|
||||
void compile(a64::Compiler& cc) override {
|
||||
FuncNode* func_node = cc.add_func(FuncSignature::build<void, void*, size_t>());
|
||||
|
||||
a64::Gp p = cc.new_gp_ptr("p");
|
||||
a64::Gp count = cc.new_gp_ptr("count");
|
||||
a64::Gp i = cc.new_gp_ptr("i");
|
||||
Label L = cc.new_label();
|
||||
|
||||
func_node->set_arg(0, p);
|
||||
func_node->set_arg(1, count);
|
||||
|
||||
cc.mov(i, 0);
|
||||
|
||||
cc.bind(L);
|
||||
cc.strb(i.w(), a64::ptr(p, i));
|
||||
cc.add(i, i, 1);
|
||||
cc.cmp(i, count);
|
||||
cc.b_ne(L);
|
||||
|
||||
cc.end_func();
|
||||
}
|
||||
|
||||
bool run(void* _func, String& result, String& expect) override {
|
||||
using Func = void (*)(void* p, size_t n);
|
||||
Func func = ptr_as_func<Func>(_func);
|
||||
|
||||
uint8_t array[16];
|
||||
func(array, 16);
|
||||
|
||||
expect.assign("ret={0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}");
|
||||
|
||||
result.assign("ret={");
|
||||
for (size_t i = 0; i < 16; i++) {
|
||||
if (i)
|
||||
result.append(", ");
|
||||
result.append_format("%d", int(array[i]));
|
||||
}
|
||||
result.append("}");
|
||||
|
||||
return result == expect;
|
||||
}
|
||||
};
|
||||
|
||||
// a64::Compiler - A64Test_Invoke1
|
||||
// ===============================
|
||||
|
||||
class A64Test_Invoke1 : public A64TestCase {
|
||||
public:
|
||||
A64Test_Invoke1()
|
||||
: A64TestCase("Invoke1") {}
|
||||
|
||||
static void add(TestApp& app) {
|
||||
app.add(new A64Test_Invoke1());
|
||||
}
|
||||
|
||||
void compile(a64::Compiler& cc) override {
|
||||
FuncNode* func_node = cc.add_func(FuncSignature::build<uint32_t, uint32_t, uint32_t>());
|
||||
|
||||
a64::Gp x = cc.new_gp32("x");
|
||||
a64::Gp y = cc.new_gp32("y");
|
||||
a64::Gp r = cc.new_gp32("r");
|
||||
a64::Gp fn = cc.new_gp_ptr("fn");
|
||||
|
||||
func_node->set_arg(0, x);
|
||||
func_node->set_arg(1, y);
|
||||
|
||||
cc.mov(fn, (uint64_t)called_fn);
|
||||
|
||||
InvokeNode* invoke_node;
|
||||
cc.invoke(Out(invoke_node), fn, FuncSignature::build<uint32_t, uint32_t, uint32_t>());
|
||||
invoke_node->set_arg(0, x);
|
||||
invoke_node->set_arg(1, y);
|
||||
invoke_node->set_ret(0, r);
|
||||
|
||||
cc.ret(r);
|
||||
cc.end_func();
|
||||
}
|
||||
|
||||
bool run(void* _func, String& result, String& expect) override {
|
||||
using Func = uint32_t (*)(uint32_t, uint32_t);
|
||||
Func func = ptr_as_func<Func>(_func);
|
||||
|
||||
uint32_t x = 49;
|
||||
uint32_t y = 7;
|
||||
|
||||
result.assign_format("ret={%u}", func(x, y));
|
||||
expect.assign_format("ret={%u}", x - y);
|
||||
|
||||
return result == expect;
|
||||
}
|
||||
|
||||
static uint32_t called_fn(uint32_t x, uint32_t y) {
|
||||
return x - y;
|
||||
}
|
||||
};
|
||||
|
||||
// a64::Compiler - A64Test_Invoke2
|
||||
// ===============================
|
||||
|
||||
class A64Test_Invoke2 : public A64TestCase {
|
||||
public:
|
||||
A64Test_Invoke2()
|
||||
: A64TestCase("Invoke2") {}
|
||||
|
||||
static void add(TestApp& app) {
|
||||
app.add(new A64Test_Invoke2());
|
||||
}
|
||||
|
||||
void compile(a64::Compiler& cc) override {
|
||||
FuncNode* func_node = cc.add_func(FuncSignature::build<double, double, double>());
|
||||
|
||||
a64::Vec x = cc.new_vec_d("x");
|
||||
a64::Vec y = cc.new_vec_d("y");
|
||||
a64::Vec r = cc.new_vec_d("r");
|
||||
a64::Gp fn = cc.new_gp_ptr("fn");
|
||||
|
||||
func_node->set_arg(0, x);
|
||||
func_node->set_arg(1, y);
|
||||
cc.mov(fn, (uint64_t)called_fn);
|
||||
|
||||
InvokeNode* invoke_node;
|
||||
cc.invoke(Out(invoke_node), fn, FuncSignature::build<double, double, double>());
|
||||
invoke_node->set_arg(0, x);
|
||||
invoke_node->set_arg(1, y);
|
||||
invoke_node->set_ret(0, r);
|
||||
|
||||
cc.ret(r);
|
||||
cc.end_func();
|
||||
}
|
||||
|
||||
bool run(void* _func, String& result, String& expect) override {
|
||||
using Func = double (*)(double, double);
|
||||
Func func = ptr_as_func<Func>(_func);
|
||||
|
||||
double x = 49;
|
||||
double y = 7;
|
||||
|
||||
result.assign_format("ret={%f}", func(x, y));
|
||||
expect.assign_format("ret={%f}", called_fn(x, y));
|
||||
|
||||
return result == expect;
|
||||
}
|
||||
|
||||
static double called_fn(double x, double y) {
|
||||
return x - y;
|
||||
}
|
||||
};
|
||||
|
||||
// a64::Compiler - A64Test_Invoke3
|
||||
// ===============================
|
||||
|
||||
class A64Test_Invoke3 : public A64TestCase {
|
||||
public:
|
||||
A64Test_Invoke3()
|
||||
: A64TestCase("Invoke3") {}
|
||||
|
||||
static void add(TestApp& app) {
|
||||
app.add(new A64Test_Invoke3());
|
||||
}
|
||||
|
||||
void compile(a64::Compiler& cc) override {
|
||||
FuncNode* func_node = cc.add_func(FuncSignature::build<double, double, double>());
|
||||
|
||||
a64::Vec x = cc.new_vec_d("x");
|
||||
a64::Vec y = cc.new_vec_d("y");
|
||||
a64::Vec r = cc.new_vec_d("r");
|
||||
a64::Gp fn = cc.new_gp_ptr("fn");
|
||||
|
||||
func_node->set_arg(0, x);
|
||||
func_node->set_arg(1, y);
|
||||
cc.mov(fn, (uint64_t)called_fn);
|
||||
|
||||
InvokeNode* invoke_node;
|
||||
cc.invoke(Out(invoke_node), fn, FuncSignature::build<double, double, double>());
|
||||
invoke_node->set_arg(0, y);
|
||||
invoke_node->set_arg(1, x);
|
||||
invoke_node->set_ret(0, r);
|
||||
|
||||
cc.ret(r);
|
||||
cc.end_func();
|
||||
}
|
||||
|
||||
bool run(void* _func, String& result, String& expect) override {
|
||||
using Func = double (*)(double, double);
|
||||
Func func = ptr_as_func<Func>(_func);
|
||||
|
||||
double x = 49;
|
||||
double y = 7;
|
||||
|
||||
result.assign_format("ret={%f}", func(x, y));
|
||||
expect.assign_format("ret={%f}", called_fn(y, x));
|
||||
|
||||
return result == expect;
|
||||
}
|
||||
|
||||
static double called_fn(double x, double y) {
|
||||
return x - y;
|
||||
}
|
||||
};
|
||||
|
||||
// a64::Compiler - A64Test_JumpTable
|
||||
// =================================
|
||||
|
||||
class A64Test_JumpTable : public A64TestCase {
|
||||
public:
|
||||
bool _annotated;
|
||||
|
||||
A64Test_JumpTable(bool annotated)
|
||||
: A64TestCase("A64Test_JumpTable"),
|
||||
_annotated(annotated) {
|
||||
_name.assign_format("JumpTable {%s}", annotated ? "Annotated" : "Unknown Target");
|
||||
}
|
||||
|
||||
enum Operator {
|
||||
kOperatorAdd = 0,
|
||||
kOperatorSub = 1,
|
||||
kOperatorMul = 2,
|
||||
kOperatorDiv = 3
|
||||
};
|
||||
|
||||
static void add(TestApp& app) {
|
||||
app.add(new A64Test_JumpTable(false));
|
||||
app.add(new A64Test_JumpTable(true));
|
||||
}
|
||||
|
||||
void compile(a64::Compiler& cc) override {
|
||||
FuncNode* func_node = cc.add_func(FuncSignature::build<float, float, float, uint32_t>());
|
||||
|
||||
a64::Vec a = cc.new_vec_s("a");
|
||||
a64::Vec b = cc.new_vec_s("b");
|
||||
a64::Gp op = cc.new_gp32("op");
|
||||
|
||||
a64::Gp target = cc.new_gp_ptr("target");
|
||||
a64::Gp offset = cc.new_gp_ptr("offset");
|
||||
|
||||
Label L_End = cc.new_label();
|
||||
|
||||
Label L_Table = cc.new_label();
|
||||
Label L_Add = cc.new_label();
|
||||
Label L_Sub = cc.new_label();
|
||||
Label L_Mul = cc.new_label();
|
||||
Label L_Div = cc.new_label();
|
||||
|
||||
func_node->set_arg(0, a);
|
||||
func_node->set_arg(1, b);
|
||||
func_node->set_arg(2, op);
|
||||
|
||||
cc.adr(target, L_Table);
|
||||
cc.ldrsw(offset, a64::ptr(target, op, a64::sxtw(2)));
|
||||
cc.add(target, target, offset);
|
||||
|
||||
// JumpAnnotation allows to annotate all possible jump targets of
|
||||
// instructions where it cannot be deduced from operands.
|
||||
if (_annotated) {
|
||||
JumpAnnotation* annotation = cc.new_jump_annotation();
|
||||
annotation->add_label(L_Add);
|
||||
annotation->add_label(L_Sub);
|
||||
annotation->add_label(L_Mul);
|
||||
annotation->add_label(L_Div);
|
||||
cc.br(target, annotation);
|
||||
}
|
||||
else {
|
||||
cc.br(target);
|
||||
}
|
||||
|
||||
cc.bind(L_Add);
|
||||
cc.fadd(a, a, b);
|
||||
cc.b(L_End);
|
||||
|
||||
cc.bind(L_Sub);
|
||||
cc.fsub(a, a, b);
|
||||
cc.b(L_End);
|
||||
|
||||
cc.bind(L_Mul);
|
||||
cc.fmul(a, a, b);
|
||||
cc.b(L_End);
|
||||
|
||||
cc.bind(L_Div);
|
||||
cc.fdiv(a, a, b);
|
||||
|
||||
cc.bind(L_End);
|
||||
cc.ret(a);
|
||||
cc.end_func();
|
||||
|
||||
cc.bind(L_Table);
|
||||
cc.embed_label_delta(L_Add, L_Table, 4);
|
||||
cc.embed_label_delta(L_Sub, L_Table, 4);
|
||||
cc.embed_label_delta(L_Mul, L_Table, 4);
|
||||
cc.embed_label_delta(L_Div, L_Table, 4);
|
||||
}
|
||||
|
||||
bool run(void* _func, String& result, String& expect) override {
|
||||
using Func = float (*)(float, float, uint32_t);
|
||||
Func func = ptr_as_func<Func>(_func);
|
||||
|
||||
float dst[4];
|
||||
float ref[4];
|
||||
|
||||
dst[0] = func(33.0f, 14.0f, kOperatorAdd);
|
||||
dst[1] = func(33.0f, 14.0f, kOperatorSub);
|
||||
dst[2] = func(10.0f, 6.0f, kOperatorMul);
|
||||
dst[3] = func(80.0f, 8.0f, kOperatorDiv);
|
||||
|
||||
ref[0] = 47.0f;
|
||||
ref[1] = 19.0f;
|
||||
ref[2] = 60.0f;
|
||||
ref[3] = 10.0f;
|
||||
|
||||
result.assign_format("ret={%f, %f, %f, %f}", double(dst[0]), double(dst[1]), double(dst[2]), double(dst[3]));
|
||||
expect.assign_format("ret={%f, %f, %f, %f}", double(ref[0]), double(ref[1]), double(ref[2]), double(ref[3]));
|
||||
|
||||
return result == expect;
|
||||
}
|
||||
};
|
||||
|
||||
// a64::Compiler - Export
|
||||
// ======================
|
||||
|
||||
void compiler_add_a64_tests(TestApp& app) {
|
||||
app.add_t<A64Test_GpArgs>();
|
||||
app.add_t<A64Test_ManyRegs>();
|
||||
app.add_t<A64Test_Simd1>();
|
||||
app.add_t<A64Test_Adr>();
|
||||
app.add_t<A64Test_Branch1>();
|
||||
app.add_t<A64Test_Invoke1>();
|
||||
app.add_t<A64Test_Invoke2>();
|
||||
app.add_t<A64Test_Invoke3>();
|
||||
app.add_t<A64Test_JumpTable>();
|
||||
}
|
||||
|
||||
#endif // !ASMJIT_NO_COMPILER && !ASMJIT_NO_AARCH64
|
||||
4694
asmjit-testing/tests/asmjit_test_compiler_x86.cpp
Normal file
4694
asmjit-testing/tests/asmjit_test_compiler_x86.cpp
Normal file
File diff suppressed because it is too large
Load Diff
328
asmjit-testing/tests/asmjit_test_emitters.cpp
Normal file
328
asmjit-testing/tests/asmjit_test_emitters.cpp
Normal file
@@ -0,0 +1,328 @@
|
||||
// This file is part of AsmJit project <https://asmjit.com>
|
||||
//
|
||||
// See <asmjit/core.h> or LICENSE.md for license and copyright information
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <asmjit/core.h>
|
||||
|
||||
#if ASMJIT_ARCH_X86 != 0
|
||||
#include <asmjit/x86.h>
|
||||
#endif
|
||||
|
||||
#if ASMJIT_ARCH_ARM == 64
|
||||
#include <asmjit/a64.h>
|
||||
#endif
|
||||
|
||||
#include <asmjit-testing/commons/asmjitutils.h>
|
||||
|
||||
using namespace asmjit;
|
||||
|
||||
static void print_app_info() noexcept {
|
||||
printf("AsmJit Emitters Test-Suite v%u.%u.%u [Arch=%s] [Mode=%s]\n\n",
|
||||
unsigned((ASMJIT_LIBRARY_VERSION >> 16) ),
|
||||
unsigned((ASMJIT_LIBRARY_VERSION >> 8) & 0xFF),
|
||||
unsigned((ASMJIT_LIBRARY_VERSION ) & 0xFF),
|
||||
asmjit_arch_as_string(Arch::kHost),
|
||||
asmjit_build_type()
|
||||
);
|
||||
}
|
||||
|
||||
#if !defined(ASMJIT_NO_JIT) && ((ASMJIT_ARCH_X86 != 0 && !defined(ASMJIT_NO_X86 )) || \
|
||||
(ASMJIT_ARCH_ARM == 64 && !defined(ASMJIT_NO_AARCH64)) )
|
||||
|
||||
// Signature of the generated function.
|
||||
using SumIntsFunc = void (*)(int* dst, const int* a, const int* b);
|
||||
|
||||
// X86 Backend
|
||||
// -----------
|
||||
|
||||
#if ASMJIT_ARCH_X86 != 0
|
||||
// This function works with both x86::Assembler and x86::Builder. It shows how
|
||||
// `x86::Emitter` can be used to make your code more generic.
|
||||
static void generate_func_with_emitter(x86::Emitter* emitter) noexcept {
|
||||
// Decide which registers will be mapped to function arguments. Try changing
|
||||
// registers of `dst`, `src_a`, and `src_b` and see what happens in function's
|
||||
// prolog and epilog.
|
||||
x86::Gp dst = emitter->zax();
|
||||
x86::Gp src_a = emitter->zcx();
|
||||
x86::Gp src_b = emitter->zdx();
|
||||
|
||||
// Decide which vector registers to use. We use these to keep the code generic,
|
||||
// you can switch to any other registers when needed.
|
||||
x86::Vec vec0 = x86::xmm0;
|
||||
x86::Vec vec1 = x86::xmm1;
|
||||
|
||||
// Create and initialize `FuncDetail` and `FuncFrame`.
|
||||
FuncDetail func;
|
||||
func.init(FuncSignature::build<void, int*, const int*, const int*>(), emitter->environment());
|
||||
|
||||
FuncFrame frame;
|
||||
frame.init(func);
|
||||
|
||||
// Make or registers dirty.
|
||||
frame.add_dirty_regs(vec0, vec1);
|
||||
|
||||
FuncArgsAssignment args(&func); // Create arguments assignment context.
|
||||
args.assign_all(dst, src_a, src_b); // Assign our registers to arguments.
|
||||
args.update_func_frame(frame); // Reflect our args in FuncFrame.
|
||||
frame.finalize();
|
||||
|
||||
// Emit prolog and allocate arguments to registers.
|
||||
emitter->emit_prolog(frame);
|
||||
emitter->emit_args_assignment(frame, args);
|
||||
|
||||
emitter->movdqu(vec0, x86::ptr(src_a)); // Load 4 ints from [src_a] to XMM0.
|
||||
emitter->movdqu(vec1, x86::ptr(src_b)); // Load 4 ints from [src_b] to XMM1.
|
||||
|
||||
emitter->paddd(vec0, vec1); // Add 4 ints in XMM1 to XMM0.
|
||||
emitter->movdqu(x86::ptr(dst), vec0); // Store the result to [dst].
|
||||
|
||||
// Emit epilog and return.
|
||||
emitter->emit_epilog(frame);
|
||||
}
|
||||
|
||||
#ifndef ASMJIT_NO_COMPILER
|
||||
// This function works with x86::Compiler, provided for comparison.
|
||||
static void generate_func_with_compiler(x86::Compiler* cc) noexcept {
|
||||
x86::Gp dst = cc->new_gp_ptr("dst");
|
||||
x86::Gp src_a = cc->new_gp_ptr("src_a");
|
||||
x86::Gp src_b = cc->new_gp_ptr("src_b");
|
||||
x86::Vec vec0 = cc->new_xmm("vec0");
|
||||
x86::Vec vec1 = cc->new_xmm("vec1");
|
||||
|
||||
FuncNode* func_node = cc->add_func(FuncSignature::build<void, int*, const int*, const int*>());
|
||||
func_node->set_arg(0, dst);
|
||||
func_node->set_arg(1, src_a);
|
||||
func_node->set_arg(2, src_b);
|
||||
|
||||
cc->movdqu(vec0, x86::ptr(src_a));
|
||||
cc->movdqu(vec1, x86::ptr(src_b));
|
||||
cc->paddd(vec0, vec1);
|
||||
cc->movdqu(x86::ptr(dst), vec0);
|
||||
cc->end_func();
|
||||
}
|
||||
#endif
|
||||
|
||||
static Error generate_func(CodeHolder& code, EmitterType emitter_type) noexcept {
|
||||
switch (emitter_type) {
|
||||
case EmitterType::kAssembler: {
|
||||
printf("Using x86::Assembler:\n");
|
||||
x86::Assembler a(&code);
|
||||
generate_func_with_emitter(a.as<x86::Emitter>());
|
||||
return Error::kOk;
|
||||
}
|
||||
|
||||
#ifndef ASMJIT_NO_BUILDER
|
||||
case EmitterType::kBuilder: {
|
||||
printf("Using x86::Builder:\n");
|
||||
x86::Builder cb(&code);
|
||||
generate_func_with_emitter(cb.as<x86::Emitter>());
|
||||
|
||||
return cb.finalize();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef ASMJIT_NO_COMPILER
|
||||
case EmitterType::kCompiler: {
|
||||
printf("Using x86::Compiler:\n");
|
||||
x86::Compiler cc(&code);
|
||||
generate_func_with_compiler(&cc);
|
||||
|
||||
return cc.finalize();
|
||||
}
|
||||
#endif
|
||||
|
||||
default: {
|
||||
printf("** FAILURE: No emitter to use **\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// AArch64 Backend
|
||||
// ---------------
|
||||
|
||||
#if ASMJIT_ARCH_ARM == 64
|
||||
// This function works with both a64::Assembler and a64::Builder. It shows how
|
||||
// `a64::Emitter` can be used to make your code more generic.
|
||||
static void generate_func_with_emitter(a64::Emitter* emitter) noexcept {
|
||||
// Decide which registers will be mapped to function arguments. Try changing
|
||||
// registers of `dst`, `src_a`, and `src_b` and see what happens in function's
|
||||
// prolog and epilog.
|
||||
a64::Gp dst = a64::x0;
|
||||
a64::Gp src_a = a64::x1;
|
||||
a64::Gp src_b = a64::x2;
|
||||
|
||||
// Decide which vector registers to use. We use these to keep the code generic,
|
||||
// you can switch to any other registers when needed.
|
||||
a64::Vec vec0 = a64::v0;
|
||||
a64::Vec vec1 = a64::v1;
|
||||
a64::Vec vec2 = a64::v2;
|
||||
|
||||
// Create and initialize `FuncDetail` and `FuncFrame`.
|
||||
FuncDetail func;
|
||||
func.init(FuncSignature::build<void, int*, const int*, const int*>(), emitter->environment());
|
||||
|
||||
FuncFrame frame;
|
||||
frame.init(func);
|
||||
|
||||
// Make XMM0 and XMM1 dirty. VEC group includes XMM|YMM|ZMM registers.
|
||||
frame.add_dirty_regs(vec0, vec1, vec2);
|
||||
|
||||
FuncArgsAssignment args(&func); // Create arguments assignment context.
|
||||
args.assign_all(dst, src_a, src_b); // Assign our registers to arguments.
|
||||
args.update_func_frame(frame); // Reflect our args in FuncFrame.
|
||||
frame.finalize();
|
||||
|
||||
// Emit prolog and allocate arguments to registers.
|
||||
emitter->emit_prolog(frame);
|
||||
emitter->emit_args_assignment(frame, args);
|
||||
|
||||
emitter->ld1(vec0.b16(), a64::ptr(src_a)); // Load 4 ints from [src_a] to vec0.
|
||||
emitter->ld1(vec1.b16(), a64::ptr(src_b)); // Load 4 ints from [src_b] to vec1.
|
||||
emitter->add(vec2.s4(), vec0.s4(), vec1.s4()); // Add 4 ints of vec0 and vec1 and store to vec2.
|
||||
emitter->st1(vec2.b16(), a64::ptr(dst)); // Store the result (vec2) to [dst].
|
||||
|
||||
// Emit epilog and return.
|
||||
emitter->emit_epilog(frame);
|
||||
}
|
||||
|
||||
#ifndef ASMJIT_NO_COMPILER
|
||||
// This function works with x86::Compiler, provided for comparison.
|
||||
static void generate_func_with_compiler(a64::Compiler* cc) noexcept {
|
||||
a64::Gp dst = cc->new_gp_ptr("dst");
|
||||
a64::Gp src_a = cc->new_gp_ptr("src_a");
|
||||
a64::Gp src_b = cc->new_gp_ptr("src_b");
|
||||
a64::Vec vec0 = cc->new_vec_q("vec0");
|
||||
a64::Vec vec1 = cc->new_vec_q("vec1");
|
||||
a64::Vec vec2 = cc->new_vec_q("vec2");
|
||||
|
||||
FuncNode* func_node = cc->add_func(FuncSignature::build<void, int*, const int*, const int*>());
|
||||
func_node->set_arg(0, dst);
|
||||
func_node->set_arg(1, src_a);
|
||||
func_node->set_arg(2, src_b);
|
||||
|
||||
cc->ld1(vec0.b16(), a64::ptr(src_a)); // Load 4 ints from [src_a] to vec0.
|
||||
cc->ld1(vec1.b16(), a64::ptr(src_b)); // Load 4 ints from [src_b] to vec1.
|
||||
cc->add(vec2.s4(), vec0.s4(), vec1.s4()); // Add 4 ints of vec0 and vec1 and store to vec2.
|
||||
cc->st1(vec2.b16(), a64::ptr(dst)); // Store the result (vec2) to [dst].
|
||||
cc->end_func();
|
||||
}
|
||||
#endif
|
||||
|
||||
static Error generate_func(CodeHolder& code, EmitterType emitter_type) noexcept {
|
||||
switch (emitter_type) {
|
||||
case EmitterType::kAssembler: {
|
||||
printf("Using a64::Assembler:\n");
|
||||
a64::Assembler a(&code);
|
||||
generate_func_with_emitter(a.as<a64::Emitter>());
|
||||
return Error::kOk;
|
||||
}
|
||||
|
||||
#ifndef ASMJIT_NO_BUILDER
|
||||
case EmitterType::kBuilder: {
|
||||
printf("Using a64::Builder:\n");
|
||||
a64::Builder cb(&code);
|
||||
generate_func_with_emitter(cb.as<a64::Emitter>());
|
||||
|
||||
return cb.finalize();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef ASMJIT_NO_COMPILER
|
||||
case EmitterType::kCompiler: {
|
||||
printf("Using a64::Compiler:\n");
|
||||
a64::Compiler cc(&code);
|
||||
generate_func_with_compiler(&cc);
|
||||
|
||||
return cc.finalize();
|
||||
}
|
||||
#endif
|
||||
|
||||
default: {
|
||||
printf("** FAILURE: No emitter to use **\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Testing
|
||||
// -------
|
||||
|
||||
static uint32_t test_func(JitRuntime& rt, EmitterType emitter_type) noexcept {
|
||||
#ifndef ASMJIT_NO_LOGGING
|
||||
FileLogger logger(stdout);
|
||||
logger.set_indentation(FormatIndentationGroup::kCode, 2);
|
||||
#endif
|
||||
|
||||
CodeHolder code;
|
||||
code.init(rt.environment(), rt.cpu_features());
|
||||
|
||||
#ifndef ASMJIT_NO_LOGGING
|
||||
code.set_logger(&logger);
|
||||
#endif
|
||||
|
||||
Error err = generate_func(code, emitter_type);
|
||||
if (err != Error::kOk) {
|
||||
printf("** FAILURE: Failed to generate a function: %s **\n", DebugUtils::error_as_string(err));
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Add the code generated to the runtime.
|
||||
SumIntsFunc fn;
|
||||
err = rt.add(&fn, &code);
|
||||
|
||||
if (err != Error::kOk) {
|
||||
printf("** FAILURE: JitRuntime::add() failed: %s **\n", DebugUtils::error_as_string(err));
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Execute the generated function.
|
||||
static const int in_a[4] = { 4, 3, 2, 1 };
|
||||
static const int in_b[4] = { 1, 5, 2, 8 };
|
||||
int out[4] {};
|
||||
fn(out, in_a, in_b);
|
||||
|
||||
// Should print {5 8 4 9}.
|
||||
printf("Result = { %d %d %d %d }\n\n", out[0], out[1], out[2], out[3]);
|
||||
|
||||
rt.release(fn);
|
||||
return out[0] == 5 && out[1] == 8 && out[2] == 4 && out[3] == 9;
|
||||
}
|
||||
|
||||
int main() {
|
||||
print_app_info();
|
||||
|
||||
JitRuntime rt;
|
||||
unsigned failed_count = 0;
|
||||
|
||||
failed_count += !test_func(rt, EmitterType::kAssembler);
|
||||
|
||||
#ifndef ASMJIT_NO_BUILDER
|
||||
failed_count += !test_func(rt, EmitterType::kBuilder);
|
||||
#endif
|
||||
|
||||
#ifndef ASMJIT_NO_COMPILER
|
||||
failed_count += !test_func(rt, EmitterType::kCompiler);
|
||||
#endif
|
||||
|
||||
if (!failed_count)
|
||||
printf("** SUCCESS **\n");
|
||||
else
|
||||
printf("** FAILURE - %u %s failed ** \n", failed_count, failed_count == 1 ? "test" : "tests");
|
||||
|
||||
return failed_count ? 1 : 0;
|
||||
}
|
||||
#else
|
||||
int main() {
|
||||
print_app_info();
|
||||
printf("!! This test is disabled: <ASMJIT_NO_JIT> or unsuitable target architecture !!\n");
|
||||
return 0;
|
||||
}
|
||||
#endif // ASMJIT_ARCH_X86 && !ASMJIT_NO_X86 && !ASMJIT_NO_JIT
|
||||
302
asmjit-testing/tests/asmjit_test_environment.cpp
Normal file
302
asmjit-testing/tests/asmjit_test_environment.cpp
Normal file
@@ -0,0 +1,302 @@
|
||||
// This file is part of AsmJit project <https://asmjit.com>
|
||||
//
|
||||
// See <asmjit/core.h> or LICENSE.md for license and copyright information
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#include <asmjit/core.h>
|
||||
|
||||
#if !defined(ASMJIT_NO_X86) && ASMJIT_ARCH_X86 != 0
|
||||
#include <asmjit/x86.h>
|
||||
#endif
|
||||
|
||||
#if !defined(ASMJIT_NO_AARCH64) && ASMJIT_ARCH_ARM == 64
|
||||
#include <asmjit/a64.h>
|
||||
#endif
|
||||
|
||||
#include <asmjit-testing/commons/asmjitutils.h>
|
||||
|
||||
using namespace asmjit;
|
||||
|
||||
static void print_app_info() {
|
||||
printf("AsmJit Environment Test v%u.%u.%u [Arch=%s] [Mode=%s]\n\n",
|
||||
unsigned((ASMJIT_LIBRARY_VERSION >> 16) ),
|
||||
unsigned((ASMJIT_LIBRARY_VERSION >> 8) & 0xFF),
|
||||
unsigned((ASMJIT_LIBRARY_VERSION ) & 0xFF),
|
||||
asmjit_arch_as_string(Arch::kHost),
|
||||
asmjit_build_type()
|
||||
);
|
||||
|
||||
printf("This application can be used to verify AsmJit build options and to verify the\n");
|
||||
printf("environment where it runs. For example to check CPU extensions available, system\n");
|
||||
printf("hardening (RWX restrictions), large page support, and virtual memory allocations.\n");
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
const char* stringify_bool(bool b) noexcept { return b ? "true" : "false"; };
|
||||
const char* stringify_result(Error err) noexcept { return err == Error::kOk ? "success" : DebugUtils::error_as_string(err); };
|
||||
|
||||
using VoidFunc = void (ASMJIT_CDECL*)(void);
|
||||
|
||||
#if !defined(ASMJIT_NO_JIT)
|
||||
|
||||
#if !defined(ASMJIT_NO_X86) && ASMJIT_ARCH_X86 != 0
|
||||
#define TEST_ENVIRONMENT_HAS_JIT
|
||||
|
||||
static void emit_void_function(CodeHolder& code) noexcept {
|
||||
x86::Assembler a(&code);
|
||||
a.ret();
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !defined(ASMJIT_NO_AARCH64) && ASMJIT_ARCH_ARM == 64
|
||||
#define TEST_ENVIRONMENT_HAS_JIT
|
||||
|
||||
static void emit_void_function(CodeHolder& code) noexcept {
|
||||
a64::Assembler a(&code);
|
||||
a.ret(a64::x30);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(TEST_ENVIRONMENT_HAS_JIT)
|
||||
static void* offset_pointer(void* ptr, size_t offset) noexcept {
|
||||
return static_cast<void*>(static_cast<uint8_t*>(ptr) + offset);
|
||||
}
|
||||
|
||||
static size_t write_empty_function_at(void* ptr, size_t size) noexcept {
|
||||
printf(" Write JIT code at addr : %p\n", ptr);
|
||||
|
||||
CodeHolder code;
|
||||
Error err = code.init(Environment::host());
|
||||
if (err != Error::kOk) {
|
||||
printf( "Failed to initialize CodeHolder (%s)\n", DebugUtils::error_as_string(err));
|
||||
return 0;
|
||||
}
|
||||
|
||||
emit_void_function(code);
|
||||
code.flatten();
|
||||
code.copy_flattened_data(ptr, size);
|
||||
|
||||
return code.code_size();
|
||||
}
|
||||
|
||||
static void flush_instruction_cache(void* ptr, size_t size) noexcept {
|
||||
printf(" Flush JIT code at addr : %p [size=%zu]\n", ptr, size);
|
||||
VirtMem::flush_instruction_cache(ptr, size);
|
||||
}
|
||||
|
||||
static void invoke_void_function(void* ptr) noexcept {
|
||||
printf(" Invoke JIT code at addr : %p\n", ptr);
|
||||
|
||||
// In case it crashes, we want to have the output flushed.
|
||||
fflush(stdout);
|
||||
|
||||
VoidFunc func = reinterpret_cast<VoidFunc>(ptr);
|
||||
func();
|
||||
}
|
||||
#endif
|
||||
|
||||
static void print_virt_mem_info_and_test_execution() noexcept {
|
||||
using MemoryFlags = VirtMem::MemoryFlags;
|
||||
using HardenedRuntimeInfo = VirtMem::HardenedRuntimeInfo;
|
||||
using HardenedRuntimeFlags = VirtMem::HardenedRuntimeFlags;
|
||||
|
||||
// Size of a virtual memory allocation.
|
||||
constexpr size_t kVMemAllocSize = 65536;
|
||||
|
||||
// Offset to the first function to execute (must be greater than 8 for UBSAN to work).
|
||||
[[maybe_unused]]
|
||||
constexpr size_t kVirtFuncOffset = 64;
|
||||
|
||||
size_t large_page_size = VirtMem::large_page_size();
|
||||
HardenedRuntimeInfo rti = VirtMem::hardened_runtime_info();
|
||||
|
||||
printf("Large/Huge Pages Info:\n");
|
||||
printf(" Large pages supported : %s\n", stringify_bool(large_page_size != 0u));
|
||||
if (large_page_size >= 1024 * 1024) {
|
||||
printf(" Large page size : %zu MiB\n", large_page_size / (1024u * 1024u));
|
||||
}
|
||||
else if (large_page_size) {
|
||||
printf(" Large page size : %zu KiB\n", large_page_size / 1024u);
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
printf("Hardened Environment Info:\n");
|
||||
printf(" Hardening was detected : %s\n", stringify_bool(rti.has_flag(HardenedRuntimeFlags::kEnabled )));
|
||||
printf(" MAP_JIT is available : %s\n", stringify_bool(rti.has_flag(HardenedRuntimeFlags::kMapJit )));
|
||||
printf(" DualMapping is available: %s\n", stringify_bool(rti.has_flag(HardenedRuntimeFlags::kDualMapping)));
|
||||
printf("\n");
|
||||
|
||||
if (!rti.has_flag(HardenedRuntimeFlags::kEnabled)) {
|
||||
printf("Virtual Memory Allocation (RWX):\n");
|
||||
|
||||
void* ptr = nullptr;
|
||||
Error result = VirtMem::alloc(&ptr, kVMemAllocSize, MemoryFlags::kAccessRWX);
|
||||
printf(" Alloc virt memory (RWX) : %s\n", stringify_result(result));
|
||||
|
||||
if (result == Error::kOk) {
|
||||
#if defined(TEST_ENVIRONMENT_HAS_JIT)
|
||||
void* func_ptr = offset_pointer(ptr, kVirtFuncOffset);
|
||||
size_t func_size = write_empty_function_at(func_ptr, kVMemAllocSize);
|
||||
|
||||
if (func_size) {
|
||||
flush_instruction_cache(func_ptr, func_size);
|
||||
invoke_void_function(func_ptr);
|
||||
}
|
||||
#endif // TEST_ENVIRONMENT_HAS_JIT
|
||||
|
||||
result = VirtMem::release(ptr, kVMemAllocSize);
|
||||
printf(" Release virt memory : %s\n", stringify_result(result));
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
{
|
||||
printf("Virtual Memory Allocation (RW - Flipping Permissions RW<->RX):\n");
|
||||
|
||||
void* ptr = nullptr;
|
||||
Error result = VirtMem::alloc(&ptr, kVMemAllocSize, MemoryFlags::kAccessRW | MemoryFlags::kMMapMaxAccessRWX);
|
||||
printf(" Alloc virt memory (RW) : %s (allocation uses kMMapMaxAccessRWX)\n", stringify_result(result));
|
||||
|
||||
if (result == Error::kOk) {
|
||||
#if defined(TEST_ENVIRONMENT_HAS_JIT)
|
||||
void* func_ptr = offset_pointer(ptr, kVirtFuncOffset);
|
||||
size_t func_size = write_empty_function_at(func_ptr, kVMemAllocSize);
|
||||
#endif // TEST_ENVIRONMENT_HAS_JIT
|
||||
|
||||
result = VirtMem::protect(ptr, kVMemAllocSize, MemoryFlags::kAccessRX);
|
||||
printf(" Protect virt memory (RX): %s\n", stringify_result(result));
|
||||
|
||||
#if defined(TEST_ENVIRONMENT_HAS_JIT)
|
||||
if (func_size) {
|
||||
flush_instruction_cache(func_ptr, func_size);
|
||||
invoke_void_function(func_ptr);
|
||||
}
|
||||
#endif // TEST_ENVIRONMENT_HAS_JIT
|
||||
|
||||
result = VirtMem::protect(ptr, kVMemAllocSize, MemoryFlags::kAccessRW);
|
||||
printf(" Protect virt memory (RW): %s\n", stringify_result(result));
|
||||
|
||||
result = VirtMem::release(ptr, kVMemAllocSize);
|
||||
printf(" Release virt memory (RW): %s\n", stringify_result(result));
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
if (rti.has_flag(HardenedRuntimeFlags::kMapJit)) {
|
||||
printf("Virtual Memory Allocation (MAP_JIT):\n");
|
||||
|
||||
void* ptr = nullptr;
|
||||
Error result = VirtMem::alloc(&ptr, kVMemAllocSize, MemoryFlags::kAccessRWX | MemoryFlags::kMMapEnableMapJit);
|
||||
printf(" Alloc virt mem (RWX) : %s (allocation uses kMMapEnableMapJit)\n", stringify_result(result));
|
||||
|
||||
if (result == Error::kOk) {
|
||||
printf(" Protect JIT Memory (RW) : (per-thread protection)\n");
|
||||
VirtMem::protect_jit_memory(VirtMem::ProtectJitAccess::kReadWrite);
|
||||
|
||||
#if defined(TEST_ENVIRONMENT_HAS_JIT)
|
||||
void* func_ptr = offset_pointer(ptr, kVirtFuncOffset);
|
||||
size_t func_size = write_empty_function_at(func_ptr, kVMemAllocSize);
|
||||
#endif // TEST_ENVIRONMENT_HAS_JIT
|
||||
|
||||
printf(" Protect JIT Memory (RX) : (per-thread protection)\n");
|
||||
VirtMem::protect_jit_memory(VirtMem::ProtectJitAccess::kReadExecute);
|
||||
|
||||
#if defined(TEST_ENVIRONMENT_HAS_JIT)
|
||||
if (func_size) {
|
||||
flush_instruction_cache(func_ptr, func_size);
|
||||
invoke_void_function(func_ptr);
|
||||
}
|
||||
#endif // TEST_ENVIRONMENT_HAS_JIT
|
||||
|
||||
result = VirtMem::release(ptr, kVMemAllocSize);
|
||||
printf(" Release virt memory : %s\n", stringify_result(result));
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
if (rti.has_flag(HardenedRuntimeFlags::kDualMapping)) {
|
||||
printf("Virtual Memory Allocation (Dual Mapping):\n");
|
||||
|
||||
VirtMem::DualMapping dm {};
|
||||
Error result = VirtMem::alloc_dual_mapping(Out(dm), kVMemAllocSize, MemoryFlags::kAccessRWX);
|
||||
printf(" Alloc dual mem (RW+RX) : %s\n", stringify_result(result));
|
||||
|
||||
if (result == Error::kOk) {
|
||||
#if defined(TEST_ENVIRONMENT_HAS_JIT)
|
||||
size_t func_size = write_empty_function_at(offset_pointer(dm.rw, kVirtFuncOffset), kVMemAllocSize);
|
||||
if (func_size) {
|
||||
flush_instruction_cache(offset_pointer(dm.rx, kVirtFuncOffset), func_size);
|
||||
invoke_void_function(offset_pointer(dm.rx, kVirtFuncOffset));
|
||||
}
|
||||
#endif // TEST_ENVIRONMENT_HAS_JIT
|
||||
|
||||
result = VirtMem::release_dual_mapping(dm, kVMemAllocSize);
|
||||
printf(" Release dual mem (RW+RX): %s\n", stringify_result(result));
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(TEST_ENVIRONMENT_HAS_JIT)
|
||||
static void print_jit_runtime_info_and_test_execution_with_params(const JitAllocator::CreateParams* params, const char* params_name) noexcept {
|
||||
printf("JitRuntime (%s):\n", params_name);
|
||||
|
||||
JitRuntime rt(params);
|
||||
CodeHolder code;
|
||||
|
||||
Error result = code.init(rt.environment());
|
||||
printf(" CodeHolder init result : %s\n", stringify_result(result));
|
||||
|
||||
if (result != Error::kOk) {
|
||||
return;
|
||||
}
|
||||
|
||||
emit_void_function(code);
|
||||
VoidFunc fn;
|
||||
|
||||
result = rt.add(&fn, &code);
|
||||
printf(" Runtime.add() result : %s\n", stringify_result(result));
|
||||
|
||||
if (result == Error::kOk) {
|
||||
invoke_void_function((void*)fn);
|
||||
|
||||
result = rt.release(fn);
|
||||
printf(" Runtime.release() result: %s\n", stringify_result(result));
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static void print_jit_runtime_info_and_test_execution() noexcept {
|
||||
print_jit_runtime_info_and_test_execution_with_params(nullptr, "<no params>");
|
||||
|
||||
if (VirtMem::large_page_size()) {
|
||||
JitAllocator::CreateParams p{};
|
||||
p.options = JitAllocatorOptions::kUseLargePages;
|
||||
|
||||
print_jit_runtime_info_and_test_execution_with_params(&p, "large pages");
|
||||
}
|
||||
}
|
||||
#endif // TEST_ENVIRONMENT_HAS_JIT
|
||||
|
||||
#endif // !ASMJIT_NO_JIT
|
||||
|
||||
int main() {
|
||||
print_app_info();
|
||||
print_build_options();
|
||||
print_cpu_info();
|
||||
|
||||
#if !defined(ASMJIT_NO_JIT)
|
||||
print_virt_mem_info_and_test_execution();
|
||||
#endif // !ASMJIT_NO_JIT
|
||||
|
||||
#if !defined(ASMJIT_NO_JIT) && defined(TEST_ENVIRONMENT_HAS_JIT)
|
||||
print_jit_runtime_info_and_test_execution();
|
||||
#endif // !ASMJIT_NO_JIT && TEST_ENVIRONMENT_HAS_JIT
|
||||
|
||||
return 0;
|
||||
}
|
||||
206
asmjit-testing/tests/asmjit_test_instinfo.cpp
Normal file
206
asmjit-testing/tests/asmjit_test_instinfo.cpp
Normal file
@@ -0,0 +1,206 @@
|
||||
// This file is part of AsmJit project <https://asmjit.com>
|
||||
//
|
||||
// See <asmjit/core.h> or LICENSE.md for license and copyright information
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#include <asmjit/core.h>
|
||||
|
||||
#if !defined(ASMJIT_NO_X86)
|
||||
#include <asmjit/x86.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <asmjit-testing/commons/asmjitutils.h>
|
||||
|
||||
using namespace asmjit;
|
||||
|
||||
static void print_app_info() noexcept {
|
||||
printf("AsmJit Instruction Info Test Suite v%u.%u.%u [Arch=%s] [Mode=%s]\n\n",
|
||||
unsigned((ASMJIT_LIBRARY_VERSION >> 16) ),
|
||||
unsigned((ASMJIT_LIBRARY_VERSION >> 8) & 0xFF),
|
||||
unsigned((ASMJIT_LIBRARY_VERSION ) & 0xFF),
|
||||
asmjit_arch_as_string(Arch::kHost),
|
||||
asmjit_build_type()
|
||||
);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
#if !defined(ASMJIT_NO_X86)
|
||||
static char access_letter(bool r, bool w) noexcept {
|
||||
return r && w ? 'X' : r ? 'R' : w ? 'W' : '_';
|
||||
}
|
||||
|
||||
static void print_info(Arch arch, const BaseInst& inst, const Operand_* operands, size_t op_count) {
|
||||
StringTmp<512> sb;
|
||||
|
||||
// Read & Write Information
|
||||
// ------------------------
|
||||
|
||||
InstRWInfo rw;
|
||||
InstAPI::query_rw_info(arch, inst, operands, op_count, &rw);
|
||||
|
||||
#ifndef ASMJIT_NO_LOGGING
|
||||
Formatter::format_instruction(sb, FormatFlags::kNone, nullptr, arch, inst, Span(operands, op_count));
|
||||
#else
|
||||
sb.append("<Logging-Not-Available>");
|
||||
#endif
|
||||
sb.append("\n");
|
||||
|
||||
sb.append(" Operands:\n");
|
||||
for (uint32_t i = 0; i < rw.op_count(); i++) {
|
||||
const OpRWInfo& op = rw.operand(i);
|
||||
|
||||
sb.append_format(" [%u] Op=%c Read=%016llX Write=%016llX Extend=%016llX",
|
||||
i,
|
||||
access_letter(op.is_read(), op.is_write()),
|
||||
op.read_byte_mask(),
|
||||
op.write_byte_mask(),
|
||||
op.extend_byte_mask());
|
||||
|
||||
if (op.is_mem_base_used()) {
|
||||
sb.append_format(" Base=%c", access_letter(op.is_mem_base_read(), op.is_mem_base_write()));
|
||||
if (op.is_mem_base_pre_modify())
|
||||
sb.append_format(" <PRE>");
|
||||
if (op.is_mem_base_post_modify())
|
||||
sb.append_format(" <POST>");
|
||||
}
|
||||
|
||||
if (op.is_mem_index_used()) {
|
||||
sb.append_format(" Index=%c", access_letter(op.is_mem_index_read(), op.is_mem_index_write()));
|
||||
}
|
||||
|
||||
sb.append("\n");
|
||||
}
|
||||
|
||||
// CPU Flags (Read/Write)
|
||||
// ----------------------
|
||||
|
||||
if ((rw.read_flags() | rw.write_flags()) != CpuRWFlags::kNone) {
|
||||
sb.append(" Flags: \n");
|
||||
|
||||
struct FlagMap {
|
||||
CpuRWFlags flag;
|
||||
char name[4];
|
||||
};
|
||||
|
||||
static const FlagMap flag_map_table[] = {
|
||||
{ CpuRWFlags::kX86_CF, "CF" },
|
||||
{ CpuRWFlags::kX86_OF, "OF" },
|
||||
{ CpuRWFlags::kX86_SF, "SF" },
|
||||
{ CpuRWFlags::kX86_ZF, "ZF" },
|
||||
{ CpuRWFlags::kX86_AF, "AF" },
|
||||
{ CpuRWFlags::kX86_PF, "PF" },
|
||||
{ CpuRWFlags::kX86_DF, "DF" },
|
||||
{ CpuRWFlags::kX86_IF, "IF" },
|
||||
{ CpuRWFlags::kX86_AC, "AC" },
|
||||
{ CpuRWFlags::kX86_C0, "C0" },
|
||||
{ CpuRWFlags::kX86_C1, "C1" },
|
||||
{ CpuRWFlags::kX86_C2, "C2" },
|
||||
{ CpuRWFlags::kX86_C3, "C3" }
|
||||
};
|
||||
|
||||
sb.append(" ");
|
||||
for (uint32_t f = 0; f < 13; f++) {
|
||||
char c = access_letter((rw.read_flags() & flag_map_table[f].flag) != CpuRWFlags::kNone,
|
||||
(rw.write_flags() & flag_map_table[f].flag) != CpuRWFlags::kNone);
|
||||
if (c != '_')
|
||||
sb.append_format("%s=%c ", flag_map_table[f].name, c);
|
||||
}
|
||||
|
||||
sb.append("\n");
|
||||
}
|
||||
|
||||
// CPU Features
|
||||
// ------------
|
||||
|
||||
CpuFeatures features;
|
||||
InstAPI::query_features(arch, inst, operands, op_count, &features);
|
||||
|
||||
#ifndef ASMJIT_NO_LOGGING
|
||||
if (!features.is_empty()) {
|
||||
sb.append(" Features:\n");
|
||||
sb.append(" ");
|
||||
|
||||
bool first = true;
|
||||
CpuFeatures::Iterator it(features.iterator());
|
||||
while (it.has_next()) {
|
||||
uint32_t feature_id = uint32_t(it.next());
|
||||
if (!first)
|
||||
sb.append(" & ");
|
||||
Formatter::format_feature(sb, arch, feature_id);
|
||||
first = false;
|
||||
}
|
||||
sb.append("\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
printf("%s\n", sb.data());
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
static void print_info_simple(Arch arch,InstId inst_id, InstOptions options, Args&&... args) {
|
||||
BaseInst inst(inst_id);
|
||||
inst.add_options(options);
|
||||
Operand_ op_array[] = { std::forward<Args>(args)... };
|
||||
print_info(arch, inst, op_array, sizeof...(args));
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
static void print_info_extra(Arch arch, InstId inst_id, InstOptions options, const Reg& extra_reg, Args&&... args) {
|
||||
BaseInst inst(inst_id);
|
||||
inst.add_options(options);
|
||||
inst.set_extra_reg(extra_reg);
|
||||
Operand_ op_array[] = { std::forward<Args>(args)... };
|
||||
print_info(arch, inst, op_array, sizeof...(args));
|
||||
}
|
||||
#endif // !ASMJIT_NO_X86
|
||||
|
||||
static void test_x86_arch() {
|
||||
#if !defined(ASMJIT_NO_X86)
|
||||
using namespace x86;
|
||||
Arch arch = Arch::kX64;
|
||||
|
||||
print_info_simple(arch, Inst::kIdAdd, InstOptions::kNone, eax, ebx);
|
||||
print_info_simple(arch, Inst::kIdXor, InstOptions::kNone, eax, eax);
|
||||
print_info_simple(arch, Inst::kIdLods, InstOptions::kNone, eax, dword_ptr(rsi));
|
||||
|
||||
print_info_simple(arch, Inst::kIdPshufd, InstOptions::kNone, xmm0, xmm1, imm(0));
|
||||
print_info_simple(arch, Inst::kIdPabsb, InstOptions::kNone, mm1, mm2);
|
||||
print_info_simple(arch, Inst::kIdPabsb, InstOptions::kNone, xmm1, xmm2);
|
||||
print_info_simple(arch, Inst::kIdPextrw, InstOptions::kNone, eax, mm1, imm(0));
|
||||
print_info_simple(arch, Inst::kIdPextrw, InstOptions::kNone, eax, xmm1, imm(0));
|
||||
print_info_simple(arch, Inst::kIdPextrw, InstOptions::kNone, ptr(rax), xmm1, imm(0));
|
||||
|
||||
print_info_simple(arch, Inst::kIdVpdpbusd, InstOptions::kNone, xmm0, xmm1, xmm2);
|
||||
print_info_simple(arch, Inst::kIdVpdpbusd, InstOptions::kX86_Vex, xmm0, xmm1, xmm2);
|
||||
|
||||
print_info_simple(arch, Inst::kIdVaddpd, InstOptions::kNone, ymm0, ymm1, ymm2);
|
||||
print_info_simple(arch, Inst::kIdVaddpd, InstOptions::kNone, ymm0, ymm30, ymm31);
|
||||
print_info_simple(arch, Inst::kIdVaddpd, InstOptions::kNone, zmm0, zmm1, zmm2);
|
||||
|
||||
print_info_simple(arch, Inst::kIdVpternlogd, InstOptions::kNone, zmm0, zmm0, zmm0, imm(0xFF));
|
||||
print_info_simple(arch, Inst::kIdVpternlogq, InstOptions::kNone, zmm0, zmm1, zmm2, imm(0x33));
|
||||
|
||||
print_info_extra(arch, Inst::kIdVaddpd, InstOptions::kNone, k1, zmm0, zmm1, zmm2);
|
||||
print_info_extra(arch, Inst::kIdVaddpd, InstOptions::kX86_ZMask, k1, zmm0, zmm1, zmm2);
|
||||
|
||||
print_info_simple(arch, Inst::kIdVcvtdq2pd, InstOptions::kNone, xmm0, xmm1);
|
||||
print_info_simple(arch, Inst::kIdVcvtdq2pd, InstOptions::kNone, ymm0, xmm1);
|
||||
print_info_simple(arch, Inst::kIdVcvtdq2pd, InstOptions::kNone, zmm0, ymm1);
|
||||
|
||||
print_info_simple(arch, Inst::kIdVcvtdq2pd, InstOptions::kNone, xmm0, ptr(rsi));
|
||||
print_info_simple(arch, Inst::kIdVcvtdq2pd, InstOptions::kNone, ymm0, ptr(rsi));
|
||||
print_info_simple(arch, Inst::kIdVcvtdq2pd, InstOptions::kNone, zmm0, ptr(rsi));
|
||||
#endif // !ASMJIT_NO_X86
|
||||
}
|
||||
|
||||
} // {anonymous}
|
||||
|
||||
int main() {
|
||||
print_app_info();
|
||||
test_x86_arch();
|
||||
|
||||
return 0;
|
||||
}
|
||||
265
asmjit-testing/tests/asmjit_test_misc.h
Normal file
265
asmjit-testing/tests/asmjit_test_misc.h
Normal file
@@ -0,0 +1,265 @@
|
||||
// This file is part of AsmJit project <https://asmjit.com>
|
||||
//
|
||||
// See <asmjit/core.h> or LICENSE.md for license and copyright information
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef ASMJIT_TEST_MISC_H_INCLUDED
|
||||
#define ASMJIT_TEST_MISC_H_INCLUDED
|
||||
|
||||
#include <asmjit/x86.h>
|
||||
|
||||
namespace asmtest {
|
||||
|
||||
using namespace asmjit;
|
||||
|
||||
// Generates a typical alpha blend function that uses SSE2 instruction set.
|
||||
// This function combines emitting instructions with control flow constructs
|
||||
// like binding Labels and jumping to them. This should be pretty representative.
|
||||
template<typename Emitter>
|
||||
static void generate_sse_alpha_blend_internal(
|
||||
Emitter& cc,
|
||||
const x86::Gp& dst, const x86::Gp& src, const x86::Gp& n,
|
||||
const x86::Gp& gp0,
|
||||
const x86::Vec& simd0, const x86::Vec& simd1, const x86::Vec& simd2, const x86::Vec& simd3,
|
||||
const x86::Vec& simd4, const x86::Vec& simd5, const x86::Vec& simd6, const x86::Vec& simd7) {
|
||||
|
||||
x86::Gp i = n;
|
||||
x86::Gp j = gp0;
|
||||
|
||||
x86::Vec vzero = simd0;
|
||||
x86::Vec v0080 = simd1;
|
||||
x86::Vec v0101 = simd2;
|
||||
|
||||
Label L_SmallLoop = cc.new_label();
|
||||
Label L_SmallEnd = cc.new_label();
|
||||
Label L_LargeLoop = cc.new_label();
|
||||
Label L_LargeEnd = cc.new_label();
|
||||
Label L_Done = cc.new_label();
|
||||
|
||||
// Load SIMD Constants.
|
||||
cc.xorps(vzero, vzero);
|
||||
cc.mov(gp0.r32(), 0x00800080);
|
||||
cc.movd(v0080, gp0.r32());
|
||||
cc.mov(gp0.r32(), 0x01010101);
|
||||
cc.movd(v0101, gp0.r32());
|
||||
cc.pshufd(v0080, v0080, x86::shuffle_imm(0, 0, 0, 0));
|
||||
cc.pshufd(v0101, v0101, x86::shuffle_imm(0, 0, 0, 0));
|
||||
|
||||
// How many pixels have to be processed to make the loop aligned.
|
||||
cc.xor_(j, j);
|
||||
cc.sub(j, dst);
|
||||
cc.and_(j, 15);
|
||||
cc.shr(j, 2);
|
||||
cc.jz(L_SmallEnd);
|
||||
|
||||
cc.cmp(j, i);
|
||||
cc.cmovg(j, i); // j = min(i, j)
|
||||
cc.sub(i, j); // i -= j
|
||||
|
||||
// Small loop.
|
||||
cc.bind(L_SmallLoop);
|
||||
{
|
||||
x86::Vec x0 = simd3;
|
||||
x86::Vec y0 = simd4;
|
||||
x86::Vec a0 = simd5;
|
||||
|
||||
cc.movd(y0, x86::ptr(src));
|
||||
cc.movd(x0, x86::ptr(dst));
|
||||
|
||||
cc.pcmpeqb(a0, a0);
|
||||
cc.pxor(a0, y0);
|
||||
cc.psrlw(a0, 8);
|
||||
cc.punpcklbw(x0, vzero);
|
||||
|
||||
cc.pshuflw(a0, a0, x86::shuffle_imm(1, 1, 1, 1));
|
||||
cc.punpcklbw(y0, vzero);
|
||||
|
||||
cc.pmullw(x0, a0);
|
||||
cc.paddsw(x0, v0080);
|
||||
cc.pmulhuw(x0, v0101);
|
||||
|
||||
cc.paddw(x0, y0);
|
||||
cc.packuswb(x0, x0);
|
||||
|
||||
cc.movd(x86::ptr(dst), x0);
|
||||
|
||||
cc.add(dst, 4);
|
||||
cc.add(src, 4);
|
||||
|
||||
cc.dec(j);
|
||||
cc.jnz(L_SmallLoop);
|
||||
}
|
||||
|
||||
// Second section, prepare for an aligned loop.
|
||||
cc.bind(L_SmallEnd);
|
||||
|
||||
cc.test(i, i);
|
||||
cc.mov(j, i);
|
||||
cc.jz(L_Done);
|
||||
|
||||
cc.and_(j, 3);
|
||||
cc.shr(i, 2);
|
||||
cc.jz(L_LargeEnd);
|
||||
|
||||
// Aligned loop.
|
||||
cc.bind(L_LargeLoop);
|
||||
{
|
||||
x86::Vec x0 = simd3;
|
||||
x86::Vec x1 = simd4;
|
||||
x86::Vec y0 = simd5;
|
||||
x86::Vec a0 = simd6;
|
||||
x86::Vec a1 = simd7;
|
||||
|
||||
cc.movups(y0, x86::ptr(src));
|
||||
cc.movaps(x0, x86::ptr(dst));
|
||||
|
||||
cc.pcmpeqb(a0, a0);
|
||||
cc.xorps(a0, y0);
|
||||
cc.movaps(x1, x0);
|
||||
|
||||
cc.psrlw(a0, 8);
|
||||
cc.punpcklbw(x0, vzero);
|
||||
|
||||
cc.movaps(a1, a0);
|
||||
cc.punpcklwd(a0, a0);
|
||||
|
||||
cc.punpckhbw(x1, vzero);
|
||||
cc.punpckhwd(a1, a1);
|
||||
|
||||
cc.pshufd(a0, a0, x86::shuffle_imm(3, 3, 1, 1));
|
||||
cc.pshufd(a1, a1, x86::shuffle_imm(3, 3, 1, 1));
|
||||
|
||||
cc.pmullw(x0, a0);
|
||||
cc.pmullw(x1, a1);
|
||||
|
||||
cc.paddsw(x0, v0080);
|
||||
cc.paddsw(x1, v0080);
|
||||
|
||||
cc.pmulhuw(x0, v0101);
|
||||
cc.pmulhuw(x1, v0101);
|
||||
|
||||
cc.add(src, 16);
|
||||
cc.packuswb(x0, x1);
|
||||
|
||||
cc.paddw(x0, y0);
|
||||
cc.movaps(x86::ptr(dst), x0);
|
||||
|
||||
cc.add(dst, 16);
|
||||
|
||||
cc.dec(i);
|
||||
cc.jnz(L_LargeLoop);
|
||||
}
|
||||
|
||||
cc.bind(L_LargeEnd);
|
||||
cc.test(j, j);
|
||||
cc.jnz(L_SmallLoop);
|
||||
|
||||
cc.bind(L_Done);
|
||||
}
|
||||
|
||||
static void generate_sse_alpha_blend(asmjit::BaseEmitter& emitter, bool emit_prolog_epilog) {
|
||||
using namespace asmjit::x86;
|
||||
|
||||
#ifndef ASMJIT_NO_COMPILER
|
||||
if (emitter.is_compiler()) {
|
||||
Compiler& cc = *emitter.as<Compiler>();
|
||||
|
||||
Gp dst = cc.new_gp_ptr("dst");
|
||||
Gp src = cc.new_gp_ptr("src");
|
||||
Gp i = cc.new_gp_ptr("i");
|
||||
Gp j = cc.new_gp_ptr("j");
|
||||
|
||||
Vec v0 = cc.new_xmm("v0");
|
||||
Vec v1 = cc.new_xmm("v1");
|
||||
Vec v2 = cc.new_xmm("v2");
|
||||
Vec v3 = cc.new_xmm("v3");
|
||||
Vec v4 = cc.new_xmm("v4");
|
||||
Vec v5 = cc.new_xmm("v5");
|
||||
Vec v6 = cc.new_xmm("v6");
|
||||
Vec v7 = cc.new_xmm("v7");
|
||||
|
||||
FuncNode* func_node = cc.add_func(FuncSignature::build<void, void*, const void*, size_t>());
|
||||
func_node->set_arg(0, dst);
|
||||
func_node->set_arg(1, src);
|
||||
func_node->set_arg(2, i);
|
||||
generate_sse_alpha_blend_internal(cc, dst, src, i, j, v0, v1, v2, v3, v4, v5, v6, v7);
|
||||
cc.end_func();
|
||||
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef ASMJIT_NO_BUILDER
|
||||
if (emitter.is_builder()) {
|
||||
Builder& cc = *emitter.as<Builder>();
|
||||
|
||||
x86::Gp dst = cc.zax();
|
||||
x86::Gp src = cc.zcx();
|
||||
x86::Gp i = cc.zdx();
|
||||
x86::Gp j = cc.zdi();
|
||||
|
||||
if (emit_prolog_epilog) {
|
||||
FuncDetail func;
|
||||
func.init(FuncSignature::build<void, void*, const void*, size_t>(), cc.environment());
|
||||
|
||||
FuncFrame frame;
|
||||
frame.init(func);
|
||||
frame.add_dirty_regs(dst, src, i, j);
|
||||
frame.add_dirty_regs(xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7);
|
||||
|
||||
FuncArgsAssignment args(&func);
|
||||
args.assign_all(dst, src, i);
|
||||
args.update_func_frame(frame);
|
||||
frame.finalize();
|
||||
|
||||
cc.emit_prolog(frame);
|
||||
cc.emit_args_assignment(frame, args);
|
||||
generate_sse_alpha_blend_internal(cc, dst, src, i, j, xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7);
|
||||
cc.emit_epilog(frame);
|
||||
}
|
||||
else {
|
||||
generate_sse_alpha_blend_internal(cc, dst, src, i, j, xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (emitter.is_assembler()) {
|
||||
Assembler& cc = *emitter.as<Assembler>();
|
||||
|
||||
x86::Gp dst = cc.zax();
|
||||
x86::Gp src = cc.zcx();
|
||||
x86::Gp i = cc.zdx();
|
||||
x86::Gp j = cc.zdi();
|
||||
|
||||
if (emit_prolog_epilog) {
|
||||
FuncDetail func;
|
||||
func.init(FuncSignature::build<void, void*, const void*, size_t>(), cc.environment());
|
||||
|
||||
FuncFrame frame;
|
||||
frame.init(func);
|
||||
frame.add_dirty_regs(dst, src, i, j);
|
||||
frame.add_dirty_regs(xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7);
|
||||
|
||||
FuncArgsAssignment args(&func);
|
||||
args.assign_all(dst, src, i);
|
||||
args.update_func_frame(frame);
|
||||
frame.finalize();
|
||||
|
||||
cc.emit_prolog(frame);
|
||||
cc.emit_args_assignment(frame, args);
|
||||
generate_sse_alpha_blend_internal(cc, dst, src, i, j, xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7);
|
||||
cc.emit_epilog(frame);
|
||||
}
|
||||
else {
|
||||
generate_sse_alpha_blend_internal(cc, dst, src, i, j, xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} // {asmtest}
|
||||
|
||||
#endif // ASMJIT_TEST_MISC_H_INCLUDED
|
||||
171
asmjit-testing/tests/asmjit_test_runner.cpp
Normal file
171
asmjit-testing/tests/asmjit_test_runner.cpp
Normal file
@@ -0,0 +1,171 @@
|
||||
// This file is part of AsmJit project <https://asmjit.com>
|
||||
//
|
||||
// See <asmjit/core.h> or LICENSE.md for license and copyright information
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#include <asmjit/core.h>
|
||||
|
||||
#if !defined(ASMJIT_NO_X86)
|
||||
#include <asmjit/x86.h>
|
||||
#endif
|
||||
|
||||
#if !defined(ASMJIT_NO_AARCH64)
|
||||
#include <asmjit/a64.h>
|
||||
#endif
|
||||
|
||||
#include <asmjit-testing/commons/asmjitutils.h>
|
||||
#include <asmjit-testing/tests/broken.h>
|
||||
|
||||
#if !defined(ASMJIT_NO_COMPILER)
|
||||
#include <asmjit/core/racfgblock_p.h>
|
||||
#include <asmjit/core/rainst_p.h>
|
||||
#include <asmjit/core/rapass_p.h>
|
||||
#endif
|
||||
|
||||
using namespace asmjit;
|
||||
|
||||
#define DUMP_TYPE(...) \
|
||||
printf(" %-26s: %u\n", #__VA_ARGS__, uint32_t(sizeof(__VA_ARGS__)))
|
||||
|
||||
static void print_type_sizes(void) noexcept {
|
||||
printf("Size of C++ types:\n");
|
||||
DUMP_TYPE(int8_t);
|
||||
DUMP_TYPE(int16_t);
|
||||
DUMP_TYPE(int32_t);
|
||||
DUMP_TYPE(int64_t);
|
||||
DUMP_TYPE(int);
|
||||
DUMP_TYPE(long);
|
||||
DUMP_TYPE(size_t);
|
||||
DUMP_TYPE(intptr_t);
|
||||
DUMP_TYPE(float);
|
||||
DUMP_TYPE(double);
|
||||
DUMP_TYPE(void*);
|
||||
printf("\n");
|
||||
|
||||
printf("Size of base classes:\n");
|
||||
DUMP_TYPE(BaseAssembler);
|
||||
DUMP_TYPE(BaseEmitter);
|
||||
DUMP_TYPE(CodeBuffer);
|
||||
DUMP_TYPE(CodeHolder);
|
||||
DUMP_TYPE(ConstPool);
|
||||
DUMP_TYPE(Fixup);
|
||||
DUMP_TYPE(LabelEntry);
|
||||
DUMP_TYPE(LabelEntry::ExtraData);
|
||||
DUMP_TYPE(RelocEntry);
|
||||
DUMP_TYPE(Section);
|
||||
DUMP_TYPE(String);
|
||||
DUMP_TYPE(Target);
|
||||
printf("\n");
|
||||
|
||||
printf("Size of arena classes:\n");
|
||||
DUMP_TYPE(Arena);
|
||||
DUMP_TYPE(ArenaHashNode);
|
||||
DUMP_TYPE(ArenaHash<ArenaHashNode>);
|
||||
DUMP_TYPE(ArenaList<int>);
|
||||
DUMP_TYPE(ArenaVector<int>);
|
||||
DUMP_TYPE(ArenaString<16>);
|
||||
printf("\n");
|
||||
|
||||
printf("Size of operand classes:\n");
|
||||
DUMP_TYPE(Operand);
|
||||
DUMP_TYPE(Reg);
|
||||
DUMP_TYPE(BaseMem);
|
||||
DUMP_TYPE(Imm);
|
||||
DUMP_TYPE(Label);
|
||||
printf("\n");
|
||||
|
||||
printf("Size of function classes:\n");
|
||||
DUMP_TYPE(CallConv);
|
||||
DUMP_TYPE(FuncFrame);
|
||||
DUMP_TYPE(FuncValue);
|
||||
DUMP_TYPE(FuncDetail);
|
||||
DUMP_TYPE(FuncSignature);
|
||||
DUMP_TYPE(FuncArgsAssignment);
|
||||
printf("\n");
|
||||
|
||||
#if !defined(ASMJIT_NO_BUILDER)
|
||||
constexpr uint32_t kBaseOpCapacity = InstNode::kBaseOpCapacity;
|
||||
constexpr uint32_t kFullOpCapacity = InstNode::kFullOpCapacity;
|
||||
|
||||
printf("Size of builder classes:\n");
|
||||
DUMP_TYPE(BaseBuilder);
|
||||
DUMP_TYPE(BaseNode);
|
||||
DUMP_TYPE(InstNode);
|
||||
DUMP_TYPE(InstNodeWithOperands<kBaseOpCapacity>);
|
||||
DUMP_TYPE(InstNodeWithOperands<kFullOpCapacity>);
|
||||
DUMP_TYPE(AlignNode);
|
||||
DUMP_TYPE(LabelNode);
|
||||
DUMP_TYPE(EmbedDataNode);
|
||||
DUMP_TYPE(EmbedLabelNode);
|
||||
DUMP_TYPE(ConstPoolNode);
|
||||
DUMP_TYPE(CommentNode);
|
||||
DUMP_TYPE(SentinelNode);
|
||||
printf("\n");
|
||||
#endif
|
||||
|
||||
#if !defined(ASMJIT_NO_COMPILER)
|
||||
printf("Size of compiler classes:\n");
|
||||
DUMP_TYPE(BaseCompiler);
|
||||
DUMP_TYPE(FuncNode);
|
||||
DUMP_TYPE(FuncRetNode);
|
||||
DUMP_TYPE(InvokeNode);
|
||||
DUMP_TYPE(VirtReg);
|
||||
printf("\n");
|
||||
|
||||
printf("Size of compiler classes (RA):\n");
|
||||
DUMP_TYPE(BaseRAPass);
|
||||
DUMP_TYPE(RABlock);
|
||||
DUMP_TYPE(RAInst);
|
||||
DUMP_TYPE(RATiedReg);
|
||||
DUMP_TYPE(RAWorkReg);
|
||||
printf("\n");
|
||||
#endif
|
||||
|
||||
#if !defined(ASMJIT_NO_X86)
|
||||
printf("Size of x86-specific classes:\n");
|
||||
DUMP_TYPE(x86::Assembler);
|
||||
#if !defined(ASMJIT_NO_BUILDER)
|
||||
DUMP_TYPE(x86::Builder);
|
||||
#endif
|
||||
#if !defined(ASMJIT_NO_COMPILER)
|
||||
DUMP_TYPE(x86::Compiler);
|
||||
#endif
|
||||
DUMP_TYPE(x86::InstDB::InstInfo);
|
||||
DUMP_TYPE(x86::InstDB::CommonInfo);
|
||||
DUMP_TYPE(x86::InstDB::OpSignature);
|
||||
DUMP_TYPE(x86::InstDB::InstSignature);
|
||||
printf("\n");
|
||||
#endif
|
||||
|
||||
#if !defined(ASMJIT_NO_AARCH64)
|
||||
printf("Size of aarch64-specific classes:\n");
|
||||
DUMP_TYPE(a64::Assembler);
|
||||
#if !defined(ASMJIT_NO_BUILDER)
|
||||
DUMP_TYPE(a64::Builder);
|
||||
#endif
|
||||
#if !defined(ASMJIT_NO_COMPILER)
|
||||
DUMP_TYPE(a64::Compiler);
|
||||
#endif
|
||||
printf("\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
#undef DUMP_TYPE
|
||||
|
||||
static void on_before_run(void) noexcept {
|
||||
print_build_options();
|
||||
print_cpu_info();
|
||||
print_type_sizes();
|
||||
}
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
printf("AsmJit Unit-Test v%u.%u.%u [Arch=%s] [Mode=%s]\n\n",
|
||||
unsigned((ASMJIT_LIBRARY_VERSION >> 16) ),
|
||||
unsigned((ASMJIT_LIBRARY_VERSION >> 8) & 0xFF),
|
||||
unsigned((ASMJIT_LIBRARY_VERSION ) & 0xFF),
|
||||
asmjit_arch_as_string(Arch::kHost),
|
||||
asmjit_build_type()
|
||||
);
|
||||
|
||||
return BrokenAPI::run(argc, argv, on_before_run);
|
||||
}
|
||||
5674
asmjit-testing/tests/asmjit_test_unicompiler.cpp
Normal file
5674
asmjit-testing/tests/asmjit_test_unicompiler.cpp
Normal file
File diff suppressed because it is too large
Load Diff
49
asmjit-testing/tests/asmjit_test_unicompiler_avx2fma.cpp
Normal file
49
asmjit-testing/tests/asmjit_test_unicompiler_avx2fma.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
// This file is part of AsmJit project <https://asmjit.com>
|
||||
//
|
||||
// See <asmjit/core.h> or LICENSE.md for license and copyright information
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#include <asmjit/ujit.h>
|
||||
|
||||
#if defined(ASMJIT_UJIT_X86)
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#include <intrin.h>
|
||||
#else
|
||||
#include <immintrin.h>
|
||||
#endif
|
||||
|
||||
namespace UniCompilerTests {
|
||||
|
||||
// A reference implementation of MUL+ADD with the use of FMA. This has to be provided otherwise the
|
||||
// compiler may use FPU registers in 32-bit x86 case, which would make the result different than when
|
||||
// compiled by JIT compiler that would use XMM registers (32/64-bit SSE/AVX operations).
|
||||
|
||||
float fmadd_fma_ref(float a, float b, float c) noexcept {
|
||||
__m128 av = _mm_set1_ps(a);
|
||||
__m128 bv = _mm_set1_ps(b);
|
||||
__m128 cv = _mm_set1_ps(c);
|
||||
|
||||
return _mm_cvtss_f32(_mm_fmadd_ss(av, bv, cv));
|
||||
}
|
||||
|
||||
double fmadd_fma_ref(double a, double b, double c) noexcept {
|
||||
__m128d av = _mm_set1_pd(a);
|
||||
__m128d bv = _mm_set1_pd(b);
|
||||
__m128d cv = _mm_set1_pd(c);
|
||||
|
||||
return _mm_cvtsd_f64(_mm_fmadd_sd(av, bv, cv));
|
||||
}
|
||||
|
||||
void madd_fma_check_valgrind_bug(const float a[4], const float b[4], const float c[4], float dst[4]) noexcept {
|
||||
__m128 av = _mm_loadu_ps(a);
|
||||
__m128 bv = _mm_loadu_ps(b);
|
||||
__m128 cv = _mm_loadu_ps(c);
|
||||
|
||||
__m128 dv = _mm_fmadd_ss(av, bv, cv);
|
||||
_mm_storeu_ps(dst, dv);
|
||||
}
|
||||
|
||||
} // {UniCompilerTests}
|
||||
|
||||
#endif // ASMJIT_UJIT_X86
|
||||
72
asmjit-testing/tests/asmjit_test_unicompiler_sse2.cpp
Normal file
72
asmjit-testing/tests/asmjit_test_unicompiler_sse2.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
// This file is part of AsmJit project <https://asmjit.com>
|
||||
//
|
||||
// See <asmjit/core.h> or LICENSE.md for license and copyright information
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#include <asmjit/ujit.h>
|
||||
|
||||
#if defined(ASMJIT_UJIT_X86)
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#include <intrin.h>
|
||||
#else
|
||||
#include <emmintrin.h>
|
||||
#endif
|
||||
|
||||
namespace UniCompilerTests {
|
||||
|
||||
// A reference implementation of MUL+ADD without the use of FMA. This has to be provided otherwise the
|
||||
// compiler may use FPU registers in 32-bit x86 case, which would make the result different than when
|
||||
// compiled by JIT compiler that would use XMM registers (32/64-bit SSE/AVX operations).
|
||||
|
||||
float fadd(float a, float b) noexcept {
|
||||
return _mm_cvtss_f32(_mm_add_ss(_mm_set1_ps(a), _mm_set1_ps(b)));
|
||||
}
|
||||
|
||||
float fsub(float a, float b) noexcept {
|
||||
return _mm_cvtss_f32(_mm_sub_ss(_mm_set1_ps(a), _mm_set1_ps(b)));
|
||||
}
|
||||
|
||||
float fmul(float a, float b) noexcept {
|
||||
return _mm_cvtss_f32(_mm_mul_ss(_mm_set1_ps(a), _mm_set1_ps(b)));
|
||||
}
|
||||
|
||||
float fdiv(float a, float b) noexcept {
|
||||
return _mm_cvtss_f32(_mm_div_ss(_mm_set1_ps(a), _mm_set1_ps(b)));
|
||||
}
|
||||
|
||||
float fsqrt(float a) noexcept {
|
||||
return _mm_cvtss_f32(_mm_sqrt_ss(_mm_set1_ps(a)));
|
||||
}
|
||||
|
||||
float fmadd_nofma_ref(float a, float b, float c) noexcept {
|
||||
return _mm_cvtss_f32(_mm_add_ss(_mm_mul_ss(_mm_set1_ps(a), _mm_set1_ps(b)), _mm_set1_ps(c)));
|
||||
}
|
||||
|
||||
double fadd(double a, double b) noexcept {
|
||||
return _mm_cvtsd_f64(_mm_add_sd(_mm_set1_pd(a), _mm_set1_pd(b)));
|
||||
}
|
||||
|
||||
double fsub(double a, double b) noexcept {
|
||||
return _mm_cvtsd_f64(_mm_sub_sd(_mm_set1_pd(a), _mm_set1_pd(b)));
|
||||
}
|
||||
|
||||
double fmul(double a, double b) noexcept {
|
||||
return _mm_cvtsd_f64(_mm_mul_sd(_mm_set1_pd(a), _mm_set1_pd(b)));
|
||||
}
|
||||
|
||||
double fdiv(double a, double b) noexcept {
|
||||
return _mm_cvtsd_f64(_mm_div_sd(_mm_set1_pd(a), _mm_set1_pd(b)));
|
||||
}
|
||||
|
||||
double fsqrt(double a) noexcept {
|
||||
return _mm_cvtsd_f64(_mm_sqrt_sd(_mm_setzero_pd(), _mm_set1_pd(a)));
|
||||
}
|
||||
|
||||
double fmadd_nofma_ref(double a, double b, double c) noexcept {
|
||||
return _mm_cvtsd_f64(_mm_add_sd(_mm_mul_sd(_mm_set1_pd(a), _mm_set1_pd(b)), _mm_set1_pd(c)));
|
||||
}
|
||||
|
||||
} // {UniCompilerTests}
|
||||
|
||||
#endif // ASMJIT_UJIT_X86
|
||||
169
asmjit-testing/tests/asmjit_test_x86_sections.cpp
Normal file
169
asmjit-testing/tests/asmjit_test_x86_sections.cpp
Normal file
@@ -0,0 +1,169 @@
|
||||
// This file is part of AsmJit project <https://asmjit.com>
|
||||
//
|
||||
// See <asmjit/core.h> or LICENSE.md for license and copyright information
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// This is a working example that demonstrates how multiple sections can be
|
||||
// used in a JIT-based code generator. It shows also the necessary tooling
|
||||
// that is expected to be done by the user when the feature is used. It's
|
||||
// important to handle the following cases:
|
||||
//
|
||||
// - Assign offsets to sections when the code generation is finished.
|
||||
// - Tell the CodeHolder to resolve unresolved fixups and check whether
|
||||
// all fixups were resolved.
|
||||
// - Relocate the code
|
||||
// - Copy the code to the destination address.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#include <asmjit/core.h>
|
||||
#if ASMJIT_ARCH_X86 && !defined(ASMJIT_NO_X86) && !defined(ASMJIT_NO_JIT)
|
||||
|
||||
#include <asmjit/x86.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
using namespace asmjit;
|
||||
|
||||
// The generated function is very simple, it only accesses the built-in data
|
||||
// (from .data section) at the index as provided by its first argument. This
|
||||
// data is inlined into the resulting function so we can use it this array
|
||||
// for verification that the function returns correct values.
|
||||
static const uint8_t data_array[] = { 2, 9, 4, 7, 1, 3, 8, 5, 6, 0 };
|
||||
|
||||
static void fail(const char* message, Error err) {
|
||||
printf("** FAILURE: %s (%s) **\n", message, DebugUtils::error_as_string(err));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int main() {
|
||||
printf("AsmJit X86 Sections Test\n\n");
|
||||
|
||||
Environment env = Environment::host();
|
||||
JitAllocator allocator;
|
||||
|
||||
#ifndef ASMJIT_NO_LOGGING
|
||||
FileLogger logger(stdout);
|
||||
logger.set_indentation(FormatIndentationGroup::kCode, 2);
|
||||
#endif
|
||||
|
||||
CodeHolder code;
|
||||
code.init(env);
|
||||
|
||||
#ifndef ASMJIT_NO_LOGGING
|
||||
code.set_logger(&logger);
|
||||
#endif
|
||||
|
||||
Section* data_section;
|
||||
Error err = code.new_section(Out(data_section), ".data", SIZE_MAX, SectionFlags::kNone, 8);
|
||||
|
||||
if (err != Error::kOk) {
|
||||
fail("Failed to create a .data section", err);
|
||||
}
|
||||
else {
|
||||
printf("Generating code:\n");
|
||||
x86::Assembler a(&code);
|
||||
x86::Gp idx = a.zax();
|
||||
x86::Gp addr = a.zcx();
|
||||
|
||||
Label data = a.new_label();
|
||||
|
||||
FuncDetail func;
|
||||
func.init(FuncSignature::build<size_t, size_t>(), code.environment());
|
||||
|
||||
FuncFrame frame;
|
||||
frame.init(func);
|
||||
frame.add_dirty_regs(idx, addr);
|
||||
|
||||
FuncArgsAssignment args(&func);
|
||||
args.assign_all(idx);
|
||||
args.update_func_frame(frame);
|
||||
frame.finalize();
|
||||
|
||||
a.emit_prolog(frame);
|
||||
a.emit_args_assignment(frame, args);
|
||||
|
||||
a.lea(addr, x86::ptr(data));
|
||||
a.movzx(idx, x86::byte_ptr(addr, idx));
|
||||
|
||||
a.emit_epilog(frame);
|
||||
|
||||
a.section(data_section);
|
||||
a.bind(data);
|
||||
|
||||
a.embed(data_array, sizeof(data_array));
|
||||
}
|
||||
|
||||
// Manually change he offsets of each section, start at 0. This code is very similar to
|
||||
// what `CodeHolder::flatten()` does, however, it's shown here how to do it explicitly.
|
||||
printf("\nCalculating section offsets:\n");
|
||||
uint64_t offset = 0;
|
||||
for (Section* section : code.sections_by_order()) {
|
||||
offset = Support::align_up(offset, section->alignment());
|
||||
section->set_offset(offset);
|
||||
offset += section->real_size();
|
||||
|
||||
printf(" [0x%08X %s] {Id=%u Size=%u}\n",
|
||||
uint32_t(section->offset()),
|
||||
section->name(),
|
||||
section->section_id(),
|
||||
uint32_t(section->real_size()));
|
||||
}
|
||||
size_t code_size = size_t(offset);
|
||||
printf(" Final code size: %zu\n", code_size);
|
||||
|
||||
// Resolve cross-section fixups (if any). On 32-bit X86 this is not necessary
|
||||
// as this is handled through relocations as the addressing is different.
|
||||
if (code.has_unresolved_fixups()) {
|
||||
printf("\nResolving cross-section fixups:\n");
|
||||
printf(" Before 'resolve_cross_section_fixups()': %zu\n", code.unresolved_fixup_count());
|
||||
|
||||
err = code.resolve_cross_section_fixups();
|
||||
if (err != Error::kOk) {
|
||||
fail("Failed to resolve cross-section fixups", err);
|
||||
}
|
||||
printf(" After 'resolve_cross_section_fixups()': %zu\n", code.unresolved_fixup_count());
|
||||
}
|
||||
|
||||
// Allocate memory for the function and relocate it there.
|
||||
JitAllocator::Span span;
|
||||
err = allocator.alloc(Out(span), code_size);
|
||||
if (err != Error::kOk)
|
||||
fail("Failed to allocate executable memory", err);
|
||||
|
||||
// Relocate to the base-address of the allocated memory.
|
||||
code.relocate_to_base(uint64_t(uintptr_t(span.rx())));
|
||||
|
||||
allocator.write(span, [&](JitAllocator::Span& span) noexcept -> Error {
|
||||
// Copy the flattened code into `mem.rw`. There are two ways. You can either copy
|
||||
// everything manually by iterating over all sections or use `copy_flattened_data`.
|
||||
// This code is similar to what `copy_flattened_data(p, code_size, 0)` would do:
|
||||
for (Section* section : code.sections_by_order())
|
||||
memcpy(static_cast<uint8_t*>(span.rw()) + size_t(section->offset()), section->data(), section->buffer_size());
|
||||
return Error::kOk;
|
||||
});
|
||||
|
||||
// Execute the function and test whether it works.
|
||||
using Func = size_t (*)(size_t idx);
|
||||
Func fn = (Func)span.rx();
|
||||
|
||||
printf("\n");
|
||||
if (fn(0) != data_array[0] ||
|
||||
fn(3) != data_array[3] ||
|
||||
fn(6) != data_array[6] ||
|
||||
fn(9) != data_array[9] ) {
|
||||
printf("** FAILURE: The generated function returned incorrect result(s) **\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("** SUCCESS **\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
int main() {
|
||||
printf("!! This test is disabled: ASMJIT_NO_JIT or unsuitable target architecture !!\n\n");
|
||||
return 0;
|
||||
}
|
||||
#endif // ASMJIT_ARCH_X86 && !ASMJIT_NO_X86 && !ASMJIT_NO_JIT
|
||||
310
asmjit-testing/tests/broken.cpp
Normal file
310
asmjit-testing/tests/broken.cpp
Normal file
@@ -0,0 +1,310 @@
|
||||
// Broken - Lightweight unit testing for C++
|
||||
//
|
||||
// This is free and unencumbered software released into the public domain.
|
||||
//
|
||||
// Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
// distribute this software, either in source code form or as a compiled
|
||||
// binary, for any purpose, commercial or non-commercial, and by any
|
||||
// means.
|
||||
//
|
||||
// In jurisdictions that recognize copyright laws, the author or authors
|
||||
// of this software dedicate any and all copyright interest in the
|
||||
// software to the public domain. We make this dedication for the benefit
|
||||
// of the public at large and to the detriment of our heirs and
|
||||
// successors. We intend this dedication to be an overt act of
|
||||
// relinquishment in perpetuity of all present and future rights to this
|
||||
// software under copyright law.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
// OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
// For more information, please refer to <http://unlicense.org>
|
||||
|
||||
#include "broken.h"
|
||||
#include <stdarg.h>
|
||||
|
||||
// Broken - Globals
|
||||
// ================
|
||||
|
||||
// Zero initialized globals.
|
||||
struct BrokenGlobal {
|
||||
// Application arguments.
|
||||
int _argc;
|
||||
const char** _argv;
|
||||
|
||||
// Output file.
|
||||
FILE* _file;
|
||||
|
||||
// Unit tests.
|
||||
BrokenAPI::Unit* _unitList;
|
||||
BrokenAPI::Unit* _unitRunning;
|
||||
|
||||
bool has_arg(const char* a) const noexcept {
|
||||
for (int i = 1; i < _argc; i++)
|
||||
if (strcmp(_argv[i], a) == 0)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
inline FILE* file() const noexcept { return _file ? _file : stdout; }
|
||||
};
|
||||
static BrokenGlobal _brokenGlobal;
|
||||
|
||||
// Broken - API
|
||||
// ============
|
||||
|
||||
// Get whether the string `a` starts with string `b`.
|
||||
static bool BrokenAPI_startsWith(const char* a, const char* b) noexcept {
|
||||
for (size_t i = 0; ; i++) {
|
||||
if (b[i] == '\0') return true;
|
||||
if (a[i] != b[i]) return false;
|
||||
}
|
||||
}
|
||||
|
||||
//! Compares names and priority of two unit tests.
|
||||
static int BrokenAPI_compareUnits(const BrokenAPI::Unit* a, const BrokenAPI::Unit* b) noexcept {
|
||||
if (a->priority == b->priority)
|
||||
return strcmp(a->name, b->name);
|
||||
else
|
||||
return a->priority > b->priority ? 1 : -1;
|
||||
}
|
||||
|
||||
// Get whether the strings `a` and `b` are equal, ignoring case and treating
|
||||
// `-` as `_`.
|
||||
static bool BrokenAPI_matchesFilter(const char* a, const char* b) noexcept {
|
||||
for (size_t i = 0; ; i++) {
|
||||
int ca = (unsigned char)a[i];
|
||||
int cb = (unsigned char)b[i];
|
||||
|
||||
// If filter is defined as wildcard the rest automatically matches.
|
||||
if (cb == '*')
|
||||
return true;
|
||||
|
||||
if (ca == '-') ca = '_';
|
||||
if (cb == '-') cb = '_';
|
||||
|
||||
if (ca >= 'A' && ca <= 'Z') ca += 'a' - 'A';
|
||||
if (cb >= 'A' && cb <= 'Z') cb += 'a' - 'A';
|
||||
|
||||
if (ca != cb)
|
||||
return false;
|
||||
|
||||
if (ca == '\0')
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static bool BrokenAPI_canRun(BrokenAPI::Unit* unit) noexcept {
|
||||
BrokenGlobal& global = _brokenGlobal;
|
||||
|
||||
int i, argc = global._argc;
|
||||
const char** argv = global._argv;
|
||||
|
||||
const char* unitName = unit->name;
|
||||
bool has_filter = false;
|
||||
|
||||
for (i = 1; i < argc; i++) {
|
||||
const char* arg = argv[i];
|
||||
|
||||
if (BrokenAPI_startsWith(arg, "--run-") && strcmp(arg, "--run-all") != 0) {
|
||||
has_filter = true;
|
||||
|
||||
if (BrokenAPI_matchesFilter(unitName, arg + 6))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// If no filter has been specified the default is to run.
|
||||
return !has_filter;
|
||||
}
|
||||
|
||||
static void BrokenAPI_runUnit(BrokenAPI::Unit* unit) noexcept {
|
||||
BrokenAPI::info("Running %s", unit->name);
|
||||
|
||||
_brokenGlobal._unitRunning = unit;
|
||||
unit->entry();
|
||||
_brokenGlobal._unitRunning = NULL;
|
||||
}
|
||||
|
||||
static void BrokenAPI_runAll() noexcept {
|
||||
BrokenAPI::Unit* unit = _brokenGlobal._unitList;
|
||||
|
||||
bool has_units = unit != NULL;
|
||||
size_t count = 0;
|
||||
int currentPriority = 0;
|
||||
|
||||
while (unit != NULL) {
|
||||
if (BrokenAPI_canRun(unit)) {
|
||||
if (currentPriority != unit->priority) {
|
||||
if (count)
|
||||
INFO("");
|
||||
INFO("[[Priority=%d]]", unit->priority);
|
||||
}
|
||||
|
||||
currentPriority = unit->priority;
|
||||
BrokenAPI_runUnit(unit);
|
||||
count++;
|
||||
}
|
||||
unit = unit->next;
|
||||
}
|
||||
|
||||
if (count) {
|
||||
INFO("\nSuccess:");
|
||||
INFO(" All tests passed!");
|
||||
}
|
||||
else {
|
||||
INFO("\nWarning:");
|
||||
INFO(" No units %s!", has_units ? "matched the filter" : "defined");
|
||||
}
|
||||
}
|
||||
|
||||
static void BrokenAPI_listAll() noexcept {
|
||||
BrokenAPI::Unit* unit = _brokenGlobal._unitList;
|
||||
|
||||
if (unit != NULL) {
|
||||
INFO("Units:");
|
||||
do {
|
||||
INFO(" %s [priority=%d]", unit->name, unit->priority);
|
||||
unit = unit->next;
|
||||
} while (unit != NULL);
|
||||
}
|
||||
else {
|
||||
INFO("Warning:");
|
||||
INFO(" No units defined!");
|
||||
}
|
||||
}
|
||||
|
||||
bool BrokenAPI::has_arg(const char* name) noexcept {
|
||||
return _brokenGlobal.has_arg(name);
|
||||
}
|
||||
|
||||
void BrokenAPI::addUnit(Unit* unit) noexcept {
|
||||
Unit** pPrev = &_brokenGlobal._unitList;
|
||||
Unit* current = *pPrev;
|
||||
|
||||
// C++ static initialization doesn't guarantee anything. We sort all units by
|
||||
// name so the execution will always happen in deterministic order.
|
||||
while (current != NULL) {
|
||||
if (BrokenAPI_compareUnits(current, unit) >= 0)
|
||||
break;
|
||||
|
||||
pPrev = ¤t->next;
|
||||
current = *pPrev;
|
||||
}
|
||||
|
||||
*pPrev = unit;
|
||||
unit->next = current;
|
||||
}
|
||||
|
||||
void BrokenAPI::setOutputFile(FILE* file) noexcept {
|
||||
BrokenGlobal& global = _brokenGlobal;
|
||||
|
||||
global._file = file;
|
||||
}
|
||||
|
||||
int BrokenAPI::run(int argc, const char* argv[], Entry on_before_run, Entry onAfterRun) {
|
||||
BrokenGlobal& global = _brokenGlobal;
|
||||
|
||||
global._argc = argc;
|
||||
global._argv = argv;
|
||||
|
||||
if (global.has_arg("--help")) {
|
||||
INFO("Options:");
|
||||
INFO(" --help - print this usage");
|
||||
INFO(" --list - list all tests");
|
||||
INFO(" --run-... - run a test(s), trailing wildcards supported");
|
||||
INFO(" --run-all - run all tests (default)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (global.has_arg("--list")) {
|
||||
BrokenAPI_listAll();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (on_before_run)
|
||||
on_before_run();
|
||||
|
||||
// We don't care about filters here, it's implemented by `runAll`.
|
||||
BrokenAPI_runAll();
|
||||
|
||||
if (onAfterRun)
|
||||
onAfterRun();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void BrokenAPI_printMessage(const char* prefix, const char* fmt, va_list ap) noexcept {
|
||||
BrokenGlobal& global = _brokenGlobal;
|
||||
FILE* dst = global.file();
|
||||
|
||||
if (!fmt || fmt[0] == '\0') {
|
||||
fprintf(dst, "\n");
|
||||
}
|
||||
else {
|
||||
// This looks scary, but we really want to use only a single call to vfprintf()
|
||||
// in multithreaded code. So we change the format a bit if necessary.
|
||||
enum : unsigned { kBufferSize = 512 };
|
||||
char staticBuffer[512];
|
||||
|
||||
size_t fmtSize = strlen(fmt);
|
||||
size_t prefix_size = strlen(prefix);
|
||||
|
||||
char* fmtBuf = staticBuffer;
|
||||
if (fmtSize > kBufferSize - 2 - prefix_size)
|
||||
fmtBuf = static_cast<char*>(malloc(fmtSize + prefix_size + 2));
|
||||
|
||||
if (!fmtBuf) {
|
||||
fprintf(dst, "%sCannot allocate buffer for vfprintf()\n", prefix);
|
||||
}
|
||||
else {
|
||||
memcpy(fmtBuf, prefix, prefix_size);
|
||||
memcpy(fmtBuf + prefix_size, fmt, fmtSize);
|
||||
|
||||
fmtSize += prefix_size;
|
||||
if (fmtBuf[fmtSize - 1] != '\n')
|
||||
fmtBuf[fmtSize++] = '\n';
|
||||
fmtBuf[fmtSize] = '\0';
|
||||
|
||||
vfprintf(dst, fmtBuf, ap);
|
||||
|
||||
if (fmtBuf != staticBuffer)
|
||||
free(fmtBuf);
|
||||
}
|
||||
}
|
||||
|
||||
fflush(dst);
|
||||
}
|
||||
|
||||
void BrokenAPI::info(const char* fmt, ...) noexcept {
|
||||
BrokenGlobal& global = _brokenGlobal;
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
BrokenAPI_printMessage(global._unitRunning ? " " : "", fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void BrokenAPI::fail(const char* file, int line, const char* expression, const char* fmt, ...) noexcept {
|
||||
BrokenGlobal& global = _brokenGlobal;
|
||||
FILE* dst = global.file();
|
||||
|
||||
fprintf(dst, " FAILED: %s\n", expression);
|
||||
|
||||
if (fmt) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
BrokenAPI_printMessage(" REASON: ", fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
fprintf(dst, " SOURCE: %s (Line: %d)\n", file, line);
|
||||
fflush(dst);
|
||||
|
||||
abort();
|
||||
}
|
||||
200
asmjit-testing/tests/broken.h
Normal file
200
asmjit-testing/tests/broken.h
Normal file
@@ -0,0 +1,200 @@
|
||||
// Broken - Lightweight unit testing for C++
|
||||
//
|
||||
// This is free and unencumbered software released into the public domain.
|
||||
//
|
||||
// Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
// distribute this software, either in source code form or as a compiled
|
||||
// binary, for any purpose, commercial or non-commercial, and by any
|
||||
// means.
|
||||
//
|
||||
// In jurisdictions that recognize copyright laws, the author or authors
|
||||
// of this software dedicate any and all copyright interest in the
|
||||
// software to the public domain. We make this dedication for the benefit
|
||||
// of the public at large and to the detriment of our heirs and
|
||||
// successors. We intend this dedication to be an overt act of
|
||||
// relinquishment in perpetuity of all present and future rights to this
|
||||
// software under copyright law.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
// OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
// For more information, please refer to <http://unlicense.org>
|
||||
|
||||
#ifndef BROKEN_H_INCLUDED
|
||||
#define BROKEN_H_INCLUDED
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <utility>
|
||||
|
||||
// Hide everything when using Doxygen. Ideally this can be protected by a macro,
|
||||
// but there is not globally and widely used one across multiple projects.
|
||||
|
||||
//! \cond
|
||||
|
||||
// Broken - API
|
||||
// ============
|
||||
|
||||
namespace BrokenAPI {
|
||||
|
||||
//! Entry point of a unit test defined by `UNIT` macro.
|
||||
typedef void (*Entry)(void);
|
||||
|
||||
enum Flags : unsigned {
|
||||
kFlagFinished = 0x1
|
||||
};
|
||||
|
||||
struct Unit;
|
||||
|
||||
bool has_arg(const char* name) noexcept;
|
||||
|
||||
//! Register a new unit test (called automatically by `AutoUnit` and `UNIT`).
|
||||
void addUnit(Unit* unit) noexcept;
|
||||
|
||||
//! Set output file to a `file`.
|
||||
void setOutputFile(FILE* file) noexcept;
|
||||
|
||||
//! Initialize `Broken` framework.
|
||||
//!
|
||||
//! Returns `true` if `run()` should be called.
|
||||
int run(int argc, const char* argv[], Entry on_before_run = nullptr, Entry onAfterRun = nullptr);
|
||||
|
||||
//! Log message, adds automatically new line if not present.
|
||||
void info(const char* fmt, ...) noexcept;
|
||||
|
||||
//! Called on `EXPECT()` failure.
|
||||
void fail(const char* file, int line, const char* expression, const char* fmt, ...) noexcept;
|
||||
|
||||
//! Test defined by `UNIT` macro.
|
||||
struct Unit {
|
||||
Entry entry;
|
||||
const char* name;
|
||||
int priority;
|
||||
unsigned flags;
|
||||
Unit* next;
|
||||
};
|
||||
|
||||
//! Automatic unit registration by using static initialization.
|
||||
class AutoUnit : public Unit {
|
||||
public:
|
||||
inline AutoUnit(Entry entry_, const char* name_, int priority_ = 0, int dummy_ = 0) noexcept {
|
||||
// Not used, only to trick `UNIT()` macro.
|
||||
(void)dummy_;
|
||||
|
||||
this->entry = entry_;
|
||||
this->name = name_;
|
||||
this->priority = priority_;
|
||||
this->flags = 0;
|
||||
this->next = nullptr;
|
||||
addUnit(this);
|
||||
}
|
||||
};
|
||||
|
||||
class Failure {
|
||||
public:
|
||||
const char* _file = nullptr;
|
||||
const char* _expression = nullptr;
|
||||
int _line = 0;
|
||||
bool _handled = false;
|
||||
|
||||
inline Failure(const char* file, int line, const char* expression) noexcept
|
||||
: _file(file),
|
||||
_expression(expression),
|
||||
_line(line) {}
|
||||
|
||||
inline ~Failure() noexcept {
|
||||
if (!_handled)
|
||||
fail(_file, _line, _expression, nullptr);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void message(const char* fmt, Args&&... args) noexcept {
|
||||
fail(_file, _line, _expression, fmt, std::forward<Args>(args)...);
|
||||
_handled = true;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Result>
|
||||
static inline bool check(Result&& result) noexcept { return !!result; }
|
||||
|
||||
template<typename LHS, typename RHS>
|
||||
static inline bool checkEq(LHS&& lhs, RHS&& rhs) noexcept { return lhs == rhs; }
|
||||
|
||||
template<typename LHS, typename RHS>
|
||||
static inline bool checkNe(LHS&& lhs, RHS&& rhs) noexcept { return lhs != rhs; }
|
||||
|
||||
template<typename LHS, typename RHS>
|
||||
static inline bool checkGt(LHS&& lhs, RHS&& rhs) noexcept { return lhs > rhs; }
|
||||
|
||||
template<typename LHS, typename RHS>
|
||||
static inline bool checkGe(LHS&& lhs, RHS&& rhs) noexcept { return lhs >= rhs; }
|
||||
|
||||
template<typename LHS, typename RHS>
|
||||
static inline bool checkLt(LHS&& lhs, RHS&& rhs) noexcept { return lhs < rhs; }
|
||||
|
||||
template<typename LHS, typename RHS>
|
||||
static inline bool checkLe(LHS&& lhs, RHS&& rhs) noexcept { return lhs <= rhs; }
|
||||
|
||||
template<typename Result>
|
||||
static inline bool checkTrue(Result&& result) noexcept { return !!result; }
|
||||
|
||||
template<typename Result>
|
||||
static inline bool checkFalse(Result&& result) noexcept { return !result; }
|
||||
|
||||
template<typename Result>
|
||||
static inline bool checkNull(Result&& result) noexcept { return result == nullptr; }
|
||||
|
||||
template<typename Result>
|
||||
static inline bool checkNotNull(Result&& result) noexcept { return result != nullptr; }
|
||||
|
||||
} // {BrokenAPI}
|
||||
|
||||
// Broken - Macros
|
||||
// ===============
|
||||
|
||||
//! Internal macro used by `UNIT()`.
|
||||
#define BROKEN_UNIT_INTERNAL(NAME, PRIORITY) \
|
||||
static void unit_##NAME##_entry(void); \
|
||||
static ::BrokenAPI::AutoUnit unit_##NAME##_autoinit(unit_##NAME##_entry, #NAME, PRIORITY); \
|
||||
static void unit_##NAME##_entry(void)
|
||||
|
||||
//! \def UNIT(NAME [, PRIORITY])
|
||||
//!
|
||||
//! Define a unit test with an optional priority.
|
||||
//!
|
||||
//! `NAME` can only contain ASCII characters, numbers and underscore. It has the same rules as identifiers in C and C++.
|
||||
//!
|
||||
//! `PRIORITY` specifies the order in which unit tests are run. Lesses value increases the priority. At the moment all
|
||||
//!units are first sorted by priority and then by name - this makes the run always deterministic.
|
||||
#define UNIT(NAME, ...) BROKEN_UNIT_INTERNAL(NAME, __VA_ARGS__ + 0)
|
||||
|
||||
//! #define INFO(FORMAT [, ...])
|
||||
//!
|
||||
//! Informative message printed to `stdout`.
|
||||
#define INFO(...) ::BrokenAPI::info(__VA_ARGS__)
|
||||
|
||||
#define BROKEN_EXPECT_INTERNAL(file, line, expression, result) \
|
||||
for (bool _testInternalResult = (result); !_testInternalResult; _testInternalResult = true) \
|
||||
::BrokenAPI::Failure(file, line, expression)
|
||||
|
||||
#define EXPECT(...) BROKEN_EXPECT_INTERNAL(__FILE__, __LINE__, "EXPECT(" #__VA_ARGS__ ")", !!(__VA_ARGS__))
|
||||
#define EXPECT_EQ(...) BROKEN_EXPECT_INTERNAL(__FILE__, __LINE__, "EXPECT_EQ(" #__VA_ARGS__ ")", ::BrokenAPI::checkEq(__VA_ARGS__))
|
||||
#define EXPECT_NE(...) BROKEN_EXPECT_INTERNAL(__FILE__, __LINE__, "EXPECT_NE(" #__VA_ARGS__ ")", ::BrokenAPI::checkNe(__VA_ARGS__))
|
||||
#define EXPECT_GT(...) BROKEN_EXPECT_INTERNAL(__FILE__, __LINE__, "EXPECT_GT(" #__VA_ARGS__ ")", ::BrokenAPI::checkGt(__VA_ARGS__))
|
||||
#define EXPECT_GE(...) BROKEN_EXPECT_INTERNAL(__FILE__, __LINE__, "EXPECT_GE(" #__VA_ARGS__ ")", ::BrokenAPI::checkGe(__VA_ARGS__))
|
||||
#define EXPECT_LT(...) BROKEN_EXPECT_INTERNAL(__FILE__, __LINE__, "EXPECT_LT(" #__VA_ARGS__ ")", ::BrokenAPI::checkLt(__VA_ARGS__))
|
||||
#define EXPECT_LE(...) BROKEN_EXPECT_INTERNAL(__FILE__, __LINE__, "EXPECT_LE(" #__VA_ARGS__ ")", ::BrokenAPI::checkLe(__VA_ARGS__))
|
||||
#define EXPECT_TRUE(...) BROKEN_EXPECT_INTERNAL(__FILE__, __LINE__, "EXPECT_TRUE(" #__VA_ARGS__ ")", ::BrokenAPI::checkTrue(__VA_ARGS__))
|
||||
#define EXPECT_FALSE(...) BROKEN_EXPECT_INTERNAL(__FILE__, __LINE__, "EXPECT_FALSE(" #__VA_ARGS__ ")", ::BrokenAPI::checkFalse(__VA_ARGS__))
|
||||
#define EXPECT_NULL(...) BROKEN_EXPECT_INTERNAL(__FILE__, __LINE__, "EXPECT_NULL(" #__VA_ARGS__ ")", ::BrokenAPI::checkNull(__VA_ARGS__))
|
||||
#define EXPECT_NOT_NULL(...) BROKEN_EXPECT_INTERNAL(__FILE__, __LINE__, "EXPECT_NOT_NULL(" #__VA_ARGS__ ")", ::BrokenAPI::checkNotNull(__VA_ARGS__))
|
||||
|
||||
//! \endcond
|
||||
|
||||
#endif // BROKEN_H_INCLUDED
|
||||
Reference in New Issue
Block a user