Skip to content

Commit

Permalink
Try to alloc executable memory within rel32 range on Linux machines (#12
Browse files Browse the repository at this point in the history
)

* Use INT32_MIN, INT32_MAX, etc. constants in yjit_asm.c

* Print warning on stderr when code past rel32 jump range

* Fix preprocessor snafu

* Move rel32 warning into --yjit-stats

* Try to allocate within rel32 offset on Linux machines

* Update yjit_asm.c

Co-authored-by: Alan Wu <XrXr@users.noreply.github.com>

* On Linux, use sysconf to get the page size

Co-authored-by: Alan Wu <XrXr@users.noreply.github.com>
  • Loading branch information
maximecb and XrXr committed Oct 20, 2021
1 parent 4a9594a commit 7548415
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 26 deletions.
101 changes: 75 additions & 26 deletions yjit_asm.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdint.h>
#include <assert.h>

// For mmapp(), sysconf()
#ifndef _WIN32
// For mmapp()
#include <unistd.h>
#include <sys/mman.h>
#endif

Expand All @@ -18,11 +20,11 @@
uint32_t sig_imm_size(int64_t imm)
{
// Compute the smallest size this immediate fits in
if (imm >= -128 && imm <= 127)
if (imm >= INT8_MIN && imm <= INT8_MAX)
return 8;
if (imm >= -32768 && imm <= 32767)
if (imm >= INT16_MIN && imm <= INT16_MAX)
return 16;
if (imm >= -2147483648 && imm <= 2147483647)
if (imm >= INT32_MIN && imm <= INT32_MAX)
return 32;

return 64;
Expand All @@ -32,11 +34,11 @@ uint32_t sig_imm_size(int64_t imm)
uint32_t unsig_imm_size(uint64_t imm)
{
// Compute the smallest size this immediate fits in
if (imm <= 255)
if (imm <= UINT8_MAX)
return 8;
else if (imm <= 65535)
else if (imm <= UINT16_MAX)
return 16;
else if (imm <= 4294967295)
else if (imm <= UINT32_MAX)
return 32;

return 64;
Expand Down Expand Up @@ -124,23 +126,73 @@ x86opnd_t const_ptr_opnd(const void *ptr)
return opnd;
}

// Align the current write position to a multiple of bytes
static uint8_t* align_ptr(uint8_t* ptr, uint32_t multiple)
{
// Compute the pointer modulo the given alignment boundary
uint32_t rem = ((uint32_t)(uintptr_t)ptr) % multiple;

// If the pointer is already aligned, stop
if (rem == 0)
return ptr;

// Pad the pointer by the necessary amount to align it
uint32_t pad = multiple - rem;

return ptr + pad;
}

// Allocate a block of executable memory
uint8_t* alloc_exec_mem(uint32_t mem_size)
{
#ifndef _WIN32
// Map the memory as executable
uint8_t* mem_block = (uint8_t*)mmap(
(void*)&alloc_exec_mem,
mem_size,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS,
-1,
0
);
uint8_t* mem_block;

// On Linux
#if defined(MAP_FIXED_NOREPLACE) && defined(_SC_PAGESIZE)
// Align the requested address to page size
uint32_t page_size = (uint32_t)sysconf(_SC_PAGESIZE);
uint8_t* req_addr = align_ptr((uint8_t*)&alloc_exec_mem, page_size);

while (req_addr < (uint8_t*)&alloc_exec_mem + INT32_MAX)
{
// Try to map a chunk of memory as executable
mem_block = (uint8_t*)mmap(
(void*)req_addr,
mem_size,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE,
-1,
0
);

// If we succeeded, stop
if (mem_block != MAP_FAILED) {
break;
}

// +4MB
req_addr += 4 * 1024 * 1024;
}

// On MacOS and other platforms
#else
// Try to map a chunk of memory as executable
mem_block = (uint8_t*)mmap(
(void*)alloc_exec_mem,
mem_size,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS,
-1,
0
);
#endif

// Fallback
if (mem_block == MAP_FAILED) {
// Try again without the address hint (e.g., valgrind)
mem_block = (uint8_t*)mmap(
NULL, // try again without the address hint (e.g., valgrind)
NULL,
mem_size,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS,
Expand All @@ -161,6 +213,7 @@ uint8_t* alloc_exec_mem(uint32_t mem_size)

return mem_block;
#else
// Windows not supported for now
return NULL;
#endif
}
Expand All @@ -180,15 +233,11 @@ void cb_align_pos(codeblock_t* cb, uint32_t multiple)
{
// Compute the pointer modulo the given alignment boundary
uint8_t* ptr = &cb->mem_block[cb->write_pos];
uint32_t rem = ((uint32_t)(uintptr_t)ptr) % multiple;

// If the pointer is already aligned, stop
if (rem == 0)
return;
uint8_t* aligned_ptr = align_ptr(ptr, multiple);

// Pad the pointer by the necessary amount to align it
uint32_t pad = multiple - rem;
cb->write_pos += pad;
ptrdiff_t pad = aligned_ptr - ptr;
cb->write_pos += (int32_t)pad;
}

// Set the current write position
Expand Down Expand Up @@ -818,7 +867,7 @@ void cb_write_jcc_ptr(codeblock_t* cb, const char* mnem, uint8_t op0, uint8_t op

// Compute the jump offset
int64_t rel64 = (int64_t)(dst_ptr - end_ptr);
assert (rel64 >= -2147483648 && rel64 <= 2147483647);
assert (rel64 >= INT32_MIN && rel64 <= INT32_MAX);

// Write the relative 32-bit jump offset
cb_write_int(cb, (int32_t)rel64, 32);
Expand Down Expand Up @@ -901,7 +950,7 @@ void call_ptr(codeblock_t* cb, x86opnd_t scratch_reg, uint8_t* dst_ptr)
int64_t rel64 = (int64_t)(dst_ptr - end_ptr);

// If the offset fits in 32-bit
if (rel64 >= -2147483648 && rel64 <= 2147483647)
if (rel64 >= INT32_MIN && rel64 <= INT32_MAX)
{
call_rel32(cb, (int32_t)rel64);
return;
Expand Down
7 changes: 7 additions & 0 deletions yjit_iface.c
Original file line number Diff line number Diff line change
Expand Up @@ -896,6 +896,13 @@ print_yjit_stats(void)
return;
}

// Warn if the executable code block is out of the relative
// 32-bit jump range away from compiled C code
ptrdiff_t start_diff = (cb->mem_block + cb->mem_size) - (uint8_t*)&print_yjit_stats;
if (start_diff < INT32_MIN || start_diff > INT32_MAX) {
fprintf(stderr, "WARNING: end of code block past rel32 offset range from C code\n");
}

// Compute the total exit count
int64_t total_exit_count = calc_total_exit_count();

Expand Down

0 comments on commit 7548415

Please sign in to comment.