From a63d41e80ba72f652c450d3bd6614327cde8531c Mon Sep 17 00:00:00 2001 From: kobalicek Date: Thu, 25 Jan 2024 22:23:13 +0100 Subject: [PATCH] Added support for mach_vm_remap() for dual mapping mach_vm_remap() allows to create a dual mapping without having to use a file descriptor, which has to open a file or shm memory. The problem is that the recent macos version started displaying a popup message when such file is opened and that annoyed a lot of users. Thus, the initial code-path is no longer used, and mach_vm_remap() is used instead. This change only applies for x86 macs. Apple silicon doesn't allow dual mapping and instead uses MAP_JIT. --- .github/workflows/build-config.json | 43 ++--- .github/workflows/build.yml | 20 +-- CMakeLists.txt | 1 + src/asmjit/core/globals.cpp | 3 +- src/asmjit/core/globals.h | 4 + src/asmjit/core/virtmem.cpp | 193 +++++++++++++++++++--- src/asmjit/core/virtmem.h | 16 +- src/asmjit/x86/x86emitter.h | 4 +- test/asmjit_test_emitters.cpp | 243 +++++++++++++++++++++------- test/asmjit_test_execute.cpp | 103 ++++++++++++ 10 files changed, 507 insertions(+), 123 deletions(-) create mode 100644 test/asmjit_test_execute.cpp diff --git a/.github/workflows/build-config.json b/.github/workflows/build-config.json index f33a8f3..e72d8e3 100644 --- a/.github/workflows/build-config.json +++ b/.github/workflows/build-config.json @@ -12,37 +12,14 @@ ], "tests": [ - { - "cmd": ["asmjit_test_unit", "--quick"], - "optional": true - }, - { - "cmd": ["asmjit_test_assembler"], - "optional": true - }, - { - "cmd": ["asmjit_test_assembler", "--validate"], - "optional": true - }, - { - "cmd": ["asmjit_test_emitters"], - "optional": true - }, - { - "cmd": ["asmjit_test_compiler"], - "optional": true - }, - { - "cmd": ["asmjit_test_instinfo"], - "optional": true - }, - { - "cmd": ["asmjit_test_x86_sections"], - "optional": true - }, - { - "cmd": ["asmjit_test_perf", "--quick"], - "optional": true - } + { "optional": true, "cmd": ["asmjit_test_unit", "--quick"] }, + { "optional": true, "cmd": ["asmjit_test_assembler"] }, + { "optional": true, "cmd": ["asmjit_test_assembler", "--validate"] }, + { "optional": true, "cmd": ["asmjit_test_emitters"] }, + { "optional": true, "cmd": ["asmjit_test_execute"] }, + { "optional": true, "cmd": ["asmjit_test_compiler"] }, + { "optional": true, "cmd": ["asmjit_test_instinfo"] }, + { "optional": true, "cmd": ["asmjit_test_x86_sections"] }, + { "optional": true, "cmd": ["asmjit_test_perf", "--quick"] } ] -} \ No newline at end of file +} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 439b8c8..96b53b5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,12 +14,12 @@ jobs: steps: - name: "Checkout" - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: "Setup node.js" - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: "16" + node-version: "*" - name: "Check Enumerations" run: | @@ -131,28 +131,28 @@ jobs: - { title: "openbsd" , host: "macos-12" , arch: "x86-64" , cc: "clang" , conf: "Release", vm: "openbsd", vm_ver: "7.4" , defs: "ASMJIT_TEST=1" } - { title: "openbsd" , host: "ubuntu-latest" , arch: "arm64" , cc: "clang" , conf: "Release", vm: "openbsd", vm_ver: "7.4" , defs: "ASMJIT_TEST=1" } - - { title: "debian" , host: "ubuntu-latest" , arch: "arm/v7" , cc: "clang" , conf: "Release", vm: "debian:unstable", defs: "ASMJIT_TEST=1" } - - { title: "debian" , host: "ubuntu-latest" , arch: "arm64" , cc: "clang" , conf: "Release", vm: "debian:unstable", defs: "ASMJIT_TEST=1" } - - { title: "debian" , host: "ubuntu-latest" , arch: "riscv64", cc: "clang" , conf: "Release", vm: "debian:unstable", defs: "ASMJIT_TEST=1" } - - { title: "debian" , host: "ubuntu-latest" , arch: "ppc64le", cc: "clang" , conf: "Release", vm: "debian:unstable", defs: "ASMJIT_TEST=1" } + - { title: "debian" , host: "ubuntu-latest" , arch: "arm/v7" , cc: "clang" , conf: "Release", vm: "debian:unstable", defs: "ASMJIT_TEST=1" } + - { title: "debian" , host: "ubuntu-latest" , arch: "arm64" , cc: "clang" , conf: "Release", vm: "debian:unstable", defs: "ASMJIT_TEST=1" } + - { title: "debian" , host: "ubuntu-latest" , arch: "riscv64", cc: "clang" , conf: "Release", vm: "debian:unstable", defs: "ASMJIT_TEST=1" } + - { title: "debian" , host: "ubuntu-latest" , arch: "ppc64le", cc: "clang" , conf: "Release", vm: "debian:unstable", defs: "ASMJIT_TEST=1" } name: "${{matrix.title}}/${{matrix.arch}}, ${{matrix.cc}} ${{matrix.conf}}" runs-on: "${{matrix.host}}" steps: - name: "Checkout" - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: "source" - name: "Checkout Build Actions" - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: build-actions/build-actions path: "build-actions" - name: "Python" - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.x" diff --git a/CMakeLists.txt b/CMakeLists.txt index 3553658..e8395c7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -666,6 +666,7 @@ if (NOT ASMJIT_EMBED) CFLAGS_REL ${ASMJIT_PRIVATE_CFLAGS_REL}) foreach(_target asmjit_test_emitters + asmjit_test_execute asmjit_test_x86_sections) asmjit_add_target(${_target} TEST SOURCES test/${_target}.cpp diff --git a/src/asmjit/core/globals.cpp b/src/asmjit/core/globals.cpp index 1fbb399..4a98431 100644 --- a/src/asmjit/core/globals.cpp +++ b/src/asmjit/core/globals.cpp @@ -87,6 +87,7 @@ ASMJIT_FAVOR_SIZE const char* DebugUtils::errorAsString(Error err) noexcept { "ExpressionOverflow\0" "FailedToOpenAnonymousMemory\0" "FailedToOpenFile\0" + "ProtectionFailure\0" "\0"; static const uint16_t sErrorIndex[] = { @@ -94,7 +95,7 @@ ASMJIT_FAVOR_SIZE const char* DebugUtils::errorAsString(Error err) noexcept { 247, 264, 283, 298, 314, 333, 352, 370, 392, 410, 429, 444, 460, 474, 488, 508, 533, 551, 573, 595, 612, 629, 645, 661, 677, 694, 709, 724, 744, 764, 784, 817, 837, 852, 869, 888, 909, 929, 943, 964, 978, 996, 1012, 1028, 1047, - 1073, 1088, 1104, 1119, 1134, 1164, 1188, 1207, 1235, 1252 + 1073, 1088, 1104, 1119, 1134, 1164, 1188, 1207, 1235, 1252, 1270 }; // @EnumStringEnd@ diff --git a/src/asmjit/core/globals.h b/src/asmjit/core/globals.h index 3058726..db921cf 100644 --- a/src/asmjit/core/globals.h +++ b/src/asmjit/core/globals.h @@ -334,6 +334,10 @@ enum ErrorCode : uint32_t { //! \note This is a generic error that is used by internal filesystem API. kErrorFailedToOpenFile, + //! Protection failure can be returned from a virtual memory allocator or when trying to change memory access + //! permissions. + kErrorProtectionFailure, + // @EnumValuesEnd@ //! Count of AsmJit error codes. diff --git a/src/asmjit/core/virtmem.cpp b/src/asmjit/core/virtmem.cpp index d9a74e8..7438477 100644 --- a/src/asmjit/core/virtmem.cpp +++ b/src/asmjit/core/virtmem.cpp @@ -76,8 +76,6 @@ #define MAP_ANONYMOUS MAP_ANON #endif - #define ASMJIT_ANONYMOUS_MEMORY_USE_FD - // Android NDK doesn't provide `shm_open()` and `shm_unlink()`. #if !defined(__BIONIC__) && !defined(ASMJIT_NO_SHM_OPEN) #define ASMJIT_HAS_SHM_OPEN @@ -89,18 +87,60 @@ #define ASMJIT_VM_SHM_DETECT 1 #endif - #if defined(__APPLE__) && TARGET_OS_OSX && ASMJIT_ARCH_ARM >= 64 - #define ASMJIT_HAS_PTHREAD_JIT_WRITE_PROTECT_NP + #if defined(__APPLE__) && TARGET_OS_OSX + #if ASMJIT_ARCH_X86 != 0 + #define ASMJIT_ANONYMOUS_MEMORY_USE_MACH_VM_REMAP + #endif + #if ASMJIT_ARCH_ARM >= 64 + #define ASMJIT_HAS_PTHREAD_JIT_WRITE_PROTECT_NP + #endif + #endif + + #if defined(__APPLE__) && ASMJIT_ARCH_X86 == 0 + #define ASMJIT_NO_DUAL_MAPPING #endif #if defined(__NetBSD__) && defined(MAP_REMAPDUP) && defined(PROT_MPROTECT) - #undef ASMJIT_ANONYMOUS_MEMORY_USE_FD #define ASMJIT_ANONYMOUS_MEMORY_USE_REMAPDUP #endif + + #if !defined(ASMJIT_ANONYMOUS_MEMORY_USE_REMAPDUP) && \ + !defined(ASMJIT_ANONYMOUS_MEMORY_USE_MACH_VM_REMAP) && \ + !defined(ASMJIT_NO_DUAL_MAPPING) + #define ASMJIT_ANONYMOUS_MEMORY_USE_FD + #endif #endif #include +#if defined(ASMJIT_ANONYMOUS_MEMORY_USE_MACH_VM_REMAP) +#include +#include + +extern "C" { + +#ifdef mig_external +mig_external +#else +extern +#endif +kern_return_t mach_vm_remap( + vm_map_t target_task, + mach_vm_address_t *target_address, + mach_vm_size_t size, + mach_vm_offset_t mask, + int flags, + vm_map_t src_task, + mach_vm_address_t src_address, + boolean_t copy, + vm_prot_t *cur_protection, + vm_prot_t *max_protection, + vm_inherit_t inheritance +); + +} // {extern "C"} +#endif + ASMJIT_BEGIN_SUB_NAMESPACE(VirtMem) // Virtual Memory Utilities @@ -757,8 +797,7 @@ static inline int mmMapJitFromMemoryFlags(MemoryFlags memoryFlags) noexcept { } static inline bool hasDualMappingSupport() noexcept { -#if defined(__APPLE__) && TARGET_OS_OSX && ASMJIT_ARCH_X86 == 0 - // Apple platforms don't allow dual-mapping on non-x86 hardware. +#if defined(ASMJIT_NO_DUAL_MAPPING) return false; #else return true; @@ -850,6 +889,7 @@ Error protect(void* p, size_t size, MemoryFlags memoryFlags) noexcept { // Virtual Memory [Posix] - Dual Mapping // ===================================== +#if !defined(ASMJIT_NO_DUAL_MAPPING) static Error unmapDualMapping(DualMapping* dm, size_t size) noexcept { Error err1 = unmapMemory(dm->rx, size); Error err2 = kErrorOk; @@ -865,6 +905,7 @@ static Error unmapDualMapping(DualMapping* dm, size_t size) noexcept { dm->rw = nullptr; return kErrorOk; } +#endif // !ASMJIT_NO_DUAL_MAPPING #if defined(ASMJIT_ANONYMOUS_MEMORY_USE_REMAPDUP) static Error allocDualMappingUsingRemapdup(DualMapping* dmOut, size_t size, MemoryFlags memoryFlags) noexcept { @@ -897,16 +938,105 @@ static Error allocDualMappingUsingRemapdup(DualMapping* dmOut, size_t size, Memo } #endif -Error allocDualMapping(DualMapping* dm, size_t size, MemoryFlags memoryFlags) noexcept { - dm->rx = nullptr; - dm->rw = nullptr; +#if defined(ASMJIT_ANONYMOUS_MEMORY_USE_MACH_VM_REMAP) +static Error asmjitErrorFromKernResult(kern_return_t result) noexcept { + switch (result) { + case KERN_PROTECTION_FAILURE: + return DebugUtils::errored(kErrorProtectionFailure); + case KERN_NO_SPACE: + return DebugUtils::errored(kErrorOutOfMemory); + case KERN_INVALID_ARGUMENT: + return DebugUtils::errored(kErrorInvalidArgument); + default: + return DebugUtils::errored(kErrorInvalidState); + } +} - if (off_t(size) <= 0) - return DebugUtils::errored(size == 0 ? kErrorInvalidArgument : kErrorTooLarge); +static Error allocDualMappingUsingMachVmRemap(DualMapping* dmOut, size_t size, MemoryFlags memoryFlags) noexcept { + DualMapping dm {}; -#if defined(ASMJIT_ANONYMOUS_MEMORY_USE_REMAPDUP) - return allocDualMappingUsingRemapdup(dm, size, memoryFlags); -#elif defined(ASMJIT_ANONYMOUS_MEMORY_USE_FD) + MemoryFlags mmapFlags = MemoryFlags::kAccessReadWrite | (memoryFlags & MemoryFlags::kMapShared); + ASMJIT_PROPAGATE(mapMemory(&dm.rx, size, mmapFlags)); + + vm_prot_t curProt; + vm_prot_t maxProt; + + int rwProtectFlags = VM_PROT_READ | VM_PROT_WRITE; + int rxProtectFlags = VM_PROT_READ; + + if (Support::test(memoryFlags, MemoryFlags::kAccessExecute)) + rxProtectFlags |= VM_PROT_EXECUTE; + + kern_return_t result {}; + do { + vm_map_t task = mach_task_self(); + mach_vm_address_t remappedAddr {}; + +#if defined(VM_FLAGS_RANDOM_ADDR) + int remapFlags = VM_FLAGS_ANYWHERE | VM_FLAGS_RANDOM_ADDR; +#else + int remapFlags = VM_FLAGS_ANYWHERE; +#endif + + // Try to remap the existing memory into a different address. + result = mach_vm_remap( + task, // target_task + &remappedAddr, // target_address + size, // size + 0, // mask + remapFlags, // flags + task, // src_task + (mach_vm_address_t)dm.rx, // src_address + false, // copy + &curProt, // cur_protection + &maxProt, // max_protection + VM_INHERIT_DEFAULT); // inheritance + + if (result != KERN_SUCCESS) + break; + + dm.rw = (void*)remappedAddr; + + // Now, try to change permissions of both map regions into RW and RX. The vm_protect() + // API is used twice as we also want to set maximum permissions, so nobody would be + // allowed to change the RX region back to RW or RWX (if RWX is allowed). + uint32_t i; + for (i = 0; i < 2; i++) { + bool setMaximum = (i == 0); + + result = vm_protect( + task, // target_task + (vm_address_t)dm.rx, // address + size, // size + setMaximum, // set_maximum + rxProtectFlags); // new_protection + + if (result != KERN_SUCCESS) + break; + + result = vm_protect(task, // target_task + (vm_address_t)dm.rw, // address + size, // size + setMaximum, // set_maximum + rwProtectFlags); // new_protection + + if (result != KERN_SUCCESS) + break; + } + } while (0); + + if (result != KERN_SUCCESS) { + unmapDualMapping(&dm, size); + return DebugUtils::errored(asmjitErrorFromKernResult(result)); + } + + *dmOut = dm; + return kErrorOk; +} +#endif // ASMJIT_ANONYMOUS_MEMORY_USE_MACH_VM_REMAP + +#if defined(ASMJIT_ANONYMOUS_MEMORY_USE_FD) +static Error allocDualMappingUsingFile(DualMapping* dm, size_t size, MemoryFlags memoryFlags) noexcept { bool preferTmpOverDevShm = Support::test(memoryFlags, MemoryFlags::kMappingPreferTmp); if (!preferTmpOverDevShm) { AnonymousMemoryStrategy strategy; @@ -932,13 +1062,39 @@ Error allocDualMapping(DualMapping* dm, size_t size, MemoryFlags memoryFlags) no dm->rx = ptr[0]; dm->rw = ptr[1]; return kErrorOk; +} +#endif // ASMJIT_ANONYMOUS_MEMORY_USE_FD + +Error allocDualMapping(DualMapping* dm, size_t size, MemoryFlags memoryFlags) noexcept { + dm->rx = nullptr; + dm->rw = nullptr; + +#if defined(ASMJIT_NO_DUAL_MAPPING) + DebugUtils::unused(size, memoryFlags); + return DebugUtils::errored(kErrorFeatureNotEnabled); #else - #error "[asmjit] VirtMem::allocDualMapping() doesn't have implementation for the target OS and compiler" + if (off_t(size) <= 0) + return DebugUtils::errored(size == 0 ? kErrorInvalidArgument : kErrorTooLarge); + +#if defined(ASMJIT_ANONYMOUS_MEMORY_USE_REMAPDUP) + return allocDualMappingUsingRemapdup(dm, size, memoryFlags); +#elif defined(ASMJIT_ANONYMOUS_MEMORY_USE_MACH_VM_REMAP) + return allocDualMappingUsingMachVmRemap(dm, size, memoryFlags); +#elif defined(ASMJIT_ANONYMOUS_MEMORY_USE_FD) + return allocDualMappingUsingFile(dm, size, memoryFlags); +#else + #error "[asmjit] VirtMem::allocDualMapping() doesn't have implementation for the target OS or architecture" #endif +#endif // ASMJIT_NO_DUAL_MAPPING } Error releaseDualMapping(DualMapping* dm, size_t size) noexcept { +#if defined(ASMJIT_NO_DUAL_MAPPING) + DebugUtils::unused(dm, size); + return DebugUtils::errored(kErrorFeatureNotEnabled); +#else return unmapDualMapping(dm, size); +#endif // ASMJIT_NO_DUAL_MAPPING } #endif @@ -1039,8 +1195,9 @@ UNIT(virt_mem) { INFO("VirtMem::hardenedRuntimeInfo():"); INFO(" flags:"); - INFO(" kEnabled: %s", Support::test(hardenedFlags, VirtMem::HardenedRuntimeFlags::kEnabled) ? "true" : "false"); - INFO(" kMapJit: %s", Support::test(hardenedFlags, VirtMem::HardenedRuntimeFlags::kMapJit) ? "true" : "false"); + INFO(" kEnabled: %s" , Support::test(hardenedFlags, VirtMem::HardenedRuntimeFlags::kEnabled ) ? "true" : "false"); + INFO(" kMapJit: %s" , Support::test(hardenedFlags, VirtMem::HardenedRuntimeFlags::kMapJit ) ? "true" : "false"); + INFO(" kDualMapping: %s", Support::test(hardenedFlags, VirtMem::HardenedRuntimeFlags::kDualMapping) ? "true" : "false"); } ASMJIT_END_NAMESPACE diff --git a/src/asmjit/core/virtmem.h b/src/asmjit/core/virtmem.h index c2d16a3..17996dc 100644 --- a/src/asmjit/core/virtmem.h +++ b/src/asmjit/core/virtmem.h @@ -10,6 +10,7 @@ #ifndef ASMJIT_NO_JIT #include "../core/globals.h" +#include "../core/support.h" ASMJIT_BEGIN_NAMESPACE @@ -218,15 +219,28 @@ enum class HardenedRuntimeFlags : uint32_t { //! Read+Write+Execute can only be allocated with MAP_JIT flag (Apple specific, only available on Apple platforms). kMapJit = 0x00000002u, - //! Read+Write+Executa can be allocated with dual mapping approach (one region with RW and the other with RX). + //! Read+Write+Execute can be allocated with dual mapping approach (one region with RW and the other with RX). kDualMapping = 0x00000004u }; ASMJIT_DEFINE_ENUM_FLAGS(HardenedRuntimeFlags) //! Hardened runtime information. struct HardenedRuntimeInfo { + //! \name Members + //! \{ + //! Hardened runtime flags. HardenedRuntimeFlags flags; + + //! \} + + //! \name Accessors + //! \{ + + //! Tests whether the hardened runtime `flag` is set. + ASMJIT_INLINE_NODEBUG bool hasFlag(HardenedRuntimeFlags flag) const noexcept { return Support::test(flags, flag); } + + //! \} }; //! Returns runtime features provided by the OS. diff --git a/src/asmjit/x86/x86emitter.h b/src/asmjit/x86/x86emitter.h index b9a6f4c..4855e99 100644 --- a/src/asmjit/x86/x86emitter.h +++ b/src/asmjit/x86/x86emitter.h @@ -2827,7 +2827,7 @@ public: ASMJIT_INST_3x(vpand, Vpand, Vec, Vec, Mem) // AVX+ ASMJIT_INST_3x(vpandd, Vpandd, Vec, Vec, Vec) // AVX512_F{kz|b32} ASMJIT_INST_3x(vpandd, Vpandd, Vec, Vec, Mem) // AVX512_F{kz|b32} - ASMJIT_INST_3x(vpandn, Vpandn, Vec, Vec, Vec) // AV+ + ASMJIT_INST_3x(vpandn, Vpandn, Vec, Vec, Vec) // AVX+ ASMJIT_INST_3x(vpandn, Vpandn, Vec, Vec, Mem) // AVX+ ASMJIT_INST_3x(vpandnd, Vpandnd, Vec, Vec, Vec) // AVX512_F{kz|b32} ASMJIT_INST_3x(vpandnd, Vpandnd, Vec, Vec, Mem) // AVX512_F{kz|b32} @@ -3186,7 +3186,7 @@ public: ASMJIT_INST_2x(vpopcntq, Vpopcntq, Vec, Mem) // AVX512_VPOPCNTDQ{kz|b64} ASMJIT_INST_2x(vpopcntw, Vpopcntw, Vec, Vec) // AVX512_BITALG{kz|b32} ASMJIT_INST_2x(vpopcntw, Vpopcntw, Vec, Mem) // AVX512_BITALG{kz|b32} - ASMJIT_INST_3x(vpor, Vpor, Vec, Vec, Vec) // AV+ + ASMJIT_INST_3x(vpor, Vpor, Vec, Vec, Vec) // AVX+ ASMJIT_INST_3x(vpor, Vpor, Vec, Vec, Mem) // AVX+ ASMJIT_INST_3x(vpord, Vpord, Vec, Vec, Vec) // AVX512_F{kz|b32} ASMJIT_INST_3x(vpord, Vpord, Vec, Vec, Mem) // AVX512_F{kz|b32} diff --git a/test/asmjit_test_emitters.cpp b/test/asmjit_test_emitters.cpp index 7f92c32..47fcaff 100644 --- a/test/asmjit_test_emitters.cpp +++ b/test/asmjit_test_emitters.cpp @@ -5,8 +5,24 @@ #include -#if ASMJIT_ARCH_X86 && !defined(ASMJIT_NO_X86) && !defined(ASMJIT_NO_JIT) +static void printInfo() noexcept { + printf("AsmJit Emitters Test-Suite v%u.%u.%u\n", + unsigned((ASMJIT_LIBRARY_VERSION >> 16) ), + unsigned((ASMJIT_LIBRARY_VERSION >> 8) & 0xFF), + unsigned((ASMJIT_LIBRARY_VERSION ) & 0xFF)); +} + +#if !defined(ASMJIT_NO_JIT) && ( \ + (ASMJIT_ARCH_X86 != 0 && !defined(ASMJIT_NO_X86 )) || \ + (ASMJIT_ARCH_ARM == 64 && !defined(ASMJIT_NO_AARCH64)) ) + +#if ASMJIT_ARCH_X86 != 0 #include +#endif + +#if ASMJIT_ARCH_ARM == 64 +#include +#endif #include #include @@ -17,9 +33,13 @@ using namespace asmjit; // Signature of the generated function. typedef void (*SumIntsFunc)(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 makeRawFunc(x86::Emitter* emitter) noexcept { +static void generateFuncWithEmitter(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. @@ -39,8 +59,8 @@ static void makeRawFunc(x86::Emitter* emitter) noexcept { FuncFrame frame; frame.init(func); - // Make XMM0 and XMM1 dirty. VEC group includes XMM|YMM|ZMM registers. - frame.addDirtyRegs(x86::xmm0, x86::xmm1); + // Make or registers dirty. + frame.addDirtyRegs(vec0, vec1); FuncArgsAssignment args(&func); // Create arguments assignment context. args.assignAll(dst, src_a, src_b); // Assign our registers to arguments. @@ -63,7 +83,7 @@ static void makeRawFunc(x86::Emitter* emitter) noexcept { #ifndef ASMJIT_NO_COMPILER // This function works with x86::Compiler, provided for comparison. -static void makeCompiledFunc(x86::Compiler* cc) noexcept { +static void generateFuncWithCompiler(x86::Compiler* cc) noexcept { x86::Gp dst = cc->newIntPtr("dst"); x86::Gp src_a = cc->newIntPtr("src_a"); x86::Gp src_b = cc->newIntPtr("src_b"); @@ -83,6 +103,154 @@ static void makeCompiledFunc(x86::Compiler* cc) noexcept { } #endif +static Error generateFunc(CodeHolder& code, EmitterType emitterType) noexcept { + switch (emitterType) { + case EmitterType::kAssembler: { + printf("Using x86::Assembler:\n"); + x86::Assembler a(&code); + generateFuncWithEmitter(a.as()); + return kErrorOk; + } + +#ifndef ASMJIT_NO_BUILDER + case EmitterType::kBuilder: { + printf("Using x86::Builder:\n"); + x86::Builder cb(&code); + generateFuncWithEmitter(cb.as()); + + return cb.finalize(); + } +#endif + +#ifndef ASMJIT_NO_COMPILER + case EmitterType::kCompiler: { + printf("Using x86::Compiler:\n"); + x86::Compiler cc(&code); + generateFuncWithCompiler(&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 generateFuncWithEmitter(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(), emitter->environment()); + + FuncFrame frame; + frame.init(func); + + // Make XMM0 and XMM1 dirty. VEC group includes XMM|YMM|ZMM registers. + frame.addDirtyRegs(vec0, vec1, vec2); + + FuncArgsAssignment args(&func); // Create arguments assignment context. + args.assignAll(dst, src_a, src_b); // Assign our registers to arguments. + args.updateFuncFrame(frame); // Reflect our args in FuncFrame. + frame.finalize(); + + // Emit prolog and allocate arguments to registers. + emitter->emitProlog(frame); + emitter->emitArgsAssignment(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->emitEpilog(frame); +} + +#ifndef ASMJIT_NO_COMPILER +// This function works with x86::Compiler, provided for comparison. +static void generateFuncWithCompiler(a64::Compiler* cc) noexcept { + a64::Gp dst = cc->newIntPtr("dst"); + a64::Gp src_a = cc->newIntPtr("src_a"); + a64::Gp src_b = cc->newIntPtr("src_b"); + a64::Vec vec0 = cc->newVecQ("vec0"); + a64::Vec vec1 = cc->newVecQ("vec1"); + a64::Vec vec2 = cc->newVecQ("vec2"); + + FuncNode* funcNode = cc->addFunc(FuncSignature::build()); + funcNode->setArg(0, dst); + funcNode->setArg(1, src_a); + funcNode->setArg(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->endFunc(); +} +#endif + +static Error generateFunc(CodeHolder& code, EmitterType emitterType) noexcept { + switch (emitterType) { + case EmitterType::kAssembler: { + printf("Using a64::Assembler:\n"); + a64::Assembler a(&code); + generateFuncWithEmitter(a.as()); + return kErrorOk; + } + +#ifndef ASMJIT_NO_BUILDER + case EmitterType::kBuilder: { + printf("Using a64::Builder:\n"); + a64::Builder cb(&code); + generateFuncWithEmitter(cb.as()); + + return cb.finalize(); + } +#endif + +#ifndef ASMJIT_NO_COMPILER + case EmitterType::kCompiler: { + printf("Using a64::Compiler:\n"); + a64::Compiler cc(&code); + generateFuncWithCompiler(&cc); + + return cc.finalize(); + } +#endif + + default: { + printf("** FAILURE: No emitter to use **\n"); + exit(1); + } + } +} +#endif + +// Testing +// ------- + static uint32_t testFunc(JitRuntime& rt, EmitterType emitterType) noexcept { #ifndef ASMJIT_NO_LOGGING FileLogger logger(stdout); @@ -96,49 +264,10 @@ static uint32_t testFunc(JitRuntime& rt, EmitterType emitterType) noexcept { code.setLogger(&logger); #endif - Error err = kErrorOk; - switch (emitterType) { - case EmitterType::kAssembler: { - printf("Using x86::Assembler:\n"); - x86::Assembler a(&code); - makeRawFunc(a.as()); - break; - } - -#ifndef ASMJIT_NO_BUILDER - case EmitterType::kBuilder: { - printf("Using x86::Builder:\n"); - x86::Builder cb(&code); - makeRawFunc(cb.as()); - - err = cb.finalize(); - if (err) { - printf("** FAILURE: x86::Builder::finalize() failed (%s) **\n", DebugUtils::errorAsString(err)); - return 1; - } - break; - } -#endif - -#ifndef ASMJIT_NO_COMPILER - case EmitterType::kCompiler: { - printf("Using x86::Compiler:\n"); - x86::Compiler cc(&code); - makeCompiledFunc(&cc); - - err = cc.finalize(); - if (err) { - printf("** FAILURE: x86::Compiler::finalize() failed (%s) **\n", DebugUtils::errorAsString(err)); - return 1; - } - break; - } -#endif - - default: { - printf("** FAILURE: No emitter to use **\n"); - return 1; - } + Error err = generateFunc(code, emitterType); + if (err) { + printf("** FAILURE: Failed to generate a function: %s **\n", DebugUtils::errorAsString(err)); + return 1; } // Add the code generated to the runtime. @@ -146,7 +275,7 @@ static uint32_t testFunc(JitRuntime& rt, EmitterType emitterType) noexcept { err = rt.add(&fn, &code); if (err) { - printf("** FAILURE: JitRuntime::add() failed (%s) **\n", DebugUtils::errorAsString(err)); + printf("** FAILURE: JitRuntime::add() failed: %s **\n", DebugUtils::errorAsString(err)); return 1; } @@ -160,27 +289,24 @@ static uint32_t testFunc(JitRuntime& rt, EmitterType emitterType) noexcept { 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); + return out[0] == 5 && out[1] == 8 && out[2] == 4 && out[3] == 9; } int main() { - printf("AsmJit Emitters Test-Suite v%u.%u.%u\n", - unsigned((ASMJIT_LIBRARY_VERSION >> 16) ), - unsigned((ASMJIT_LIBRARY_VERSION >> 8) & 0xFF), - unsigned((ASMJIT_LIBRARY_VERSION ) & 0xFF)); + printInfo(); printf("\n"); JitRuntime rt; unsigned nFailed = 0; - nFailed += testFunc(rt, EmitterType::kAssembler); + nFailed += !testFunc(rt, EmitterType::kAssembler); #ifndef ASMJIT_NO_BUILDER - nFailed += testFunc(rt, EmitterType::kBuilder); + nFailed += !testFunc(rt, EmitterType::kBuilder); #endif #ifndef ASMJIT_NO_COMPILER - nFailed += testFunc(rt, EmitterType::kCompiler); + nFailed += !testFunc(rt, EmitterType::kCompiler); #endif if (!nFailed) @@ -192,7 +318,8 @@ int main() { } #else int main() { - printf("AsmJit X86 Emitter Test is disabled on non-x86 hosts or when compiled with ASMJIT_NO_JIT option\n\n"); + printInfo(); + printf("\nThis test is currently disabled - no JIT or no support for the target architecture\n"); return 0; } #endif // ASMJIT_ARCH_X86 && !ASMJIT_NO_X86 && !ASMJIT_NO_JIT diff --git a/test/asmjit_test_execute.cpp b/test/asmjit_test_execute.cpp new file mode 100644 index 0000000..ce002dc --- /dev/null +++ b/test/asmjit_test_execute.cpp @@ -0,0 +1,103 @@ +// This file is part of AsmJit project +// +// See asmjit.h or LICENSE.md for license and copyright information +// SPDX-License-Identifier: Zlib + +#include + +static void printInfo() noexcept { + printf("AsmJit Execute Test-Suite v%u.%u.%u\n", + unsigned((ASMJIT_LIBRARY_VERSION >> 16) ), + unsigned((ASMJIT_LIBRARY_VERSION >> 8) & 0xFF), + unsigned((ASMJIT_LIBRARY_VERSION ) & 0xFF)); +} + +#if !defined(ASMJIT_NO_JIT) && ( \ + (ASMJIT_ARCH_X86 != 0 && !defined(ASMJIT_NO_X86 )) || \ + (ASMJIT_ARCH_ARM == 64 && !defined(ASMJIT_NO_AARCH64)) ) + +#if ASMJIT_ARCH_X86 != 0 +#include +#endif + +#if ASMJIT_ARCH_ARM == 64 +#include +#endif + +#include +#include +#include + +using namespace asmjit; + +// Signature of the generated function. +typedef void (*EmptyFunc)(void); + +// Generate Empty Function +// ----------------------- + +#if ASMJIT_ARCH_X86 != 0 +static void generateEmptyFunc(CodeHolder& code) noexcept { + x86::Assembler a(&code); + a.ret(); +} +#endif + +#if ASMJIT_ARCH_ARM == 64 +static void generateEmptyFunc(CodeHolder& code) noexcept { + a64::Assembler a(&code); + a.ret(a64::x30); +} +#endif + +// Testing +// ------- + +static void executeEmptyFunc(JitRuntime& rt) noexcept { + CodeHolder code; + code.init(rt.environment(), rt.cpuFeatures()); + + EmptyFunc fn; + + generateEmptyFunc(code); + Error err = rt.add(&fn, &code); + + if (err) { + printf("** FAILURE: JitRuntime::add() failed: %s **\n", DebugUtils::errorAsString(err)); + exit(1); + } + + fn(); + + rt.release(&fn); +} + +int main() { + printInfo(); + printf("\n"); + + { + printf("Trying to execute empty function with JitRuntime (default settings)\n"); + JitRuntime rt; + executeEmptyFunc(rt); + } + + if (VirtMem::hardenedRuntimeInfo().hasFlag(VirtMem::HardenedRuntimeFlags::kDualMapping)) { + printf("Trying to execute empty function with JitRuntime (dual-mapped)\n"); + JitAllocator::CreateParams params {}; + params.options |= JitAllocatorOptions::kUseDualMapping; + JitRuntime rt; + executeEmptyFunc(rt); + } + + // If we are here we were successful, otherwise the process would crash. + printf("** SUCCESS **\n"); + return 0; +} +#else +int main() { + printInfo(); + printf("\nThis test is currently disabled - no JIT or no support for the target architecture\n"); + return 0; +} +#endif // ASMJIT_ARCH_X86 && !ASMJIT_NO_X86 && !ASMJIT_NO_JIT