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:
kobalicek
2025-11-02 22:31:14 +01:00
parent 5134d396bd
commit b56f4176cb
209 changed files with 1687 additions and 1857 deletions

View 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;
}

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

View 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

View 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

View 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

View 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;
}

View 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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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;
}

View 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;
}

View 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

View 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);
}

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

View 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 = &current->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();
}

View 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