Skip to content

Commit d3cb347

Browse files
authored
ZJIT: Share more code with YJIT in jit.c (ruby#14520)
* ZJIT: Share more code with YJIT in jit.c * Fix ZJIT references to JIT
1 parent eaf64af commit d3cb347

File tree

13 files changed

+218
-392
lines changed

13 files changed

+218
-392
lines changed

jit.c

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,10 +533,184 @@ for_each_iseq_i(void *vstart, void *vend, size_t stride, void *data)
533533
return 0;
534534
}
535535

536+
uint32_t
537+
rb_jit_get_page_size(void)
538+
{
539+
#if defined(_SC_PAGESIZE)
540+
long page_size = sysconf(_SC_PAGESIZE);
541+
if (page_size <= 0) rb_bug("jit: failed to get page size");
542+
543+
// 1 GiB limit. x86 CPUs with PDPE1GB can do this and anything larger is unexpected.
544+
// Though our design sort of assume we have fine grained control over memory protection
545+
// which require small page sizes.
546+
if (page_size > 0x40000000l) rb_bug("jit page size too large");
547+
548+
return (uint32_t)page_size;
549+
#else
550+
#error "JIT supports POSIX only for now"
551+
#endif
552+
}
553+
554+
#if defined(MAP_FIXED_NOREPLACE) && defined(_SC_PAGESIZE)
555+
// Align the current write position to a multiple of bytes
556+
static uint8_t *
557+
align_ptr(uint8_t *ptr, uint32_t multiple)
558+
{
559+
// Compute the pointer modulo the given alignment boundary
560+
uint32_t rem = ((uint32_t)(uintptr_t)ptr) % multiple;
561+
562+
// If the pointer is already aligned, stop
563+
if (rem == 0)
564+
return ptr;
565+
566+
// Pad the pointer by the necessary amount to align it
567+
uint32_t pad = multiple - rem;
568+
569+
return ptr + pad;
570+
}
571+
#endif
572+
573+
// Address space reservation. Memory pages are mapped on an as needed basis.
574+
// See the Rust mm module for details.
575+
uint8_t *
576+
rb_jit_reserve_addr_space(uint32_t mem_size)
577+
{
578+
#ifndef _WIN32
579+
uint8_t *mem_block;
580+
581+
// On Linux
582+
#if defined(MAP_FIXED_NOREPLACE) && defined(_SC_PAGESIZE)
583+
uint32_t const page_size = (uint32_t)sysconf(_SC_PAGESIZE);
584+
uint8_t *const cfunc_sample_addr = (void *)(uintptr_t)&rb_jit_reserve_addr_space;
585+
uint8_t *const probe_region_end = cfunc_sample_addr + INT32_MAX;
586+
// Align the requested address to page size
587+
uint8_t *req_addr = align_ptr(cfunc_sample_addr, page_size);
588+
589+
// Probe for addresses close to this function using MAP_FIXED_NOREPLACE
590+
// to improve odds of being in range for 32-bit relative call instructions.
591+
do {
592+
mem_block = mmap(
593+
req_addr,
594+
mem_size,
595+
PROT_NONE,
596+
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE,
597+
-1,
598+
0
599+
);
600+
601+
// If we succeeded, stop
602+
if (mem_block != MAP_FAILED) {
603+
ruby_annotate_mmap(mem_block, mem_size, "Ruby:rb_jit_reserve_addr_space");
604+
break;
605+
}
606+
607+
// -4MiB. Downwards to probe away from the heap. (On x86/A64 Linux
608+
// main_code_addr < heap_addr, and in case we are in a shared
609+
// library mapped higher than the heap, downwards is still better
610+
// since it's towards the end of the heap rather than the stack.)
611+
req_addr -= 4 * 1024 * 1024;
612+
} while (req_addr < probe_region_end);
613+
614+
// On MacOS and other platforms
615+
#else
616+
// Try to map a chunk of memory as executable
617+
mem_block = mmap(
618+
(void *)rb_jit_reserve_addr_space,
619+
mem_size,
620+
PROT_NONE,
621+
MAP_PRIVATE | MAP_ANONYMOUS,
622+
-1,
623+
0
624+
);
625+
#endif
626+
627+
// Fallback
628+
if (mem_block == MAP_FAILED) {
629+
// Try again without the address hint (e.g., valgrind)
630+
mem_block = mmap(
631+
NULL,
632+
mem_size,
633+
PROT_NONE,
634+
MAP_PRIVATE | MAP_ANONYMOUS,
635+
-1,
636+
0
637+
);
638+
639+
if (mem_block != MAP_FAILED) {
640+
ruby_annotate_mmap(mem_block, mem_size, "Ruby:rb_jit_reserve_addr_space:fallback");
641+
}
642+
}
643+
644+
// Check that the memory mapping was successful
645+
if (mem_block == MAP_FAILED) {
646+
perror("ruby: jit: mmap:");
647+
if(errno == ENOMEM) {
648+
// No crash report if it's only insufficient memory
649+
exit(EXIT_FAILURE);
650+
}
651+
rb_bug("mmap failed");
652+
}
653+
654+
return mem_block;
655+
#else
656+
// Windows not supported for now
657+
return NULL;
658+
#endif
659+
}
660+
536661
// Walk all ISEQs in the heap and invoke the callback - shared between YJIT and ZJIT
537662
void
538663
rb_jit_for_each_iseq(rb_iseq_callback callback, void *data)
539664
{
540665
struct iseq_callback_data callback_data = { .callback = callback, .data = data };
541666
rb_objspace_each_objects(for_each_iseq_i, (void *)&callback_data);
542667
}
668+
669+
bool
670+
rb_jit_mark_writable(void *mem_block, uint32_t mem_size)
671+
{
672+
return mprotect(mem_block, mem_size, PROT_READ | PROT_WRITE) == 0;
673+
}
674+
675+
void
676+
rb_jit_mark_executable(void *mem_block, uint32_t mem_size)
677+
{
678+
// Do not call mprotect when mem_size is zero. Some platforms may return
679+
// an error for it. https://github.com/Shopify/ruby/issues/450
680+
if (mem_size == 0) {
681+
return;
682+
}
683+
if (mprotect(mem_block, mem_size, PROT_READ | PROT_EXEC)) {
684+
rb_bug("Couldn't make JIT page (%p, %lu bytes) executable, errno: %s",
685+
mem_block, (unsigned long)mem_size, strerror(errno));
686+
}
687+
}
688+
689+
// Free the specified memory block.
690+
bool
691+
rb_jit_mark_unused(void *mem_block, uint32_t mem_size)
692+
{
693+
// On Linux, you need to use madvise MADV_DONTNEED to free memory.
694+
// We might not need to call this on macOS, but it's not really documented.
695+
// We generally prefer to do the same thing on both to ease testing too.
696+
madvise(mem_block, mem_size, MADV_DONTNEED);
697+
698+
// On macOS, mprotect PROT_NONE seems to reduce RSS.
699+
// We also call this on Linux to avoid executing unused pages.
700+
return mprotect(mem_block, mem_size, PROT_NONE) == 0;
701+
}
702+
703+
// Invalidate icache for arm64.
704+
// `start` is inclusive and `end` is exclusive.
705+
void
706+
rb_jit_icache_invalidate(void *start, void *end)
707+
{
708+
// Clear/invalidate the instruction cache. Compiles to nothing on x86_64
709+
// but required on ARM before running freshly written code.
710+
// On Darwin it's the same as calling sys_icache_invalidate().
711+
#ifdef __GNUC__
712+
__builtin___clear_cache(start, end);
713+
#elif defined(__aarch64__)
714+
#error No instruction cache clear available with this compiler on Aarch64!
715+
#endif
716+
}

yjit.c

Lines changed: 0 additions & 173 deletions
Original file line numberDiff line numberDiff line change
@@ -69,60 +69,12 @@ STATIC_ASSERT(pointer_tagging_scheme, USE_FLONUM);
6969
// The "_yjit_" part is for trying to be informative. We might want different
7070
// suffixes for symbols meant for Rust and symbols meant for broader CRuby.
7171

72-
bool
73-
rb_yjit_mark_writable(void *mem_block, uint32_t mem_size)
74-
{
75-
return mprotect(mem_block, mem_size, PROT_READ | PROT_WRITE) == 0;
76-
}
77-
78-
void
79-
rb_yjit_mark_executable(void *mem_block, uint32_t mem_size)
80-
{
81-
// Do not call mprotect when mem_size is zero. Some platforms may return
82-
// an error for it. https://github.com/Shopify/ruby/issues/450
83-
if (mem_size == 0) {
84-
return;
85-
}
86-
if (mprotect(mem_block, mem_size, PROT_READ | PROT_EXEC)) {
87-
rb_bug("Couldn't make JIT page (%p, %lu bytes) executable, errno: %s",
88-
mem_block, (unsigned long)mem_size, strerror(errno));
89-
}
90-
}
91-
92-
// Free the specified memory block.
93-
bool
94-
rb_yjit_mark_unused(void *mem_block, uint32_t mem_size)
95-
{
96-
// On Linux, you need to use madvise MADV_DONTNEED to free memory.
97-
// We might not need to call this on macOS, but it's not really documented.
98-
// We generally prefer to do the same thing on both to ease testing too.
99-
madvise(mem_block, mem_size, MADV_DONTNEED);
100-
101-
// On macOS, mprotect PROT_NONE seems to reduce RSS.
102-
// We also call this on Linux to avoid executing unused pages.
103-
return mprotect(mem_block, mem_size, PROT_NONE) == 0;
104-
}
105-
10672
long
10773
rb_yjit_array_len(VALUE a)
10874
{
10975
return rb_array_len(a);
11076
}
11177

112-
// `start` is inclusive and `end` is exclusive.
113-
void
114-
rb_yjit_icache_invalidate(void *start, void *end)
115-
{
116-
// Clear/invalidate the instruction cache. Compiles to nothing on x86_64
117-
// but required on ARM before running freshly written code.
118-
// On Darwin it's the same as calling sys_icache_invalidate().
119-
#ifdef __GNUC__
120-
__builtin___clear_cache(start, end);
121-
#elif defined(__aarch64__)
122-
#error No instruction cache clear available with this compiler on Aarch64!
123-
#endif
124-
}
125-
12678
# define PTR2NUM(x) (rb_int2inum((intptr_t)(void *)(x)))
12779

12880
// For a given raw_sample (frame), set the hash with the caller's
@@ -217,131 +169,6 @@ rb_yjit_exit_locations_dict(VALUE *yjit_raw_samples, int *yjit_line_samples, int
217169
return result;
218170
}
219171

220-
uint32_t
221-
rb_yjit_get_page_size(void)
222-
{
223-
#if defined(_SC_PAGESIZE)
224-
long page_size = sysconf(_SC_PAGESIZE);
225-
if (page_size <= 0) rb_bug("yjit: failed to get page size");
226-
227-
// 1 GiB limit. x86 CPUs with PDPE1GB can do this and anything larger is unexpected.
228-
// Though our design sort of assume we have fine grained control over memory protection
229-
// which require small page sizes.
230-
if (page_size > 0x40000000l) rb_bug("yjit page size too large");
231-
232-
return (uint32_t)page_size;
233-
#else
234-
#error "YJIT supports POSIX only for now"
235-
#endif
236-
}
237-
238-
#if defined(MAP_FIXED_NOREPLACE) && defined(_SC_PAGESIZE)
239-
// Align the current write position to a multiple of bytes
240-
static uint8_t *
241-
align_ptr(uint8_t *ptr, uint32_t multiple)
242-
{
243-
// Compute the pointer modulo the given alignment boundary
244-
uint32_t rem = ((uint32_t)(uintptr_t)ptr) % multiple;
245-
246-
// If the pointer is already aligned, stop
247-
if (rem == 0)
248-
return ptr;
249-
250-
// Pad the pointer by the necessary amount to align it
251-
uint32_t pad = multiple - rem;
252-
253-
return ptr + pad;
254-
}
255-
#endif
256-
257-
// Address space reservation. Memory pages are mapped on an as needed basis.
258-
// See the Rust mm module for details.
259-
uint8_t *
260-
rb_yjit_reserve_addr_space(uint32_t mem_size)
261-
{
262-
#ifndef _WIN32
263-
uint8_t *mem_block;
264-
265-
// On Linux
266-
#if defined(MAP_FIXED_NOREPLACE) && defined(_SC_PAGESIZE)
267-
uint32_t const page_size = (uint32_t)sysconf(_SC_PAGESIZE);
268-
uint8_t *const cfunc_sample_addr = (void *)(uintptr_t)&rb_yjit_reserve_addr_space;
269-
uint8_t *const probe_region_end = cfunc_sample_addr + INT32_MAX;
270-
// Align the requested address to page size
271-
uint8_t *req_addr = align_ptr(cfunc_sample_addr, page_size);
272-
273-
// Probe for addresses close to this function using MAP_FIXED_NOREPLACE
274-
// to improve odds of being in range for 32-bit relative call instructions.
275-
do {
276-
mem_block = mmap(
277-
req_addr,
278-
mem_size,
279-
PROT_NONE,
280-
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE,
281-
-1,
282-
0
283-
);
284-
285-
// If we succeeded, stop
286-
if (mem_block != MAP_FAILED) {
287-
ruby_annotate_mmap(mem_block, mem_size, "Ruby:rb_yjit_reserve_addr_space");
288-
break;
289-
}
290-
291-
// -4MiB. Downwards to probe away from the heap. (On x86/A64 Linux
292-
// main_code_addr < heap_addr, and in case we are in a shared
293-
// library mapped higher than the heap, downwards is still better
294-
// since it's towards the end of the heap rather than the stack.)
295-
req_addr -= 4 * 1024 * 1024;
296-
} while (req_addr < probe_region_end);
297-
298-
// On MacOS and other platforms
299-
#else
300-
// Try to map a chunk of memory as executable
301-
mem_block = mmap(
302-
(void *)rb_yjit_reserve_addr_space,
303-
mem_size,
304-
PROT_NONE,
305-
MAP_PRIVATE | MAP_ANONYMOUS,
306-
-1,
307-
0
308-
);
309-
#endif
310-
311-
// Fallback
312-
if (mem_block == MAP_FAILED) {
313-
// Try again without the address hint (e.g., valgrind)
314-
mem_block = mmap(
315-
NULL,
316-
mem_size,
317-
PROT_NONE,
318-
MAP_PRIVATE | MAP_ANONYMOUS,
319-
-1,
320-
0
321-
);
322-
323-
if (mem_block != MAP_FAILED) {
324-
ruby_annotate_mmap(mem_block, mem_size, "Ruby:rb_yjit_reserve_addr_space:fallback");
325-
}
326-
}
327-
328-
// Check that the memory mapping was successful
329-
if (mem_block == MAP_FAILED) {
330-
perror("ruby: yjit: mmap:");
331-
if(errno == ENOMEM) {
332-
// No crash report if it's only insufficient memory
333-
exit(EXIT_FAILURE);
334-
}
335-
rb_bug("mmap failed");
336-
}
337-
338-
return mem_block;
339-
#else
340-
// Windows not supported for now
341-
return NULL;
342-
#endif
343-
}
344-
345172
// Is anyone listening for :c_call and :c_return event currently?
346173
bool
347174
rb_c_method_tracing_currently_enabled(const rb_execution_context_t *ec)

0 commit comments

Comments
 (0)