Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ The following default gems are updated.
* io-wait 0.3.2
* json 2.13.2
* optparse 0.7.0.dev.2
* prism 1.4.0
* prism 1.5.0
* psych 5.2.6
* resolv 0.6.2
* stringio 3.1.8.dev
Expand Down
2 changes: 2 additions & 0 deletions depend
Original file line number Diff line number Diff line change
Expand Up @@ -1415,6 +1415,7 @@ compile.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h
compile.$(OBJEXT): $(top_srcdir)/prism/util/pm_strpbrk.h
compile.$(OBJEXT): $(top_srcdir)/prism/version.h
compile.$(OBJEXT): $(top_srcdir)/prism_compile.c
compile.$(OBJEXT): $(top_srcdir)/version.h
compile.$(OBJEXT): {$(VPATH)}assert.h
compile.$(OBJEXT): {$(VPATH)}atomic.h
compile.$(OBJEXT): {$(VPATH)}backward/2/assume.h
Expand Down Expand Up @@ -1605,6 +1606,7 @@ compile.$(OBJEXT): {$(VPATH)}prism_compile.h
compile.$(OBJEXT): {$(VPATH)}ractor.h
compile.$(OBJEXT): {$(VPATH)}re.h
compile.$(OBJEXT): {$(VPATH)}regex.h
compile.$(OBJEXT): {$(VPATH)}revision.h
compile.$(OBJEXT): {$(VPATH)}ruby_assert.h
compile.$(OBJEXT): {$(VPATH)}ruby_atomic.h
compile.$(OBJEXT): {$(VPATH)}rubyparser.h
Expand Down
174 changes: 174 additions & 0 deletions jit.c
Original file line number Diff line number Diff line change
Expand Up @@ -533,10 +533,184 @@ for_each_iseq_i(void *vstart, void *vend, size_t stride, void *data)
return 0;
}

uint32_t
rb_jit_get_page_size(void)
{
#if defined(_SC_PAGESIZE)
long page_size = sysconf(_SC_PAGESIZE);
if (page_size <= 0) rb_bug("jit: failed to get page size");

// 1 GiB limit. x86 CPUs with PDPE1GB can do this and anything larger is unexpected.
// Though our design sort of assume we have fine grained control over memory protection
// which require small page sizes.
if (page_size > 0x40000000l) rb_bug("jit page size too large");

return (uint32_t)page_size;
#else
#error "JIT supports POSIX only for now"
#endif
}

#if defined(MAP_FIXED_NOREPLACE) && defined(_SC_PAGESIZE)
// 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;
}
#endif

// Address space reservation. Memory pages are mapped on an as needed basis.
// See the Rust mm module for details.
uint8_t *
rb_jit_reserve_addr_space(uint32_t mem_size)
{
#ifndef _WIN32
uint8_t *mem_block;

// On Linux
#if defined(MAP_FIXED_NOREPLACE) && defined(_SC_PAGESIZE)
uint32_t const page_size = (uint32_t)sysconf(_SC_PAGESIZE);
uint8_t *const cfunc_sample_addr = (void *)(uintptr_t)&rb_jit_reserve_addr_space;
uint8_t *const probe_region_end = cfunc_sample_addr + INT32_MAX;
// Align the requested address to page size
uint8_t *req_addr = align_ptr(cfunc_sample_addr, page_size);

// Probe for addresses close to this function using MAP_FIXED_NOREPLACE
// to improve odds of being in range for 32-bit relative call instructions.
do {
mem_block = mmap(
req_addr,
mem_size,
PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE,
-1,
0
);

// If we succeeded, stop
if (mem_block != MAP_FAILED) {
ruby_annotate_mmap(mem_block, mem_size, "Ruby:rb_jit_reserve_addr_space");
break;
}

// -4MiB. Downwards to probe away from the heap. (On x86/A64 Linux
// main_code_addr < heap_addr, and in case we are in a shared
// library mapped higher than the heap, downwards is still better
// since it's towards the end of the heap rather than the stack.)
req_addr -= 4 * 1024 * 1024;
} while (req_addr < probe_region_end);

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

// Fallback
if (mem_block == MAP_FAILED) {
// Try again without the address hint (e.g., valgrind)
mem_block = mmap(
NULL,
mem_size,
PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1,
0
);

if (mem_block != MAP_FAILED) {
ruby_annotate_mmap(mem_block, mem_size, "Ruby:rb_jit_reserve_addr_space:fallback");
}
}

// Check that the memory mapping was successful
if (mem_block == MAP_FAILED) {
perror("ruby: jit: mmap:");
if(errno == ENOMEM) {
// No crash report if it's only insufficient memory
exit(EXIT_FAILURE);
}
rb_bug("mmap failed");
}

return mem_block;
#else
// Windows not supported for now
return NULL;
#endif
}

// Walk all ISEQs in the heap and invoke the callback - shared between YJIT and ZJIT
void
rb_jit_for_each_iseq(rb_iseq_callback callback, void *data)
{
struct iseq_callback_data callback_data = { .callback = callback, .data = data };
rb_objspace_each_objects(for_each_iseq_i, (void *)&callback_data);
}

bool
rb_jit_mark_writable(void *mem_block, uint32_t mem_size)
{
return mprotect(mem_block, mem_size, PROT_READ | PROT_WRITE) == 0;
}

void
rb_jit_mark_executable(void *mem_block, uint32_t mem_size)
{
// Do not call mprotect when mem_size is zero. Some platforms may return
// an error for it. https://github.com/Shopify/ruby/issues/450
if (mem_size == 0) {
return;
}
if (mprotect(mem_block, mem_size, PROT_READ | PROT_EXEC)) {
rb_bug("Couldn't make JIT page (%p, %lu bytes) executable, errno: %s",
mem_block, (unsigned long)mem_size, strerror(errno));
}
}

// Free the specified memory block.
bool
rb_jit_mark_unused(void *mem_block, uint32_t mem_size)
{
// On Linux, you need to use madvise MADV_DONTNEED to free memory.
// We might not need to call this on macOS, but it's not really documented.
// We generally prefer to do the same thing on both to ease testing too.
madvise(mem_block, mem_size, MADV_DONTNEED);

// On macOS, mprotect PROT_NONE seems to reduce RSS.
// We also call this on Linux to avoid executing unused pages.
return mprotect(mem_block, mem_size, PROT_NONE) == 0;
}

// Invalidate icache for arm64.
// `start` is inclusive and `end` is exclusive.
void
rb_jit_icache_invalidate(void *start, void *end)
{
// Clear/invalidate the instruction cache. Compiles to nothing on x86_64
// but required on ARM before running freshly written code.
// On Darwin it's the same as calling sys_icache_invalidate().
#ifdef __GNUC__
__builtin___clear_cache(start, end);
#elif defined(__aarch64__)
#error No instruction cache clear available with this compiler on Aarch64!
#endif
}
Loading