Skip to content

Commit 85510fc

Browse files
st0012k0kubun
authored andcommitted
ZJIT: Support annotating builtin functions
This allows us to annotate builtin functions with their return type.
1 parent 19cbf84 commit 85510fc

File tree

4 files changed

+101
-12
lines changed

4 files changed

+101
-12
lines changed

zjit/src/codegen.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
340340
Insn::SendWithoutBlockDirect { cd, state, self_val, args, .. } if args.len() + 1 > C_ARG_OPNDS.len() => // +1 for self
341341
gen_send_without_block(jit, asm, *cd, &function.frame_state(*state), opnd!(self_val), opnds!(args))?,
342342
Insn::SendWithoutBlockDirect { cme, iseq, self_val, args, state, .. } => gen_send_without_block_direct(cb, jit, asm, *cme, *iseq, opnd!(self_val), opnds!(args), &function.frame_state(*state))?,
343-
Insn::InvokeBuiltin { bf, args, state } => gen_invokebuiltin(asm, &function.frame_state(*state), bf, opnds!(args))?,
343+
Insn::InvokeBuiltin { bf, args, state, .. } => gen_invokebuiltin(asm, &function.frame_state(*state), bf, opnds!(args))?,
344344
Insn::Return { val } => return Some(gen_return(asm, opnd!(val))?),
345345
Insn::FixnumAdd { left, right, state } => gen_fixnum_add(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state))?,
346346
Insn::FixnumSub { left, right, state } => gen_fixnum_sub(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state))?,

zjit/src/cruby_methods.rs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ 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>,
1823
}
1924

2025
/// Runtime behaviors of C functions that implement a Ruby method
@@ -41,6 +46,25 @@ impl Annotations {
4146
};
4247
self.cfuncs.get(&fn_ptr).copied()
4348
}
49+
50+
/// 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> {
53+
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
67+
}
4468
}
4569

4670
fn annotate_c_method(props_map: &mut HashMap<*mut c_void, FnProperties>, class: VALUE, method_name: &'static str, props: FnProperties) {
@@ -63,6 +87,7 @@ fn annotate_c_method(props_map: &mut HashMap<*mut c_void, FnProperties>, class:
6387
/// are about the stock versions of methods.
6488
pub fn init() -> Annotations {
6589
let cfuncs = &mut HashMap::new();
90+
let builtin_names = &mut HashMap::new();
6691

6792
macro_rules! annotate {
6893
($module:ident, $method_name:literal, $return_type:expr, $($properties:ident),+) => {
@@ -74,6 +99,22 @@ pub fn init() -> Annotations {
7499
}
75100
}
76101

102+
macro_rules! annotate_builtin {
103+
($name:literal, $return_type:expr) => {
104+
annotate_builtin!($name, $return_type, no_gc, leaf, elidable)
105+
};
106+
($name:literal, $return_type:expr, $($properties:ident),+) => {
107+
let mut props = FnProperties {
108+
no_gc: false,
109+
leaf: false,
110+
elidable: false,
111+
return_type: $return_type
112+
};
113+
$(props.$properties = true;)+
114+
builtin_names.insert($name, props);
115+
}
116+
}
117+
77118
annotate!(rb_mKernel, "itself", types::BasicObject, no_gc, leaf, elidable);
78119
annotate!(rb_cString, "bytesize", types::Fixnum, no_gc, leaf);
79120
annotate!(rb_cModule, "name", types::StringExact.union(types::NilClass), no_gc, leaf, elidable);
@@ -83,7 +124,13 @@ pub fn init() -> Annotations {
83124
annotate!(rb_cNilClass, "nil?", types::TrueClass, no_gc, leaf, elidable);
84125
annotate!(rb_mKernel, "nil?", types::FalseClass, no_gc, leaf, elidable);
85126

127+
// Annotate builtin functions
128+
annotate_builtin!("rb_f_float", types::Flonum);
129+
annotate_builtin!("rb_f_float1", types::Flonum);
130+
86131
Annotations {
87-
cfuncs: std::mem::take(cfuncs)
132+
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)
88135
}
89136
}

zjit/src/hir.rs

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -522,7 +522,12 @@ pub enum Insn {
522522
},
523523

524524
// Invoke a builtin function
525-
InvokeBuiltin { bf: rb_builtin_function, args: Vec<InsnId>, state: InsnId },
525+
InvokeBuiltin {
526+
bf: rb_builtin_function,
527+
args: Vec<InsnId>,
528+
state: InsnId,
529+
return_type: Option<Type>, // None for unannotated builtins
530+
},
526531

527532
/// Control flow instructions
528533
Return { val: InsnId },
@@ -1163,7 +1168,7 @@ impl Function {
11631168
args: find_vec!(args),
11641169
state,
11651170
},
1166-
&InvokeBuiltin { bf, ref args, state } => InvokeBuiltin { bf: bf, args: find_vec!(args), state },
1171+
&InvokeBuiltin { bf, ref args, state, return_type } => InvokeBuiltin { bf, args: find_vec!(args), state, return_type },
11671172
&ArrayDup { val, state } => ArrayDup { val: find!(val), state },
11681173
&HashDup { val, state } => HashDup { val: find!(val), state },
11691174
&CCall { cfun, ref args, name, return_type, elidable } => CCall { cfun, args: find_vec!(args), name, return_type, elidable },
@@ -1260,7 +1265,7 @@ impl Function {
12601265
Insn::SendWithoutBlock { .. } => types::BasicObject,
12611266
Insn::SendWithoutBlockDirect { .. } => types::BasicObject,
12621267
Insn::Send { .. } => types::BasicObject,
1263-
Insn::InvokeBuiltin { .. } => types::BasicObject,
1268+
Insn::InvokeBuiltin { return_type, .. } => return_type.unwrap_or(types::BasicObject),
12641269
Insn::Defined { .. } => types::BasicObject,
12651270
Insn::DefinedIvar { .. } => types::BasicObject,
12661271
Insn::GetConstantPath { .. } => types::BasicObject,
@@ -3119,7 +3124,18 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
31193124
args.reverse();
31203125

31213126
let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
3122-
let insn_id = fun.push_insn(block, Insn::InvokeBuiltin { bf, args, state: exit_id });
3127+
3128+
// Check if this builtin is annotated
3129+
let return_type = ZJITState::get_method_annotations()
3130+
.get_builtin_properties(&bf)
3131+
.map(|props| props.return_type);
3132+
3133+
let insn_id = fun.push_insn(block, Insn::InvokeBuiltin {
3134+
bf,
3135+
args,
3136+
state: exit_id,
3137+
return_type,
3138+
});
31233139
state.stack_push(insn_id);
31243140
}
31253141
YARVINSN_opt_invokebuiltin_delegate |
@@ -3134,7 +3150,18 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
31343150
}
31353151

31363152
let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
3137-
let insn_id = fun.push_insn(block, Insn::InvokeBuiltin { bf, args, state: exit_id });
3153+
3154+
// Check if this builtin is annotated
3155+
let return_type = ZJITState::get_method_annotations()
3156+
.get_builtin_properties(&bf)
3157+
.map(|props| props.return_type);
3158+
3159+
let insn_id = fun.push_insn(block, Insn::InvokeBuiltin {
3160+
bf,
3161+
args,
3162+
state: exit_id,
3163+
return_type,
3164+
});
31383165
state.stack_push(insn_id);
31393166
}
31403167
YARVINSN_objtostring => {
@@ -4973,17 +5000,32 @@ mod tests {
49735000
}
49745001

49755002
#[test]
4976-
fn test_invokebuiltin_delegate_with_args() {
5003+
fn test_invokebuiltin_delegate_annotated() {
49775004
assert_method_hir_with_opcode("Float", YARVINSN_opt_invokebuiltin_delegate_leave, expect![[r#"
49785005
fn Float@<internal:kernel>:197:
49795006
bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject, v3:BasicObject):
4980-
v6:BasicObject = InvokeBuiltin rb_f_float, v0, v1, v2
5007+
v6:Flonum = InvokeBuiltin rb_f_float, v0, v1, v2
49815008
Jump bb1(v0, v1, v2, v3, v6)
4982-
bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject, v11:BasicObject, v12:BasicObject):
5009+
bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject, v11:BasicObject, v12:Flonum):
49835010
Return v12
49845011
"#]]);
49855012
}
49865013

5014+
#[test]
5015+
fn test_invokebuiltin_delegate_with_args() {
5016+
// Using an unannotated builtin to test InvokeBuiltin generation
5017+
let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("Dir", "open"));
5018+
assert!(iseq_contains_opcode(iseq, YARVINSN_opt_invokebuiltin_delegate), "iseq Dir.open does not contain invokebuiltin");
5019+
let function = iseq_to_hir(iseq).unwrap();
5020+
assert_function_hir(function, expect![[r#"
5021+
fn open@<internal:dir>:184:
5022+
bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject, v3:BasicObject, v4:BasicObject):
5023+
v5:NilClass = Const Value(nil)
5024+
v8:BasicObject = InvokeBuiltin dir_s_open, v0, v1, v2
5025+
SideExit UnknownOpcode(getblockparamproxy)
5026+
"#]]);
5027+
}
5028+
49875029
#[test]
49885030
fn test_invokebuiltin_delegate_without_args() {
49895031
assert_method_hir_with_opcode("class", YARVINSN_opt_invokebuiltin_delegate_leave, expect![[r#"

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

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

0 commit comments

Comments
 (0)