Ruby 4.1.0dev (2026-09-07 revision b57404b461ba8bf34e802d86b0db78388216e182)
zjit.c (b57404b461ba8bf34e802d86b0db78388216e182)
1#include "internal.h"
2#include "internal/sanitizers.h"
3#include "internal/string.h"
4#include "internal/hash.h"
5#include "internal/variable.h"
6#include "internal/compile.h"
7#include "internal/class.h"
8#include "internal/fixnum.h"
9#include "internal/numeric.h"
10#include "internal/gc.h"
11#include "internal/vm.h"
12#include "yjit.h"
13#include "vm_core.h"
14#include "vm_callinfo.h"
15#include "builtin.h"
16#include "insns.inc"
17#include "insns_info.inc"
18#include "zjit.h"
19#include "vm_insnhelper.h"
20#include "probes.h"
21#include "probes_helper.h"
22#include "constant.h"
23#include "iseq.h"
24#include "ruby/debug.h"
25#include "internal/cont.h"
26#include "ractor_core.h"
27#include "shape.h"
28
29#ifndef _WIN32
30#include <sys/mman.h>
31#endif
32
33// This build config impacts the pointer tagging scheme and we only want to
34// support one scheme for simplicity.
35STATIC_ASSERT(pointer_tagging_scheme, USE_FLONUM);
36
37enum zjit_struct_offsets {
38 ISEQ_BODY_OFFSET_PARAM = offsetof(struct rb_iseq_constant_body, param),
39 ISEQ_BODY_OFFSET_OUTER_VARIABLES = offsetof(struct rb_iseq_constant_body, outer_variables),
40 RUBY_OFFSET_THREAD_RACTOR = offsetof(rb_thread_t, ractor),
41};
42
43// Struct offsets that cannot be constants in the checked-in bindgen output
44// (zjit/src/cruby_bindings.inc.rs) because they vary with the build target
45// and configuration. For example, offsetof(rb_ractor_t, newobj_cache) depends
46// on the sizes of pthread types embedded in rb_ractor_t, which differ across
47// architectures and OSes, as well as on VM_CHECK_MODE and RACTOR_CHECK_MODE.
48// This table is filled out at C compile time and read by Rust at JIT compile
49// time. Offsets that are identical on all supported builds should be added to
50// enum zjit_struct_offsets above instead.
52 int32_t ractor_newobj_cache;
53 int32_t ractor_objspace;
54};
56 .ractor_newobj_cache = offsetof(rb_ractor_t, newobj_cache),
57 .ractor_objspace = offsetof(rb_ractor_t, objspace),
58};
59
60// Special JITFrame used by all C method calls. We don't control the native
61// stack layout for C frames, so cfp->jit_return points at this static frame
62// via the ZJIT_JIT_RETURN_C_FRAME sentinel instead of a per-call allocation.
63const zjit_jit_frame_t rb_zjit_c_frame = (zjit_jit_frame_t) {
64 .pc = 0,
65 .iseq = 0,
66 .materialize_block_code = false,
67};
68
69#if !defined(_WIN32) && defined(MAP_ANONYMOUS)
70uint8_t *rb_jit_align_ptr(uint8_t *ptr, uint32_t multiple); // defined in jit.c
71
72// Reserve address space that lives entirely below INT32_MAX for JITFrame.
73//
74// When a JITFrame pointer fits in 32 bits, x86_64 can encode the store
75// as `mov qword ptr [mem], imm32` (8 bytes) instead of `movabs` + a store,
76// and arm64 materializes it in two instructions instead of four.
77//
78// Like rb_jit_reserve_addr_space in jit.c, this only reserves address space (PROT_NONE).
79// VirtualMem is in charge of mapping physical memory into the reserved space page by page.
80void *
81rb_zjit_reserve_low_addr_space(size_t size)
82{
83 void *mem_block = MAP_FAILED;
84
85 // Linux (x86_64): Use MAP_32BIT to map within the first 2GiB of address space.
86 // This works only for x86_64, and the kernel restricts it to [1GiB, 2GiB).
87 #ifdef MAP_32BIT
88 mem_block = mmap(NULL, size, PROT_NONE,
89 MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0);
90 #endif
91
92 // Linux (all arch): Probe a free hole below 2GiB if MAP_32BIT is not possible.
93 // MAP_FIXED_NOREPLACE fails rather than clobbering an existing mapping.
94 #if defined(MAP_FIXED_NOREPLACE) && defined(_SC_PAGESIZE)
95 if (mem_block == MAP_FAILED) {
96 // Distance between probes. 64MiB sweeps the usable 2GiB in at most 32 mmap calls.
97 const uintptr_t probe_stride = 64 * 1024 * 1024;
98 const uint32_t page_size = (uint32_t)sysconf(_SC_PAGESIZE);
99 const uintptr_t limit = (uintptr_t)INT32_MAX - size;
100 for (uintptr_t addr = probe_stride; addr < limit; addr += probe_stride) {
101 // mmap only honors a hint that is page-aligned.
102 void *req = rb_jit_align_ptr((uint8_t *)addr, page_size);
103 mem_block = mmap(req, size, PROT_NONE,
104 MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0);
105 if (mem_block != MAP_FAILED) break;
106 }
107 }
108 #endif
109
110 if (mem_block == MAP_FAILED) return NULL;
111
112 // Both MAP_32BIT and MAP_FIXED_NOREPLACE are advisory in some platforms, e.g.
113 // sandboxes or older kernels. Fallback to normal allocation if it doesn't work.
114 if ((uintptr_t)mem_block + size > (uintptr_t)INT32_MAX) {
115 munmap(mem_block, size);
116 return NULL;
117 }
118 ruby_annotate_mmap(mem_block, size, "Ruby:rb_zjit_reserve_low_addr_space");
119 return mem_block;
120}
121
122#else
123
124// Windows not supported for now
125void *rb_zjit_reserve_low_addr_space(size_t size) { return NULL; }
126
127#endif
128
129void rb_zjit_profile_disable(const rb_iseq_t *iseq);
130int rb_zjit_insn_to_bare_insn(int insn);
131
132void
133rb_zjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit_exception)
134{
135 RB_VM_LOCKING() {
136 rb_vm_barrier();
137
138 // Compile a block version starting at the current instruction
139 uint8_t *rb_zjit_iseq_gen_entry_point(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit_exception); // defined in Rust
140 uintptr_t code_ptr = (uintptr_t)rb_zjit_iseq_gen_entry_point(iseq, ec, jit_exception);
141
142 if (jit_exception) {
143 ISEQ_BODY(iseq)->jit_exception = (rb_jit_func_t)code_ptr;
144 }
145 else {
146 ISEQ_BODY(iseq)->jit_entry = (rb_jit_func_t)code_ptr;
147 }
148 }
149}
150
151extern VALUE *rb_vm_base_ptr(struct rb_control_frame_struct *cfp);
152
153// Convert a given ISEQ's instructions to zjit_* instructions
154void
155rb_zjit_profile_enable(const rb_iseq_t *iseq)
156{
157 // This table encodes an opcode into the instruction's address
158 const void *const *insn_table = rb_vm_get_insns_address_table();
159
160 unsigned int insn_idx = 0;
161 while (insn_idx < ISEQ_BODY(iseq)->iseq_size) {
162 int insn = rb_vm_insn_addr2opcode((void *)ISEQ_BODY(iseq)->iseq_encoded[insn_idx]);
163 int zjit_insn = vm_bare_insn_to_zjit_insn(insn);
164 if (insn != zjit_insn) {
165 ISEQ_BODY(iseq)->iseq_encoded[insn_idx] = (VALUE)insn_table[zjit_insn];
166 }
167 insn_idx += insn_len(insn);
168 }
169}
170
171// Convert a given ISEQ's ZJIT instructions to bare instructions
172void
173rb_zjit_profile_disable(const rb_iseq_t *iseq)
174{
175 // This table encodes an opcode into the instruction's address
176 const void *const *insn_table = rb_vm_get_insns_address_table();
177
178 unsigned int insn_idx = 0;
179 while (insn_idx < ISEQ_BODY(iseq)->iseq_size) {
180 int insn = rb_vm_insn_addr2opcode((void *)ISEQ_BODY(iseq)->iseq_encoded[insn_idx]);
181 int bare_insn = vm_zjit_insn_to_bare_insn(insn);
182 if (insn != bare_insn) {
183 ISEQ_BODY(iseq)->iseq_encoded[insn_idx] = (VALUE)insn_table[bare_insn];
184 }
185 insn_idx += insn_len(insn);
186 }
187}
188
189// Map `zjit_* instructions back to their bare form. This is an identity function for all others.
190int
191rb_zjit_insn_to_bare_insn(int insn)
192{
193 return vm_zjit_insn_to_bare_insn(insn);
194}
195
196// Update a YARV instruction to a given opcode (to disable ZJIT profiling).
197void
198rb_zjit_iseq_insn_set(const rb_iseq_t *iseq, unsigned int insn_idx, enum ruby_vminsn_type bare_insn)
199{
200#if RUBY_DEBUG
201 int insn = rb_vm_insn_addr2opcode((void *)ISEQ_BODY(iseq)->iseq_encoded[insn_idx]);
202 RUBY_ASSERT(vm_zjit_insn_to_bare_insn(insn) == (int)bare_insn);
203#endif
204 const void *const *insn_table = rb_vm_get_insns_address_table();
205 ISEQ_BODY(iseq)->iseq_encoded[insn_idx] = (VALUE)insn_table[bare_insn];
206}
207
208void
209rb_zjit_print_exception(void)
210{
211 VALUE exception = rb_errinfo();
212 rb_set_errinfo(Qnil);
213 assert(RTEST(exception));
214 rb_warn("Ruby error: %"PRIsVALUE"", rb_funcall(exception, rb_intern("full_message"), 0));
215}
216
217bool
218rb_zjit_singleton_class_p(VALUE klass)
219{
220 return RCLASS_SINGLETON_P(klass);
221}
222
223/* Sets all of the required shape flags for the object including the layout type,
224 * the frozen status, and the slot size. Mimics `rb_newobj`.
225 */
226VALUE
227rb_zjit_new_obj_shape(VALUE flags, size_t alloc_size)
228{
229 shape_id_t shape_id;
230 switch (flags & T_MASK) {
231 case T_OBJECT:
232 shape_id = ROOT_SHAPE_ID;
233 break;
234 case T_STRUCT:
235 shape_id = ROOT_SHAPE_ID | SHAPE_ID_LAYOUT_EXTENDED;
236 break;
237 case T_DATA:
238 shape_id = ROOT_SHAPE_ID | SHAPE_ID_LAYOUT_RDATA;
239 break;
240 default:
241 shape_id = ROOT_SHAPE_ID | SHAPE_ID_LAYOUT_OTHER;
242 break;
243 }
244
245 if (flags & FL_FREEZE) {
246 shape_id = rb_shape_transition_frozen(shape_id);
247 }
248
249 shape_id = rb_shape_transition_slot_size(shape_id, rb_gc_size_slot_size(alloc_size));
250
251 return (flags & SHAPE_FLAG_MASK) | ((VALUE)shape_id << SHAPE_FLAG_SHIFT);
252}
253
254VALUE
255rb_zjit_defined_ivar(VALUE obj, ID id, VALUE pushval)
256{
257 VALUE result = rb_ivar_defined(obj, id);
258 return result ? pushval : Qnil;
259}
260
261bool
262rb_zjit_method_tracing_currently_enabled(void)
263{
264 rb_event_flag_t tracing_events;
265 if (rb_multi_ractor_p()) {
266 tracing_events = ruby_vm_event_enabled_global_flags;
267 }
268 else {
269 // At the time of writing, events are never removed from
270 // ruby_vm_event_enabled_global_flags so always checking using it would
271 // mean we don't compile even after tracing is disabled.
272 tracing_events = rb_ec_ractor_hooks(GET_EC())->events;
273 }
274
275 return tracing_events & (RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN);
276}
277
278// Check if any ISEQ trace events are currently enabled.
279// Used to prevent ZJIT from compiling while tracing is active, since ZJIT's
280// send fallback (rb_vm_opt_send_without_block) uses VM_EXEC which sets
281// VM_FRAME_FLAG_FINISH on the callee frame, changing exception handling
282// semantics for throw TAG_RETURN (e.g. return from rescue).
283bool
284rb_zjit_iseq_tracing_currently_enabled(void)
285{
286 rb_event_flag_t tracing_events;
287 if (rb_multi_ractor_p()) {
288 tracing_events = ruby_vm_event_enabled_global_flags;
289 }
290 else {
291 tracing_events = rb_ec_ractor_hooks(GET_EC())->events;
292 }
293
294 return tracing_events & ISEQ_TRACE_EVENTS;
295}
296
297bool
298rb_zjit_insn_leaf(int insn, const VALUE *opes)
299{
300 return insn_leaf(insn, opes);
301}
302
303ID
304rb_zjit_local_id(const rb_iseq_t *iseq, unsigned idx)
305{
306 return ISEQ_BODY(iseq)->local_table[idx];
307}
308
309bool rb_zjit_cme_is_cfunc(const rb_callable_method_entry_t *me, const void *func);
310
312rb_zjit_vm_search_method(VALUE cd_owner, struct rb_call_data *cd, VALUE recv);
313
314bool
315rb_zjit_class_initialized_p(VALUE klass)
316{
317 return RCLASS_INITIALIZED_P(klass);
318}
319
320rb_alloc_func_t rb_zjit_class_get_alloc_func(VALUE klass);
321
322VALUE rb_class_allocate_instance(VALUE klass);
323
324bool
325rb_zjit_class_has_default_allocator(VALUE klass)
326{
327 assert(RCLASS_INITIALIZED_P(klass));
328 assert(!RCLASS_SINGLETON_P(klass));
329 rb_alloc_func_t alloc = rb_zjit_class_get_alloc_func(klass);
330 return alloc == rb_class_allocate_instance;
331}
332
333
334VALUE rb_vm_untag_block_handler(VALUE block_handler);
335VALUE rb_vm_get_untagged_block_handler(rb_control_frame_t *reg_cfp);
336
337// Primitives used by zjit.rb. Don't put other functions below, which wouldn't use them.
338VALUE rb_zjit_enable(rb_execution_context_t *ec, VALUE self);
339VALUE rb_zjit_assert_compiles(rb_execution_context_t *ec, VALUE self);
340VALUE rb_zjit_stats(rb_execution_context_t *ec, VALUE self, VALUE target_key);
341VALUE rb_zjit_reset_stats_bang(rb_execution_context_t *ec, VALUE self);
342VALUE rb_zjit_stats_enabled_p(rb_execution_context_t *ec, VALUE self);
343VALUE rb_zjit_print_stats_p(rb_execution_context_t *ec, VALUE self);
344VALUE rb_zjit_get_stats_file_path_p(rb_execution_context_t *ec, VALUE self);
345VALUE rb_zjit_trace_exit_locations_enabled_p(rb_execution_context_t *ec, VALUE self);
346VALUE rb_zjit_get_exit_locations(rb_execution_context_t *ec, VALUE self);
347
348// Preprocessed zjit.rb generated during build
349#include "zjit.rbinc"
#define RUBY_ASSERT(...)
Asserts that the given expression is truthy if and only if RUBY_DEBUG is truthy.
Definition assert.h:219
#define RUBY_EVENT_C_CALL
A method, written in C, is called.
Definition event.h:43
#define RUBY_EVENT_C_RETURN
Return from a method, written in C.
Definition event.h:44
uint32_t rb_event_flag_t
Represents event(s).
Definition event.h:108
#define T_MASK
Old name of RUBY_T_MASK.
Definition value_type.h:68
#define T_STRUCT
Old name of RUBY_T_STRUCT.
Definition value_type.h:79
#define T_DATA
Old name of RUBY_T_DATA.
Definition value_type.h:60
#define Qnil
Old name of RUBY_Qnil.
#define T_OBJECT
Old name of RUBY_T_OBJECT.
Definition value_type.h:75
#define FL_FREEZE
Old name of RUBY_FL_FREEZE.
Definition fl_type.h:65
void rb_warn(const char *fmt,...)
Identical to rb_warning(), except it reports unless $VERBOSE is nil.
Definition error.c:468
VALUE rb_funcall(VALUE recv, ID mid, int n,...)
Calls a method.
Definition vm_eval.c:1123
Defines RBIMPL_HAS_BUILTIN.
VALUE rb_ivar_defined(VALUE obj, ID name)
Queries if the instance variable is defined at the object.
Definition variable.c:2120
VALUE(* rb_alloc_func_t)(VALUE klass)
This is the type of functions that ruby calls when trying to allocate an object.
Definition vm.h:219
#define RTEST
This is an old name of RB_TEST.
#define USE_FLONUM
Definition method.h:63
uintptr_t ID
Type that represents a Ruby identifier such as a variable name.
Definition value.h:52
uintptr_t VALUE
Type that represents a Ruby object.
Definition value.h:40