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.
This commit is contained in:
kobalicek
2024-01-25 22:23:13 +01:00
parent bfa0bf690c
commit a63d41e80b
10 changed files with 507 additions and 123 deletions

View File

@@ -12,37 +12,14 @@
], ],
"tests": [ "tests": [
{ { "optional": true, "cmd": ["asmjit_test_unit", "--quick"] },
"cmd": ["asmjit_test_unit", "--quick"], { "optional": true, "cmd": ["asmjit_test_assembler"] },
"optional": true { "optional": true, "cmd": ["asmjit_test_assembler", "--validate"] },
}, { "optional": true, "cmd": ["asmjit_test_emitters"] },
{ { "optional": true, "cmd": ["asmjit_test_execute"] },
"cmd": ["asmjit_test_assembler"], { "optional": true, "cmd": ["asmjit_test_compiler"] },
"optional": true { "optional": true, "cmd": ["asmjit_test_instinfo"] },
}, { "optional": true, "cmd": ["asmjit_test_x86_sections"] },
{ { "optional": true, "cmd": ["asmjit_test_perf", "--quick"] }
"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
}
] ]
} }

View File

@@ -14,12 +14,12 @@ jobs:
steps: steps:
- name: "Checkout" - name: "Checkout"
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: "Setup node.js" - name: "Setup node.js"
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
node-version: "16" node-version: "*"
- name: "Check Enumerations" - name: "Check Enumerations"
run: | 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: "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: "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: "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: "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: "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: "ppc64le", cc: "clang" , conf: "Release", vm: "debian:unstable", defs: "ASMJIT_TEST=1" }
name: "${{matrix.title}}/${{matrix.arch}}, ${{matrix.cc}} ${{matrix.conf}}" name: "${{matrix.title}}/${{matrix.arch}}, ${{matrix.cc}} ${{matrix.conf}}"
runs-on: "${{matrix.host}}" runs-on: "${{matrix.host}}"
steps: steps:
- name: "Checkout" - name: "Checkout"
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
path: "source" path: "source"
- name: "Checkout Build Actions" - name: "Checkout Build Actions"
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
repository: build-actions/build-actions repository: build-actions/build-actions
path: "build-actions" path: "build-actions"
- name: "Python" - name: "Python"
uses: actions/setup-python@v4 uses: actions/setup-python@v5
with: with:
python-version: "3.x" python-version: "3.x"

View File

@@ -666,6 +666,7 @@ if (NOT ASMJIT_EMBED)
CFLAGS_REL ${ASMJIT_PRIVATE_CFLAGS_REL}) CFLAGS_REL ${ASMJIT_PRIVATE_CFLAGS_REL})
foreach(_target asmjit_test_emitters foreach(_target asmjit_test_emitters
asmjit_test_execute
asmjit_test_x86_sections) asmjit_test_x86_sections)
asmjit_add_target(${_target} TEST asmjit_add_target(${_target} TEST
SOURCES test/${_target}.cpp SOURCES test/${_target}.cpp

View File

@@ -87,6 +87,7 @@ ASMJIT_FAVOR_SIZE const char* DebugUtils::errorAsString(Error err) noexcept {
"ExpressionOverflow\0" "ExpressionOverflow\0"
"FailedToOpenAnonymousMemory\0" "FailedToOpenAnonymousMemory\0"
"FailedToOpenFile\0" "FailedToOpenFile\0"
"ProtectionFailure\0"
"<Unknown>\0"; "<Unknown>\0";
static const uint16_t sErrorIndex[] = { 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, 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, 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, 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@ // @EnumStringEnd@

View File

@@ -334,6 +334,10 @@ enum ErrorCode : uint32_t {
//! \note This is a generic error that is used by internal filesystem API. //! \note This is a generic error that is used by internal filesystem API.
kErrorFailedToOpenFile, kErrorFailedToOpenFile,
//! Protection failure can be returned from a virtual memory allocator or when trying to change memory access
//! permissions.
kErrorProtectionFailure,
// @EnumValuesEnd@ // @EnumValuesEnd@
//! Count of AsmJit error codes. //! Count of AsmJit error codes.

View File

@@ -76,8 +76,6 @@
#define MAP_ANONYMOUS MAP_ANON #define MAP_ANONYMOUS MAP_ANON
#endif #endif
#define ASMJIT_ANONYMOUS_MEMORY_USE_FD
// Android NDK doesn't provide `shm_open()` and `shm_unlink()`. // Android NDK doesn't provide `shm_open()` and `shm_unlink()`.
#if !defined(__BIONIC__) && !defined(ASMJIT_NO_SHM_OPEN) #if !defined(__BIONIC__) && !defined(ASMJIT_NO_SHM_OPEN)
#define ASMJIT_HAS_SHM_OPEN #define ASMJIT_HAS_SHM_OPEN
@@ -89,18 +87,60 @@
#define ASMJIT_VM_SHM_DETECT 1 #define ASMJIT_VM_SHM_DETECT 1
#endif #endif
#if defined(__APPLE__) && TARGET_OS_OSX && ASMJIT_ARCH_ARM >= 64 #if defined(__APPLE__) && TARGET_OS_OSX
#define ASMJIT_HAS_PTHREAD_JIT_WRITE_PROTECT_NP #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 #endif
#if defined(__NetBSD__) && defined(MAP_REMAPDUP) && defined(PROT_MPROTECT) #if defined(__NetBSD__) && defined(MAP_REMAPDUP) && defined(PROT_MPROTECT)
#undef ASMJIT_ANONYMOUS_MEMORY_USE_FD
#define ASMJIT_ANONYMOUS_MEMORY_USE_REMAPDUP #define ASMJIT_ANONYMOUS_MEMORY_USE_REMAPDUP
#endif #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 #endif
#include <atomic> #include <atomic>
#if defined(ASMJIT_ANONYMOUS_MEMORY_USE_MACH_VM_REMAP)
#include <mach/mach.h>
#include <mach/mach_time.h>
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) ASMJIT_BEGIN_SUB_NAMESPACE(VirtMem)
// Virtual Memory Utilities // Virtual Memory Utilities
@@ -757,8 +797,7 @@ static inline int mmMapJitFromMemoryFlags(MemoryFlags memoryFlags) noexcept {
} }
static inline bool hasDualMappingSupport() noexcept { static inline bool hasDualMappingSupport() noexcept {
#if defined(__APPLE__) && TARGET_OS_OSX && ASMJIT_ARCH_X86 == 0 #if defined(ASMJIT_NO_DUAL_MAPPING)
// Apple platforms don't allow dual-mapping on non-x86 hardware.
return false; return false;
#else #else
return true; return true;
@@ -850,6 +889,7 @@ Error protect(void* p, size_t size, MemoryFlags memoryFlags) noexcept {
// Virtual Memory [Posix] - Dual Mapping // Virtual Memory [Posix] - Dual Mapping
// ===================================== // =====================================
#if !defined(ASMJIT_NO_DUAL_MAPPING)
static Error unmapDualMapping(DualMapping* dm, size_t size) noexcept { static Error unmapDualMapping(DualMapping* dm, size_t size) noexcept {
Error err1 = unmapMemory(dm->rx, size); Error err1 = unmapMemory(dm->rx, size);
Error err2 = kErrorOk; Error err2 = kErrorOk;
@@ -865,6 +905,7 @@ static Error unmapDualMapping(DualMapping* dm, size_t size) noexcept {
dm->rw = nullptr; dm->rw = nullptr;
return kErrorOk; return kErrorOk;
} }
#endif // !ASMJIT_NO_DUAL_MAPPING
#if defined(ASMJIT_ANONYMOUS_MEMORY_USE_REMAPDUP) #if defined(ASMJIT_ANONYMOUS_MEMORY_USE_REMAPDUP)
static Error allocDualMappingUsingRemapdup(DualMapping* dmOut, size_t size, MemoryFlags memoryFlags) noexcept { 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 #endif
Error allocDualMapping(DualMapping* dm, size_t size, MemoryFlags memoryFlags) noexcept { #if defined(ASMJIT_ANONYMOUS_MEMORY_USE_MACH_VM_REMAP)
dm->rx = nullptr; static Error asmjitErrorFromKernResult(kern_return_t result) noexcept {
dm->rw = nullptr; 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) static Error allocDualMappingUsingMachVmRemap(DualMapping* dmOut, size_t size, MemoryFlags memoryFlags) noexcept {
return DebugUtils::errored(size == 0 ? kErrorInvalidArgument : kErrorTooLarge); DualMapping dm {};
#if defined(ASMJIT_ANONYMOUS_MEMORY_USE_REMAPDUP) MemoryFlags mmapFlags = MemoryFlags::kAccessReadWrite | (memoryFlags & MemoryFlags::kMapShared);
return allocDualMappingUsingRemapdup(dm, size, memoryFlags); ASMJIT_PROPAGATE(mapMemory(&dm.rx, size, mmapFlags));
#elif defined(ASMJIT_ANONYMOUS_MEMORY_USE_FD)
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); bool preferTmpOverDevShm = Support::test(memoryFlags, MemoryFlags::kMappingPreferTmp);
if (!preferTmpOverDevShm) { if (!preferTmpOverDevShm) {
AnonymousMemoryStrategy strategy; AnonymousMemoryStrategy strategy;
@@ -932,13 +1062,39 @@ Error allocDualMapping(DualMapping* dm, size_t size, MemoryFlags memoryFlags) no
dm->rx = ptr[0]; dm->rx = ptr[0];
dm->rw = ptr[1]; dm->rw = ptr[1];
return kErrorOk; 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 #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
#endif // ASMJIT_NO_DUAL_MAPPING
} }
Error releaseDualMapping(DualMapping* dm, size_t size) noexcept { 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); return unmapDualMapping(dm, size);
#endif // ASMJIT_NO_DUAL_MAPPING
} }
#endif #endif
@@ -1039,8 +1195,9 @@ UNIT(virt_mem) {
INFO("VirtMem::hardenedRuntimeInfo():"); INFO("VirtMem::hardenedRuntimeInfo():");
INFO(" flags:"); INFO(" flags:");
INFO(" kEnabled: %s", Support::test(hardenedFlags, VirtMem::HardenedRuntimeFlags::kEnabled) ? "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(" kMapJit: %s" , Support::test(hardenedFlags, VirtMem::HardenedRuntimeFlags::kMapJit ) ? "true" : "false");
INFO(" kDualMapping: %s", Support::test(hardenedFlags, VirtMem::HardenedRuntimeFlags::kDualMapping) ? "true" : "false");
} }
ASMJIT_END_NAMESPACE ASMJIT_END_NAMESPACE

View File

@@ -10,6 +10,7 @@
#ifndef ASMJIT_NO_JIT #ifndef ASMJIT_NO_JIT
#include "../core/globals.h" #include "../core/globals.h"
#include "../core/support.h"
ASMJIT_BEGIN_NAMESPACE 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). //! Read+Write+Execute can only be allocated with MAP_JIT flag (Apple specific, only available on Apple platforms).
kMapJit = 0x00000002u, 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 kDualMapping = 0x00000004u
}; };
ASMJIT_DEFINE_ENUM_FLAGS(HardenedRuntimeFlags) ASMJIT_DEFINE_ENUM_FLAGS(HardenedRuntimeFlags)
//! Hardened runtime information. //! Hardened runtime information.
struct HardenedRuntimeInfo { struct HardenedRuntimeInfo {
//! \name Members
//! \{
//! Hardened runtime flags. //! Hardened runtime flags.
HardenedRuntimeFlags 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. //! Returns runtime features provided by the OS.

View File

@@ -2827,7 +2827,7 @@ public:
ASMJIT_INST_3x(vpand, Vpand, Vec, Vec, Mem) // AVX+ 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, Vec) // AVX512_F{kz|b32}
ASMJIT_INST_3x(vpandd, Vpandd, Vec, Vec, Mem) // 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(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, Vec) // AVX512_F{kz|b32}
ASMJIT_INST_3x(vpandnd, Vpandnd, Vec, Vec, Mem) // 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(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, Vec) // AVX512_BITALG{kz|b32}
ASMJIT_INST_2x(vpopcntw, Vpopcntw, Vec, Mem) // 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(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, Vec) // AVX512_F{kz|b32}
ASMJIT_INST_3x(vpord, Vpord, Vec, Vec, Mem) // AVX512_F{kz|b32} ASMJIT_INST_3x(vpord, Vpord, Vec, Vec, Mem) // AVX512_F{kz|b32}

View File

@@ -5,8 +5,24 @@
#include <asmjit/core.h> #include <asmjit/core.h>
#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 <asmjit/x86.h> #include <asmjit/x86.h>
#endif
#if ASMJIT_ARCH_ARM == 64
#include <asmjit/a64.h>
#endif
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@@ -17,9 +33,13 @@ using namespace asmjit;
// Signature of the generated function. // Signature of the generated function.
typedef void (*SumIntsFunc)(int* dst, const int* a, const int* b); 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 // This function works with both x86::Assembler and x86::Builder. It shows how
// `x86::Emitter` can be used to make your code more generic. // `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 // 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 // registers of `dst`, `src_a`, and `src_b` and see what happens in function's
// prolog and epilog. // prolog and epilog.
@@ -39,8 +59,8 @@ static void makeRawFunc(x86::Emitter* emitter) noexcept {
FuncFrame frame; FuncFrame frame;
frame.init(func); frame.init(func);
// Make XMM0 and XMM1 dirty. VEC group includes XMM|YMM|ZMM registers. // Make or registers dirty.
frame.addDirtyRegs(x86::xmm0, x86::xmm1); frame.addDirtyRegs(vec0, vec1);
FuncArgsAssignment args(&func); // Create arguments assignment context. FuncArgsAssignment args(&func); // Create arguments assignment context.
args.assignAll(dst, src_a, src_b); // Assign our registers to arguments. 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 #ifndef ASMJIT_NO_COMPILER
// This function works with x86::Compiler, provided for comparison. // 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 dst = cc->newIntPtr("dst");
x86::Gp src_a = cc->newIntPtr("src_a"); x86::Gp src_a = cc->newIntPtr("src_a");
x86::Gp src_b = cc->newIntPtr("src_b"); x86::Gp src_b = cc->newIntPtr("src_b");
@@ -83,6 +103,154 @@ static void makeCompiledFunc(x86::Compiler* cc) noexcept {
} }
#endif #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<x86::Emitter>());
return kErrorOk;
}
#ifndef ASMJIT_NO_BUILDER
case EmitterType::kBuilder: {
printf("Using x86::Builder:\n");
x86::Builder cb(&code);
generateFuncWithEmitter(cb.as<x86::Emitter>());
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<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.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<void, int*, const int*, const int*>());
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<a64::Emitter>());
return kErrorOk;
}
#ifndef ASMJIT_NO_BUILDER
case EmitterType::kBuilder: {
printf("Using a64::Builder:\n");
a64::Builder cb(&code);
generateFuncWithEmitter(cb.as<a64::Emitter>());
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 { static uint32_t testFunc(JitRuntime& rt, EmitterType emitterType) noexcept {
#ifndef ASMJIT_NO_LOGGING #ifndef ASMJIT_NO_LOGGING
FileLogger logger(stdout); FileLogger logger(stdout);
@@ -96,49 +264,10 @@ static uint32_t testFunc(JitRuntime& rt, EmitterType emitterType) noexcept {
code.setLogger(&logger); code.setLogger(&logger);
#endif #endif
Error err = kErrorOk; Error err = generateFunc(code, emitterType);
switch (emitterType) { if (err) {
case EmitterType::kAssembler: { printf("** FAILURE: Failed to generate a function: %s **\n", DebugUtils::errorAsString(err));
printf("Using x86::Assembler:\n"); return 1;
x86::Assembler a(&code);
makeRawFunc(a.as<x86::Emitter>());
break;
}
#ifndef ASMJIT_NO_BUILDER
case EmitterType::kBuilder: {
printf("Using x86::Builder:\n");
x86::Builder cb(&code);
makeRawFunc(cb.as<x86::Emitter>());
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;
}
} }
// Add the code generated to the runtime. // 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); err = rt.add(&fn, &code);
if (err) { if (err) {
printf("** FAILURE: JitRuntime::add() failed (%s) **\n", DebugUtils::errorAsString(err)); printf("** FAILURE: JitRuntime::add() failed: %s **\n", DebugUtils::errorAsString(err));
return 1; 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]); printf("Result = { %d %d %d %d }\n\n", out[0], out[1], out[2], out[3]);
rt.release(fn); 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() { int main() {
printf("AsmJit Emitters Test-Suite v%u.%u.%u\n", printInfo();
unsigned((ASMJIT_LIBRARY_VERSION >> 16) ),
unsigned((ASMJIT_LIBRARY_VERSION >> 8) & 0xFF),
unsigned((ASMJIT_LIBRARY_VERSION ) & 0xFF));
printf("\n"); printf("\n");
JitRuntime rt; JitRuntime rt;
unsigned nFailed = 0; unsigned nFailed = 0;
nFailed += testFunc(rt, EmitterType::kAssembler); nFailed += !testFunc(rt, EmitterType::kAssembler);
#ifndef ASMJIT_NO_BUILDER #ifndef ASMJIT_NO_BUILDER
nFailed += testFunc(rt, EmitterType::kBuilder); nFailed += !testFunc(rt, EmitterType::kBuilder);
#endif #endif
#ifndef ASMJIT_NO_COMPILER #ifndef ASMJIT_NO_COMPILER
nFailed += testFunc(rt, EmitterType::kCompiler); nFailed += !testFunc(rt, EmitterType::kCompiler);
#endif #endif
if (!nFailed) if (!nFailed)
@@ -192,7 +318,8 @@ int main() {
} }
#else #else
int main() { 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; return 0;
} }
#endif // ASMJIT_ARCH_X86 && !ASMJIT_NO_X86 && !ASMJIT_NO_JIT #endif // ASMJIT_ARCH_X86 && !ASMJIT_NO_X86 && !ASMJIT_NO_JIT

View File

@@ -0,0 +1,103 @@
// This file is part of AsmJit project <https://asmjit.com>
//
// See asmjit.h or LICENSE.md for license and copyright information
// SPDX-License-Identifier: Zlib
#include <asmjit/core.h>
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 <asmjit/x86.h>
#endif
#if ASMJIT_ARCH_ARM == 64
#include <asmjit/a64.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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