Skip to content

Commit a06c055

Browse files
committed
ZJIT: Improve builtin function annotation collection
1 parent a85b1af commit a06c055

2 files changed

Lines changed: 69 additions & 32 deletions

File tree

zjit/src/cruby_methods.rs

Lines changed: 67 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,7 @@ use crate::hir_type::{types, Type};
1515

1616
pub struct Annotations {
1717
cfuncs: HashMap<*mut c_void, FnProperties>,
18-
// Builtin annotations by function pointer for fast runtime lookup
19-
// Uses Option to cache both hits and misses
20-
builtin_ptrs: HashMap<*mut c_void, Option<FnProperties>>,
21-
// Temporary name-based annotations used during initialization
22-
builtin_names: HashMap<&'static str, FnProperties>,
18+
builtin_funcs: HashMap<*mut c_void, FnProperties>,
2319
}
2420

2521
/// Runtime behaviors of C functions that implement a Ruby method
@@ -48,22 +44,9 @@ impl Annotations {
4844
}
4945

5046
/// Query about properties of a builtin function by its pointer
51-
/// If not found by pointer, checks by name and caches the result (including misses)
52-
pub fn get_builtin_properties(&mut self, bf: *const rb_builtin_function) -> Option<FnProperties> {
47+
pub fn get_builtin_properties(&self, bf: *const rb_builtin_function) -> Option<FnProperties> {
5348
let func_ptr = unsafe { (*bf).func_ptr as *mut c_void };
54-
55-
// First check if we already have it cached by pointer
56-
if let Some(&cached_result) = self.builtin_ptrs.get(&func_ptr) {
57-
return cached_result;
58-
}
59-
60-
// If not found, check by name and cache the result
61-
let name = unsafe { std::ffi::CStr::from_ptr((*bf).name).to_str().ok()? };
62-
let result = self.builtin_names.get(name).copied();
63-
64-
// Cache the result (both hits and misses)
65-
self.builtin_ptrs.insert(func_ptr, result);
66-
result
49+
self.builtin_funcs.get(&func_ptr).copied()
6750
}
6851
}
6952

@@ -83,11 +66,66 @@ fn annotate_c_method(props_map: &mut HashMap<*mut c_void, FnProperties>, class:
8366
props_map.insert(fn_ptr, props);
8467
}
8568

69+
/// Look up a method and find its builtin function pointer by parsing its ISEQ
70+
fn annotate_builtin_method(props_map: &mut HashMap<*mut c_void, FnProperties>, class: VALUE, method_name: &'static str, props: FnProperties) {
71+
unsafe {
72+
let method_id = rb_intern2(method_name.as_ptr().cast(), method_name.len().try_into().unwrap());
73+
let method = rb_method_entry_at(class, method_id);
74+
if method.is_null() {
75+
panic!("Method {}#{} not found", std::ffi::CStr::from_ptr(rb_class2name(class)).to_str().unwrap_or("?"), method_name);
76+
}
77+
78+
// Cast ME to CME - they have identical layout
79+
let cme = method.cast::<rb_callable_method_entry_t>();
80+
let def_type = get_cme_def_type(cme);
81+
82+
if def_type != VM_METHOD_TYPE_ISEQ {
83+
panic!("Method {}#{} is not an ISEQ method (type: {})",
84+
std::ffi::CStr::from_ptr(rb_class2name(class)).to_str().unwrap_or("?"),
85+
method_name, def_type);
86+
}
87+
88+
// Get the ISEQ from the method definition
89+
let iseq = get_def_iseq_ptr((*cme).def);
90+
if iseq.is_null() {
91+
panic!("Failed to get ISEQ for {}#{}",
92+
std::ffi::CStr::from_ptr(rb_class2name(class)).to_str().unwrap_or("?"),
93+
method_name);
94+
}
95+
96+
// Get the size of the ISEQ in instruction units
97+
let encoded_size = rb_iseq_encoded_size(iseq);
98+
99+
// Scan through the ISEQ to find invokebuiltin instructions
100+
let mut insn_idx: u32 = 0;
101+
while insn_idx < encoded_size {
102+
// Get the PC for this instruction index
103+
let pc = rb_iseq_pc_at_idx(iseq, insn_idx);
104+
105+
// Get the opcode using the proper decoder
106+
let opcode = rb_iseq_opcode_at_pc(iseq, pc);
107+
108+
if opcode == YARVINSN_invokebuiltin as i32 ||
109+
opcode == YARVINSN_opt_invokebuiltin_delegate as i32 ||
110+
opcode == YARVINSN_opt_invokebuiltin_delegate_leave as i32 {
111+
// The first operand is the builtin function pointer
112+
let bf_value = *pc.add(1);
113+
let bf_ptr = bf_value.as_ptr() as *const rb_builtin_function;
114+
let func_ptr = (*bf_ptr).func_ptr as *mut c_void;
115+
props_map.insert(func_ptr, props);
116+
}
117+
118+
// Move to the next instruction using the proper length
119+
insn_idx = insn_idx.saturating_add(rb_insn_len(VALUE(opcode as usize)).try_into().unwrap());
120+
}
121+
}
122+
}
123+
86124
/// Gather annotations. Run this right after boot since the annotations
87125
/// are about the stock versions of methods.
88126
pub fn init() -> Annotations {
89127
let cfuncs = &mut HashMap::new();
90-
let builtin_names = &mut HashMap::new();
128+
let builtin_funcs = &mut HashMap::new();
91129

92130
macro_rules! annotate {
93131
($module:ident, $method_name:literal, $return_type:expr, $($properties:ident),+) => {
@@ -100,18 +138,18 @@ pub fn init() -> Annotations {
100138
}
101139

102140
macro_rules! annotate_builtin {
103-
($name:literal, $return_type:expr) => {
104-
annotate_builtin!($name, $return_type, no_gc, leaf, elidable)
141+
($module:ident, $method_name:literal, $return_type:expr) => {
142+
annotate_builtin!($module, $method_name, $return_type, no_gc, leaf, elidable)
105143
};
106-
($name:literal, $return_type:expr, $($properties:ident),+) => {
144+
($module:ident, $method_name:literal, $return_type:expr, $($properties:ident),+) => {
107145
let mut props = FnProperties {
108146
no_gc: false,
109147
leaf: false,
110148
elidable: false,
111149
return_type: $return_type
112150
};
113151
$(props.$properties = true;)+
114-
builtin_names.insert($name, props);
152+
annotate_builtin_method(builtin_funcs, unsafe { $module }, $method_name, props);
115153
}
116154
}
117155

@@ -124,13 +162,12 @@ pub fn init() -> Annotations {
124162
annotate!(rb_cNilClass, "nil?", types::TrueClass, no_gc, leaf, elidable);
125163
annotate!(rb_mKernel, "nil?", types::FalseClass, no_gc, leaf, elidable);
126164

127-
// Annotate builtin functions
128-
annotate_builtin!("rb_f_float", types::Flonum);
129-
annotate_builtin!("rb_f_float1", types::Flonum);
165+
// Annotate builtin functions using the new method-based approach
166+
annotate_builtin!(rb_mKernel, "Float", types::Flonum);
167+
annotate_builtin!(rb_mKernel, "Integer", types::Integer);
130168

131169
Annotations {
132170
cfuncs: std::mem::take(cfuncs),
133-
builtin_ptrs: HashMap::new(), // Cache queried built-in functions by pointer
134-
builtin_names: std::mem::take(builtin_names)
171+
builtin_funcs: std::mem::take(builtin_funcs),
135172
}
136173
}

zjit/src/state.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,8 @@ impl ZJITState {
117117
&mut ZJITState::get_instance().invariants
118118
}
119119

120-
pub fn get_method_annotations() -> &'static mut cruby_methods::Annotations {
121-
&mut ZJITState::get_instance().method_annotations
120+
pub fn get_method_annotations() -> &'static cruby_methods::Annotations {
121+
&ZJITState::get_instance().method_annotations
122122
}
123123

124124
/// Return true if successful compilation should be asserted

0 commit comments

Comments
 (0)