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": [
{
"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"] }
]
}

View File

@@ -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: |
@@ -141,18 +141,18 @@ jobs:
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"

View File

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

View File

@@ -87,6 +87,7 @@ ASMJIT_FAVOR_SIZE const char* DebugUtils::errorAsString(Error err) noexcept {
"ExpressionOverflow\0"
"FailedToOpenAnonymousMemory\0"
"FailedToOpenFile\0"
"ProtectionFailure\0"
"<Unknown>\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@

View File

@@ -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.

View File

@@ -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
#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 <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)
// 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
@@ -1041,6 +1197,7 @@ UNIT(virt_mem) {
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(" kDualMapping: %s", Support::test(hardenedFlags, VirtMem::HardenedRuntimeFlags::kDualMapping) ? "true" : "false");
}
ASMJIT_END_NAMESPACE

View File

@@ -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.

View File

@@ -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}

View File

@@ -5,8 +5,24 @@
#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>
#endif
#if ASMJIT_ARCH_ARM == 64
#include <asmjit/a64.h>
#endif
#include <stdio.h>
#include <stdlib.h>
@@ -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<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 {
#ifndef ASMJIT_NO_LOGGING
FileLogger logger(stdout);
@@ -96,57 +264,18 @@ 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<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();
Error err = generateFunc(code, emitterType);
if (err) {
printf("** FAILURE: x86::Builder::finalize() failed (%s) **\n", DebugUtils::errorAsString(err));
printf("** FAILURE: Failed to generate a function: %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.
SumIntsFunc fn;
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

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