mirror of
https://github.com/asmjit/asmjit.git
synced 2025-12-18 04:54:36 +03:00
Codebase update and improvements, instruction DB update
* Denested src folder to root, renamed testing to asmjit-testing
* Refactored how headers are included into <asmjit/...> form. This
is necessary as compilers would never simplify a path once a ..
appears in include directory - then paths such as ../core/../core
appeared in asserts, which was ugly
* Moved support utilities into asmjit/support/... (still included
by asmjit/core.h for convenience and compatibility)
* Added CMakePresets.json for making it easy to develop AsmJit
* Reworked CMakeLists to be shorter and use CMake option(),
etc... This simplifies it and makes it using more standard
features
* ASMJIT_EMBED now creates asmjit_embed INTERFACE library,
which is accessible via asmjit::asmjit target - this simplifies
embedding and makes it the same as library targets from a CMake
perspective
* Removed ASMJIT_DEPS - this is now provided by cmake target
aliases - 'asmjit::asmjit' so users should not need this variable
* Changed meaning of ASMJIT_LIBS - this now contains only AsmJit
dependencies without asmjit::asmjit target alias. Don't rely on
ASMJIT_LIBS anymore as it's only used internally
* Removed ASMJIT_NO_DEPRECATED option - AsmJit is not going
to provide controllable deprecations in the future
* Removed ASMJIT_NO_VALIDATION in favor of ASMJIT_NO_INTROSPECTION,
which now controls query, features, and validation API presence
* Removed ASMJIT_DIR option - it was never really needed
* Removed AMX_TRANSPOSE feature from instruction database (X86).
Intel has removed it as well, so it's a feature that won't
be siliconized
This commit is contained in:
107
asmjit-testing/tests/asmjit_test_assembler.cpp
Normal file
107
asmjit-testing/tests/asmjit_test_assembler.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
// This file is part of AsmJit project <https://asmjit.com>
|
||||
//
|
||||
// See <asmjit/core.h> or LICENSE.md for license and copyright information
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#include <asmjit/core.h>
|
||||
|
||||
#include <asmjit-testing/commons/asmjitutils.h>
|
||||
#include <asmjit-testing/commons/cmdline.h>
|
||||
#include <asmjit-testing/tests/asmjit_test_assembler.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
using namespace asmjit;
|
||||
|
||||
#if !defined(ASMJIT_NO_X86)
|
||||
bool test_x86_assembler(const TestSettings& settings) noexcept;
|
||||
bool test_x64_assembler(const TestSettings& settings) noexcept;
|
||||
#endif
|
||||
|
||||
#if !defined(ASMJIT_NO_AARCH64)
|
||||
bool test_aarch64_assembler(const TestSettings& settings) noexcept;
|
||||
#endif
|
||||
|
||||
static void print_app_info() noexcept {
|
||||
printf("AsmJit Assembler Test-Suite v%u.%u.%u [Arch=%s] [Mode=%s]\n\n",
|
||||
unsigned((ASMJIT_LIBRARY_VERSION >> 16) ),
|
||||
unsigned((ASMJIT_LIBRARY_VERSION >> 8) & 0xFF),
|
||||
unsigned((ASMJIT_LIBRARY_VERSION ) & 0xFF),
|
||||
asmjit_arch_as_string(Arch::kHost),
|
||||
asmjit_build_type()
|
||||
);
|
||||
}
|
||||
|
||||
static void print_app_usage(const TestSettings& settings) noexcept {
|
||||
printf("Usage:\n");
|
||||
printf(" --help Show usage only\n");
|
||||
printf(" --verbose Show only assembling errors [%s]\n", settings.verbose ? "x" : " ");
|
||||
printf(" --validate Use instruction validation [%s]\n", settings.validate ? "x" : " ");
|
||||
printf(" --arch=<ARCH> Select architecture to run ('all' by default)\n");
|
||||
printf("\n");
|
||||
|
||||
printf("Architectures:\n");
|
||||
#if !defined(ASMJIT_NO_X86)
|
||||
printf(" --arch=x86 32-bit X86 architecture (X86)\n");
|
||||
printf(" --arch=x64 64-bit X86 architecture (X86_64)\n");
|
||||
#endif
|
||||
#if !defined(ASMJIT_NO_AARCH64)
|
||||
printf(" --arch=aarch64 64-bit ARM architecture (AArch64)\n");
|
||||
#endif
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
CmdLine cmd_line(argc, argv);
|
||||
|
||||
TestSettings settings {};
|
||||
settings.verbose = cmd_line.has_arg("--verbose");
|
||||
settings.validate = cmd_line.has_arg("--validate");
|
||||
|
||||
print_app_info();
|
||||
print_app_usage(settings);
|
||||
|
||||
if (cmd_line.has_arg("--help")) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* arch = cmd_line.value_of("--arch", "all");
|
||||
bool x86_failed = false;
|
||||
bool x64_failed = false;
|
||||
bool aarch64_failed = false;
|
||||
|
||||
#if !defined(ASMJIT_NO_X86)
|
||||
if ((strcmp(arch, "all") == 0 || strcmp(arch, "x86") == 0))
|
||||
x86_failed = !test_x86_assembler(settings);
|
||||
|
||||
if ((strcmp(arch, "all") == 0 || strcmp(arch, "x64") == 0))
|
||||
x64_failed = !test_x64_assembler(settings);
|
||||
#endif
|
||||
|
||||
#if !defined(ASMJIT_NO_AARCH64)
|
||||
if ((strcmp(arch, "all") == 0 || strcmp(arch, "aarch64") == 0))
|
||||
aarch64_failed = !test_aarch64_assembler(settings);
|
||||
#endif
|
||||
|
||||
bool failed = x86_failed || x64_failed || aarch64_failed;
|
||||
|
||||
if (failed) {
|
||||
if (x86_failed)
|
||||
printf("** X86 test suite failed **\n");
|
||||
|
||||
if (x64_failed)
|
||||
printf("** X64 test suite failed **\n");
|
||||
|
||||
if (aarch64_failed)
|
||||
printf("** AArch64 test suite failed **\n");
|
||||
|
||||
printf("** FAILURE **\n");
|
||||
}
|
||||
else {
|
||||
printf("** SUCCESS **\n");
|
||||
}
|
||||
|
||||
return failed ? 1 : 0;
|
||||
}
|
||||
109
asmjit-testing/tests/asmjit_test_assembler.h
Normal file
109
asmjit-testing/tests/asmjit_test_assembler.h
Normal file
@@ -0,0 +1,109 @@
|
||||
// This file is part of AsmJit project <https://asmjit.com>
|
||||
//
|
||||
// See <asmjit/core.h> or LICENSE.md for license and copyright information
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef ASMJIT_TEST_ASSEMBLER_H_INCLUDED
|
||||
#define ASMJIT_TEST_ASSEMBLER_H_INCLUDED
|
||||
|
||||
#include <asmjit/core.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
struct TestSettings {
|
||||
bool verbose;
|
||||
bool validate;
|
||||
};
|
||||
|
||||
template<typename AssemblerType>
|
||||
class AssemblerTester {
|
||||
public:
|
||||
asmjit::Environment env {};
|
||||
asmjit::CodeHolder code {};
|
||||
AssemblerType assembler {};
|
||||
asmjit::Label L0 {};
|
||||
const TestSettings& settings;
|
||||
|
||||
size_t passed {};
|
||||
size_t count {};
|
||||
|
||||
AssemblerTester(asmjit::Arch arch, const TestSettings& settings) noexcept
|
||||
: env(arch),
|
||||
settings(settings) {
|
||||
prepare();
|
||||
}
|
||||
|
||||
void print_header(const char* arch_name) noexcept {
|
||||
printf("%s assembler tests:\n", arch_name);
|
||||
}
|
||||
|
||||
void print_summary() noexcept {
|
||||
printf(" Passed: %zu / %zu tests\n\n", passed, count);
|
||||
}
|
||||
|
||||
bool did_pass() const noexcept { return passed == count; }
|
||||
|
||||
void prepare() noexcept {
|
||||
code.reset();
|
||||
code.init(env, 0);
|
||||
code.attach(&assembler);
|
||||
L0 = assembler.new_label();
|
||||
|
||||
if (settings.validate)
|
||||
assembler.add_diagnostic_options(asmjit::DiagnosticOptions::kValidateAssembler);
|
||||
}
|
||||
|
||||
ASMJIT_NOINLINE bool test_valid_instruction(const char* s, const char* expected_opcode, asmjit::Error err = asmjit::Error::kOk) noexcept {
|
||||
count++;
|
||||
|
||||
if (err != asmjit::Error::kOk) {
|
||||
printf(" !! %s\n"
|
||||
" <%s>\n", s, asmjit::DebugUtils::error_as_string(err));
|
||||
prepare();
|
||||
return false;
|
||||
}
|
||||
|
||||
asmjit::String encoded_opcode;
|
||||
asmjit::Section* text = code.text_section();
|
||||
|
||||
encoded_opcode.append_hex(text->data(), text->buffer_size());
|
||||
if (encoded_opcode != expected_opcode) {
|
||||
printf(" !! [%s] <- %s\n"
|
||||
" [%s] (Expected)\n", encoded_opcode.data(), s, expected_opcode);
|
||||
prepare();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (settings.verbose)
|
||||
printf(" OK [%s] <- %s\n", encoded_opcode.data(), s);
|
||||
|
||||
passed++;
|
||||
prepare();
|
||||
return true;
|
||||
}
|
||||
|
||||
ASMJIT_NOINLINE bool test_invalid_instruction(const char* s, asmjit::Error expected_error, asmjit::Error err) noexcept {
|
||||
count++;
|
||||
|
||||
if (err == asmjit::Error::kOk) {
|
||||
printf(" !! %s passed, but should have failed with <%s> error\n", s, asmjit::DebugUtils::error_as_string(expected_error));
|
||||
prepare();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (err != asmjit::Error::kOk) {
|
||||
printf(" !! %s failed with <%s>, but should have failed with <%s>\n", s, asmjit::DebugUtils::error_as_string(err), asmjit::DebugUtils::error_as_string(expected_error));
|
||||
prepare();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (settings.verbose)
|
||||
printf(" OK [%s] <- %s\n", asmjit::DebugUtils::error_as_string(err), s);
|
||||
|
||||
passed++;
|
||||
prepare();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // ASMJIT_TEST_ASSEMBLER_H_INCLUDED
|
||||
4053
asmjit-testing/tests/asmjit_test_assembler_a64.cpp
Normal file
4053
asmjit-testing/tests/asmjit_test_assembler_a64.cpp
Normal file
File diff suppressed because it is too large
Load Diff
18073
asmjit-testing/tests/asmjit_test_assembler_x64.cpp
Normal file
18073
asmjit-testing/tests/asmjit_test_assembler_x64.cpp
Normal file
File diff suppressed because it is too large
Load Diff
8492
asmjit-testing/tests/asmjit_test_assembler_x86.cpp
Normal file
8492
asmjit-testing/tests/asmjit_test_assembler_x86.cpp
Normal file
File diff suppressed because it is too large
Load Diff
407
asmjit-testing/tests/asmjit_test_compiler.cpp
Normal file
407
asmjit-testing/tests/asmjit_test_compiler.cpp
Normal file
@@ -0,0 +1,407 @@
|
||||
// This file is part of AsmJit project <https://asmjit.com>
|
||||
//
|
||||
// See <asmjit/core.h> or LICENSE.md for license and copyright information
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#include <asmjit/core.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
|
||||
#if !defined(ASMJIT_NO_COMPILER)
|
||||
|
||||
#include <asmjit-testing/commons/asmjitutils.h>
|
||||
#include <asmjit-testing/commons/cmdline.h>
|
||||
#include <asmjit-testing/commons/performancetimer.h>
|
||||
#include <asmjit-testing/tests/asmjit_test_compiler.h>
|
||||
|
||||
#if !defined(ASMJIT_NO_X86)
|
||||
#include <asmjit/x86.h>
|
||||
void compiler_add_x86_tests(TestApp& app);
|
||||
#endif // !ASMJIT_NO_X86
|
||||
|
||||
#if !defined(ASMJIT_NO_AARCH64)
|
||||
#include <asmjit/a64.h>
|
||||
void compiler_add_a64_tests(TestApp& app);
|
||||
#endif // !ASMJIT_NO_AARCH64
|
||||
|
||||
using namespace asmjit;
|
||||
|
||||
int TestApp::handle_args(int argc, const char* const* argv) {
|
||||
CmdLine cmd(argc, argv);
|
||||
_arch = cmd.value_of("--arch", "all");
|
||||
_filter = cmd.value_of("--filter", nullptr);
|
||||
|
||||
if (cmd.has_arg("--help")) _help_only = true;
|
||||
if (cmd.has_arg("--verbose")) _verbose = true;
|
||||
|
||||
#ifndef ASMJIT_NO_LOGGING
|
||||
if (cmd.has_arg("--dump-asm")) _dump_asm = true;
|
||||
#endif // !ASMJIT_NO_LOGGING
|
||||
|
||||
if (cmd.has_arg("--dump-hex")) _dump_hex = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void TestApp::show_info() {
|
||||
printf("AsmJit Compiler Test-Suite v%u.%u.%u [Arch=%s] [Mode=%s]\n\n",
|
||||
unsigned((ASMJIT_LIBRARY_VERSION >> 16) ),
|
||||
unsigned((ASMJIT_LIBRARY_VERSION >> 8) & 0xFF),
|
||||
unsigned((ASMJIT_LIBRARY_VERSION ) & 0xFF),
|
||||
asmjit_arch_as_string(Arch::kHost),
|
||||
asmjit_build_type()
|
||||
);
|
||||
|
||||
printf("Usage:\n");
|
||||
printf(" --help Show usage only\n");
|
||||
printf(" --arch=<NAME> Select architecture to run ('all' by default)\n");
|
||||
printf(" --filter=<NAME> Use a filter to restrict which test is called\n");
|
||||
printf(" --verbose Verbose output\n");
|
||||
printf(" --dump-asm Assembler output\n");
|
||||
printf(" --dump-hex Hexadecimal output (relocated, only for host arch)\n");
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
#ifndef ASMJIT_NO_LOGGING
|
||||
class IndentedStdoutLogger : public Logger {
|
||||
public:
|
||||
ASMJIT_NONCOPYABLE(IndentedStdoutLogger)
|
||||
|
||||
size_t _indentation = 0;
|
||||
|
||||
explicit IndentedStdoutLogger(size_t indentation) noexcept
|
||||
: _indentation(indentation) {}
|
||||
|
||||
Error _log(const char* data, size_t size = SIZE_MAX) noexcept override {
|
||||
asmjit::Support::maybe_unused(size);
|
||||
print_indented(data, _indentation);
|
||||
return Error::kOk;
|
||||
}
|
||||
};
|
||||
#endif // !ASMJIT_NO_LOGGING
|
||||
|
||||
bool TestApp::should_run(const TestCase* tc) {
|
||||
if (!_filter)
|
||||
return true;
|
||||
|
||||
return strstr(tc->name(), _filter) != nullptr;
|
||||
}
|
||||
|
||||
int TestApp::run() {
|
||||
#ifndef ASMJIT_NO_LOGGING
|
||||
FormatOptions format_options;
|
||||
format_options.add_flags(
|
||||
FormatFlags::kMachineCode |
|
||||
FormatFlags::kShowAliases |
|
||||
FormatFlags::kExplainImms |
|
||||
FormatFlags::kRegCasts );
|
||||
format_options.set_indentation(FormatIndentationGroup::kCode, 2);
|
||||
|
||||
IndentedStdoutLogger print_logger(4);
|
||||
print_logger.set_options(format_options);
|
||||
|
||||
StringLogger string_logger;
|
||||
string_logger.set_options(format_options);
|
||||
|
||||
auto print_string_logger_content = [&]() {
|
||||
if (!_verbose) {
|
||||
printf("%s", string_logger.data());
|
||||
fflush(stdout);
|
||||
}
|
||||
};
|
||||
#else
|
||||
auto print_string_logger_content = [&]() {};
|
||||
#endif // !ASMJIT_NO_LOGGING
|
||||
|
||||
// maybe unused...
|
||||
Support::maybe_unused(print_string_logger_content);
|
||||
|
||||
#ifndef ASMJIT_NO_JIT
|
||||
JitRuntime runtime;
|
||||
#endif // !ASMJIT_NO_JIT
|
||||
|
||||
PerformanceTimer compile_timer;
|
||||
PerformanceTimer finalize_timer;
|
||||
|
||||
double compile_time = 0;
|
||||
double finalize_time = 0;
|
||||
|
||||
for (std::unique_ptr<TestCase>& test : _tests) {
|
||||
if (!should_run(test.get()))
|
||||
continue;
|
||||
|
||||
_num_tests++;
|
||||
|
||||
for (uint32_t pass = 0; pass < 2; pass++) {
|
||||
bool runnable = false;
|
||||
CodeHolder code;
|
||||
SimpleErrorHandler error_handler;
|
||||
|
||||
const char* status_separator = " ";
|
||||
|
||||
// Filter architecture to run.
|
||||
if (strcmp(_arch, "all") != 0) {
|
||||
switch (test->arch()) {
|
||||
case Arch::kX86:
|
||||
if (strcmp(_arch, "x86") == 0)
|
||||
break;
|
||||
continue;
|
||||
case Arch::kX64:
|
||||
if (strcmp(_arch, "x64") == 0)
|
||||
break;
|
||||
continue;
|
||||
case Arch::kAArch64:
|
||||
if (strcmp(_arch, "aarch64") == 0)
|
||||
break;
|
||||
continue;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Use platform environment and CPU features when the test can run on the arch.
|
||||
#ifndef ASMJIT_NO_JIT
|
||||
if (runtime.arch() == test->arch()) {
|
||||
code.init(runtime.environment(), runtime.cpu_features());
|
||||
runnable = true;
|
||||
}
|
||||
#endif // !ASMJIT_NO_JIT
|
||||
|
||||
if (!code.is_initialized()) {
|
||||
Environment custom_env;
|
||||
CpuFeatures features;
|
||||
|
||||
switch (test->arch()) {
|
||||
case Arch::kX86:
|
||||
case Arch::kX64:
|
||||
features.add(CpuFeatures::X86::kADX,
|
||||
CpuFeatures::X86::kAVX,
|
||||
CpuFeatures::X86::kAVX2,
|
||||
CpuFeatures::X86::kBMI,
|
||||
CpuFeatures::X86::kBMI2,
|
||||
CpuFeatures::X86::kCMOV,
|
||||
CpuFeatures::X86::kF16C,
|
||||
CpuFeatures::X86::kFMA,
|
||||
CpuFeatures::X86::kFPU,
|
||||
CpuFeatures::X86::kI486,
|
||||
CpuFeatures::X86::kLZCNT,
|
||||
CpuFeatures::X86::kMMX,
|
||||
CpuFeatures::X86::kMMX2,
|
||||
CpuFeatures::X86::kPOPCNT,
|
||||
CpuFeatures::X86::kSSE,
|
||||
CpuFeatures::X86::kSSE2,
|
||||
CpuFeatures::X86::kSSE3,
|
||||
CpuFeatures::X86::kSSSE3,
|
||||
CpuFeatures::X86::kSSE4_1,
|
||||
CpuFeatures::X86::kSSE4_2);
|
||||
break;
|
||||
|
||||
case Arch::kAArch64:
|
||||
features.add(CpuFeatures::ARM::kAES,
|
||||
CpuFeatures::ARM::kASIMD,
|
||||
CpuFeatures::ARM::kIDIVA,
|
||||
CpuFeatures::ARM::kIDIVT,
|
||||
CpuFeatures::ARM::kPMULL);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
custom_env.init(test->arch());
|
||||
code.init(custom_env, features);
|
||||
}
|
||||
|
||||
code.set_error_handler(&error_handler);
|
||||
|
||||
if (pass != 0) {
|
||||
printf("[Test:%s] %s", asmjit_arch_as_string(test->arch()), test->name());
|
||||
fflush(stdout);
|
||||
|
||||
#ifndef ASMJIT_NO_LOGGING
|
||||
if (_verbose || _dump_asm || _dump_hex) {
|
||||
printf("\n");
|
||||
status_separator = " ";
|
||||
}
|
||||
|
||||
if (_verbose) {
|
||||
printf(" [Log]\n");
|
||||
code.set_logger(&print_logger);
|
||||
}
|
||||
else {
|
||||
string_logger.clear();
|
||||
code.set_logger(&string_logger);
|
||||
}
|
||||
#endif // !ASMJIT_NO_LOGGING
|
||||
}
|
||||
|
||||
std::unique_ptr<BaseCompiler> cc;
|
||||
|
||||
#ifndef ASMJIT_NO_X86
|
||||
if (code.arch() == Arch::kX86 || code.arch() == Arch::kX64)
|
||||
cc = std::make_unique<x86::Compiler>(&code);
|
||||
#endif // !ASMJIT_NO_X86
|
||||
|
||||
#ifndef ASMJIT_NO_AARCH64
|
||||
if (code.arch() == Arch::kAArch64)
|
||||
cc = std::make_unique<a64::Compiler>(&code);
|
||||
#endif // !ASMJIT_NO_AARCH64
|
||||
|
||||
if (!cc)
|
||||
continue;
|
||||
|
||||
#ifndef ASMJIT_NO_LOGGING
|
||||
cc->add_diagnostic_options(DiagnosticOptions::kRAAnnotate | DiagnosticOptions::kRADebugAll);
|
||||
#endif // !ASMJIT_NO_LOGGING
|
||||
|
||||
compile_timer.start();
|
||||
test->compile(*cc);
|
||||
compile_timer.stop();
|
||||
|
||||
Error err = error_handler._err;
|
||||
if (err == Error::kOk) {
|
||||
finalize_timer.start();
|
||||
err = cc->finalize();
|
||||
finalize_timer.stop();
|
||||
}
|
||||
|
||||
// The first pass is only used for timing of serialization and compilation, because otherwise it would be
|
||||
// biased by logging, which takes much more time than finalize() does. We want to benchmark Compiler the
|
||||
// way it would be used in the production.
|
||||
if (pass == 0) {
|
||||
_output_size += code.code_size();
|
||||
compile_time += compile_timer.duration();
|
||||
finalize_time += finalize_timer.duration();
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifndef ASMJIT_NO_LOGGING
|
||||
if (_dump_asm) {
|
||||
String sb;
|
||||
Formatter::format_node_list(sb, format_options, cc.get());
|
||||
printf(" [Assembly]\n");
|
||||
print_indented(sb.data(), 4);
|
||||
}
|
||||
#endif // !ASMJIT_NO_LOGGING
|
||||
|
||||
#ifndef ASMJIT_NO_JIT
|
||||
if (runnable) {
|
||||
void* func = nullptr;
|
||||
if (err == Error::kOk)
|
||||
err = runtime.add(&func, &code);
|
||||
|
||||
if (err == Error::kOk && _dump_hex) {
|
||||
String sb;
|
||||
sb.append_hex((void*)func, code.code_size());
|
||||
printf(" [Hex Dump]:\n");
|
||||
for (size_t i = 0; i < sb.size(); i += 76) {
|
||||
printf(" %.60s\n", sb.data() + i);
|
||||
}
|
||||
}
|
||||
|
||||
if (_verbose)
|
||||
fflush(stdout);
|
||||
|
||||
if (err == Error::kOk) {
|
||||
StringTmp<128> result;
|
||||
StringTmp<128> expect;
|
||||
|
||||
if (test->run(func, result, expect)) {
|
||||
if (!_verbose)
|
||||
printf("%s[RUN OK]\n", status_separator);
|
||||
}
|
||||
else {
|
||||
if (!_verbose)
|
||||
printf("%s[RUN FAILED]\n", status_separator);
|
||||
|
||||
print_string_logger_content();
|
||||
printf(" [Output]\n");
|
||||
printf(" Returned: %s\n", result.data());
|
||||
printf(" Expected: %s\n", expect.data());
|
||||
_num_failed++;
|
||||
}
|
||||
|
||||
if (_dump_asm)
|
||||
printf("\n");
|
||||
|
||||
runtime.release(func);
|
||||
}
|
||||
else {
|
||||
if (!_verbose)
|
||||
printf("%s[COMPILE FAILED]\n", status_separator);
|
||||
|
||||
print_string_logger_content();
|
||||
printf(" [Status]\n");
|
||||
printf(" ERROR 0x%08X: %s\n", unsigned(err), error_handler._message.data());
|
||||
_num_failed++;
|
||||
}
|
||||
}
|
||||
#endif // !ASMJIT_NO_JIT
|
||||
|
||||
if (!runnable) {
|
||||
if (err != Error::kOk) {
|
||||
printf(" [Status]\n");
|
||||
printf(" ERROR 0x%08X: %s\n", unsigned(err), error_handler._message.data());
|
||||
_num_failed++;
|
||||
}
|
||||
else {
|
||||
printf("%s[COMPILE OK]\n", status_separator);
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef ASMJIT_NO_LOGGING
|
||||
if (_verbose || _dump_asm || _dump_hex) {
|
||||
printf("\n");
|
||||
}
|
||||
#endif // !ASMJIT_NO_LOGGING
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
printf("Summary:\n");
|
||||
printf(" OutputSize: %zu bytes\n", _output_size);
|
||||
printf(" CompileTime: %.2f ms\n", compile_time);
|
||||
printf(" FinalizeTime: %.2f ms\n", finalize_time);
|
||||
printf("\n");
|
||||
|
||||
if (_num_failed == 0)
|
||||
printf("** SUCCESS: All %u tests passed **\n", _num_tests);
|
||||
else
|
||||
printf("** FAILURE: %u of %u tests failed **\n", _num_failed, _num_tests);
|
||||
|
||||
return _num_failed == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
TestApp app;
|
||||
|
||||
app.handle_args(argc, argv);
|
||||
app.show_info();
|
||||
|
||||
#if !defined(ASMJIT_NO_X86)
|
||||
compiler_add_x86_tests(app);
|
||||
#endif // !ASMJIT_NO_X86
|
||||
|
||||
#if !defined(ASMJIT_NO_AARCH64)
|
||||
compiler_add_a64_tests(app);
|
||||
#endif // !ASMJIT_NO_AARCH64
|
||||
|
||||
return app.run();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
Support::maybe_unused(argc, argv);
|
||||
|
||||
printf("AsmJit Compiler Test suite is disabled when compiling with ASMJIT_NO_COMPILER\n\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // !ASMJIT_NO_COMPILER
|
||||
83
asmjit-testing/tests/asmjit_test_compiler.h
Normal file
83
asmjit-testing/tests/asmjit_test_compiler.h
Normal file
@@ -0,0 +1,83 @@
|
||||
// This file is part of AsmJit project <https://asmjit.com>
|
||||
//
|
||||
// See <asmjit/core.h> or LICENSE.md for license and copyright information
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef ASMJIT_TEST_COMPILER_H_INCLUDED
|
||||
#define ASMJIT_TEST_COMPILER_H_INCLUDED
|
||||
|
||||
#include <asmjit/core.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
class SimpleErrorHandler : public asmjit::ErrorHandler {
|
||||
public:
|
||||
SimpleErrorHandler()
|
||||
: _err(asmjit::Error::kOk) {}
|
||||
|
||||
void handle_error(asmjit::Error err, const char* message, asmjit::BaseEmitter* origin) override {
|
||||
asmjit::Support::maybe_unused(origin);
|
||||
_err = err;
|
||||
_message.assign(message);
|
||||
}
|
||||
|
||||
asmjit::Error _err;
|
||||
asmjit::String _message;
|
||||
};
|
||||
|
||||
//! A test case interface for testing AsmJit's Compiler.
|
||||
class TestCase {
|
||||
public:
|
||||
TestCase(const char* name, asmjit::Arch arch) {
|
||||
if (name)
|
||||
_name.assign(name);
|
||||
_arch = arch;
|
||||
}
|
||||
|
||||
virtual ~TestCase() {}
|
||||
|
||||
inline const char* name() const { return _name.data(); }
|
||||
inline asmjit::Arch arch() const { return _arch; }
|
||||
|
||||
virtual void compile(asmjit::BaseCompiler& cc) = 0;
|
||||
virtual bool run(void* func, asmjit::String& result, asmjit::String& expect) = 0;
|
||||
|
||||
asmjit::String _name;
|
||||
asmjit::Arch _arch;
|
||||
};
|
||||
|
||||
class TestApp {
|
||||
public:
|
||||
std::vector<std::unique_ptr<TestCase>> _tests;
|
||||
|
||||
const char* _arch = nullptr;
|
||||
const char* _filter = nullptr;
|
||||
bool _help_only = false;
|
||||
bool _verbose = false;
|
||||
bool _dump_asm = false;
|
||||
bool _dump_hex = false;
|
||||
|
||||
unsigned _num_tests = 0;
|
||||
unsigned _num_failed = 0;
|
||||
size_t _output_size = 0;
|
||||
|
||||
TestApp() noexcept
|
||||
: _arch("all") {}
|
||||
~TestApp() noexcept {}
|
||||
|
||||
void add(TestCase* test) noexcept {
|
||||
_tests.push_back(std::unique_ptr<TestCase>(test));
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline void add_t() { T::add(*this); }
|
||||
|
||||
int handle_args(int argc, const char* const* argv);
|
||||
void show_info();
|
||||
|
||||
bool should_run(const TestCase* tc);
|
||||
int run();
|
||||
};
|
||||
|
||||
#endif // ASMJIT_TEST_COMPILER_H_INCLUDED
|
||||
688
asmjit-testing/tests/asmjit_test_compiler_a64.cpp
Normal file
688
asmjit-testing/tests/asmjit_test_compiler_a64.cpp
Normal file
@@ -0,0 +1,688 @@
|
||||
// This file is part of AsmJit project <https://asmjit.com>
|
||||
//
|
||||
// See <asmjit/core.h> or LICENSE.md for license and copyright information
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#include <asmjit/core.h>
|
||||
#if !defined(ASMJIT_NO_COMPILER) && !defined(ASMJIT_NO_AARCH64)
|
||||
|
||||
#include <asmjit/a64.h>
|
||||
|
||||
#include <asmjit-testing/tests/asmjit_test_compiler.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
using namespace asmjit;
|
||||
|
||||
// a64::Compiler - A64TestCase
|
||||
// ===========================
|
||||
|
||||
class A64TestCase : public TestCase {
|
||||
public:
|
||||
A64TestCase(const char* name = nullptr)
|
||||
: TestCase(name, Arch::kAArch64) {}
|
||||
|
||||
void compile(BaseCompiler& cc) override {
|
||||
compile(static_cast<a64::Compiler&>(cc));
|
||||
}
|
||||
|
||||
virtual void compile(a64::Compiler& cc) = 0;
|
||||
};
|
||||
|
||||
// a64::Compiler - A64Test_GpArgs
|
||||
// ==============================
|
||||
|
||||
class A64Test_GpArgs : public A64TestCase {
|
||||
public:
|
||||
uint32_t _arg_count;
|
||||
bool _preserve_fp;
|
||||
|
||||
A64Test_GpArgs(uint32_t arg_count, bool preserve_fp)
|
||||
: _arg_count(arg_count),
|
||||
_preserve_fp(preserve_fp) {
|
||||
_name.assign_format("GpArgs {NumArgs=%u PreserveFP=%c}", arg_count, preserve_fp ? 'Y' : 'N');
|
||||
}
|
||||
|
||||
static void add(TestApp& app) {
|
||||
for (uint32_t i = 0; i <= 16; i++) {
|
||||
app.add(new A64Test_GpArgs(i, true));
|
||||
app.add(new A64Test_GpArgs(i, false));
|
||||
}
|
||||
}
|
||||
|
||||
void compile(a64::Compiler& cc) override {
|
||||
uint32_t arg_count = _arg_count;
|
||||
FuncSignature signature;
|
||||
|
||||
signature.set_ret_t<int>();
|
||||
for (uint32_t i = 0; i < arg_count; i++) {
|
||||
signature.add_arg_t<int>();
|
||||
}
|
||||
|
||||
FuncNode* func_node = cc.add_func(signature);
|
||||
if (_preserve_fp)
|
||||
func_node->frame().set_preserved_fp();
|
||||
|
||||
a64::Gp sum;
|
||||
|
||||
if (arg_count) {
|
||||
for (uint32_t i = 0; i < arg_count; i++) {
|
||||
a64::Gp i_reg = cc.new_gp32("i%u", i);
|
||||
func_node->set_arg(i, i_reg);
|
||||
|
||||
if (i == 0)
|
||||
sum = i_reg;
|
||||
else
|
||||
cc.add(sum, sum, i_reg);
|
||||
}
|
||||
}
|
||||
else {
|
||||
sum = cc.new_gp32("i");
|
||||
cc.mov(sum, 0);
|
||||
}
|
||||
|
||||
cc.ret(sum);
|
||||
cc.end_func();
|
||||
}
|
||||
|
||||
bool run(void* _func, String& result, String& expect) override {
|
||||
using U = unsigned int;
|
||||
|
||||
using Func0 = U (*)();
|
||||
using Func1 = U (*)(U);
|
||||
using Func2 = U (*)(U, U);
|
||||
using Func3 = U (*)(U, U, U);
|
||||
using Func4 = U (*)(U, U, U, U);
|
||||
using Func5 = U (*)(U, U, U, U, U);
|
||||
using Func6 = U (*)(U, U, U, U, U, U);
|
||||
using Func7 = U (*)(U, U, U, U, U, U, U);
|
||||
using Func8 = U (*)(U, U, U, U, U, U, U, U);
|
||||
using Func9 = U (*)(U, U, U, U, U, U, U, U, U);
|
||||
using Func10 = U (*)(U, U, U, U, U, U, U, U, U, U);
|
||||
using Func11 = U (*)(U, U, U, U, U, U, U, U, U, U, U);
|
||||
using Func12 = U (*)(U, U, U, U, U, U, U, U, U, U, U, U);
|
||||
using Func13 = U (*)(U, U, U, U, U, U, U, U, U, U, U, U, U);
|
||||
using Func14 = U (*)(U, U, U, U, U, U, U, U, U, U, U, U, U, U);
|
||||
using Func15 = U (*)(U, U, U, U, U, U, U, U, U, U, U, U, U, U, U);
|
||||
using Func16 = U (*)(U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U);
|
||||
|
||||
unsigned int result_ret = 0;
|
||||
unsigned int expect_ret = 0;
|
||||
|
||||
switch (_arg_count) {
|
||||
case 0:
|
||||
result_ret = ptr_as_func<Func0>(_func)();
|
||||
expect_ret = 0;
|
||||
break;
|
||||
case 1:
|
||||
result_ret = ptr_as_func<Func1>(_func)(1);
|
||||
expect_ret = 1;
|
||||
break;
|
||||
case 2:
|
||||
result_ret = ptr_as_func<Func2>(_func)(1, 2);
|
||||
expect_ret = 1 + 2;
|
||||
break;
|
||||
case 3:
|
||||
result_ret = ptr_as_func<Func3>(_func)(1, 2, 3);
|
||||
expect_ret = 1 + 2 + 3;
|
||||
break;
|
||||
case 4:
|
||||
result_ret = ptr_as_func<Func4>(_func)(1, 2, 3, 4);
|
||||
expect_ret = 1 + 2 + 3 + 4;
|
||||
break;
|
||||
case 5:
|
||||
result_ret = ptr_as_func<Func5>(_func)(1, 2, 3, 4, 5);
|
||||
expect_ret = 1 + 2 + 3 + 4 + 5;
|
||||
break;
|
||||
case 6:
|
||||
result_ret = ptr_as_func<Func6>(_func)(1, 2, 3, 4, 5, 6);
|
||||
expect_ret = 1 + 2 + 3 + 4 + 5 + 6;
|
||||
break;
|
||||
case 7:
|
||||
result_ret = ptr_as_func<Func7>(_func)(1, 2, 3, 4, 5, 6, 7);
|
||||
expect_ret = 1 + 2 + 3 + 4 + 5 + 6 + 7;
|
||||
break;
|
||||
case 8:
|
||||
result_ret = ptr_as_func<Func8>(_func)(1, 2, 3, 4, 5, 6, 7, 8);
|
||||
expect_ret = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8;
|
||||
break;
|
||||
case 9:
|
||||
result_ret = ptr_as_func<Func9>(_func)(1, 2, 3, 4, 5, 6, 7, 8, 9);
|
||||
expect_ret = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9;
|
||||
break;
|
||||
case 10:
|
||||
result_ret = ptr_as_func<Func10>(_func)(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
|
||||
expect_ret = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10;
|
||||
break;
|
||||
case 11:
|
||||
result_ret = ptr_as_func<Func11>(_func)(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
|
||||
expect_ret = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11;
|
||||
break;
|
||||
case 12:
|
||||
result_ret = ptr_as_func<Func12>(_func)(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
|
||||
expect_ret = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12;
|
||||
break;
|
||||
case 13:
|
||||
result_ret = ptr_as_func<Func13>(_func)(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13);
|
||||
expect_ret = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13;
|
||||
break;
|
||||
case 14:
|
||||
result_ret = ptr_as_func<Func14>(_func)(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14);
|
||||
expect_ret = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14;
|
||||
break;
|
||||
case 15:
|
||||
result_ret = ptr_as_func<Func15>(_func)(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
|
||||
expect_ret = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15;
|
||||
break;
|
||||
case 16:
|
||||
result_ret = ptr_as_func<Func16>(_func)(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
|
||||
expect_ret = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16;
|
||||
break;
|
||||
}
|
||||
|
||||
result.assign_format("ret={%u, %u}", result_ret >> 28, result_ret & 0x0FFFFFFFu);
|
||||
expect.assign_format("ret={%u, %u}", expect_ret >> 28, expect_ret & 0x0FFFFFFFu);
|
||||
|
||||
return result == expect;
|
||||
}
|
||||
};
|
||||
|
||||
// a64::Compiler - A64Test_Simd1
|
||||
// =============================
|
||||
|
||||
class A64Test_Simd1 : public A64TestCase {
|
||||
public:
|
||||
A64Test_Simd1()
|
||||
: A64TestCase("Simd1") {}
|
||||
|
||||
static void add(TestApp& app) {
|
||||
app.add(new A64Test_Simd1());
|
||||
}
|
||||
|
||||
void compile(a64::Compiler& cc) override {
|
||||
FuncNode* func_node = cc.add_func(FuncSignature::build<void, void*, const void*, const void*>());
|
||||
|
||||
a64::Gp dst = cc.new_gp_ptr("dst");
|
||||
a64::Gp src1 = cc.new_gp_ptr("src1");
|
||||
a64::Gp src2 = cc.new_gp_ptr("src2");
|
||||
|
||||
func_node->set_arg(0, dst);
|
||||
func_node->set_arg(1, src1);
|
||||
func_node->set_arg(2, src2);
|
||||
|
||||
a64::Vec v1 = cc.new_vec_q("vec1");
|
||||
a64::Vec v2 = cc.new_vec_q("vec2");
|
||||
a64::Vec v3 = cc.new_vec_q("vec3");
|
||||
|
||||
cc.ldr(v2, a64::ptr(src1));
|
||||
cc.ldr(v3, a64::ptr(src2));
|
||||
cc.add(v1.b16(), v2.b16(), v3.b16());
|
||||
cc.str(v1, a64::ptr(dst));
|
||||
|
||||
cc.end_func();
|
||||
}
|
||||
|
||||
bool run(void* _func, String& result, String& expect) override {
|
||||
using Func = void (*)(void*, const void*, const void*);
|
||||
|
||||
uint32_t dst[4];
|
||||
uint32_t a_src[4] = { 0 , 1 , 2 , 255 };
|
||||
uint32_t b_src[4] = { 99, 17, 33, 1 };
|
||||
|
||||
// NOTE: It's a byte-add, so uint8_t(255+1) == 0.
|
||||
uint32_t ref[4] = { 99, 18, 35, 0 };
|
||||
|
||||
ptr_as_func<Func>(_func)(dst, a_src, b_src);
|
||||
|
||||
result.assign_format("ret={%u, %u, %u, %u}", dst[0], dst[1], dst[2], dst[3]);
|
||||
expect.assign_format("ret={%u, %u, %u, %u}", ref[0], ref[1], ref[2], ref[3]);
|
||||
|
||||
return result == expect;
|
||||
}
|
||||
};
|
||||
|
||||
// a64::Compiler - A64Test_ManyRegs
|
||||
// ================================
|
||||
|
||||
class A64Test_ManyRegs : public A64TestCase {
|
||||
public:
|
||||
uint32_t _reg_count;
|
||||
|
||||
A64Test_ManyRegs(uint32_t n)
|
||||
: A64TestCase(),
|
||||
_reg_count(n) {
|
||||
_name.assign_format("GpRegs {NumRegs=%u}", n);
|
||||
}
|
||||
|
||||
static void add(TestApp& app) {
|
||||
for (uint32_t i = 2; i < 64; i++)
|
||||
app.add(new A64Test_ManyRegs(i));
|
||||
}
|
||||
|
||||
void compile(a64::Compiler& cc) override {
|
||||
cc.add_func(FuncSignature::build<int>());
|
||||
|
||||
a64::Gp* regs = static_cast<a64::Gp*>(malloc(_reg_count * sizeof(a64::Gp)));
|
||||
|
||||
for (uint32_t i = 0; i < _reg_count; i++) {
|
||||
regs[i] = cc.new_gp32("reg%u", i);
|
||||
cc.mov(regs[i], i + 1);
|
||||
}
|
||||
|
||||
a64::Gp sum = cc.new_gp32("sum");
|
||||
cc.mov(sum, 0);
|
||||
|
||||
for (uint32_t i = 0; i < _reg_count; i++) {
|
||||
cc.add(sum, sum, regs[i]);
|
||||
}
|
||||
|
||||
cc.ret(sum);
|
||||
cc.end_func();
|
||||
|
||||
free(regs);
|
||||
}
|
||||
|
||||
bool run(void* _func, String& result, String& expect) override {
|
||||
using Func = int (*)(void);
|
||||
Func func = ptr_as_func<Func>(_func);
|
||||
|
||||
result.assign_format("ret={%d}", func());
|
||||
expect.assign_format("ret={%d}", calc_sum());
|
||||
|
||||
return result == expect;
|
||||
}
|
||||
|
||||
uint32_t calc_sum() const {
|
||||
return (_reg_count | 1) * ((_reg_count + 1) / 2);
|
||||
}
|
||||
};
|
||||
|
||||
// a64::Compiler - A64Test_Adr
|
||||
// ===========================
|
||||
|
||||
class A64Test_Adr : public A64TestCase {
|
||||
public:
|
||||
A64Test_Adr()
|
||||
: A64TestCase("Adr") {}
|
||||
|
||||
static void add(TestApp& app) {
|
||||
app.add(new A64Test_Adr());
|
||||
}
|
||||
|
||||
void compile(a64::Compiler& cc) override {
|
||||
cc.add_func(FuncSignature::build<int>());
|
||||
|
||||
a64::Gp addr = cc.new_gp_ptr("addr");
|
||||
a64::Gp val = cc.new_gp_ptr("val");
|
||||
|
||||
Label L_Table = cc.new_label();
|
||||
|
||||
cc.adr(addr, L_Table);
|
||||
cc.ldrsw(val, a64::ptr(addr, 8));
|
||||
cc.ret(val);
|
||||
cc.end_func();
|
||||
|
||||
cc.bind(L_Table);
|
||||
cc.embed_int32(1);
|
||||
cc.embed_int32(2);
|
||||
cc.embed_int32(3);
|
||||
cc.embed_int32(4);
|
||||
cc.embed_int32(5);
|
||||
}
|
||||
|
||||
bool run(void* _func, String& result, String& expect) override {
|
||||
using Func = int (*)(void);
|
||||
Func func = ptr_as_func<Func>(_func);
|
||||
|
||||
result.assign_format("ret={%d}", func());
|
||||
expect.assign_format("ret={%d}", 3);
|
||||
|
||||
return result == expect;
|
||||
}
|
||||
};
|
||||
|
||||
// a64::Compiler - A64Test_Branch1
|
||||
// ===============================
|
||||
|
||||
class A64Test_Branch1 : public A64TestCase {
|
||||
public:
|
||||
A64Test_Branch1()
|
||||
: A64TestCase("Branch1") {}
|
||||
|
||||
static void add(TestApp& app) {
|
||||
app.add(new A64Test_Branch1());
|
||||
}
|
||||
|
||||
void compile(a64::Compiler& cc) override {
|
||||
FuncNode* func_node = cc.add_func(FuncSignature::build<void, void*, size_t>());
|
||||
|
||||
a64::Gp p = cc.new_gp_ptr("p");
|
||||
a64::Gp count = cc.new_gp_ptr("count");
|
||||
a64::Gp i = cc.new_gp_ptr("i");
|
||||
Label L = cc.new_label();
|
||||
|
||||
func_node->set_arg(0, p);
|
||||
func_node->set_arg(1, count);
|
||||
|
||||
cc.mov(i, 0);
|
||||
|
||||
cc.bind(L);
|
||||
cc.strb(i.w(), a64::ptr(p, i));
|
||||
cc.add(i, i, 1);
|
||||
cc.cmp(i, count);
|
||||
cc.b_ne(L);
|
||||
|
||||
cc.end_func();
|
||||
}
|
||||
|
||||
bool run(void* _func, String& result, String& expect) override {
|
||||
using Func = void (*)(void* p, size_t n);
|
||||
Func func = ptr_as_func<Func>(_func);
|
||||
|
||||
uint8_t array[16];
|
||||
func(array, 16);
|
||||
|
||||
expect.assign("ret={0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}");
|
||||
|
||||
result.assign("ret={");
|
||||
for (size_t i = 0; i < 16; i++) {
|
||||
if (i)
|
||||
result.append(", ");
|
||||
result.append_format("%d", int(array[i]));
|
||||
}
|
||||
result.append("}");
|
||||
|
||||
return result == expect;
|
||||
}
|
||||
};
|
||||
|
||||
// a64::Compiler - A64Test_Invoke1
|
||||
// ===============================
|
||||
|
||||
class A64Test_Invoke1 : public A64TestCase {
|
||||
public:
|
||||
A64Test_Invoke1()
|
||||
: A64TestCase("Invoke1") {}
|
||||
|
||||
static void add(TestApp& app) {
|
||||
app.add(new A64Test_Invoke1());
|
||||
}
|
||||
|
||||
void compile(a64::Compiler& cc) override {
|
||||
FuncNode* func_node = cc.add_func(FuncSignature::build<uint32_t, uint32_t, uint32_t>());
|
||||
|
||||
a64::Gp x = cc.new_gp32("x");
|
||||
a64::Gp y = cc.new_gp32("y");
|
||||
a64::Gp r = cc.new_gp32("r");
|
||||
a64::Gp fn = cc.new_gp_ptr("fn");
|
||||
|
||||
func_node->set_arg(0, x);
|
||||
func_node->set_arg(1, y);
|
||||
|
||||
cc.mov(fn, (uint64_t)called_fn);
|
||||
|
||||
InvokeNode* invoke_node;
|
||||
cc.invoke(Out(invoke_node), fn, FuncSignature::build<uint32_t, uint32_t, uint32_t>());
|
||||
invoke_node->set_arg(0, x);
|
||||
invoke_node->set_arg(1, y);
|
||||
invoke_node->set_ret(0, r);
|
||||
|
||||
cc.ret(r);
|
||||
cc.end_func();
|
||||
}
|
||||
|
||||
bool run(void* _func, String& result, String& expect) override {
|
||||
using Func = uint32_t (*)(uint32_t, uint32_t);
|
||||
Func func = ptr_as_func<Func>(_func);
|
||||
|
||||
uint32_t x = 49;
|
||||
uint32_t y = 7;
|
||||
|
||||
result.assign_format("ret={%u}", func(x, y));
|
||||
expect.assign_format("ret={%u}", x - y);
|
||||
|
||||
return result == expect;
|
||||
}
|
||||
|
||||
static uint32_t called_fn(uint32_t x, uint32_t y) {
|
||||
return x - y;
|
||||
}
|
||||
};
|
||||
|
||||
// a64::Compiler - A64Test_Invoke2
|
||||
// ===============================
|
||||
|
||||
class A64Test_Invoke2 : public A64TestCase {
|
||||
public:
|
||||
A64Test_Invoke2()
|
||||
: A64TestCase("Invoke2") {}
|
||||
|
||||
static void add(TestApp& app) {
|
||||
app.add(new A64Test_Invoke2());
|
||||
}
|
||||
|
||||
void compile(a64::Compiler& cc) override {
|
||||
FuncNode* func_node = cc.add_func(FuncSignature::build<double, double, double>());
|
||||
|
||||
a64::Vec x = cc.new_vec_d("x");
|
||||
a64::Vec y = cc.new_vec_d("y");
|
||||
a64::Vec r = cc.new_vec_d("r");
|
||||
a64::Gp fn = cc.new_gp_ptr("fn");
|
||||
|
||||
func_node->set_arg(0, x);
|
||||
func_node->set_arg(1, y);
|
||||
cc.mov(fn, (uint64_t)called_fn);
|
||||
|
||||
InvokeNode* invoke_node;
|
||||
cc.invoke(Out(invoke_node), fn, FuncSignature::build<double, double, double>());
|
||||
invoke_node->set_arg(0, x);
|
||||
invoke_node->set_arg(1, y);
|
||||
invoke_node->set_ret(0, r);
|
||||
|
||||
cc.ret(r);
|
||||
cc.end_func();
|
||||
}
|
||||
|
||||
bool run(void* _func, String& result, String& expect) override {
|
||||
using Func = double (*)(double, double);
|
||||
Func func = ptr_as_func<Func>(_func);
|
||||
|
||||
double x = 49;
|
||||
double y = 7;
|
||||
|
||||
result.assign_format("ret={%f}", func(x, y));
|
||||
expect.assign_format("ret={%f}", called_fn(x, y));
|
||||
|
||||
return result == expect;
|
||||
}
|
||||
|
||||
static double called_fn(double x, double y) {
|
||||
return x - y;
|
||||
}
|
||||
};
|
||||
|
||||
// a64::Compiler - A64Test_Invoke3
|
||||
// ===============================
|
||||
|
||||
class A64Test_Invoke3 : public A64TestCase {
|
||||
public:
|
||||
A64Test_Invoke3()
|
||||
: A64TestCase("Invoke3") {}
|
||||
|
||||
static void add(TestApp& app) {
|
||||
app.add(new A64Test_Invoke3());
|
||||
}
|
||||
|
||||
void compile(a64::Compiler& cc) override {
|
||||
FuncNode* func_node = cc.add_func(FuncSignature::build<double, double, double>());
|
||||
|
||||
a64::Vec x = cc.new_vec_d("x");
|
||||
a64::Vec y = cc.new_vec_d("y");
|
||||
a64::Vec r = cc.new_vec_d("r");
|
||||
a64::Gp fn = cc.new_gp_ptr("fn");
|
||||
|
||||
func_node->set_arg(0, x);
|
||||
func_node->set_arg(1, y);
|
||||
cc.mov(fn, (uint64_t)called_fn);
|
||||
|
||||
InvokeNode* invoke_node;
|
||||
cc.invoke(Out(invoke_node), fn, FuncSignature::build<double, double, double>());
|
||||
invoke_node->set_arg(0, y);
|
||||
invoke_node->set_arg(1, x);
|
||||
invoke_node->set_ret(0, r);
|
||||
|
||||
cc.ret(r);
|
||||
cc.end_func();
|
||||
}
|
||||
|
||||
bool run(void* _func, String& result, String& expect) override {
|
||||
using Func = double (*)(double, double);
|
||||
Func func = ptr_as_func<Func>(_func);
|
||||
|
||||
double x = 49;
|
||||
double y = 7;
|
||||
|
||||
result.assign_format("ret={%f}", func(x, y));
|
||||
expect.assign_format("ret={%f}", called_fn(y, x));
|
||||
|
||||
return result == expect;
|
||||
}
|
||||
|
||||
static double called_fn(double x, double y) {
|
||||
return x - y;
|
||||
}
|
||||
};
|
||||
|
||||
// a64::Compiler - A64Test_JumpTable
|
||||
// =================================
|
||||
|
||||
class A64Test_JumpTable : public A64TestCase {
|
||||
public:
|
||||
bool _annotated;
|
||||
|
||||
A64Test_JumpTable(bool annotated)
|
||||
: A64TestCase("A64Test_JumpTable"),
|
||||
_annotated(annotated) {
|
||||
_name.assign_format("JumpTable {%s}", annotated ? "Annotated" : "Unknown Target");
|
||||
}
|
||||
|
||||
enum Operator {
|
||||
kOperatorAdd = 0,
|
||||
kOperatorSub = 1,
|
||||
kOperatorMul = 2,
|
||||
kOperatorDiv = 3
|
||||
};
|
||||
|
||||
static void add(TestApp& app) {
|
||||
app.add(new A64Test_JumpTable(false));
|
||||
app.add(new A64Test_JumpTable(true));
|
||||
}
|
||||
|
||||
void compile(a64::Compiler& cc) override {
|
||||
FuncNode* func_node = cc.add_func(FuncSignature::build<float, float, float, uint32_t>());
|
||||
|
||||
a64::Vec a = cc.new_vec_s("a");
|
||||
a64::Vec b = cc.new_vec_s("b");
|
||||
a64::Gp op = cc.new_gp32("op");
|
||||
|
||||
a64::Gp target = cc.new_gp_ptr("target");
|
||||
a64::Gp offset = cc.new_gp_ptr("offset");
|
||||
|
||||
Label L_End = cc.new_label();
|
||||
|
||||
Label L_Table = cc.new_label();
|
||||
Label L_Add = cc.new_label();
|
||||
Label L_Sub = cc.new_label();
|
||||
Label L_Mul = cc.new_label();
|
||||
Label L_Div = cc.new_label();
|
||||
|
||||
func_node->set_arg(0, a);
|
||||
func_node->set_arg(1, b);
|
||||
func_node->set_arg(2, op);
|
||||
|
||||
cc.adr(target, L_Table);
|
||||
cc.ldrsw(offset, a64::ptr(target, op, a64::sxtw(2)));
|
||||
cc.add(target, target, offset);
|
||||
|
||||
// JumpAnnotation allows to annotate all possible jump targets of
|
||||
// instructions where it cannot be deduced from operands.
|
||||
if (_annotated) {
|
||||
JumpAnnotation* annotation = cc.new_jump_annotation();
|
||||
annotation->add_label(L_Add);
|
||||
annotation->add_label(L_Sub);
|
||||
annotation->add_label(L_Mul);
|
||||
annotation->add_label(L_Div);
|
||||
cc.br(target, annotation);
|
||||
}
|
||||
else {
|
||||
cc.br(target);
|
||||
}
|
||||
|
||||
cc.bind(L_Add);
|
||||
cc.fadd(a, a, b);
|
||||
cc.b(L_End);
|
||||
|
||||
cc.bind(L_Sub);
|
||||
cc.fsub(a, a, b);
|
||||
cc.b(L_End);
|
||||
|
||||
cc.bind(L_Mul);
|
||||
cc.fmul(a, a, b);
|
||||
cc.b(L_End);
|
||||
|
||||
cc.bind(L_Div);
|
||||
cc.fdiv(a, a, b);
|
||||
|
||||
cc.bind(L_End);
|
||||
cc.ret(a);
|
||||
cc.end_func();
|
||||
|
||||
cc.bind(L_Table);
|
||||
cc.embed_label_delta(L_Add, L_Table, 4);
|
||||
cc.embed_label_delta(L_Sub, L_Table, 4);
|
||||
cc.embed_label_delta(L_Mul, L_Table, 4);
|
||||
cc.embed_label_delta(L_Div, L_Table, 4);
|
||||
}
|
||||
|
||||
bool run(void* _func, String& result, String& expect) override {
|
||||
using Func = float (*)(float, float, uint32_t);
|
||||
Func func = ptr_as_func<Func>(_func);
|
||||
|
||||
float dst[4];
|
||||
float ref[4];
|
||||
|
||||
dst[0] = func(33.0f, 14.0f, kOperatorAdd);
|
||||
dst[1] = func(33.0f, 14.0f, kOperatorSub);
|
||||
dst[2] = func(10.0f, 6.0f, kOperatorMul);
|
||||
dst[3] = func(80.0f, 8.0f, kOperatorDiv);
|
||||
|
||||
ref[0] = 47.0f;
|
||||
ref[1] = 19.0f;
|
||||
ref[2] = 60.0f;
|
||||
ref[3] = 10.0f;
|
||||
|
||||
result.assign_format("ret={%f, %f, %f, %f}", double(dst[0]), double(dst[1]), double(dst[2]), double(dst[3]));
|
||||
expect.assign_format("ret={%f, %f, %f, %f}", double(ref[0]), double(ref[1]), double(ref[2]), double(ref[3]));
|
||||
|
||||
return result == expect;
|
||||
}
|
||||
};
|
||||
|
||||
// a64::Compiler - Export
|
||||
// ======================
|
||||
|
||||
void compiler_add_a64_tests(TestApp& app) {
|
||||
app.add_t<A64Test_GpArgs>();
|
||||
app.add_t<A64Test_ManyRegs>();
|
||||
app.add_t<A64Test_Simd1>();
|
||||
app.add_t<A64Test_Adr>();
|
||||
app.add_t<A64Test_Branch1>();
|
||||
app.add_t<A64Test_Invoke1>();
|
||||
app.add_t<A64Test_Invoke2>();
|
||||
app.add_t<A64Test_Invoke3>();
|
||||
app.add_t<A64Test_JumpTable>();
|
||||
}
|
||||
|
||||
#endif // !ASMJIT_NO_COMPILER && !ASMJIT_NO_AARCH64
|
||||
4694
asmjit-testing/tests/asmjit_test_compiler_x86.cpp
Normal file
4694
asmjit-testing/tests/asmjit_test_compiler_x86.cpp
Normal file
File diff suppressed because it is too large
Load Diff
328
asmjit-testing/tests/asmjit_test_emitters.cpp
Normal file
328
asmjit-testing/tests/asmjit_test_emitters.cpp
Normal file
@@ -0,0 +1,328 @@
|
||||
// This file is part of AsmJit project <https://asmjit.com>
|
||||
//
|
||||
// See <asmjit/core.h> or LICENSE.md for license and copyright information
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <asmjit/core.h>
|
||||
|
||||
#if ASMJIT_ARCH_X86 != 0
|
||||
#include <asmjit/x86.h>
|
||||
#endif
|
||||
|
||||
#if ASMJIT_ARCH_ARM == 64
|
||||
#include <asmjit/a64.h>
|
||||
#endif
|
||||
|
||||
#include <asmjit-testing/commons/asmjitutils.h>
|
||||
|
||||
using namespace asmjit;
|
||||
|
||||
static void print_app_info() noexcept {
|
||||
printf("AsmJit Emitters Test-Suite v%u.%u.%u [Arch=%s] [Mode=%s]\n\n",
|
||||
unsigned((ASMJIT_LIBRARY_VERSION >> 16) ),
|
||||
unsigned((ASMJIT_LIBRARY_VERSION >> 8) & 0xFF),
|
||||
unsigned((ASMJIT_LIBRARY_VERSION ) & 0xFF),
|
||||
asmjit_arch_as_string(Arch::kHost),
|
||||
asmjit_build_type()
|
||||
);
|
||||
}
|
||||
|
||||
#if !defined(ASMJIT_NO_JIT) && ((ASMJIT_ARCH_X86 != 0 && !defined(ASMJIT_NO_X86 )) || \
|
||||
(ASMJIT_ARCH_ARM == 64 && !defined(ASMJIT_NO_AARCH64)) )
|
||||
|
||||
// Signature of the generated function.
|
||||
using SumIntsFunc = void (*)(int* dst, const int* a, const int* b);
|
||||
|
||||
// X86 Backend
|
||||
// -----------
|
||||
|
||||
#if ASMJIT_ARCH_X86 != 0
|
||||
// This function works with both x86::Assembler and x86::Builder. It shows how
|
||||
// `x86::Emitter` can be used to make your code more generic.
|
||||
static void generate_func_with_emitter(x86::Emitter* emitter) noexcept {
|
||||
// Decide which registers will be mapped to function arguments. Try changing
|
||||
// registers of `dst`, `src_a`, and `src_b` and see what happens in function's
|
||||
// prolog and epilog.
|
||||
x86::Gp dst = emitter->zax();
|
||||
x86::Gp src_a = emitter->zcx();
|
||||
x86::Gp src_b = emitter->zdx();
|
||||
|
||||
// Decide which vector registers to use. We use these to keep the code generic,
|
||||
// you can switch to any other registers when needed.
|
||||
x86::Vec vec0 = x86::xmm0;
|
||||
x86::Vec vec1 = x86::xmm1;
|
||||
|
||||
// Create and initialize `FuncDetail` and `FuncFrame`.
|
||||
FuncDetail func;
|
||||
func.init(FuncSignature::build<void, int*, const int*, const int*>(), emitter->environment());
|
||||
|
||||
FuncFrame frame;
|
||||
frame.init(func);
|
||||
|
||||
// Make or registers dirty.
|
||||
frame.add_dirty_regs(vec0, vec1);
|
||||
|
||||
FuncArgsAssignment args(&func); // Create arguments assignment context.
|
||||
args.assign_all(dst, src_a, src_b); // Assign our registers to arguments.
|
||||
args.update_func_frame(frame); // Reflect our args in FuncFrame.
|
||||
frame.finalize();
|
||||
|
||||
// Emit prolog and allocate arguments to registers.
|
||||
emitter->emit_prolog(frame);
|
||||
emitter->emit_args_assignment(frame, args);
|
||||
|
||||
emitter->movdqu(vec0, x86::ptr(src_a)); // Load 4 ints from [src_a] to XMM0.
|
||||
emitter->movdqu(vec1, x86::ptr(src_b)); // Load 4 ints from [src_b] to XMM1.
|
||||
|
||||
emitter->paddd(vec0, vec1); // Add 4 ints in XMM1 to XMM0.
|
||||
emitter->movdqu(x86::ptr(dst), vec0); // Store the result to [dst].
|
||||
|
||||
// Emit epilog and return.
|
||||
emitter->emit_epilog(frame);
|
||||
}
|
||||
|
||||
#ifndef ASMJIT_NO_COMPILER
|
||||
// This function works with x86::Compiler, provided for comparison.
|
||||
static void generate_func_with_compiler(x86::Compiler* cc) noexcept {
|
||||
x86::Gp dst = cc->new_gp_ptr("dst");
|
||||
x86::Gp src_a = cc->new_gp_ptr("src_a");
|
||||
x86::Gp src_b = cc->new_gp_ptr("src_b");
|
||||
x86::Vec vec0 = cc->new_xmm("vec0");
|
||||
x86::Vec vec1 = cc->new_xmm("vec1");
|
||||
|
||||
FuncNode* func_node = cc->add_func(FuncSignature::build<void, int*, const int*, const int*>());
|
||||
func_node->set_arg(0, dst);
|
||||
func_node->set_arg(1, src_a);
|
||||
func_node->set_arg(2, src_b);
|
||||
|
||||
cc->movdqu(vec0, x86::ptr(src_a));
|
||||
cc->movdqu(vec1, x86::ptr(src_b));
|
||||
cc->paddd(vec0, vec1);
|
||||
cc->movdqu(x86::ptr(dst), vec0);
|
||||
cc->end_func();
|
||||
}
|
||||
#endif
|
||||
|
||||
static Error generate_func(CodeHolder& code, EmitterType emitter_type) noexcept {
|
||||
switch (emitter_type) {
|
||||
case EmitterType::kAssembler: {
|
||||
printf("Using x86::Assembler:\n");
|
||||
x86::Assembler a(&code);
|
||||
generate_func_with_emitter(a.as<x86::Emitter>());
|
||||
return Error::kOk;
|
||||
}
|
||||
|
||||
#ifndef ASMJIT_NO_BUILDER
|
||||
case EmitterType::kBuilder: {
|
||||
printf("Using x86::Builder:\n");
|
||||
x86::Builder cb(&code);
|
||||
generate_func_with_emitter(cb.as<x86::Emitter>());
|
||||
|
||||
return cb.finalize();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef ASMJIT_NO_COMPILER
|
||||
case EmitterType::kCompiler: {
|
||||
printf("Using x86::Compiler:\n");
|
||||
x86::Compiler cc(&code);
|
||||
generate_func_with_compiler(&cc);
|
||||
|
||||
return cc.finalize();
|
||||
}
|
||||
#endif
|
||||
|
||||
default: {
|
||||
printf("** FAILURE: No emitter to use **\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// AArch64 Backend
|
||||
// ---------------
|
||||
|
||||
#if ASMJIT_ARCH_ARM == 64
|
||||
// This function works with both a64::Assembler and a64::Builder. It shows how
|
||||
// `a64::Emitter` can be used to make your code more generic.
|
||||
static void generate_func_with_emitter(a64::Emitter* emitter) noexcept {
|
||||
// Decide which registers will be mapped to function arguments. Try changing
|
||||
// registers of `dst`, `src_a`, and `src_b` and see what happens in function's
|
||||
// prolog and epilog.
|
||||
a64::Gp dst = a64::x0;
|
||||
a64::Gp src_a = a64::x1;
|
||||
a64::Gp src_b = a64::x2;
|
||||
|
||||
// Decide which vector registers to use. We use these to keep the code generic,
|
||||
// you can switch to any other registers when needed.
|
||||
a64::Vec vec0 = a64::v0;
|
||||
a64::Vec vec1 = a64::v1;
|
||||
a64::Vec vec2 = a64::v2;
|
||||
|
||||
// Create and initialize `FuncDetail` and `FuncFrame`.
|
||||
FuncDetail func;
|
||||
func.init(FuncSignature::build<void, int*, const int*, const int*>(), emitter->environment());
|
||||
|
||||
FuncFrame frame;
|
||||
frame.init(func);
|
||||
|
||||
// Make XMM0 and XMM1 dirty. VEC group includes XMM|YMM|ZMM registers.
|
||||
frame.add_dirty_regs(vec0, vec1, vec2);
|
||||
|
||||
FuncArgsAssignment args(&func); // Create arguments assignment context.
|
||||
args.assign_all(dst, src_a, src_b); // Assign our registers to arguments.
|
||||
args.update_func_frame(frame); // Reflect our args in FuncFrame.
|
||||
frame.finalize();
|
||||
|
||||
// Emit prolog and allocate arguments to registers.
|
||||
emitter->emit_prolog(frame);
|
||||
emitter->emit_args_assignment(frame, args);
|
||||
|
||||
emitter->ld1(vec0.b16(), a64::ptr(src_a)); // Load 4 ints from [src_a] to vec0.
|
||||
emitter->ld1(vec1.b16(), a64::ptr(src_b)); // Load 4 ints from [src_b] to vec1.
|
||||
emitter->add(vec2.s4(), vec0.s4(), vec1.s4()); // Add 4 ints of vec0 and vec1 and store to vec2.
|
||||
emitter->st1(vec2.b16(), a64::ptr(dst)); // Store the result (vec2) to [dst].
|
||||
|
||||
// Emit epilog and return.
|
||||
emitter->emit_epilog(frame);
|
||||
}
|
||||
|
||||
#ifndef ASMJIT_NO_COMPILER
|
||||
// This function works with x86::Compiler, provided for comparison.
|
||||
static void generate_func_with_compiler(a64::Compiler* cc) noexcept {
|
||||
a64::Gp dst = cc->new_gp_ptr("dst");
|
||||
a64::Gp src_a = cc->new_gp_ptr("src_a");
|
||||
a64::Gp src_b = cc->new_gp_ptr("src_b");
|
||||
a64::Vec vec0 = cc->new_vec_q("vec0");
|
||||
a64::Vec vec1 = cc->new_vec_q("vec1");
|
||||
a64::Vec vec2 = cc->new_vec_q("vec2");
|
||||
|
||||
FuncNode* func_node = cc->add_func(FuncSignature::build<void, int*, const int*, const int*>());
|
||||
func_node->set_arg(0, dst);
|
||||
func_node->set_arg(1, src_a);
|
||||
func_node->set_arg(2, src_b);
|
||||
|
||||
cc->ld1(vec0.b16(), a64::ptr(src_a)); // Load 4 ints from [src_a] to vec0.
|
||||
cc->ld1(vec1.b16(), a64::ptr(src_b)); // Load 4 ints from [src_b] to vec1.
|
||||
cc->add(vec2.s4(), vec0.s4(), vec1.s4()); // Add 4 ints of vec0 and vec1 and store to vec2.
|
||||
cc->st1(vec2.b16(), a64::ptr(dst)); // Store the result (vec2) to [dst].
|
||||
cc->end_func();
|
||||
}
|
||||
#endif
|
||||
|
||||
static Error generate_func(CodeHolder& code, EmitterType emitter_type) noexcept {
|
||||
switch (emitter_type) {
|
||||
case EmitterType::kAssembler: {
|
||||
printf("Using a64::Assembler:\n");
|
||||
a64::Assembler a(&code);
|
||||
generate_func_with_emitter(a.as<a64::Emitter>());
|
||||
return Error::kOk;
|
||||
}
|
||||
|
||||
#ifndef ASMJIT_NO_BUILDER
|
||||
case EmitterType::kBuilder: {
|
||||
printf("Using a64::Builder:\n");
|
||||
a64::Builder cb(&code);
|
||||
generate_func_with_emitter(cb.as<a64::Emitter>());
|
||||
|
||||
return cb.finalize();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef ASMJIT_NO_COMPILER
|
||||
case EmitterType::kCompiler: {
|
||||
printf("Using a64::Compiler:\n");
|
||||
a64::Compiler cc(&code);
|
||||
generate_func_with_compiler(&cc);
|
||||
|
||||
return cc.finalize();
|
||||
}
|
||||
#endif
|
||||
|
||||
default: {
|
||||
printf("** FAILURE: No emitter to use **\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Testing
|
||||
// -------
|
||||
|
||||
static uint32_t test_func(JitRuntime& rt, EmitterType emitter_type) noexcept {
|
||||
#ifndef ASMJIT_NO_LOGGING
|
||||
FileLogger logger(stdout);
|
||||
logger.set_indentation(FormatIndentationGroup::kCode, 2);
|
||||
#endif
|
||||
|
||||
CodeHolder code;
|
||||
code.init(rt.environment(), rt.cpu_features());
|
||||
|
||||
#ifndef ASMJIT_NO_LOGGING
|
||||
code.set_logger(&logger);
|
||||
#endif
|
||||
|
||||
Error err = generate_func(code, emitter_type);
|
||||
if (err != Error::kOk) {
|
||||
printf("** FAILURE: Failed to generate a function: %s **\n", DebugUtils::error_as_string(err));
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Add the code generated to the runtime.
|
||||
SumIntsFunc fn;
|
||||
err = rt.add(&fn, &code);
|
||||
|
||||
if (err != Error::kOk) {
|
||||
printf("** FAILURE: JitRuntime::add() failed: %s **\n", DebugUtils::error_as_string(err));
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Execute the generated function.
|
||||
static const int in_a[4] = { 4, 3, 2, 1 };
|
||||
static const int in_b[4] = { 1, 5, 2, 8 };
|
||||
int out[4] {};
|
||||
fn(out, in_a, in_b);
|
||||
|
||||
// Should print {5 8 4 9}.
|
||||
printf("Result = { %d %d %d %d }\n\n", out[0], out[1], out[2], out[3]);
|
||||
|
||||
rt.release(fn);
|
||||
return out[0] == 5 && out[1] == 8 && out[2] == 4 && out[3] == 9;
|
||||
}
|
||||
|
||||
int main() {
|
||||
print_app_info();
|
||||
|
||||
JitRuntime rt;
|
||||
unsigned failed_count = 0;
|
||||
|
||||
failed_count += !test_func(rt, EmitterType::kAssembler);
|
||||
|
||||
#ifndef ASMJIT_NO_BUILDER
|
||||
failed_count += !test_func(rt, EmitterType::kBuilder);
|
||||
#endif
|
||||
|
||||
#ifndef ASMJIT_NO_COMPILER
|
||||
failed_count += !test_func(rt, EmitterType::kCompiler);
|
||||
#endif
|
||||
|
||||
if (!failed_count)
|
||||
printf("** SUCCESS **\n");
|
||||
else
|
||||
printf("** FAILURE - %u %s failed ** \n", failed_count, failed_count == 1 ? "test" : "tests");
|
||||
|
||||
return failed_count ? 1 : 0;
|
||||
}
|
||||
#else
|
||||
int main() {
|
||||
print_app_info();
|
||||
printf("!! This test is disabled: <ASMJIT_NO_JIT> or unsuitable target architecture !!\n");
|
||||
return 0;
|
||||
}
|
||||
#endif // ASMJIT_ARCH_X86 && !ASMJIT_NO_X86 && !ASMJIT_NO_JIT
|
||||
302
asmjit-testing/tests/asmjit_test_environment.cpp
Normal file
302
asmjit-testing/tests/asmjit_test_environment.cpp
Normal file
@@ -0,0 +1,302 @@
|
||||
// This file is part of AsmJit project <https://asmjit.com>
|
||||
//
|
||||
// See <asmjit/core.h> or LICENSE.md for license and copyright information
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#include <asmjit/core.h>
|
||||
|
||||
#if !defined(ASMJIT_NO_X86) && ASMJIT_ARCH_X86 != 0
|
||||
#include <asmjit/x86.h>
|
||||
#endif
|
||||
|
||||
#if !defined(ASMJIT_NO_AARCH64) && ASMJIT_ARCH_ARM == 64
|
||||
#include <asmjit/a64.h>
|
||||
#endif
|
||||
|
||||
#include <asmjit-testing/commons/asmjitutils.h>
|
||||
|
||||
using namespace asmjit;
|
||||
|
||||
static void print_app_info() {
|
||||
printf("AsmJit Environment Test v%u.%u.%u [Arch=%s] [Mode=%s]\n\n",
|
||||
unsigned((ASMJIT_LIBRARY_VERSION >> 16) ),
|
||||
unsigned((ASMJIT_LIBRARY_VERSION >> 8) & 0xFF),
|
||||
unsigned((ASMJIT_LIBRARY_VERSION ) & 0xFF),
|
||||
asmjit_arch_as_string(Arch::kHost),
|
||||
asmjit_build_type()
|
||||
);
|
||||
|
||||
printf("This application can be used to verify AsmJit build options and to verify the\n");
|
||||
printf("environment where it runs. For example to check CPU extensions available, system\n");
|
||||
printf("hardening (RWX restrictions), large page support, and virtual memory allocations.\n");
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
const char* stringify_bool(bool b) noexcept { return b ? "true" : "false"; };
|
||||
const char* stringify_result(Error err) noexcept { return err == Error::kOk ? "success" : DebugUtils::error_as_string(err); };
|
||||
|
||||
using VoidFunc = void (ASMJIT_CDECL*)(void);
|
||||
|
||||
#if !defined(ASMJIT_NO_JIT)
|
||||
|
||||
#if !defined(ASMJIT_NO_X86) && ASMJIT_ARCH_X86 != 0
|
||||
#define TEST_ENVIRONMENT_HAS_JIT
|
||||
|
||||
static void emit_void_function(CodeHolder& code) noexcept {
|
||||
x86::Assembler a(&code);
|
||||
a.ret();
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !defined(ASMJIT_NO_AARCH64) && ASMJIT_ARCH_ARM == 64
|
||||
#define TEST_ENVIRONMENT_HAS_JIT
|
||||
|
||||
static void emit_void_function(CodeHolder& code) noexcept {
|
||||
a64::Assembler a(&code);
|
||||
a.ret(a64::x30);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(TEST_ENVIRONMENT_HAS_JIT)
|
||||
static void* offset_pointer(void* ptr, size_t offset) noexcept {
|
||||
return static_cast<void*>(static_cast<uint8_t*>(ptr) + offset);
|
||||
}
|
||||
|
||||
static size_t write_empty_function_at(void* ptr, size_t size) noexcept {
|
||||
printf(" Write JIT code at addr : %p\n", ptr);
|
||||
|
||||
CodeHolder code;
|
||||
Error err = code.init(Environment::host());
|
||||
if (err != Error::kOk) {
|
||||
printf( "Failed to initialize CodeHolder (%s)\n", DebugUtils::error_as_string(err));
|
||||
return 0;
|
||||
}
|
||||
|
||||
emit_void_function(code);
|
||||
code.flatten();
|
||||
code.copy_flattened_data(ptr, size);
|
||||
|
||||
return code.code_size();
|
||||
}
|
||||
|
||||
static void flush_instruction_cache(void* ptr, size_t size) noexcept {
|
||||
printf(" Flush JIT code at addr : %p [size=%zu]\n", ptr, size);
|
||||
VirtMem::flush_instruction_cache(ptr, size);
|
||||
}
|
||||
|
||||
static void invoke_void_function(void* ptr) noexcept {
|
||||
printf(" Invoke JIT code at addr : %p\n", ptr);
|
||||
|
||||
// In case it crashes, we want to have the output flushed.
|
||||
fflush(stdout);
|
||||
|
||||
VoidFunc func = reinterpret_cast<VoidFunc>(ptr);
|
||||
func();
|
||||
}
|
||||
#endif
|
||||
|
||||
static void print_virt_mem_info_and_test_execution() noexcept {
|
||||
using MemoryFlags = VirtMem::MemoryFlags;
|
||||
using HardenedRuntimeInfo = VirtMem::HardenedRuntimeInfo;
|
||||
using HardenedRuntimeFlags = VirtMem::HardenedRuntimeFlags;
|
||||
|
||||
// Size of a virtual memory allocation.
|
||||
constexpr size_t kVMemAllocSize = 65536;
|
||||
|
||||
// Offset to the first function to execute (must be greater than 8 for UBSAN to work).
|
||||
[[maybe_unused]]
|
||||
constexpr size_t kVirtFuncOffset = 64;
|
||||
|
||||
size_t large_page_size = VirtMem::large_page_size();
|
||||
HardenedRuntimeInfo rti = VirtMem::hardened_runtime_info();
|
||||
|
||||
printf("Large/Huge Pages Info:\n");
|
||||
printf(" Large pages supported : %s\n", stringify_bool(large_page_size != 0u));
|
||||
if (large_page_size >= 1024 * 1024) {
|
||||
printf(" Large page size : %zu MiB\n", large_page_size / (1024u * 1024u));
|
||||
}
|
||||
else if (large_page_size) {
|
||||
printf(" Large page size : %zu KiB\n", large_page_size / 1024u);
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
printf("Hardened Environment Info:\n");
|
||||
printf(" Hardening was detected : %s\n", stringify_bool(rti.has_flag(HardenedRuntimeFlags::kEnabled )));
|
||||
printf(" MAP_JIT is available : %s\n", stringify_bool(rti.has_flag(HardenedRuntimeFlags::kMapJit )));
|
||||
printf(" DualMapping is available: %s\n", stringify_bool(rti.has_flag(HardenedRuntimeFlags::kDualMapping)));
|
||||
printf("\n");
|
||||
|
||||
if (!rti.has_flag(HardenedRuntimeFlags::kEnabled)) {
|
||||
printf("Virtual Memory Allocation (RWX):\n");
|
||||
|
||||
void* ptr = nullptr;
|
||||
Error result = VirtMem::alloc(&ptr, kVMemAllocSize, MemoryFlags::kAccessRWX);
|
||||
printf(" Alloc virt memory (RWX) : %s\n", stringify_result(result));
|
||||
|
||||
if (result == Error::kOk) {
|
||||
#if defined(TEST_ENVIRONMENT_HAS_JIT)
|
||||
void* func_ptr = offset_pointer(ptr, kVirtFuncOffset);
|
||||
size_t func_size = write_empty_function_at(func_ptr, kVMemAllocSize);
|
||||
|
||||
if (func_size) {
|
||||
flush_instruction_cache(func_ptr, func_size);
|
||||
invoke_void_function(func_ptr);
|
||||
}
|
||||
#endif // TEST_ENVIRONMENT_HAS_JIT
|
||||
|
||||
result = VirtMem::release(ptr, kVMemAllocSize);
|
||||
printf(" Release virt memory : %s\n", stringify_result(result));
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
{
|
||||
printf("Virtual Memory Allocation (RW - Flipping Permissions RW<->RX):\n");
|
||||
|
||||
void* ptr = nullptr;
|
||||
Error result = VirtMem::alloc(&ptr, kVMemAllocSize, MemoryFlags::kAccessRW | MemoryFlags::kMMapMaxAccessRWX);
|
||||
printf(" Alloc virt memory (RW) : %s (allocation uses kMMapMaxAccessRWX)\n", stringify_result(result));
|
||||
|
||||
if (result == Error::kOk) {
|
||||
#if defined(TEST_ENVIRONMENT_HAS_JIT)
|
||||
void* func_ptr = offset_pointer(ptr, kVirtFuncOffset);
|
||||
size_t func_size = write_empty_function_at(func_ptr, kVMemAllocSize);
|
||||
#endif // TEST_ENVIRONMENT_HAS_JIT
|
||||
|
||||
result = VirtMem::protect(ptr, kVMemAllocSize, MemoryFlags::kAccessRX);
|
||||
printf(" Protect virt memory (RX): %s\n", stringify_result(result));
|
||||
|
||||
#if defined(TEST_ENVIRONMENT_HAS_JIT)
|
||||
if (func_size) {
|
||||
flush_instruction_cache(func_ptr, func_size);
|
||||
invoke_void_function(func_ptr);
|
||||
}
|
||||
#endif // TEST_ENVIRONMENT_HAS_JIT
|
||||
|
||||
result = VirtMem::protect(ptr, kVMemAllocSize, MemoryFlags::kAccessRW);
|
||||
printf(" Protect virt memory (RW): %s\n", stringify_result(result));
|
||||
|
||||
result = VirtMem::release(ptr, kVMemAllocSize);
|
||||
printf(" Release virt memory (RW): %s\n", stringify_result(result));
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
if (rti.has_flag(HardenedRuntimeFlags::kMapJit)) {
|
||||
printf("Virtual Memory Allocation (MAP_JIT):\n");
|
||||
|
||||
void* ptr = nullptr;
|
||||
Error result = VirtMem::alloc(&ptr, kVMemAllocSize, MemoryFlags::kAccessRWX | MemoryFlags::kMMapEnableMapJit);
|
||||
printf(" Alloc virt mem (RWX) : %s (allocation uses kMMapEnableMapJit)\n", stringify_result(result));
|
||||
|
||||
if (result == Error::kOk) {
|
||||
printf(" Protect JIT Memory (RW) : (per-thread protection)\n");
|
||||
VirtMem::protect_jit_memory(VirtMem::ProtectJitAccess::kReadWrite);
|
||||
|
||||
#if defined(TEST_ENVIRONMENT_HAS_JIT)
|
||||
void* func_ptr = offset_pointer(ptr, kVirtFuncOffset);
|
||||
size_t func_size = write_empty_function_at(func_ptr, kVMemAllocSize);
|
||||
#endif // TEST_ENVIRONMENT_HAS_JIT
|
||||
|
||||
printf(" Protect JIT Memory (RX) : (per-thread protection)\n");
|
||||
VirtMem::protect_jit_memory(VirtMem::ProtectJitAccess::kReadExecute);
|
||||
|
||||
#if defined(TEST_ENVIRONMENT_HAS_JIT)
|
||||
if (func_size) {
|
||||
flush_instruction_cache(func_ptr, func_size);
|
||||
invoke_void_function(func_ptr);
|
||||
}
|
||||
#endif // TEST_ENVIRONMENT_HAS_JIT
|
||||
|
||||
result = VirtMem::release(ptr, kVMemAllocSize);
|
||||
printf(" Release virt memory : %s\n", stringify_result(result));
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
if (rti.has_flag(HardenedRuntimeFlags::kDualMapping)) {
|
||||
printf("Virtual Memory Allocation (Dual Mapping):\n");
|
||||
|
||||
VirtMem::DualMapping dm {};
|
||||
Error result = VirtMem::alloc_dual_mapping(Out(dm), kVMemAllocSize, MemoryFlags::kAccessRWX);
|
||||
printf(" Alloc dual mem (RW+RX) : %s\n", stringify_result(result));
|
||||
|
||||
if (result == Error::kOk) {
|
||||
#if defined(TEST_ENVIRONMENT_HAS_JIT)
|
||||
size_t func_size = write_empty_function_at(offset_pointer(dm.rw, kVirtFuncOffset), kVMemAllocSize);
|
||||
if (func_size) {
|
||||
flush_instruction_cache(offset_pointer(dm.rx, kVirtFuncOffset), func_size);
|
||||
invoke_void_function(offset_pointer(dm.rx, kVirtFuncOffset));
|
||||
}
|
||||
#endif // TEST_ENVIRONMENT_HAS_JIT
|
||||
|
||||
result = VirtMem::release_dual_mapping(dm, kVMemAllocSize);
|
||||
printf(" Release dual mem (RW+RX): %s\n", stringify_result(result));
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(TEST_ENVIRONMENT_HAS_JIT)
|
||||
static void print_jit_runtime_info_and_test_execution_with_params(const JitAllocator::CreateParams* params, const char* params_name) noexcept {
|
||||
printf("JitRuntime (%s):\n", params_name);
|
||||
|
||||
JitRuntime rt(params);
|
||||
CodeHolder code;
|
||||
|
||||
Error result = code.init(rt.environment());
|
||||
printf(" CodeHolder init result : %s\n", stringify_result(result));
|
||||
|
||||
if (result != Error::kOk) {
|
||||
return;
|
||||
}
|
||||
|
||||
emit_void_function(code);
|
||||
VoidFunc fn;
|
||||
|
||||
result = rt.add(&fn, &code);
|
||||
printf(" Runtime.add() result : %s\n", stringify_result(result));
|
||||
|
||||
if (result == Error::kOk) {
|
||||
invoke_void_function((void*)fn);
|
||||
|
||||
result = rt.release(fn);
|
||||
printf(" Runtime.release() result: %s\n", stringify_result(result));
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static void print_jit_runtime_info_and_test_execution() noexcept {
|
||||
print_jit_runtime_info_and_test_execution_with_params(nullptr, "<no params>");
|
||||
|
||||
if (VirtMem::large_page_size()) {
|
||||
JitAllocator::CreateParams p{};
|
||||
p.options = JitAllocatorOptions::kUseLargePages;
|
||||
|
||||
print_jit_runtime_info_and_test_execution_with_params(&p, "large pages");
|
||||
}
|
||||
}
|
||||
#endif // TEST_ENVIRONMENT_HAS_JIT
|
||||
|
||||
#endif // !ASMJIT_NO_JIT
|
||||
|
||||
int main() {
|
||||
print_app_info();
|
||||
print_build_options();
|
||||
print_cpu_info();
|
||||
|
||||
#if !defined(ASMJIT_NO_JIT)
|
||||
print_virt_mem_info_and_test_execution();
|
||||
#endif // !ASMJIT_NO_JIT
|
||||
|
||||
#if !defined(ASMJIT_NO_JIT) && defined(TEST_ENVIRONMENT_HAS_JIT)
|
||||
print_jit_runtime_info_and_test_execution();
|
||||
#endif // !ASMJIT_NO_JIT && TEST_ENVIRONMENT_HAS_JIT
|
||||
|
||||
return 0;
|
||||
}
|
||||
206
asmjit-testing/tests/asmjit_test_instinfo.cpp
Normal file
206
asmjit-testing/tests/asmjit_test_instinfo.cpp
Normal file
@@ -0,0 +1,206 @@
|
||||
// This file is part of AsmJit project <https://asmjit.com>
|
||||
//
|
||||
// See <asmjit/core.h> or LICENSE.md for license and copyright information
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#include <asmjit/core.h>
|
||||
|
||||
#if !defined(ASMJIT_NO_X86)
|
||||
#include <asmjit/x86.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <asmjit-testing/commons/asmjitutils.h>
|
||||
|
||||
using namespace asmjit;
|
||||
|
||||
static void print_app_info() noexcept {
|
||||
printf("AsmJit Instruction Info Test Suite v%u.%u.%u [Arch=%s] [Mode=%s]\n\n",
|
||||
unsigned((ASMJIT_LIBRARY_VERSION >> 16) ),
|
||||
unsigned((ASMJIT_LIBRARY_VERSION >> 8) & 0xFF),
|
||||
unsigned((ASMJIT_LIBRARY_VERSION ) & 0xFF),
|
||||
asmjit_arch_as_string(Arch::kHost),
|
||||
asmjit_build_type()
|
||||
);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
#if !defined(ASMJIT_NO_X86)
|
||||
static char access_letter(bool r, bool w) noexcept {
|
||||
return r && w ? 'X' : r ? 'R' : w ? 'W' : '_';
|
||||
}
|
||||
|
||||
static void print_info(Arch arch, const BaseInst& inst, const Operand_* operands, size_t op_count) {
|
||||
StringTmp<512> sb;
|
||||
|
||||
// Read & Write Information
|
||||
// ------------------------
|
||||
|
||||
InstRWInfo rw;
|
||||
InstAPI::query_rw_info(arch, inst, operands, op_count, &rw);
|
||||
|
||||
#ifndef ASMJIT_NO_LOGGING
|
||||
Formatter::format_instruction(sb, FormatFlags::kNone, nullptr, arch, inst, Span(operands, op_count));
|
||||
#else
|
||||
sb.append("<Logging-Not-Available>");
|
||||
#endif
|
||||
sb.append("\n");
|
||||
|
||||
sb.append(" Operands:\n");
|
||||
for (uint32_t i = 0; i < rw.op_count(); i++) {
|
||||
const OpRWInfo& op = rw.operand(i);
|
||||
|
||||
sb.append_format(" [%u] Op=%c Read=%016llX Write=%016llX Extend=%016llX",
|
||||
i,
|
||||
access_letter(op.is_read(), op.is_write()),
|
||||
op.read_byte_mask(),
|
||||
op.write_byte_mask(),
|
||||
op.extend_byte_mask());
|
||||
|
||||
if (op.is_mem_base_used()) {
|
||||
sb.append_format(" Base=%c", access_letter(op.is_mem_base_read(), op.is_mem_base_write()));
|
||||
if (op.is_mem_base_pre_modify())
|
||||
sb.append_format(" <PRE>");
|
||||
if (op.is_mem_base_post_modify())
|
||||
sb.append_format(" <POST>");
|
||||
}
|
||||
|
||||
if (op.is_mem_index_used()) {
|
||||
sb.append_format(" Index=%c", access_letter(op.is_mem_index_read(), op.is_mem_index_write()));
|
||||
}
|
||||
|
||||
sb.append("\n");
|
||||
}
|
||||
|
||||
// CPU Flags (Read/Write)
|
||||
// ----------------------
|
||||
|
||||
if ((rw.read_flags() | rw.write_flags()) != CpuRWFlags::kNone) {
|
||||
sb.append(" Flags: \n");
|
||||
|
||||
struct FlagMap {
|
||||
CpuRWFlags flag;
|
||||
char name[4];
|
||||
};
|
||||
|
||||
static const FlagMap flag_map_table[] = {
|
||||
{ CpuRWFlags::kX86_CF, "CF" },
|
||||
{ CpuRWFlags::kX86_OF, "OF" },
|
||||
{ CpuRWFlags::kX86_SF, "SF" },
|
||||
{ CpuRWFlags::kX86_ZF, "ZF" },
|
||||
{ CpuRWFlags::kX86_AF, "AF" },
|
||||
{ CpuRWFlags::kX86_PF, "PF" },
|
||||
{ CpuRWFlags::kX86_DF, "DF" },
|
||||
{ CpuRWFlags::kX86_IF, "IF" },
|
||||
{ CpuRWFlags::kX86_AC, "AC" },
|
||||
{ CpuRWFlags::kX86_C0, "C0" },
|
||||
{ CpuRWFlags::kX86_C1, "C1" },
|
||||
{ CpuRWFlags::kX86_C2, "C2" },
|
||||
{ CpuRWFlags::kX86_C3, "C3" }
|
||||
};
|
||||
|
||||
sb.append(" ");
|
||||
for (uint32_t f = 0; f < 13; f++) {
|
||||
char c = access_letter((rw.read_flags() & flag_map_table[f].flag) != CpuRWFlags::kNone,
|
||||
(rw.write_flags() & flag_map_table[f].flag) != CpuRWFlags::kNone);
|
||||
if (c != '_')
|
||||
sb.append_format("%s=%c ", flag_map_table[f].name, c);
|
||||
}
|
||||
|
||||
sb.append("\n");
|
||||
}
|
||||
|
||||
// CPU Features
|
||||
// ------------
|
||||
|
||||
CpuFeatures features;
|
||||
InstAPI::query_features(arch, inst, operands, op_count, &features);
|
||||
|
||||
#ifndef ASMJIT_NO_LOGGING
|
||||
if (!features.is_empty()) {
|
||||
sb.append(" Features:\n");
|
||||
sb.append(" ");
|
||||
|
||||
bool first = true;
|
||||
CpuFeatures::Iterator it(features.iterator());
|
||||
while (it.has_next()) {
|
||||
uint32_t feature_id = uint32_t(it.next());
|
||||
if (!first)
|
||||
sb.append(" & ");
|
||||
Formatter::format_feature(sb, arch, feature_id);
|
||||
first = false;
|
||||
}
|
||||
sb.append("\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
printf("%s\n", sb.data());
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
static void print_info_simple(Arch arch,InstId inst_id, InstOptions options, Args&&... args) {
|
||||
BaseInst inst(inst_id);
|
||||
inst.add_options(options);
|
||||
Operand_ op_array[] = { std::forward<Args>(args)... };
|
||||
print_info(arch, inst, op_array, sizeof...(args));
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
static void print_info_extra(Arch arch, InstId inst_id, InstOptions options, const Reg& extra_reg, Args&&... args) {
|
||||
BaseInst inst(inst_id);
|
||||
inst.add_options(options);
|
||||
inst.set_extra_reg(extra_reg);
|
||||
Operand_ op_array[] = { std::forward<Args>(args)... };
|
||||
print_info(arch, inst, op_array, sizeof...(args));
|
||||
}
|
||||
#endif // !ASMJIT_NO_X86
|
||||
|
||||
static void test_x86_arch() {
|
||||
#if !defined(ASMJIT_NO_X86)
|
||||
using namespace x86;
|
||||
Arch arch = Arch::kX64;
|
||||
|
||||
print_info_simple(arch, Inst::kIdAdd, InstOptions::kNone, eax, ebx);
|
||||
print_info_simple(arch, Inst::kIdXor, InstOptions::kNone, eax, eax);
|
||||
print_info_simple(arch, Inst::kIdLods, InstOptions::kNone, eax, dword_ptr(rsi));
|
||||
|
||||
print_info_simple(arch, Inst::kIdPshufd, InstOptions::kNone, xmm0, xmm1, imm(0));
|
||||
print_info_simple(arch, Inst::kIdPabsb, InstOptions::kNone, mm1, mm2);
|
||||
print_info_simple(arch, Inst::kIdPabsb, InstOptions::kNone, xmm1, xmm2);
|
||||
print_info_simple(arch, Inst::kIdPextrw, InstOptions::kNone, eax, mm1, imm(0));
|
||||
print_info_simple(arch, Inst::kIdPextrw, InstOptions::kNone, eax, xmm1, imm(0));
|
||||
print_info_simple(arch, Inst::kIdPextrw, InstOptions::kNone, ptr(rax), xmm1, imm(0));
|
||||
|
||||
print_info_simple(arch, Inst::kIdVpdpbusd, InstOptions::kNone, xmm0, xmm1, xmm2);
|
||||
print_info_simple(arch, Inst::kIdVpdpbusd, InstOptions::kX86_Vex, xmm0, xmm1, xmm2);
|
||||
|
||||
print_info_simple(arch, Inst::kIdVaddpd, InstOptions::kNone, ymm0, ymm1, ymm2);
|
||||
print_info_simple(arch, Inst::kIdVaddpd, InstOptions::kNone, ymm0, ymm30, ymm31);
|
||||
print_info_simple(arch, Inst::kIdVaddpd, InstOptions::kNone, zmm0, zmm1, zmm2);
|
||||
|
||||
print_info_simple(arch, Inst::kIdVpternlogd, InstOptions::kNone, zmm0, zmm0, zmm0, imm(0xFF));
|
||||
print_info_simple(arch, Inst::kIdVpternlogq, InstOptions::kNone, zmm0, zmm1, zmm2, imm(0x33));
|
||||
|
||||
print_info_extra(arch, Inst::kIdVaddpd, InstOptions::kNone, k1, zmm0, zmm1, zmm2);
|
||||
print_info_extra(arch, Inst::kIdVaddpd, InstOptions::kX86_ZMask, k1, zmm0, zmm1, zmm2);
|
||||
|
||||
print_info_simple(arch, Inst::kIdVcvtdq2pd, InstOptions::kNone, xmm0, xmm1);
|
||||
print_info_simple(arch, Inst::kIdVcvtdq2pd, InstOptions::kNone, ymm0, xmm1);
|
||||
print_info_simple(arch, Inst::kIdVcvtdq2pd, InstOptions::kNone, zmm0, ymm1);
|
||||
|
||||
print_info_simple(arch, Inst::kIdVcvtdq2pd, InstOptions::kNone, xmm0, ptr(rsi));
|
||||
print_info_simple(arch, Inst::kIdVcvtdq2pd, InstOptions::kNone, ymm0, ptr(rsi));
|
||||
print_info_simple(arch, Inst::kIdVcvtdq2pd, InstOptions::kNone, zmm0, ptr(rsi));
|
||||
#endif // !ASMJIT_NO_X86
|
||||
}
|
||||
|
||||
} // {anonymous}
|
||||
|
||||
int main() {
|
||||
print_app_info();
|
||||
test_x86_arch();
|
||||
|
||||
return 0;
|
||||
}
|
||||
265
asmjit-testing/tests/asmjit_test_misc.h
Normal file
265
asmjit-testing/tests/asmjit_test_misc.h
Normal file
@@ -0,0 +1,265 @@
|
||||
// This file is part of AsmJit project <https://asmjit.com>
|
||||
//
|
||||
// See <asmjit/core.h> or LICENSE.md for license and copyright information
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#ifndef ASMJIT_TEST_MISC_H_INCLUDED
|
||||
#define ASMJIT_TEST_MISC_H_INCLUDED
|
||||
|
||||
#include <asmjit/x86.h>
|
||||
|
||||
namespace asmtest {
|
||||
|
||||
using namespace asmjit;
|
||||
|
||||
// Generates a typical alpha blend function that uses SSE2 instruction set.
|
||||
// This function combines emitting instructions with control flow constructs
|
||||
// like binding Labels and jumping to them. This should be pretty representative.
|
||||
template<typename Emitter>
|
||||
static void generate_sse_alpha_blend_internal(
|
||||
Emitter& cc,
|
||||
const x86::Gp& dst, const x86::Gp& src, const x86::Gp& n,
|
||||
const x86::Gp& gp0,
|
||||
const x86::Vec& simd0, const x86::Vec& simd1, const x86::Vec& simd2, const x86::Vec& simd3,
|
||||
const x86::Vec& simd4, const x86::Vec& simd5, const x86::Vec& simd6, const x86::Vec& simd7) {
|
||||
|
||||
x86::Gp i = n;
|
||||
x86::Gp j = gp0;
|
||||
|
||||
x86::Vec vzero = simd0;
|
||||
x86::Vec v0080 = simd1;
|
||||
x86::Vec v0101 = simd2;
|
||||
|
||||
Label L_SmallLoop = cc.new_label();
|
||||
Label L_SmallEnd = cc.new_label();
|
||||
Label L_LargeLoop = cc.new_label();
|
||||
Label L_LargeEnd = cc.new_label();
|
||||
Label L_Done = cc.new_label();
|
||||
|
||||
// Load SIMD Constants.
|
||||
cc.xorps(vzero, vzero);
|
||||
cc.mov(gp0.r32(), 0x00800080);
|
||||
cc.movd(v0080, gp0.r32());
|
||||
cc.mov(gp0.r32(), 0x01010101);
|
||||
cc.movd(v0101, gp0.r32());
|
||||
cc.pshufd(v0080, v0080, x86::shuffle_imm(0, 0, 0, 0));
|
||||
cc.pshufd(v0101, v0101, x86::shuffle_imm(0, 0, 0, 0));
|
||||
|
||||
// How many pixels have to be processed to make the loop aligned.
|
||||
cc.xor_(j, j);
|
||||
cc.sub(j, dst);
|
||||
cc.and_(j, 15);
|
||||
cc.shr(j, 2);
|
||||
cc.jz(L_SmallEnd);
|
||||
|
||||
cc.cmp(j, i);
|
||||
cc.cmovg(j, i); // j = min(i, j)
|
||||
cc.sub(i, j); // i -= j
|
||||
|
||||
// Small loop.
|
||||
cc.bind(L_SmallLoop);
|
||||
{
|
||||
x86::Vec x0 = simd3;
|
||||
x86::Vec y0 = simd4;
|
||||
x86::Vec a0 = simd5;
|
||||
|
||||
cc.movd(y0, x86::ptr(src));
|
||||
cc.movd(x0, x86::ptr(dst));
|
||||
|
||||
cc.pcmpeqb(a0, a0);
|
||||
cc.pxor(a0, y0);
|
||||
cc.psrlw(a0, 8);
|
||||
cc.punpcklbw(x0, vzero);
|
||||
|
||||
cc.pshuflw(a0, a0, x86::shuffle_imm(1, 1, 1, 1));
|
||||
cc.punpcklbw(y0, vzero);
|
||||
|
||||
cc.pmullw(x0, a0);
|
||||
cc.paddsw(x0, v0080);
|
||||
cc.pmulhuw(x0, v0101);
|
||||
|
||||
cc.paddw(x0, y0);
|
||||
cc.packuswb(x0, x0);
|
||||
|
||||
cc.movd(x86::ptr(dst), x0);
|
||||
|
||||
cc.add(dst, 4);
|
||||
cc.add(src, 4);
|
||||
|
||||
cc.dec(j);
|
||||
cc.jnz(L_SmallLoop);
|
||||
}
|
||||
|
||||
// Second section, prepare for an aligned loop.
|
||||
cc.bind(L_SmallEnd);
|
||||
|
||||
cc.test(i, i);
|
||||
cc.mov(j, i);
|
||||
cc.jz(L_Done);
|
||||
|
||||
cc.and_(j, 3);
|
||||
cc.shr(i, 2);
|
||||
cc.jz(L_LargeEnd);
|
||||
|
||||
// Aligned loop.
|
||||
cc.bind(L_LargeLoop);
|
||||
{
|
||||
x86::Vec x0 = simd3;
|
||||
x86::Vec x1 = simd4;
|
||||
x86::Vec y0 = simd5;
|
||||
x86::Vec a0 = simd6;
|
||||
x86::Vec a1 = simd7;
|
||||
|
||||
cc.movups(y0, x86::ptr(src));
|
||||
cc.movaps(x0, x86::ptr(dst));
|
||||
|
||||
cc.pcmpeqb(a0, a0);
|
||||
cc.xorps(a0, y0);
|
||||
cc.movaps(x1, x0);
|
||||
|
||||
cc.psrlw(a0, 8);
|
||||
cc.punpcklbw(x0, vzero);
|
||||
|
||||
cc.movaps(a1, a0);
|
||||
cc.punpcklwd(a0, a0);
|
||||
|
||||
cc.punpckhbw(x1, vzero);
|
||||
cc.punpckhwd(a1, a1);
|
||||
|
||||
cc.pshufd(a0, a0, x86::shuffle_imm(3, 3, 1, 1));
|
||||
cc.pshufd(a1, a1, x86::shuffle_imm(3, 3, 1, 1));
|
||||
|
||||
cc.pmullw(x0, a0);
|
||||
cc.pmullw(x1, a1);
|
||||
|
||||
cc.paddsw(x0, v0080);
|
||||
cc.paddsw(x1, v0080);
|
||||
|
||||
cc.pmulhuw(x0, v0101);
|
||||
cc.pmulhuw(x1, v0101);
|
||||
|
||||
cc.add(src, 16);
|
||||
cc.packuswb(x0, x1);
|
||||
|
||||
cc.paddw(x0, y0);
|
||||
cc.movaps(x86::ptr(dst), x0);
|
||||
|
||||
cc.add(dst, 16);
|
||||
|
||||
cc.dec(i);
|
||||
cc.jnz(L_LargeLoop);
|
||||
}
|
||||
|
||||
cc.bind(L_LargeEnd);
|
||||
cc.test(j, j);
|
||||
cc.jnz(L_SmallLoop);
|
||||
|
||||
cc.bind(L_Done);
|
||||
}
|
||||
|
||||
static void generate_sse_alpha_blend(asmjit::BaseEmitter& emitter, bool emit_prolog_epilog) {
|
||||
using namespace asmjit::x86;
|
||||
|
||||
#ifndef ASMJIT_NO_COMPILER
|
||||
if (emitter.is_compiler()) {
|
||||
Compiler& cc = *emitter.as<Compiler>();
|
||||
|
||||
Gp dst = cc.new_gp_ptr("dst");
|
||||
Gp src = cc.new_gp_ptr("src");
|
||||
Gp i = cc.new_gp_ptr("i");
|
||||
Gp j = cc.new_gp_ptr("j");
|
||||
|
||||
Vec v0 = cc.new_xmm("v0");
|
||||
Vec v1 = cc.new_xmm("v1");
|
||||
Vec v2 = cc.new_xmm("v2");
|
||||
Vec v3 = cc.new_xmm("v3");
|
||||
Vec v4 = cc.new_xmm("v4");
|
||||
Vec v5 = cc.new_xmm("v5");
|
||||
Vec v6 = cc.new_xmm("v6");
|
||||
Vec v7 = cc.new_xmm("v7");
|
||||
|
||||
FuncNode* func_node = cc.add_func(FuncSignature::build<void, void*, const void*, size_t>());
|
||||
func_node->set_arg(0, dst);
|
||||
func_node->set_arg(1, src);
|
||||
func_node->set_arg(2, i);
|
||||
generate_sse_alpha_blend_internal(cc, dst, src, i, j, v0, v1, v2, v3, v4, v5, v6, v7);
|
||||
cc.end_func();
|
||||
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef ASMJIT_NO_BUILDER
|
||||
if (emitter.is_builder()) {
|
||||
Builder& cc = *emitter.as<Builder>();
|
||||
|
||||
x86::Gp dst = cc.zax();
|
||||
x86::Gp src = cc.zcx();
|
||||
x86::Gp i = cc.zdx();
|
||||
x86::Gp j = cc.zdi();
|
||||
|
||||
if (emit_prolog_epilog) {
|
||||
FuncDetail func;
|
||||
func.init(FuncSignature::build<void, void*, const void*, size_t>(), cc.environment());
|
||||
|
||||
FuncFrame frame;
|
||||
frame.init(func);
|
||||
frame.add_dirty_regs(dst, src, i, j);
|
||||
frame.add_dirty_regs(xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7);
|
||||
|
||||
FuncArgsAssignment args(&func);
|
||||
args.assign_all(dst, src, i);
|
||||
args.update_func_frame(frame);
|
||||
frame.finalize();
|
||||
|
||||
cc.emit_prolog(frame);
|
||||
cc.emit_args_assignment(frame, args);
|
||||
generate_sse_alpha_blend_internal(cc, dst, src, i, j, xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7);
|
||||
cc.emit_epilog(frame);
|
||||
}
|
||||
else {
|
||||
generate_sse_alpha_blend_internal(cc, dst, src, i, j, xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (emitter.is_assembler()) {
|
||||
Assembler& cc = *emitter.as<Assembler>();
|
||||
|
||||
x86::Gp dst = cc.zax();
|
||||
x86::Gp src = cc.zcx();
|
||||
x86::Gp i = cc.zdx();
|
||||
x86::Gp j = cc.zdi();
|
||||
|
||||
if (emit_prolog_epilog) {
|
||||
FuncDetail func;
|
||||
func.init(FuncSignature::build<void, void*, const void*, size_t>(), cc.environment());
|
||||
|
||||
FuncFrame frame;
|
||||
frame.init(func);
|
||||
frame.add_dirty_regs(dst, src, i, j);
|
||||
frame.add_dirty_regs(xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7);
|
||||
|
||||
FuncArgsAssignment args(&func);
|
||||
args.assign_all(dst, src, i);
|
||||
args.update_func_frame(frame);
|
||||
frame.finalize();
|
||||
|
||||
cc.emit_prolog(frame);
|
||||
cc.emit_args_assignment(frame, args);
|
||||
generate_sse_alpha_blend_internal(cc, dst, src, i, j, xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7);
|
||||
cc.emit_epilog(frame);
|
||||
}
|
||||
else {
|
||||
generate_sse_alpha_blend_internal(cc, dst, src, i, j, xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} // {asmtest}
|
||||
|
||||
#endif // ASMJIT_TEST_MISC_H_INCLUDED
|
||||
171
asmjit-testing/tests/asmjit_test_runner.cpp
Normal file
171
asmjit-testing/tests/asmjit_test_runner.cpp
Normal file
@@ -0,0 +1,171 @@
|
||||
// This file is part of AsmJit project <https://asmjit.com>
|
||||
//
|
||||
// See <asmjit/core.h> or LICENSE.md for license and copyright information
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#include <asmjit/core.h>
|
||||
|
||||
#if !defined(ASMJIT_NO_X86)
|
||||
#include <asmjit/x86.h>
|
||||
#endif
|
||||
|
||||
#if !defined(ASMJIT_NO_AARCH64)
|
||||
#include <asmjit/a64.h>
|
||||
#endif
|
||||
|
||||
#include <asmjit-testing/commons/asmjitutils.h>
|
||||
#include <asmjit-testing/tests/broken.h>
|
||||
|
||||
#if !defined(ASMJIT_NO_COMPILER)
|
||||
#include <asmjit/core/racfgblock_p.h>
|
||||
#include <asmjit/core/rainst_p.h>
|
||||
#include <asmjit/core/rapass_p.h>
|
||||
#endif
|
||||
|
||||
using namespace asmjit;
|
||||
|
||||
#define DUMP_TYPE(...) \
|
||||
printf(" %-26s: %u\n", #__VA_ARGS__, uint32_t(sizeof(__VA_ARGS__)))
|
||||
|
||||
static void print_type_sizes(void) noexcept {
|
||||
printf("Size of C++ types:\n");
|
||||
DUMP_TYPE(int8_t);
|
||||
DUMP_TYPE(int16_t);
|
||||
DUMP_TYPE(int32_t);
|
||||
DUMP_TYPE(int64_t);
|
||||
DUMP_TYPE(int);
|
||||
DUMP_TYPE(long);
|
||||
DUMP_TYPE(size_t);
|
||||
DUMP_TYPE(intptr_t);
|
||||
DUMP_TYPE(float);
|
||||
DUMP_TYPE(double);
|
||||
DUMP_TYPE(void*);
|
||||
printf("\n");
|
||||
|
||||
printf("Size of base classes:\n");
|
||||
DUMP_TYPE(BaseAssembler);
|
||||
DUMP_TYPE(BaseEmitter);
|
||||
DUMP_TYPE(CodeBuffer);
|
||||
DUMP_TYPE(CodeHolder);
|
||||
DUMP_TYPE(ConstPool);
|
||||
DUMP_TYPE(Fixup);
|
||||
DUMP_TYPE(LabelEntry);
|
||||
DUMP_TYPE(LabelEntry::ExtraData);
|
||||
DUMP_TYPE(RelocEntry);
|
||||
DUMP_TYPE(Section);
|
||||
DUMP_TYPE(String);
|
||||
DUMP_TYPE(Target);
|
||||
printf("\n");
|
||||
|
||||
printf("Size of arena classes:\n");
|
||||
DUMP_TYPE(Arena);
|
||||
DUMP_TYPE(ArenaHashNode);
|
||||
DUMP_TYPE(ArenaHash<ArenaHashNode>);
|
||||
DUMP_TYPE(ArenaList<int>);
|
||||
DUMP_TYPE(ArenaVector<int>);
|
||||
DUMP_TYPE(ArenaString<16>);
|
||||
printf("\n");
|
||||
|
||||
printf("Size of operand classes:\n");
|
||||
DUMP_TYPE(Operand);
|
||||
DUMP_TYPE(Reg);
|
||||
DUMP_TYPE(BaseMem);
|
||||
DUMP_TYPE(Imm);
|
||||
DUMP_TYPE(Label);
|
||||
printf("\n");
|
||||
|
||||
printf("Size of function classes:\n");
|
||||
DUMP_TYPE(CallConv);
|
||||
DUMP_TYPE(FuncFrame);
|
||||
DUMP_TYPE(FuncValue);
|
||||
DUMP_TYPE(FuncDetail);
|
||||
DUMP_TYPE(FuncSignature);
|
||||
DUMP_TYPE(FuncArgsAssignment);
|
||||
printf("\n");
|
||||
|
||||
#if !defined(ASMJIT_NO_BUILDER)
|
||||
constexpr uint32_t kBaseOpCapacity = InstNode::kBaseOpCapacity;
|
||||
constexpr uint32_t kFullOpCapacity = InstNode::kFullOpCapacity;
|
||||
|
||||
printf("Size of builder classes:\n");
|
||||
DUMP_TYPE(BaseBuilder);
|
||||
DUMP_TYPE(BaseNode);
|
||||
DUMP_TYPE(InstNode);
|
||||
DUMP_TYPE(InstNodeWithOperands<kBaseOpCapacity>);
|
||||
DUMP_TYPE(InstNodeWithOperands<kFullOpCapacity>);
|
||||
DUMP_TYPE(AlignNode);
|
||||
DUMP_TYPE(LabelNode);
|
||||
DUMP_TYPE(EmbedDataNode);
|
||||
DUMP_TYPE(EmbedLabelNode);
|
||||
DUMP_TYPE(ConstPoolNode);
|
||||
DUMP_TYPE(CommentNode);
|
||||
DUMP_TYPE(SentinelNode);
|
||||
printf("\n");
|
||||
#endif
|
||||
|
||||
#if !defined(ASMJIT_NO_COMPILER)
|
||||
printf("Size of compiler classes:\n");
|
||||
DUMP_TYPE(BaseCompiler);
|
||||
DUMP_TYPE(FuncNode);
|
||||
DUMP_TYPE(FuncRetNode);
|
||||
DUMP_TYPE(InvokeNode);
|
||||
DUMP_TYPE(VirtReg);
|
||||
printf("\n");
|
||||
|
||||
printf("Size of compiler classes (RA):\n");
|
||||
DUMP_TYPE(BaseRAPass);
|
||||
DUMP_TYPE(RABlock);
|
||||
DUMP_TYPE(RAInst);
|
||||
DUMP_TYPE(RATiedReg);
|
||||
DUMP_TYPE(RAWorkReg);
|
||||
printf("\n");
|
||||
#endif
|
||||
|
||||
#if !defined(ASMJIT_NO_X86)
|
||||
printf("Size of x86-specific classes:\n");
|
||||
DUMP_TYPE(x86::Assembler);
|
||||
#if !defined(ASMJIT_NO_BUILDER)
|
||||
DUMP_TYPE(x86::Builder);
|
||||
#endif
|
||||
#if !defined(ASMJIT_NO_COMPILER)
|
||||
DUMP_TYPE(x86::Compiler);
|
||||
#endif
|
||||
DUMP_TYPE(x86::InstDB::InstInfo);
|
||||
DUMP_TYPE(x86::InstDB::CommonInfo);
|
||||
DUMP_TYPE(x86::InstDB::OpSignature);
|
||||
DUMP_TYPE(x86::InstDB::InstSignature);
|
||||
printf("\n");
|
||||
#endif
|
||||
|
||||
#if !defined(ASMJIT_NO_AARCH64)
|
||||
printf("Size of aarch64-specific classes:\n");
|
||||
DUMP_TYPE(a64::Assembler);
|
||||
#if !defined(ASMJIT_NO_BUILDER)
|
||||
DUMP_TYPE(a64::Builder);
|
||||
#endif
|
||||
#if !defined(ASMJIT_NO_COMPILER)
|
||||
DUMP_TYPE(a64::Compiler);
|
||||
#endif
|
||||
printf("\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
#undef DUMP_TYPE
|
||||
|
||||
static void on_before_run(void) noexcept {
|
||||
print_build_options();
|
||||
print_cpu_info();
|
||||
print_type_sizes();
|
||||
}
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
printf("AsmJit Unit-Test v%u.%u.%u [Arch=%s] [Mode=%s]\n\n",
|
||||
unsigned((ASMJIT_LIBRARY_VERSION >> 16) ),
|
||||
unsigned((ASMJIT_LIBRARY_VERSION >> 8) & 0xFF),
|
||||
unsigned((ASMJIT_LIBRARY_VERSION ) & 0xFF),
|
||||
asmjit_arch_as_string(Arch::kHost),
|
||||
asmjit_build_type()
|
||||
);
|
||||
|
||||
return BrokenAPI::run(argc, argv, on_before_run);
|
||||
}
|
||||
5674
asmjit-testing/tests/asmjit_test_unicompiler.cpp
Normal file
5674
asmjit-testing/tests/asmjit_test_unicompiler.cpp
Normal file
File diff suppressed because it is too large
Load Diff
49
asmjit-testing/tests/asmjit_test_unicompiler_avx2fma.cpp
Normal file
49
asmjit-testing/tests/asmjit_test_unicompiler_avx2fma.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
// This file is part of AsmJit project <https://asmjit.com>
|
||||
//
|
||||
// See <asmjit/core.h> or LICENSE.md for license and copyright information
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#include <asmjit/ujit.h>
|
||||
|
||||
#if defined(ASMJIT_UJIT_X86)
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#include <intrin.h>
|
||||
#else
|
||||
#include <immintrin.h>
|
||||
#endif
|
||||
|
||||
namespace UniCompilerTests {
|
||||
|
||||
// A reference implementation of MUL+ADD with the use of FMA. This has to be provided otherwise the
|
||||
// compiler may use FPU registers in 32-bit x86 case, which would make the result different than when
|
||||
// compiled by JIT compiler that would use XMM registers (32/64-bit SSE/AVX operations).
|
||||
|
||||
float fmadd_fma_ref(float a, float b, float c) noexcept {
|
||||
__m128 av = _mm_set1_ps(a);
|
||||
__m128 bv = _mm_set1_ps(b);
|
||||
__m128 cv = _mm_set1_ps(c);
|
||||
|
||||
return _mm_cvtss_f32(_mm_fmadd_ss(av, bv, cv));
|
||||
}
|
||||
|
||||
double fmadd_fma_ref(double a, double b, double c) noexcept {
|
||||
__m128d av = _mm_set1_pd(a);
|
||||
__m128d bv = _mm_set1_pd(b);
|
||||
__m128d cv = _mm_set1_pd(c);
|
||||
|
||||
return _mm_cvtsd_f64(_mm_fmadd_sd(av, bv, cv));
|
||||
}
|
||||
|
||||
void madd_fma_check_valgrind_bug(const float a[4], const float b[4], const float c[4], float dst[4]) noexcept {
|
||||
__m128 av = _mm_loadu_ps(a);
|
||||
__m128 bv = _mm_loadu_ps(b);
|
||||
__m128 cv = _mm_loadu_ps(c);
|
||||
|
||||
__m128 dv = _mm_fmadd_ss(av, bv, cv);
|
||||
_mm_storeu_ps(dst, dv);
|
||||
}
|
||||
|
||||
} // {UniCompilerTests}
|
||||
|
||||
#endif // ASMJIT_UJIT_X86
|
||||
72
asmjit-testing/tests/asmjit_test_unicompiler_sse2.cpp
Normal file
72
asmjit-testing/tests/asmjit_test_unicompiler_sse2.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
// This file is part of AsmJit project <https://asmjit.com>
|
||||
//
|
||||
// See <asmjit/core.h> or LICENSE.md for license and copyright information
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#include <asmjit/ujit.h>
|
||||
|
||||
#if defined(ASMJIT_UJIT_X86)
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#include <intrin.h>
|
||||
#else
|
||||
#include <emmintrin.h>
|
||||
#endif
|
||||
|
||||
namespace UniCompilerTests {
|
||||
|
||||
// A reference implementation of MUL+ADD without the use of FMA. This has to be provided otherwise the
|
||||
// compiler may use FPU registers in 32-bit x86 case, which would make the result different than when
|
||||
// compiled by JIT compiler that would use XMM registers (32/64-bit SSE/AVX operations).
|
||||
|
||||
float fadd(float a, float b) noexcept {
|
||||
return _mm_cvtss_f32(_mm_add_ss(_mm_set1_ps(a), _mm_set1_ps(b)));
|
||||
}
|
||||
|
||||
float fsub(float a, float b) noexcept {
|
||||
return _mm_cvtss_f32(_mm_sub_ss(_mm_set1_ps(a), _mm_set1_ps(b)));
|
||||
}
|
||||
|
||||
float fmul(float a, float b) noexcept {
|
||||
return _mm_cvtss_f32(_mm_mul_ss(_mm_set1_ps(a), _mm_set1_ps(b)));
|
||||
}
|
||||
|
||||
float fdiv(float a, float b) noexcept {
|
||||
return _mm_cvtss_f32(_mm_div_ss(_mm_set1_ps(a), _mm_set1_ps(b)));
|
||||
}
|
||||
|
||||
float fsqrt(float a) noexcept {
|
||||
return _mm_cvtss_f32(_mm_sqrt_ss(_mm_set1_ps(a)));
|
||||
}
|
||||
|
||||
float fmadd_nofma_ref(float a, float b, float c) noexcept {
|
||||
return _mm_cvtss_f32(_mm_add_ss(_mm_mul_ss(_mm_set1_ps(a), _mm_set1_ps(b)), _mm_set1_ps(c)));
|
||||
}
|
||||
|
||||
double fadd(double a, double b) noexcept {
|
||||
return _mm_cvtsd_f64(_mm_add_sd(_mm_set1_pd(a), _mm_set1_pd(b)));
|
||||
}
|
||||
|
||||
double fsub(double a, double b) noexcept {
|
||||
return _mm_cvtsd_f64(_mm_sub_sd(_mm_set1_pd(a), _mm_set1_pd(b)));
|
||||
}
|
||||
|
||||
double fmul(double a, double b) noexcept {
|
||||
return _mm_cvtsd_f64(_mm_mul_sd(_mm_set1_pd(a), _mm_set1_pd(b)));
|
||||
}
|
||||
|
||||
double fdiv(double a, double b) noexcept {
|
||||
return _mm_cvtsd_f64(_mm_div_sd(_mm_set1_pd(a), _mm_set1_pd(b)));
|
||||
}
|
||||
|
||||
double fsqrt(double a) noexcept {
|
||||
return _mm_cvtsd_f64(_mm_sqrt_sd(_mm_setzero_pd(), _mm_set1_pd(a)));
|
||||
}
|
||||
|
||||
double fmadd_nofma_ref(double a, double b, double c) noexcept {
|
||||
return _mm_cvtsd_f64(_mm_add_sd(_mm_mul_sd(_mm_set1_pd(a), _mm_set1_pd(b)), _mm_set1_pd(c)));
|
||||
}
|
||||
|
||||
} // {UniCompilerTests}
|
||||
|
||||
#endif // ASMJIT_UJIT_X86
|
||||
169
asmjit-testing/tests/asmjit_test_x86_sections.cpp
Normal file
169
asmjit-testing/tests/asmjit_test_x86_sections.cpp
Normal file
@@ -0,0 +1,169 @@
|
||||
// This file is part of AsmJit project <https://asmjit.com>
|
||||
//
|
||||
// See <asmjit/core.h> or LICENSE.md for license and copyright information
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// This is a working example that demonstrates how multiple sections can be
|
||||
// used in a JIT-based code generator. It shows also the necessary tooling
|
||||
// that is expected to be done by the user when the feature is used. It's
|
||||
// important to handle the following cases:
|
||||
//
|
||||
// - Assign offsets to sections when the code generation is finished.
|
||||
// - Tell the CodeHolder to resolve unresolved fixups and check whether
|
||||
// all fixups were resolved.
|
||||
// - Relocate the code
|
||||
// - Copy the code to the destination address.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#include <asmjit/core.h>
|
||||
#if ASMJIT_ARCH_X86 && !defined(ASMJIT_NO_X86) && !defined(ASMJIT_NO_JIT)
|
||||
|
||||
#include <asmjit/x86.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
using namespace asmjit;
|
||||
|
||||
// The generated function is very simple, it only accesses the built-in data
|
||||
// (from .data section) at the index as provided by its first argument. This
|
||||
// data is inlined into the resulting function so we can use it this array
|
||||
// for verification that the function returns correct values.
|
||||
static const uint8_t data_array[] = { 2, 9, 4, 7, 1, 3, 8, 5, 6, 0 };
|
||||
|
||||
static void fail(const char* message, Error err) {
|
||||
printf("** FAILURE: %s (%s) **\n", message, DebugUtils::error_as_string(err));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int main() {
|
||||
printf("AsmJit X86 Sections Test\n\n");
|
||||
|
||||
Environment env = Environment::host();
|
||||
JitAllocator allocator;
|
||||
|
||||
#ifndef ASMJIT_NO_LOGGING
|
||||
FileLogger logger(stdout);
|
||||
logger.set_indentation(FormatIndentationGroup::kCode, 2);
|
||||
#endif
|
||||
|
||||
CodeHolder code;
|
||||
code.init(env);
|
||||
|
||||
#ifndef ASMJIT_NO_LOGGING
|
||||
code.set_logger(&logger);
|
||||
#endif
|
||||
|
||||
Section* data_section;
|
||||
Error err = code.new_section(Out(data_section), ".data", SIZE_MAX, SectionFlags::kNone, 8);
|
||||
|
||||
if (err != Error::kOk) {
|
||||
fail("Failed to create a .data section", err);
|
||||
}
|
||||
else {
|
||||
printf("Generating code:\n");
|
||||
x86::Assembler a(&code);
|
||||
x86::Gp idx = a.zax();
|
||||
x86::Gp addr = a.zcx();
|
||||
|
||||
Label data = a.new_label();
|
||||
|
||||
FuncDetail func;
|
||||
func.init(FuncSignature::build<size_t, size_t>(), code.environment());
|
||||
|
||||
FuncFrame frame;
|
||||
frame.init(func);
|
||||
frame.add_dirty_regs(idx, addr);
|
||||
|
||||
FuncArgsAssignment args(&func);
|
||||
args.assign_all(idx);
|
||||
args.update_func_frame(frame);
|
||||
frame.finalize();
|
||||
|
||||
a.emit_prolog(frame);
|
||||
a.emit_args_assignment(frame, args);
|
||||
|
||||
a.lea(addr, x86::ptr(data));
|
||||
a.movzx(idx, x86::byte_ptr(addr, idx));
|
||||
|
||||
a.emit_epilog(frame);
|
||||
|
||||
a.section(data_section);
|
||||
a.bind(data);
|
||||
|
||||
a.embed(data_array, sizeof(data_array));
|
||||
}
|
||||
|
||||
// Manually change he offsets of each section, start at 0. This code is very similar to
|
||||
// what `CodeHolder::flatten()` does, however, it's shown here how to do it explicitly.
|
||||
printf("\nCalculating section offsets:\n");
|
||||
uint64_t offset = 0;
|
||||
for (Section* section : code.sections_by_order()) {
|
||||
offset = Support::align_up(offset, section->alignment());
|
||||
section->set_offset(offset);
|
||||
offset += section->real_size();
|
||||
|
||||
printf(" [0x%08X %s] {Id=%u Size=%u}\n",
|
||||
uint32_t(section->offset()),
|
||||
section->name(),
|
||||
section->section_id(),
|
||||
uint32_t(section->real_size()));
|
||||
}
|
||||
size_t code_size = size_t(offset);
|
||||
printf(" Final code size: %zu\n", code_size);
|
||||
|
||||
// Resolve cross-section fixups (if any). On 32-bit X86 this is not necessary
|
||||
// as this is handled through relocations as the addressing is different.
|
||||
if (code.has_unresolved_fixups()) {
|
||||
printf("\nResolving cross-section fixups:\n");
|
||||
printf(" Before 'resolve_cross_section_fixups()': %zu\n", code.unresolved_fixup_count());
|
||||
|
||||
err = code.resolve_cross_section_fixups();
|
||||
if (err != Error::kOk) {
|
||||
fail("Failed to resolve cross-section fixups", err);
|
||||
}
|
||||
printf(" After 'resolve_cross_section_fixups()': %zu\n", code.unresolved_fixup_count());
|
||||
}
|
||||
|
||||
// Allocate memory for the function and relocate it there.
|
||||
JitAllocator::Span span;
|
||||
err = allocator.alloc(Out(span), code_size);
|
||||
if (err != Error::kOk)
|
||||
fail("Failed to allocate executable memory", err);
|
||||
|
||||
// Relocate to the base-address of the allocated memory.
|
||||
code.relocate_to_base(uint64_t(uintptr_t(span.rx())));
|
||||
|
||||
allocator.write(span, [&](JitAllocator::Span& span) noexcept -> Error {
|
||||
// Copy the flattened code into `mem.rw`. There are two ways. You can either copy
|
||||
// everything manually by iterating over all sections or use `copy_flattened_data`.
|
||||
// This code is similar to what `copy_flattened_data(p, code_size, 0)` would do:
|
||||
for (Section* section : code.sections_by_order())
|
||||
memcpy(static_cast<uint8_t*>(span.rw()) + size_t(section->offset()), section->data(), section->buffer_size());
|
||||
return Error::kOk;
|
||||
});
|
||||
|
||||
// Execute the function and test whether it works.
|
||||
using Func = size_t (*)(size_t idx);
|
||||
Func fn = (Func)span.rx();
|
||||
|
||||
printf("\n");
|
||||
if (fn(0) != data_array[0] ||
|
||||
fn(3) != data_array[3] ||
|
||||
fn(6) != data_array[6] ||
|
||||
fn(9) != data_array[9] ) {
|
||||
printf("** FAILURE: The generated function returned incorrect result(s) **\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("** SUCCESS **\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
int main() {
|
||||
printf("!! This test is disabled: ASMJIT_NO_JIT or unsuitable target architecture !!\n\n");
|
||||
return 0;
|
||||
}
|
||||
#endif // ASMJIT_ARCH_X86 && !ASMJIT_NO_X86 && !ASMJIT_NO_JIT
|
||||
310
asmjit-testing/tests/broken.cpp
Normal file
310
asmjit-testing/tests/broken.cpp
Normal file
@@ -0,0 +1,310 @@
|
||||
// Broken - Lightweight unit testing for C++
|
||||
//
|
||||
// This is free and unencumbered software released into the public domain.
|
||||
//
|
||||
// Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
// distribute this software, either in source code form or as a compiled
|
||||
// binary, for any purpose, commercial or non-commercial, and by any
|
||||
// means.
|
||||
//
|
||||
// In jurisdictions that recognize copyright laws, the author or authors
|
||||
// of this software dedicate any and all copyright interest in the
|
||||
// software to the public domain. We make this dedication for the benefit
|
||||
// of the public at large and to the detriment of our heirs and
|
||||
// successors. We intend this dedication to be an overt act of
|
||||
// relinquishment in perpetuity of all present and future rights to this
|
||||
// software under copyright law.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
// OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
// For more information, please refer to <http://unlicense.org>
|
||||
|
||||
#include "broken.h"
|
||||
#include <stdarg.h>
|
||||
|
||||
// Broken - Globals
|
||||
// ================
|
||||
|
||||
// Zero initialized globals.
|
||||
struct BrokenGlobal {
|
||||
// Application arguments.
|
||||
int _argc;
|
||||
const char** _argv;
|
||||
|
||||
// Output file.
|
||||
FILE* _file;
|
||||
|
||||
// Unit tests.
|
||||
BrokenAPI::Unit* _unitList;
|
||||
BrokenAPI::Unit* _unitRunning;
|
||||
|
||||
bool has_arg(const char* a) const noexcept {
|
||||
for (int i = 1; i < _argc; i++)
|
||||
if (strcmp(_argv[i], a) == 0)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
inline FILE* file() const noexcept { return _file ? _file : stdout; }
|
||||
};
|
||||
static BrokenGlobal _brokenGlobal;
|
||||
|
||||
// Broken - API
|
||||
// ============
|
||||
|
||||
// Get whether the string `a` starts with string `b`.
|
||||
static bool BrokenAPI_startsWith(const char* a, const char* b) noexcept {
|
||||
for (size_t i = 0; ; i++) {
|
||||
if (b[i] == '\0') return true;
|
||||
if (a[i] != b[i]) return false;
|
||||
}
|
||||
}
|
||||
|
||||
//! Compares names and priority of two unit tests.
|
||||
static int BrokenAPI_compareUnits(const BrokenAPI::Unit* a, const BrokenAPI::Unit* b) noexcept {
|
||||
if (a->priority == b->priority)
|
||||
return strcmp(a->name, b->name);
|
||||
else
|
||||
return a->priority > b->priority ? 1 : -1;
|
||||
}
|
||||
|
||||
// Get whether the strings `a` and `b` are equal, ignoring case and treating
|
||||
// `-` as `_`.
|
||||
static bool BrokenAPI_matchesFilter(const char* a, const char* b) noexcept {
|
||||
for (size_t i = 0; ; i++) {
|
||||
int ca = (unsigned char)a[i];
|
||||
int cb = (unsigned char)b[i];
|
||||
|
||||
// If filter is defined as wildcard the rest automatically matches.
|
||||
if (cb == '*')
|
||||
return true;
|
||||
|
||||
if (ca == '-') ca = '_';
|
||||
if (cb == '-') cb = '_';
|
||||
|
||||
if (ca >= 'A' && ca <= 'Z') ca += 'a' - 'A';
|
||||
if (cb >= 'A' && cb <= 'Z') cb += 'a' - 'A';
|
||||
|
||||
if (ca != cb)
|
||||
return false;
|
||||
|
||||
if (ca == '\0')
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static bool BrokenAPI_canRun(BrokenAPI::Unit* unit) noexcept {
|
||||
BrokenGlobal& global = _brokenGlobal;
|
||||
|
||||
int i, argc = global._argc;
|
||||
const char** argv = global._argv;
|
||||
|
||||
const char* unitName = unit->name;
|
||||
bool has_filter = false;
|
||||
|
||||
for (i = 1; i < argc; i++) {
|
||||
const char* arg = argv[i];
|
||||
|
||||
if (BrokenAPI_startsWith(arg, "--run-") && strcmp(arg, "--run-all") != 0) {
|
||||
has_filter = true;
|
||||
|
||||
if (BrokenAPI_matchesFilter(unitName, arg + 6))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// If no filter has been specified the default is to run.
|
||||
return !has_filter;
|
||||
}
|
||||
|
||||
static void BrokenAPI_runUnit(BrokenAPI::Unit* unit) noexcept {
|
||||
BrokenAPI::info("Running %s", unit->name);
|
||||
|
||||
_brokenGlobal._unitRunning = unit;
|
||||
unit->entry();
|
||||
_brokenGlobal._unitRunning = NULL;
|
||||
}
|
||||
|
||||
static void BrokenAPI_runAll() noexcept {
|
||||
BrokenAPI::Unit* unit = _brokenGlobal._unitList;
|
||||
|
||||
bool has_units = unit != NULL;
|
||||
size_t count = 0;
|
||||
int currentPriority = 0;
|
||||
|
||||
while (unit != NULL) {
|
||||
if (BrokenAPI_canRun(unit)) {
|
||||
if (currentPriority != unit->priority) {
|
||||
if (count)
|
||||
INFO("");
|
||||
INFO("[[Priority=%d]]", unit->priority);
|
||||
}
|
||||
|
||||
currentPriority = unit->priority;
|
||||
BrokenAPI_runUnit(unit);
|
||||
count++;
|
||||
}
|
||||
unit = unit->next;
|
||||
}
|
||||
|
||||
if (count) {
|
||||
INFO("\nSuccess:");
|
||||
INFO(" All tests passed!");
|
||||
}
|
||||
else {
|
||||
INFO("\nWarning:");
|
||||
INFO(" No units %s!", has_units ? "matched the filter" : "defined");
|
||||
}
|
||||
}
|
||||
|
||||
static void BrokenAPI_listAll() noexcept {
|
||||
BrokenAPI::Unit* unit = _brokenGlobal._unitList;
|
||||
|
||||
if (unit != NULL) {
|
||||
INFO("Units:");
|
||||
do {
|
||||
INFO(" %s [priority=%d]", unit->name, unit->priority);
|
||||
unit = unit->next;
|
||||
} while (unit != NULL);
|
||||
}
|
||||
else {
|
||||
INFO("Warning:");
|
||||
INFO(" No units defined!");
|
||||
}
|
||||
}
|
||||
|
||||
bool BrokenAPI::has_arg(const char* name) noexcept {
|
||||
return _brokenGlobal.has_arg(name);
|
||||
}
|
||||
|
||||
void BrokenAPI::addUnit(Unit* unit) noexcept {
|
||||
Unit** pPrev = &_brokenGlobal._unitList;
|
||||
Unit* current = *pPrev;
|
||||
|
||||
// C++ static initialization doesn't guarantee anything. We sort all units by
|
||||
// name so the execution will always happen in deterministic order.
|
||||
while (current != NULL) {
|
||||
if (BrokenAPI_compareUnits(current, unit) >= 0)
|
||||
break;
|
||||
|
||||
pPrev = ¤t->next;
|
||||
current = *pPrev;
|
||||
}
|
||||
|
||||
*pPrev = unit;
|
||||
unit->next = current;
|
||||
}
|
||||
|
||||
void BrokenAPI::setOutputFile(FILE* file) noexcept {
|
||||
BrokenGlobal& global = _brokenGlobal;
|
||||
|
||||
global._file = file;
|
||||
}
|
||||
|
||||
int BrokenAPI::run(int argc, const char* argv[], Entry on_before_run, Entry onAfterRun) {
|
||||
BrokenGlobal& global = _brokenGlobal;
|
||||
|
||||
global._argc = argc;
|
||||
global._argv = argv;
|
||||
|
||||
if (global.has_arg("--help")) {
|
||||
INFO("Options:");
|
||||
INFO(" --help - print this usage");
|
||||
INFO(" --list - list all tests");
|
||||
INFO(" --run-... - run a test(s), trailing wildcards supported");
|
||||
INFO(" --run-all - run all tests (default)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (global.has_arg("--list")) {
|
||||
BrokenAPI_listAll();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (on_before_run)
|
||||
on_before_run();
|
||||
|
||||
// We don't care about filters here, it's implemented by `runAll`.
|
||||
BrokenAPI_runAll();
|
||||
|
||||
if (onAfterRun)
|
||||
onAfterRun();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void BrokenAPI_printMessage(const char* prefix, const char* fmt, va_list ap) noexcept {
|
||||
BrokenGlobal& global = _brokenGlobal;
|
||||
FILE* dst = global.file();
|
||||
|
||||
if (!fmt || fmt[0] == '\0') {
|
||||
fprintf(dst, "\n");
|
||||
}
|
||||
else {
|
||||
// This looks scary, but we really want to use only a single call to vfprintf()
|
||||
// in multithreaded code. So we change the format a bit if necessary.
|
||||
enum : unsigned { kBufferSize = 512 };
|
||||
char staticBuffer[512];
|
||||
|
||||
size_t fmtSize = strlen(fmt);
|
||||
size_t prefix_size = strlen(prefix);
|
||||
|
||||
char* fmtBuf = staticBuffer;
|
||||
if (fmtSize > kBufferSize - 2 - prefix_size)
|
||||
fmtBuf = static_cast<char*>(malloc(fmtSize + prefix_size + 2));
|
||||
|
||||
if (!fmtBuf) {
|
||||
fprintf(dst, "%sCannot allocate buffer for vfprintf()\n", prefix);
|
||||
}
|
||||
else {
|
||||
memcpy(fmtBuf, prefix, prefix_size);
|
||||
memcpy(fmtBuf + prefix_size, fmt, fmtSize);
|
||||
|
||||
fmtSize += prefix_size;
|
||||
if (fmtBuf[fmtSize - 1] != '\n')
|
||||
fmtBuf[fmtSize++] = '\n';
|
||||
fmtBuf[fmtSize] = '\0';
|
||||
|
||||
vfprintf(dst, fmtBuf, ap);
|
||||
|
||||
if (fmtBuf != staticBuffer)
|
||||
free(fmtBuf);
|
||||
}
|
||||
}
|
||||
|
||||
fflush(dst);
|
||||
}
|
||||
|
||||
void BrokenAPI::info(const char* fmt, ...) noexcept {
|
||||
BrokenGlobal& global = _brokenGlobal;
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
BrokenAPI_printMessage(global._unitRunning ? " " : "", fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void BrokenAPI::fail(const char* file, int line, const char* expression, const char* fmt, ...) noexcept {
|
||||
BrokenGlobal& global = _brokenGlobal;
|
||||
FILE* dst = global.file();
|
||||
|
||||
fprintf(dst, " FAILED: %s\n", expression);
|
||||
|
||||
if (fmt) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
BrokenAPI_printMessage(" REASON: ", fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
fprintf(dst, " SOURCE: %s (Line: %d)\n", file, line);
|
||||
fflush(dst);
|
||||
|
||||
abort();
|
||||
}
|
||||
200
asmjit-testing/tests/broken.h
Normal file
200
asmjit-testing/tests/broken.h
Normal file
@@ -0,0 +1,200 @@
|
||||
// Broken - Lightweight unit testing for C++
|
||||
//
|
||||
// This is free and unencumbered software released into the public domain.
|
||||
//
|
||||
// Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
// distribute this software, either in source code form or as a compiled
|
||||
// binary, for any purpose, commercial or non-commercial, and by any
|
||||
// means.
|
||||
//
|
||||
// In jurisdictions that recognize copyright laws, the author or authors
|
||||
// of this software dedicate any and all copyright interest in the
|
||||
// software to the public domain. We make this dedication for the benefit
|
||||
// of the public at large and to the detriment of our heirs and
|
||||
// successors. We intend this dedication to be an overt act of
|
||||
// relinquishment in perpetuity of all present and future rights to this
|
||||
// software under copyright law.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
// OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
// For more information, please refer to <http://unlicense.org>
|
||||
|
||||
#ifndef BROKEN_H_INCLUDED
|
||||
#define BROKEN_H_INCLUDED
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <utility>
|
||||
|
||||
// Hide everything when using Doxygen. Ideally this can be protected by a macro,
|
||||
// but there is not globally and widely used one across multiple projects.
|
||||
|
||||
//! \cond
|
||||
|
||||
// Broken - API
|
||||
// ============
|
||||
|
||||
namespace BrokenAPI {
|
||||
|
||||
//! Entry point of a unit test defined by `UNIT` macro.
|
||||
typedef void (*Entry)(void);
|
||||
|
||||
enum Flags : unsigned {
|
||||
kFlagFinished = 0x1
|
||||
};
|
||||
|
||||
struct Unit;
|
||||
|
||||
bool has_arg(const char* name) noexcept;
|
||||
|
||||
//! Register a new unit test (called automatically by `AutoUnit` and `UNIT`).
|
||||
void addUnit(Unit* unit) noexcept;
|
||||
|
||||
//! Set output file to a `file`.
|
||||
void setOutputFile(FILE* file) noexcept;
|
||||
|
||||
//! Initialize `Broken` framework.
|
||||
//!
|
||||
//! Returns `true` if `run()` should be called.
|
||||
int run(int argc, const char* argv[], Entry on_before_run = nullptr, Entry onAfterRun = nullptr);
|
||||
|
||||
//! Log message, adds automatically new line if not present.
|
||||
void info(const char* fmt, ...) noexcept;
|
||||
|
||||
//! Called on `EXPECT()` failure.
|
||||
void fail(const char* file, int line, const char* expression, const char* fmt, ...) noexcept;
|
||||
|
||||
//! Test defined by `UNIT` macro.
|
||||
struct Unit {
|
||||
Entry entry;
|
||||
const char* name;
|
||||
int priority;
|
||||
unsigned flags;
|
||||
Unit* next;
|
||||
};
|
||||
|
||||
//! Automatic unit registration by using static initialization.
|
||||
class AutoUnit : public Unit {
|
||||
public:
|
||||
inline AutoUnit(Entry entry_, const char* name_, int priority_ = 0, int dummy_ = 0) noexcept {
|
||||
// Not used, only to trick `UNIT()` macro.
|
||||
(void)dummy_;
|
||||
|
||||
this->entry = entry_;
|
||||
this->name = name_;
|
||||
this->priority = priority_;
|
||||
this->flags = 0;
|
||||
this->next = nullptr;
|
||||
addUnit(this);
|
||||
}
|
||||
};
|
||||
|
||||
class Failure {
|
||||
public:
|
||||
const char* _file = nullptr;
|
||||
const char* _expression = nullptr;
|
||||
int _line = 0;
|
||||
bool _handled = false;
|
||||
|
||||
inline Failure(const char* file, int line, const char* expression) noexcept
|
||||
: _file(file),
|
||||
_expression(expression),
|
||||
_line(line) {}
|
||||
|
||||
inline ~Failure() noexcept {
|
||||
if (!_handled)
|
||||
fail(_file, _line, _expression, nullptr);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void message(const char* fmt, Args&&... args) noexcept {
|
||||
fail(_file, _line, _expression, fmt, std::forward<Args>(args)...);
|
||||
_handled = true;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Result>
|
||||
static inline bool check(Result&& result) noexcept { return !!result; }
|
||||
|
||||
template<typename LHS, typename RHS>
|
||||
static inline bool checkEq(LHS&& lhs, RHS&& rhs) noexcept { return lhs == rhs; }
|
||||
|
||||
template<typename LHS, typename RHS>
|
||||
static inline bool checkNe(LHS&& lhs, RHS&& rhs) noexcept { return lhs != rhs; }
|
||||
|
||||
template<typename LHS, typename RHS>
|
||||
static inline bool checkGt(LHS&& lhs, RHS&& rhs) noexcept { return lhs > rhs; }
|
||||
|
||||
template<typename LHS, typename RHS>
|
||||
static inline bool checkGe(LHS&& lhs, RHS&& rhs) noexcept { return lhs >= rhs; }
|
||||
|
||||
template<typename LHS, typename RHS>
|
||||
static inline bool checkLt(LHS&& lhs, RHS&& rhs) noexcept { return lhs < rhs; }
|
||||
|
||||
template<typename LHS, typename RHS>
|
||||
static inline bool checkLe(LHS&& lhs, RHS&& rhs) noexcept { return lhs <= rhs; }
|
||||
|
||||
template<typename Result>
|
||||
static inline bool checkTrue(Result&& result) noexcept { return !!result; }
|
||||
|
||||
template<typename Result>
|
||||
static inline bool checkFalse(Result&& result) noexcept { return !result; }
|
||||
|
||||
template<typename Result>
|
||||
static inline bool checkNull(Result&& result) noexcept { return result == nullptr; }
|
||||
|
||||
template<typename Result>
|
||||
static inline bool checkNotNull(Result&& result) noexcept { return result != nullptr; }
|
||||
|
||||
} // {BrokenAPI}
|
||||
|
||||
// Broken - Macros
|
||||
// ===============
|
||||
|
||||
//! Internal macro used by `UNIT()`.
|
||||
#define BROKEN_UNIT_INTERNAL(NAME, PRIORITY) \
|
||||
static void unit_##NAME##_entry(void); \
|
||||
static ::BrokenAPI::AutoUnit unit_##NAME##_autoinit(unit_##NAME##_entry, #NAME, PRIORITY); \
|
||||
static void unit_##NAME##_entry(void)
|
||||
|
||||
//! \def UNIT(NAME [, PRIORITY])
|
||||
//!
|
||||
//! Define a unit test with an optional priority.
|
||||
//!
|
||||
//! `NAME` can only contain ASCII characters, numbers and underscore. It has the same rules as identifiers in C and C++.
|
||||
//!
|
||||
//! `PRIORITY` specifies the order in which unit tests are run. Lesses value increases the priority. At the moment all
|
||||
//!units are first sorted by priority and then by name - this makes the run always deterministic.
|
||||
#define UNIT(NAME, ...) BROKEN_UNIT_INTERNAL(NAME, __VA_ARGS__ + 0)
|
||||
|
||||
//! #define INFO(FORMAT [, ...])
|
||||
//!
|
||||
//! Informative message printed to `stdout`.
|
||||
#define INFO(...) ::BrokenAPI::info(__VA_ARGS__)
|
||||
|
||||
#define BROKEN_EXPECT_INTERNAL(file, line, expression, result) \
|
||||
for (bool _testInternalResult = (result); !_testInternalResult; _testInternalResult = true) \
|
||||
::BrokenAPI::Failure(file, line, expression)
|
||||
|
||||
#define EXPECT(...) BROKEN_EXPECT_INTERNAL(__FILE__, __LINE__, "EXPECT(" #__VA_ARGS__ ")", !!(__VA_ARGS__))
|
||||
#define EXPECT_EQ(...) BROKEN_EXPECT_INTERNAL(__FILE__, __LINE__, "EXPECT_EQ(" #__VA_ARGS__ ")", ::BrokenAPI::checkEq(__VA_ARGS__))
|
||||
#define EXPECT_NE(...) BROKEN_EXPECT_INTERNAL(__FILE__, __LINE__, "EXPECT_NE(" #__VA_ARGS__ ")", ::BrokenAPI::checkNe(__VA_ARGS__))
|
||||
#define EXPECT_GT(...) BROKEN_EXPECT_INTERNAL(__FILE__, __LINE__, "EXPECT_GT(" #__VA_ARGS__ ")", ::BrokenAPI::checkGt(__VA_ARGS__))
|
||||
#define EXPECT_GE(...) BROKEN_EXPECT_INTERNAL(__FILE__, __LINE__, "EXPECT_GE(" #__VA_ARGS__ ")", ::BrokenAPI::checkGe(__VA_ARGS__))
|
||||
#define EXPECT_LT(...) BROKEN_EXPECT_INTERNAL(__FILE__, __LINE__, "EXPECT_LT(" #__VA_ARGS__ ")", ::BrokenAPI::checkLt(__VA_ARGS__))
|
||||
#define EXPECT_LE(...) BROKEN_EXPECT_INTERNAL(__FILE__, __LINE__, "EXPECT_LE(" #__VA_ARGS__ ")", ::BrokenAPI::checkLe(__VA_ARGS__))
|
||||
#define EXPECT_TRUE(...) BROKEN_EXPECT_INTERNAL(__FILE__, __LINE__, "EXPECT_TRUE(" #__VA_ARGS__ ")", ::BrokenAPI::checkTrue(__VA_ARGS__))
|
||||
#define EXPECT_FALSE(...) BROKEN_EXPECT_INTERNAL(__FILE__, __LINE__, "EXPECT_FALSE(" #__VA_ARGS__ ")", ::BrokenAPI::checkFalse(__VA_ARGS__))
|
||||
#define EXPECT_NULL(...) BROKEN_EXPECT_INTERNAL(__FILE__, __LINE__, "EXPECT_NULL(" #__VA_ARGS__ ")", ::BrokenAPI::checkNull(__VA_ARGS__))
|
||||
#define EXPECT_NOT_NULL(...) BROKEN_EXPECT_INTERNAL(__FILE__, __LINE__, "EXPECT_NOT_NULL(" #__VA_ARGS__ ")", ::BrokenAPI::checkNotNull(__VA_ARGS__))
|
||||
|
||||
//! \endcond
|
||||
|
||||
#endif // BROKEN_H_INCLUDED
|
||||
Reference in New Issue
Block a user