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,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