Profile instructions for fixnum arithmetic (https://github.com/Shopify/zjit/pull/24)

* Profile instructions for fixnum arithmetic

* Drop PartialEq from Type

* Do not push PatchPoint onto the stack

* Avoid pushing the output of the guards

* Pop operands after guards

* Test HIR from profiled runs

* Implement Display for new instructions

* Drop unused FIXNUM_BITS

* Use a Rust function to split lines

* Use Display for GuardType operands

Co-authored-by: Max Bernstein <max@bernsteinbear.com>

* Fix tests with Display-ed values

---------

Co-authored-by: Max Bernstein <max@bernsteinbear.com>
This commit is contained in:
Takashi Kokubun 2025-03-05 11:17:36 -08:00
parent 9e31d29e0e
commit 8b2a4625cb
Notes: git 2025-04-18 13:48:26 +00:00
6 changed files with 679 additions and 176 deletions

View file

@ -1280,6 +1280,7 @@ opt_minus
(CALL_DATA cd)
(VALUE recv, VALUE obj)
(VALUE val)
// attr bool zjit_profile = true;
{
val = vm_opt_minus(recv, obj);
@ -1294,6 +1295,7 @@ opt_mult
(CALL_DATA cd)
(VALUE recv, VALUE obj)
(VALUE val)
// attr bool zjit_profile = true;
{
val = vm_opt_mult(recv, obj);
@ -1311,6 +1313,7 @@ opt_div
/* In case of division by zero, it raises. Thus
* ZeroDivisionError#initialize is called. */
// attr bool leaf = false;
// attr bool zjit_profile = true;
{
val = vm_opt_div(recv, obj);
@ -1327,6 +1330,7 @@ opt_mod
(VALUE val)
/* Same discussion as opt_div. */
// attr bool leaf = false;
// attr bool zjit_profile = true;
{
val = vm_opt_mod(recv, obj);
@ -1341,6 +1345,7 @@ opt_eq
(CALL_DATA cd)
(VALUE recv, VALUE obj)
(VALUE val)
// attr bool zjit_profile = true;
{
val = opt_equality(GET_ISEQ(), recv, obj, cd);
@ -1355,6 +1360,7 @@ opt_neq
(CALL_DATA cd_eq, CALL_DATA cd)
(VALUE recv, VALUE obj)
(VALUE val)
// attr bool zjit_profile = true;
{
val = vm_opt_neq(GET_ISEQ(), cd, cd_eq, recv, obj);
@ -1369,6 +1375,7 @@ opt_lt
(CALL_DATA cd)
(VALUE recv, VALUE obj)
(VALUE val)
// attr bool zjit_profile = true;
{
val = vm_opt_lt(recv, obj);
@ -1383,6 +1390,7 @@ opt_le
(CALL_DATA cd)
(VALUE recv, VALUE obj)
(VALUE val)
// attr bool zjit_profile = true;
{
val = vm_opt_le(recv, obj);
@ -1397,6 +1405,7 @@ opt_gt
(CALL_DATA cd)
(VALUE recv, VALUE obj)
(VALUE val)
// attr bool zjit_profile = true;
{
val = vm_opt_gt(recv, obj);
@ -1411,6 +1420,7 @@ opt_ge
(CALL_DATA cd)
(VALUE recv, VALUE obj)
(VALUE val)
// attr bool zjit_profile = true;
{
val = vm_opt_ge(recv, obj);

View file

@ -853,7 +853,9 @@ pub use manual_defs::*;
#[cfg(test)]
pub mod test_utils {
use crate::{options::init_options, state::ZJITState};
use std::ptr::null;
use crate::{options::init_options, rb_zjit_enabled_p, state::ZJITState};
use super::*;
@ -879,6 +881,9 @@ pub mod test_utils {
// Set up globals for convenience
ZJITState::init(init_options());
// Enable zjit_* instructions
unsafe { rb_zjit_enabled_p = true; }
let mut state: c_int = 0;
unsafe { super::rb_protect(Some(callback_wrapper), VALUE((&mut data) as *mut _ as usize), &mut state) };
// TODO(alan): there should be a way to print the exception instead of swallowing it
@ -887,18 +892,66 @@ pub mod test_utils {
/// Compile an ISeq via `RubyVM::InstructionSequence.compile`.
pub fn compile_to_iseq(program: &str) -> *const rb_iseq_t {
let bytes = program.as_bytes().as_ptr() as *const c_char;
unsafe {
let program_str = rb_utf8_str_new(bytes, program.len().try_into().unwrap());
let wrapped_iseq = rb_funcallv(rb_cISeq, ID!(compile), 1, &program_str);
rb_iseqw_to_iseq(wrapped_iseq)
}
let wrapped_iseq = compile_to_wrapped_iseq(program);
unsafe { rb_iseqw_to_iseq(wrapped_iseq) }
}
pub fn define_class(name: &str, superclass: VALUE) -> VALUE {
let name = CString::new(name).unwrap();
unsafe { rb_define_class(name.as_ptr(), superclass) }
}
/// Evaluate a given Ruby program
pub fn eval(program: &str) -> VALUE {
let wrapped_iseq = compile_to_wrapped_iseq(&unindent(program, false));
unsafe { rb_funcallv(wrapped_iseq, ID!(eval), 0, null()) }
}
/// Get the ISeq of a specified method
pub fn get_method_iseq(name: &str) -> *const rb_iseq_t {
let wrapped_iseq = eval(&format!("RubyVM::InstructionSequence.of(method(:{}))", name));
unsafe { rb_iseqw_to_iseq(wrapped_iseq) }
}
/// Remove the minimum indent from every line, skipping the first and last lines if `trim_lines`.
pub fn unindent(string: &str, trim_lines: bool) -> String {
// Break up a string into multiple lines
let mut lines: Vec<String> = string.split_inclusive("\n").map(|s| s.to_string()).collect();
if trim_lines { // raw string literals come with extra lines
lines.remove(0);
lines.remove(lines.len() - 1);
}
// Count the minimum number of spaces
let spaces = lines.iter().filter_map(|line| {
for (i, ch) in line.as_bytes().iter().enumerate() {
if *ch != b' ' {
return Some(i);
}
}
None
}).min().unwrap_or(0);
// Join lines, removing spaces
let mut unindented: Vec<u8> = vec![];
for line in lines.iter() {
if line.len() > spaces {
unindented.extend_from_slice(&line.as_bytes()[spaces..]);
} else {
unindented.extend_from_slice(&line.as_bytes());
}
}
String::from_utf8(unindented).unwrap()
}
/// Compile a program into a RubyVM::InstructionSequence object
fn compile_to_wrapped_iseq(program: &str) -> VALUE {
let bytes = program.as_bytes().as_ptr() as *const c_char;
unsafe {
let program_str = rb_utf8_str_new(bytes, program.len().try_into().unwrap());
rb_funcallv(rb_cISeq, ID!(compile), 1, &program_str)
}
}
}
#[cfg(test)]
pub use test_utils::*;
@ -941,6 +994,7 @@ pub(crate) mod ids {
name: eq content: b"=="
name: include_p content: b"include?"
name: compile content: b"compile"
name: eval content: b"eval"
}
}

View file

@ -598,118 +598,138 @@ pub const YARVINSN_setlocal_WC_1: ruby_vminsn_type = 107;
pub const YARVINSN_putobject_INT2FIX_0_: ruby_vminsn_type = 108;
pub const YARVINSN_putobject_INT2FIX_1_: ruby_vminsn_type = 109;
pub const YARVINSN_zjit_opt_plus: ruby_vminsn_type = 110;
pub const YARVINSN_trace_nop: ruby_vminsn_type = 111;
pub const YARVINSN_trace_getlocal: ruby_vminsn_type = 112;
pub const YARVINSN_trace_setlocal: ruby_vminsn_type = 113;
pub const YARVINSN_trace_getblockparam: ruby_vminsn_type = 114;
pub const YARVINSN_trace_setblockparam: ruby_vminsn_type = 115;
pub const YARVINSN_trace_getblockparamproxy: ruby_vminsn_type = 116;
pub const YARVINSN_trace_getspecial: ruby_vminsn_type = 117;
pub const YARVINSN_trace_setspecial: ruby_vminsn_type = 118;
pub const YARVINSN_trace_getinstancevariable: ruby_vminsn_type = 119;
pub const YARVINSN_trace_setinstancevariable: ruby_vminsn_type = 120;
pub const YARVINSN_trace_getclassvariable: ruby_vminsn_type = 121;
pub const YARVINSN_trace_setclassvariable: ruby_vminsn_type = 122;
pub const YARVINSN_trace_opt_getconstant_path: ruby_vminsn_type = 123;
pub const YARVINSN_trace_getconstant: ruby_vminsn_type = 124;
pub const YARVINSN_trace_setconstant: ruby_vminsn_type = 125;
pub const YARVINSN_trace_getglobal: ruby_vminsn_type = 126;
pub const YARVINSN_trace_setglobal: ruby_vminsn_type = 127;
pub const YARVINSN_trace_putnil: ruby_vminsn_type = 128;
pub const YARVINSN_trace_putself: ruby_vminsn_type = 129;
pub const YARVINSN_trace_putobject: ruby_vminsn_type = 130;
pub const YARVINSN_trace_putspecialobject: ruby_vminsn_type = 131;
pub const YARVINSN_trace_putstring: ruby_vminsn_type = 132;
pub const YARVINSN_trace_putchilledstring: ruby_vminsn_type = 133;
pub const YARVINSN_trace_concatstrings: ruby_vminsn_type = 134;
pub const YARVINSN_trace_anytostring: ruby_vminsn_type = 135;
pub const YARVINSN_trace_toregexp: ruby_vminsn_type = 136;
pub const YARVINSN_trace_intern: ruby_vminsn_type = 137;
pub const YARVINSN_trace_newarray: ruby_vminsn_type = 138;
pub const YARVINSN_trace_pushtoarraykwsplat: ruby_vminsn_type = 139;
pub const YARVINSN_trace_duparray: ruby_vminsn_type = 140;
pub const YARVINSN_trace_duphash: ruby_vminsn_type = 141;
pub const YARVINSN_trace_expandarray: ruby_vminsn_type = 142;
pub const YARVINSN_trace_concatarray: ruby_vminsn_type = 143;
pub const YARVINSN_trace_concattoarray: ruby_vminsn_type = 144;
pub const YARVINSN_trace_pushtoarray: ruby_vminsn_type = 145;
pub const YARVINSN_trace_splatarray: ruby_vminsn_type = 146;
pub const YARVINSN_trace_splatkw: ruby_vminsn_type = 147;
pub const YARVINSN_trace_newhash: ruby_vminsn_type = 148;
pub const YARVINSN_trace_newrange: ruby_vminsn_type = 149;
pub const YARVINSN_trace_pop: ruby_vminsn_type = 150;
pub const YARVINSN_trace_dup: ruby_vminsn_type = 151;
pub const YARVINSN_trace_dupn: ruby_vminsn_type = 152;
pub const YARVINSN_trace_swap: ruby_vminsn_type = 153;
pub const YARVINSN_trace_opt_reverse: ruby_vminsn_type = 154;
pub const YARVINSN_trace_topn: ruby_vminsn_type = 155;
pub const YARVINSN_trace_setn: ruby_vminsn_type = 156;
pub const YARVINSN_trace_adjuststack: ruby_vminsn_type = 157;
pub const YARVINSN_trace_defined: ruby_vminsn_type = 158;
pub const YARVINSN_trace_definedivar: ruby_vminsn_type = 159;
pub const YARVINSN_trace_checkmatch: ruby_vminsn_type = 160;
pub const YARVINSN_trace_checkkeyword: ruby_vminsn_type = 161;
pub const YARVINSN_trace_checktype: ruby_vminsn_type = 162;
pub const YARVINSN_trace_defineclass: ruby_vminsn_type = 163;
pub const YARVINSN_trace_definemethod: ruby_vminsn_type = 164;
pub const YARVINSN_trace_definesmethod: ruby_vminsn_type = 165;
pub const YARVINSN_trace_send: ruby_vminsn_type = 166;
pub const YARVINSN_trace_sendforward: ruby_vminsn_type = 167;
pub const YARVINSN_trace_opt_send_without_block: ruby_vminsn_type = 168;
pub const YARVINSN_trace_objtostring: ruby_vminsn_type = 169;
pub const YARVINSN_trace_opt_ary_freeze: ruby_vminsn_type = 170;
pub const YARVINSN_trace_opt_hash_freeze: ruby_vminsn_type = 171;
pub const YARVINSN_trace_opt_str_freeze: ruby_vminsn_type = 172;
pub const YARVINSN_trace_opt_nil_p: ruby_vminsn_type = 173;
pub const YARVINSN_trace_opt_str_uminus: ruby_vminsn_type = 174;
pub const YARVINSN_trace_opt_duparray_send: ruby_vminsn_type = 175;
pub const YARVINSN_trace_opt_newarray_send: ruby_vminsn_type = 176;
pub const YARVINSN_trace_invokesuper: ruby_vminsn_type = 177;
pub const YARVINSN_trace_invokesuperforward: ruby_vminsn_type = 178;
pub const YARVINSN_trace_invokeblock: ruby_vminsn_type = 179;
pub const YARVINSN_trace_leave: ruby_vminsn_type = 180;
pub const YARVINSN_trace_throw: ruby_vminsn_type = 181;
pub const YARVINSN_trace_jump: ruby_vminsn_type = 182;
pub const YARVINSN_trace_branchif: ruby_vminsn_type = 183;
pub const YARVINSN_trace_branchunless: ruby_vminsn_type = 184;
pub const YARVINSN_trace_branchnil: ruby_vminsn_type = 185;
pub const YARVINSN_trace_once: ruby_vminsn_type = 186;
pub const YARVINSN_trace_opt_case_dispatch: ruby_vminsn_type = 187;
pub const YARVINSN_trace_opt_plus: ruby_vminsn_type = 188;
pub const YARVINSN_trace_opt_minus: ruby_vminsn_type = 189;
pub const YARVINSN_trace_opt_mult: ruby_vminsn_type = 190;
pub const YARVINSN_trace_opt_div: ruby_vminsn_type = 191;
pub const YARVINSN_trace_opt_mod: ruby_vminsn_type = 192;
pub const YARVINSN_trace_opt_eq: ruby_vminsn_type = 193;
pub const YARVINSN_trace_opt_neq: ruby_vminsn_type = 194;
pub const YARVINSN_trace_opt_lt: ruby_vminsn_type = 195;
pub const YARVINSN_trace_opt_le: ruby_vminsn_type = 196;
pub const YARVINSN_trace_opt_gt: ruby_vminsn_type = 197;
pub const YARVINSN_trace_opt_ge: ruby_vminsn_type = 198;
pub const YARVINSN_trace_opt_ltlt: ruby_vminsn_type = 199;
pub const YARVINSN_trace_opt_and: ruby_vminsn_type = 200;
pub const YARVINSN_trace_opt_or: ruby_vminsn_type = 201;
pub const YARVINSN_trace_opt_aref: ruby_vminsn_type = 202;
pub const YARVINSN_trace_opt_aset: ruby_vminsn_type = 203;
pub const YARVINSN_trace_opt_aset_with: ruby_vminsn_type = 204;
pub const YARVINSN_trace_opt_aref_with: ruby_vminsn_type = 205;
pub const YARVINSN_trace_opt_length: ruby_vminsn_type = 206;
pub const YARVINSN_trace_opt_size: ruby_vminsn_type = 207;
pub const YARVINSN_trace_opt_empty_p: ruby_vminsn_type = 208;
pub const YARVINSN_trace_opt_succ: ruby_vminsn_type = 209;
pub const YARVINSN_trace_opt_not: ruby_vminsn_type = 210;
pub const YARVINSN_trace_opt_regexpmatch2: ruby_vminsn_type = 211;
pub const YARVINSN_trace_invokebuiltin: ruby_vminsn_type = 212;
pub const YARVINSN_trace_opt_invokebuiltin_delegate: ruby_vminsn_type = 213;
pub const YARVINSN_trace_opt_invokebuiltin_delegate_leave: ruby_vminsn_type = 214;
pub const YARVINSN_trace_getlocal_WC_0: ruby_vminsn_type = 215;
pub const YARVINSN_trace_getlocal_WC_1: ruby_vminsn_type = 216;
pub const YARVINSN_trace_setlocal_WC_0: ruby_vminsn_type = 217;
pub const YARVINSN_trace_setlocal_WC_1: ruby_vminsn_type = 218;
pub const YARVINSN_trace_putobject_INT2FIX_0_: ruby_vminsn_type = 219;
pub const YARVINSN_trace_putobject_INT2FIX_1_: ruby_vminsn_type = 220;
pub const YARVINSN_trace_zjit_opt_plus: ruby_vminsn_type = 221;
pub const VM_INSTRUCTION_SIZE: ruby_vminsn_type = 222;
pub const YARVINSN_zjit_opt_minus: ruby_vminsn_type = 111;
pub const YARVINSN_zjit_opt_mult: ruby_vminsn_type = 112;
pub const YARVINSN_zjit_opt_div: ruby_vminsn_type = 113;
pub const YARVINSN_zjit_opt_mod: ruby_vminsn_type = 114;
pub const YARVINSN_zjit_opt_eq: ruby_vminsn_type = 115;
pub const YARVINSN_zjit_opt_neq: ruby_vminsn_type = 116;
pub const YARVINSN_zjit_opt_lt: ruby_vminsn_type = 117;
pub const YARVINSN_zjit_opt_le: ruby_vminsn_type = 118;
pub const YARVINSN_zjit_opt_gt: ruby_vminsn_type = 119;
pub const YARVINSN_zjit_opt_ge: ruby_vminsn_type = 120;
pub const YARVINSN_trace_nop: ruby_vminsn_type = 121;
pub const YARVINSN_trace_getlocal: ruby_vminsn_type = 122;
pub const YARVINSN_trace_setlocal: ruby_vminsn_type = 123;
pub const YARVINSN_trace_getblockparam: ruby_vminsn_type = 124;
pub const YARVINSN_trace_setblockparam: ruby_vminsn_type = 125;
pub const YARVINSN_trace_getblockparamproxy: ruby_vminsn_type = 126;
pub const YARVINSN_trace_getspecial: ruby_vminsn_type = 127;
pub const YARVINSN_trace_setspecial: ruby_vminsn_type = 128;
pub const YARVINSN_trace_getinstancevariable: ruby_vminsn_type = 129;
pub const YARVINSN_trace_setinstancevariable: ruby_vminsn_type = 130;
pub const YARVINSN_trace_getclassvariable: ruby_vminsn_type = 131;
pub const YARVINSN_trace_setclassvariable: ruby_vminsn_type = 132;
pub const YARVINSN_trace_opt_getconstant_path: ruby_vminsn_type = 133;
pub const YARVINSN_trace_getconstant: ruby_vminsn_type = 134;
pub const YARVINSN_trace_setconstant: ruby_vminsn_type = 135;
pub const YARVINSN_trace_getglobal: ruby_vminsn_type = 136;
pub const YARVINSN_trace_setglobal: ruby_vminsn_type = 137;
pub const YARVINSN_trace_putnil: ruby_vminsn_type = 138;
pub const YARVINSN_trace_putself: ruby_vminsn_type = 139;
pub const YARVINSN_trace_putobject: ruby_vminsn_type = 140;
pub const YARVINSN_trace_putspecialobject: ruby_vminsn_type = 141;
pub const YARVINSN_trace_putstring: ruby_vminsn_type = 142;
pub const YARVINSN_trace_putchilledstring: ruby_vminsn_type = 143;
pub const YARVINSN_trace_concatstrings: ruby_vminsn_type = 144;
pub const YARVINSN_trace_anytostring: ruby_vminsn_type = 145;
pub const YARVINSN_trace_toregexp: ruby_vminsn_type = 146;
pub const YARVINSN_trace_intern: ruby_vminsn_type = 147;
pub const YARVINSN_trace_newarray: ruby_vminsn_type = 148;
pub const YARVINSN_trace_pushtoarraykwsplat: ruby_vminsn_type = 149;
pub const YARVINSN_trace_duparray: ruby_vminsn_type = 150;
pub const YARVINSN_trace_duphash: ruby_vminsn_type = 151;
pub const YARVINSN_trace_expandarray: ruby_vminsn_type = 152;
pub const YARVINSN_trace_concatarray: ruby_vminsn_type = 153;
pub const YARVINSN_trace_concattoarray: ruby_vminsn_type = 154;
pub const YARVINSN_trace_pushtoarray: ruby_vminsn_type = 155;
pub const YARVINSN_trace_splatarray: ruby_vminsn_type = 156;
pub const YARVINSN_trace_splatkw: ruby_vminsn_type = 157;
pub const YARVINSN_trace_newhash: ruby_vminsn_type = 158;
pub const YARVINSN_trace_newrange: ruby_vminsn_type = 159;
pub const YARVINSN_trace_pop: ruby_vminsn_type = 160;
pub const YARVINSN_trace_dup: ruby_vminsn_type = 161;
pub const YARVINSN_trace_dupn: ruby_vminsn_type = 162;
pub const YARVINSN_trace_swap: ruby_vminsn_type = 163;
pub const YARVINSN_trace_opt_reverse: ruby_vminsn_type = 164;
pub const YARVINSN_trace_topn: ruby_vminsn_type = 165;
pub const YARVINSN_trace_setn: ruby_vminsn_type = 166;
pub const YARVINSN_trace_adjuststack: ruby_vminsn_type = 167;
pub const YARVINSN_trace_defined: ruby_vminsn_type = 168;
pub const YARVINSN_trace_definedivar: ruby_vminsn_type = 169;
pub const YARVINSN_trace_checkmatch: ruby_vminsn_type = 170;
pub const YARVINSN_trace_checkkeyword: ruby_vminsn_type = 171;
pub const YARVINSN_trace_checktype: ruby_vminsn_type = 172;
pub const YARVINSN_trace_defineclass: ruby_vminsn_type = 173;
pub const YARVINSN_trace_definemethod: ruby_vminsn_type = 174;
pub const YARVINSN_trace_definesmethod: ruby_vminsn_type = 175;
pub const YARVINSN_trace_send: ruby_vminsn_type = 176;
pub const YARVINSN_trace_sendforward: ruby_vminsn_type = 177;
pub const YARVINSN_trace_opt_send_without_block: ruby_vminsn_type = 178;
pub const YARVINSN_trace_objtostring: ruby_vminsn_type = 179;
pub const YARVINSN_trace_opt_ary_freeze: ruby_vminsn_type = 180;
pub const YARVINSN_trace_opt_hash_freeze: ruby_vminsn_type = 181;
pub const YARVINSN_trace_opt_str_freeze: ruby_vminsn_type = 182;
pub const YARVINSN_trace_opt_nil_p: ruby_vminsn_type = 183;
pub const YARVINSN_trace_opt_str_uminus: ruby_vminsn_type = 184;
pub const YARVINSN_trace_opt_duparray_send: ruby_vminsn_type = 185;
pub const YARVINSN_trace_opt_newarray_send: ruby_vminsn_type = 186;
pub const YARVINSN_trace_invokesuper: ruby_vminsn_type = 187;
pub const YARVINSN_trace_invokesuperforward: ruby_vminsn_type = 188;
pub const YARVINSN_trace_invokeblock: ruby_vminsn_type = 189;
pub const YARVINSN_trace_leave: ruby_vminsn_type = 190;
pub const YARVINSN_trace_throw: ruby_vminsn_type = 191;
pub const YARVINSN_trace_jump: ruby_vminsn_type = 192;
pub const YARVINSN_trace_branchif: ruby_vminsn_type = 193;
pub const YARVINSN_trace_branchunless: ruby_vminsn_type = 194;
pub const YARVINSN_trace_branchnil: ruby_vminsn_type = 195;
pub const YARVINSN_trace_once: ruby_vminsn_type = 196;
pub const YARVINSN_trace_opt_case_dispatch: ruby_vminsn_type = 197;
pub const YARVINSN_trace_opt_plus: ruby_vminsn_type = 198;
pub const YARVINSN_trace_opt_minus: ruby_vminsn_type = 199;
pub const YARVINSN_trace_opt_mult: ruby_vminsn_type = 200;
pub const YARVINSN_trace_opt_div: ruby_vminsn_type = 201;
pub const YARVINSN_trace_opt_mod: ruby_vminsn_type = 202;
pub const YARVINSN_trace_opt_eq: ruby_vminsn_type = 203;
pub const YARVINSN_trace_opt_neq: ruby_vminsn_type = 204;
pub const YARVINSN_trace_opt_lt: ruby_vminsn_type = 205;
pub const YARVINSN_trace_opt_le: ruby_vminsn_type = 206;
pub const YARVINSN_trace_opt_gt: ruby_vminsn_type = 207;
pub const YARVINSN_trace_opt_ge: ruby_vminsn_type = 208;
pub const YARVINSN_trace_opt_ltlt: ruby_vminsn_type = 209;
pub const YARVINSN_trace_opt_and: ruby_vminsn_type = 210;
pub const YARVINSN_trace_opt_or: ruby_vminsn_type = 211;
pub const YARVINSN_trace_opt_aref: ruby_vminsn_type = 212;
pub const YARVINSN_trace_opt_aset: ruby_vminsn_type = 213;
pub const YARVINSN_trace_opt_aset_with: ruby_vminsn_type = 214;
pub const YARVINSN_trace_opt_aref_with: ruby_vminsn_type = 215;
pub const YARVINSN_trace_opt_length: ruby_vminsn_type = 216;
pub const YARVINSN_trace_opt_size: ruby_vminsn_type = 217;
pub const YARVINSN_trace_opt_empty_p: ruby_vminsn_type = 218;
pub const YARVINSN_trace_opt_succ: ruby_vminsn_type = 219;
pub const YARVINSN_trace_opt_not: ruby_vminsn_type = 220;
pub const YARVINSN_trace_opt_regexpmatch2: ruby_vminsn_type = 221;
pub const YARVINSN_trace_invokebuiltin: ruby_vminsn_type = 222;
pub const YARVINSN_trace_opt_invokebuiltin_delegate: ruby_vminsn_type = 223;
pub const YARVINSN_trace_opt_invokebuiltin_delegate_leave: ruby_vminsn_type = 224;
pub const YARVINSN_trace_getlocal_WC_0: ruby_vminsn_type = 225;
pub const YARVINSN_trace_getlocal_WC_1: ruby_vminsn_type = 226;
pub const YARVINSN_trace_setlocal_WC_0: ruby_vminsn_type = 227;
pub const YARVINSN_trace_setlocal_WC_1: ruby_vminsn_type = 228;
pub const YARVINSN_trace_putobject_INT2FIX_0_: ruby_vminsn_type = 229;
pub const YARVINSN_trace_putobject_INT2FIX_1_: ruby_vminsn_type = 230;
pub const YARVINSN_trace_zjit_opt_plus: ruby_vminsn_type = 231;
pub const YARVINSN_trace_zjit_opt_minus: ruby_vminsn_type = 232;
pub const YARVINSN_trace_zjit_opt_mult: ruby_vminsn_type = 233;
pub const YARVINSN_trace_zjit_opt_div: ruby_vminsn_type = 234;
pub const YARVINSN_trace_zjit_opt_mod: ruby_vminsn_type = 235;
pub const YARVINSN_trace_zjit_opt_eq: ruby_vminsn_type = 236;
pub const YARVINSN_trace_zjit_opt_neq: ruby_vminsn_type = 237;
pub const YARVINSN_trace_zjit_opt_lt: ruby_vminsn_type = 238;
pub const YARVINSN_trace_zjit_opt_le: ruby_vminsn_type = 239;
pub const YARVINSN_trace_zjit_opt_gt: ruby_vminsn_type = 240;
pub const YARVINSN_trace_zjit_opt_ge: ruby_vminsn_type = 241;
pub const VM_INSTRUCTION_SIZE: ruby_vminsn_type = 242;
pub type ruby_vminsn_type = u32;
pub type rb_iseq_callback = ::std::option::Option<
unsafe extern "C" fn(arg1: *const rb_iseq_t, arg2: *mut ::std::os::raw::c_void),

View file

@ -2,12 +2,12 @@
#![allow(non_upper_case_globals)]
use crate::{
cruby::*,
get_option,
options::DumpHIR, profile::{get_or_create_iseq_payload, InsnProfile}
cruby::*, get_option, hir_type::types::Fixnum, options::DumpHIR, profile::get_or_create_iseq_payload
};
use std::collections::{HashMap, HashSet};
use crate::hir_type::Type;
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct InsnId(pub usize);
@ -89,6 +89,36 @@ pub enum Invariant {
},
}
impl std::fmt::Display for Invariant {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::BOPRedefined { klass, bop } => {
write!(f, "BOPRedefined(")?;
match *klass {
INTEGER_REDEFINED_OP_FLAG => write!(f, "INTEGER_REDEFINED_OP_FLAG")?,
_ => write!(f, "{klass}")?,
}
write!(f, ", ")?;
match *bop {
BOP_PLUS => write!(f, "BOP_PLUS")?,
BOP_MINUS => write!(f, "BOP_MINUS")?,
BOP_MULT => write!(f, "BOP_MULT")?,
BOP_DIV => write!(f, "BOP_DIV")?,
BOP_MOD => write!(f, "BOP_MOD")?,
BOP_EQ => write!(f, "BOP_EQ")?,
BOP_NEQ => write!(f, "BOP_NEQ")?,
BOP_LT => write!(f, "BOP_LT")?,
BOP_LE => write!(f, "BOP_LE")?,
BOP_GT => write!(f, "BOP_GT")?,
BOP_GE => write!(f, "BOP_GE")?,
_ => write!(f, "{bop}")?,
}
write!(f, ")")
}
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Const {
Value(VALUE),
@ -161,12 +191,22 @@ pub enum Insn {
// Control flow instructions
Return { val: InsnId },
/// Fixnum + Fixnum
FixnumAdd { recv: InsnId, obj: InsnId },
/// Fixnum +, -, *, /, %, ==, !=, <, <=, >, >=
FixnumAdd { left: InsnId, right: InsnId },
FixnumSub { left: InsnId, right: InsnId },
FixnumMult { left: InsnId, right: InsnId },
FixnumDiv { left: InsnId, right: InsnId },
FixnumMod { left: InsnId, right: InsnId },
FixnumEq { left: InsnId, right: InsnId },
FixnumNeq { left: InsnId, right: InsnId },
FixnumLt { left: InsnId, right: InsnId },
FixnumLe { left: InsnId, right: InsnId },
FixnumGt { left: InsnId, right: InsnId },
FixnumGe { left: InsnId, right: InsnId },
/// Side-exist if val doesn't have the expected type.
// TODO: Replace is_fixnum with the type lattice
GuardType { val: InsnId, is_fixnum: bool },
GuardType { val: InsnId, guard_type: Type },
/// Generate no code (or padding if necessary) and insert a patch point
/// that can be rewritten to a side exit when the Invariant is broken.
@ -365,23 +405,36 @@ impl<'a> std::fmt::Display for FunctionPrinter<'a> {
}
write!(f, " {insn_id} = ")?;
match &fun.insns[insn_id.0] {
Insn::Param { idx } => { write!(f, "Param {idx}")?; }
Insn::Const { val } => { write!(f, "Const {val}")?; }
Insn::IfTrue { val, target } => { write!(f, "IfTrue {val}, {target}")?; }
Insn::IfFalse { val, target } => { write!(f, "IfFalse {val}, {target}")?; }
Insn::Jump(target) => { write!(f, "Jump {target}")?; }
Insn::Return { val } => { write!(f, "Return {val}")?; }
Insn::Param { idx } => { write!(f, "Param {idx}")?; }
Insn::NewArray { count } => { write!(f, "NewArray {count}")?; }
Insn::ArraySet { idx, val } => { write!(f, "ArraySet {idx}, {val}")?; }
Insn::ArrayDup { val } => { write!(f, "ArrayDup {val}")?; }
Insn::Test { val } => { write!(f, "Test {val}")?; }
Insn::Snapshot { state } => { write!(f, "Snapshot {state}")?; }
Insn::Jump(target) => { write!(f, "Jump {target}")?; }
Insn::IfTrue { val, target } => { write!(f, "IfTrue {val}, {target}")?; }
Insn::IfFalse { val, target } => { write!(f, "IfFalse {val}, {target}")?; }
Insn::Send { self_val, call_info, args } => {
write!(f, "Send {self_val}, :{}", call_info.name)?;
for arg in args {
write!(f, ", {arg}")?;
}
}
Insn::Test { val } => { write!(f, "Test {val}")?; }
Insn::Snapshot { state } => { write!(f, "Snapshot {state}")?; }
Insn::Return { val } => { write!(f, "Return {val}")?; }
Insn::FixnumAdd { left, right } => { write!(f, "FixnumAdd {left}, {right}")?; },
Insn::FixnumSub { left, right } => { write!(f, "FixnumSub {left}, {right}")?; },
Insn::FixnumMult { left, right } => { write!(f, "FixnumMult {left}, {right}")?; },
Insn::FixnumDiv { left, right } => { write!(f, "FixnumDiv {left}, {right}")?; },
Insn::FixnumMod { left, right } => { write!(f, "FixnumMod {left}, {right}")?; },
Insn::FixnumEq { left, right } => { write!(f, "FixnumEq {left}, {right}")?; },
Insn::FixnumNeq { left, right } => { write!(f, "FixnumNeq {left}, {right}")?; },
Insn::FixnumLt { left, right } => { write!(f, "FixnumLt {left}, {right}")?; },
Insn::FixnumLe { left, right } => { write!(f, "FixnumLe {left}, {right}")?; },
Insn::FixnumGt { left, right } => { write!(f, "FixnumGt {left}, {right}")?; },
Insn::FixnumGe { left, right } => { write!(f, "FixnumGe {left}, {right}")?; },
Insn::GuardType { val, guard_type } => { write!(f, "GuardType {val}, {guard_type}")?; },
Insn::PatchPoint(invariant) => { write!(f, "PatchPoint {invariant:}")?; },
insn => { write!(f, "{insn:?}")?; }
}
writeln!(f, "")?;
@ -440,6 +493,13 @@ impl FrameState {
self.stack.last().ok_or_else(|| ParseError::StackUnderflow(self.clone())).copied()
}
fn stack_opnd(&self, idx: usize) -> Result<InsnId, ParseError> {
match self.stack.get(self.stack.len() - idx - 1) {
Some(&opnd) => Ok(opnd),
_ => Err(ParseError::StackUnderflow(self.clone())),
}
}
fn pop(&mut self) -> Result<InsnId, ParseError> {
self.stack.pop().ok_or_else(|| ParseError::StackUnderflow(self.clone()))
}
@ -586,8 +646,10 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
// try_into() call below is unfortunate. Maybe pick i32 instead of usize for opcodes.
let opcode: u32 = unsafe { rb_iseq_opcode_at_pc(iseq, pc) }
.try_into()
.try_into()
.unwrap();
// Preserve the actual index for the instruction being compiled
let current_insn_idx = insn_idx;
// Move to the next instruction to compile
insn_idx += insn_len(opcode as usize);
@ -705,29 +767,126 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
}
YARVINSN_opt_plus | YARVINSN_zjit_opt_plus => {
let right = state.pop()?;
let left = state.pop()?;
if let Some(InsnProfile::OptPlus { recv_is_fixnum: true, obj_is_fixnum: true }) = payload.get_insn_profile(insn_idx as usize) {
state.push(fun.push_insn(block, Insn::PatchPoint(Invariant::BOPRedefined { klass: INTEGER_REDEFINED_OP_FLAG, bop: BOP_PLUS })));
let left_fixnum = fun.push_insn(block, Insn::GuardType { val: left, is_fixnum: true });
state.push(left_fixnum);
let right_fixnum = fun.push_insn(block, Insn::GuardType { val: right, is_fixnum: true });
state.push(right_fixnum);
state.push(fun.push_insn(block, Insn::FixnumAdd { recv: left_fixnum, obj: right_fixnum }));
if payload.have_two_fixnums(current_insn_idx as usize) {
fun.push_insn(block, Insn::PatchPoint(Invariant::BOPRedefined { klass: INTEGER_REDEFINED_OP_FLAG, bop: BOP_PLUS }));
let (left, right) = guard_two_fixnums(&mut state, &mut fun, block)?;
state.push(fun.push_insn(block, Insn::FixnumAdd { left, right }));
} else {
let right = state.pop()?;
let left = state.pop()?;
state.push(fun.push_insn(block, Insn::Send { self_val: left, call_info: CallInfo { name: "+".into() }, args: vec![right] }));
}
}
YARVINSN_opt_div => {
let right = state.pop()?;
let left = state.pop()?;
state.push(fun.push_insn(block, Insn::Send { self_val: left, call_info: CallInfo { name: "/".into() }, args: vec![right] }));
YARVINSN_opt_minus | YARVINSN_zjit_opt_minus => {
if payload.have_two_fixnums(current_insn_idx as usize) {
fun.push_insn(block, Insn::PatchPoint(Invariant::BOPRedefined { klass: INTEGER_REDEFINED_OP_FLAG, bop: BOP_MINUS }));
let (left, right) = guard_two_fixnums(&mut state, &mut fun, block)?;
state.push(fun.push_insn(block, Insn::FixnumSub { left, right }));
} else {
let right = state.pop()?;
let left = state.pop()?;
state.push(fun.push_insn(block, Insn::Send { self_val: left, call_info: CallInfo { name: "-".into() }, args: vec![right] }));
}
}
YARVINSN_opt_mult | YARVINSN_zjit_opt_mult => {
if payload.have_two_fixnums(current_insn_idx as usize) {
fun.push_insn(block, Insn::PatchPoint(Invariant::BOPRedefined { klass: INTEGER_REDEFINED_OP_FLAG, bop: BOP_MULT }));
let (left, right) = guard_two_fixnums(&mut state, &mut fun, block)?;
state.push(fun.push_insn(block, Insn::FixnumMult { left, right }));
} else {
let right = state.pop()?;
let left = state.pop()?;
state.push(fun.push_insn(block, Insn::Send { self_val: left, call_info: CallInfo { name: "*".into() }, args: vec![right] }));
}
}
YARVINSN_opt_div | YARVINSN_zjit_opt_div => {
if payload.have_two_fixnums(current_insn_idx as usize) {
fun.push_insn(block, Insn::PatchPoint(Invariant::BOPRedefined { klass: INTEGER_REDEFINED_OP_FLAG, bop: BOP_DIV }));
let (left, right) = guard_two_fixnums(&mut state, &mut fun, block)?;
state.push(fun.push_insn(block, Insn::FixnumDiv { left, right }));
} else {
let right = state.pop()?;
let left = state.pop()?;
state.push(fun.push_insn(block, Insn::Send { self_val: left, call_info: CallInfo { name: "/".into() }, args: vec![right] }));
}
}
YARVINSN_opt_mod | YARVINSN_zjit_opt_mod => {
if payload.have_two_fixnums(current_insn_idx as usize) {
fun.push_insn(block, Insn::PatchPoint(Invariant::BOPRedefined { klass: INTEGER_REDEFINED_OP_FLAG, bop: BOP_MOD }));
let (left, right) = guard_two_fixnums(&mut state, &mut fun, block)?;
state.push(fun.push_insn(block, Insn::FixnumMod { left, right }));
} else {
let right = state.pop()?;
let left = state.pop()?;
state.push(fun.push_insn(block, Insn::Send { self_val: left, call_info: CallInfo { name: "%".into() }, args: vec![right] }));
}
}
YARVINSN_opt_lt => {
let right = state.pop()?;
let left = state.pop()?;
state.push(fun.push_insn(block, Insn::Send { self_val: left, call_info: CallInfo { name: "<".into() }, args: vec![right] }));
YARVINSN_opt_eq | YARVINSN_zjit_opt_eq => {
if payload.have_two_fixnums(current_insn_idx as usize) {
fun.push_insn(block, Insn::PatchPoint(Invariant::BOPRedefined { klass: INTEGER_REDEFINED_OP_FLAG, bop: BOP_EQ }));
let (left, right) = guard_two_fixnums(&mut state, &mut fun, block)?;
state.push(fun.push_insn(block, Insn::FixnumEq { left, right }));
} else {
let right = state.pop()?;
let left = state.pop()?;
state.push(fun.push_insn(block, Insn::Send { self_val: left, call_info: CallInfo { name: "==".into() }, args: vec![right] }));
}
}
YARVINSN_opt_neq | YARVINSN_zjit_opt_neq => {
if payload.have_two_fixnums(current_insn_idx as usize) {
fun.push_insn(block, Insn::PatchPoint(Invariant::BOPRedefined { klass: INTEGER_REDEFINED_OP_FLAG, bop: BOP_NEQ }));
let (left, right) = guard_two_fixnums(&mut state, &mut fun, block)?;
state.push(fun.push_insn(block, Insn::FixnumNeq { left, right }));
} else {
let right = state.pop()?;
let left = state.pop()?;
state.push(fun.push_insn(block, Insn::Send { self_val: left, call_info: CallInfo { name: "!=".into() }, args: vec![right] }));
}
}
YARVINSN_opt_lt | YARVINSN_zjit_opt_lt => {
if payload.have_two_fixnums(current_insn_idx as usize) {
fun.push_insn(block, Insn::PatchPoint(Invariant::BOPRedefined { klass: INTEGER_REDEFINED_OP_FLAG, bop: BOP_LT }));
let (left, right) = guard_two_fixnums(&mut state, &mut fun, block)?;
state.push(fun.push_insn(block, Insn::FixnumLt { left, right }));
} else {
let right = state.pop()?;
let left = state.pop()?;
state.push(fun.push_insn(block, Insn::Send { self_val: left, call_info: CallInfo { name: "<".into() }, args: vec![right] }));
}
}
YARVINSN_opt_le | YARVINSN_zjit_opt_le => {
if payload.have_two_fixnums(current_insn_idx as usize) {
fun.push_insn(block, Insn::PatchPoint(Invariant::BOPRedefined { klass: INTEGER_REDEFINED_OP_FLAG, bop: BOP_LE }));
let (left, right) = guard_two_fixnums(&mut state, &mut fun, block)?;
state.push(fun.push_insn(block, Insn::FixnumLe { left, right }));
} else {
let right = state.pop()?;
let left = state.pop()?;
state.push(fun.push_insn(block, Insn::Send { self_val: left, call_info: CallInfo { name: "<=".into() }, args: vec![right] }));
}
}
YARVINSN_opt_gt | YARVINSN_zjit_opt_gt => {
if payload.have_two_fixnums(current_insn_idx as usize) {
fun.push_insn(block, Insn::PatchPoint(Invariant::BOPRedefined { klass: INTEGER_REDEFINED_OP_FLAG, bop: BOP_GT }));
let (left, right) = guard_two_fixnums(&mut state, &mut fun, block)?;
state.push(fun.push_insn(block, Insn::FixnumGt { left, right }));
} else {
let right = state.pop()?;
let left = state.pop()?;
state.push(fun.push_insn(block, Insn::Send { self_val: left, call_info: CallInfo { name: "<".into() }, args: vec![right] }));
}
}
YARVINSN_opt_ge | YARVINSN_zjit_opt_ge => {
if payload.have_two_fixnums(current_insn_idx as usize) {
fun.push_insn(block, Insn::PatchPoint(Invariant::BOPRedefined { klass: INTEGER_REDEFINED_OP_FLAG, bop: BOP_GE }));
let (left, right) = guard_two_fixnums(&mut state, &mut fun, block)?;
state.push(fun.push_insn(block, Insn::FixnumGe { left, right }));
} else {
let right = state.pop()?;
let left = state.pop()?;
state.push(fun.push_insn(block, Insn::Send { self_val: left, call_info: CallInfo { name: "<=".into() }, args: vec![right] }));
}
}
YARVINSN_opt_ltlt => {
let right = state.pop()?;
@ -788,6 +947,18 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
Ok(fun)
}
/// Generate guards for two fixnum outputs
fn guard_two_fixnums(state: &mut FrameState, fun: &mut Function, block: BlockId) -> Result<(InsnId, InsnId), ParseError> {
let left = fun.push_insn(block, Insn::GuardType { val: state.stack_opnd(1)?, guard_type: Fixnum });
let right = fun.push_insn(block, Insn::GuardType { val: state.stack_opnd(0)?, guard_type: Fixnum });
// Pop operands after guards for side exits
state.pop()?;
state.pop()?;
Ok((left, right))
}
#[cfg(test)]
mod union_find_tests {
use super::UnionFind;
@ -842,6 +1013,19 @@ mod tests {
};
}
macro_rules! assert_hir {
($method:expr, $hir:expr) => {
{
let iseq = get_method_iseq($method);
let function = iseq_to_hir(iseq).unwrap();
let actual_hir = format!("{}", FunctionPrinter::without_snapshot(&function));
let expected_hir = unindent($hir, true);
assert_eq!(actual_hir, expected_hir);
}
};
}
#[test]
fn boot_vm() {
crate::cruby::with_rubyvm(|| {
@ -923,4 +1107,224 @@ mod tests {
assert_matches!(function.insns.get(16), Some(Insn::Return { val: InsnId(14) }));
});
}
#[test]
fn test_opt_plus_fixnum() {
crate::cruby::with_rubyvm(|| {
eval("
def test(a, b) = a + b
test(1, 2); test(1, 2)
");
assert_hir!("test", "
bb0:
v0 = Param 0
v1 = Param 1
v5 = PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS)
v6 = GuardType v0, Fixnum
v7 = GuardType v1, Fixnum
v8 = FixnumAdd v6, v7
v10 = Return v8
");
});
}
#[test]
fn test_opt_minus_fixnum() {
crate::cruby::with_rubyvm(|| {
eval("
def test(a, b) = a - b
test(1, 2); test(1, 2)
");
assert_hir!("test", "
bb0:
v0 = Param 0
v1 = Param 1
v5 = PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MINUS)
v6 = GuardType v0, Fixnum
v7 = GuardType v1, Fixnum
v8 = FixnumSub v6, v7
v10 = Return v8
");
});
}
#[test]
fn test_opt_mult_fixnum() {
crate::cruby::with_rubyvm(|| {
eval("
def test(a, b) = a * b
test(1, 2); test(1, 2)
");
assert_hir!("test", "
bb0:
v0 = Param 0
v1 = Param 1
v5 = PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MULT)
v6 = GuardType v0, Fixnum
v7 = GuardType v1, Fixnum
v8 = FixnumMult v6, v7
v10 = Return v8
");
});
}
#[test]
fn test_opt_div_fixnum() {
crate::cruby::with_rubyvm(|| {
eval("
def test(a, b) = a / b
test(1, 2); test(1, 2)
");
assert_hir!("test", "
bb0:
v0 = Param 0
v1 = Param 1
v5 = PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_DIV)
v6 = GuardType v0, Fixnum
v7 = GuardType v1, Fixnum
v8 = FixnumDiv v6, v7
v10 = Return v8
");
});
}
#[test]
fn test_opt_mod_fixnum() {
crate::cruby::with_rubyvm(|| {
eval("
def test(a, b) = a % b
test(1, 2); test(1, 2)
");
assert_hir!("test", "
bb0:
v0 = Param 0
v1 = Param 1
v5 = PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MOD)
v6 = GuardType v0, Fixnum
v7 = GuardType v1, Fixnum
v8 = FixnumMod v6, v7
v10 = Return v8
");
});
}
#[test]
fn test_opt_eq_fixnum() {
crate::cruby::with_rubyvm(|| {
eval("
def test(a, b) = a == b
test(1, 2); test(1, 2)
");
assert_hir!("test", "
bb0:
v0 = Param 0
v1 = Param 1
v5 = PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ)
v6 = GuardType v0, Fixnum
v7 = GuardType v1, Fixnum
v8 = FixnumEq v6, v7
v10 = Return v8
");
});
}
#[test]
fn test_opt_neq_fixnum() {
crate::cruby::with_rubyvm(|| {
eval("
def test(a, b) = a != b
test(1, 2); test(1, 2)
");
assert_hir!("test", "
bb0:
v0 = Param 0
v1 = Param 1
v5 = PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_NEQ)
v6 = GuardType v0, Fixnum
v7 = GuardType v1, Fixnum
v8 = FixnumNeq v6, v7
v10 = Return v8
");
});
}
#[test]
fn test_opt_lt_fixnum() {
crate::cruby::with_rubyvm(|| {
eval("
def test(a, b) = a < b
test(1, 2); test(1, 2)
");
assert_hir!("test", "
bb0:
v0 = Param 0
v1 = Param 1
v5 = PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LT)
v6 = GuardType v0, Fixnum
v7 = GuardType v1, Fixnum
v8 = FixnumLt v6, v7
v10 = Return v8
");
});
}
#[test]
fn test_opt_le_fixnum() {
crate::cruby::with_rubyvm(|| {
eval("
def test(a, b) = a <= b
test(1, 2); test(1, 2)
");
assert_hir!("test", "
bb0:
v0 = Param 0
v1 = Param 1
v5 = PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LE)
v6 = GuardType v0, Fixnum
v7 = GuardType v1, Fixnum
v8 = FixnumLe v6, v7
v10 = Return v8
");
});
}
#[test]
fn test_opt_gt_fixnum() {
crate::cruby::with_rubyvm(|| {
eval("
def test(a, b) = a > b
test(1, 2); test(1, 2)
");
assert_hir!("test", "
bb0:
v0 = Param 0
v1 = Param 1
v5 = PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_GT)
v6 = GuardType v0, Fixnum
v7 = GuardType v1, Fixnum
v8 = FixnumGt v6, v7
v10 = Return v8
");
});
}
#[test]
fn test_opt_ge_fixnum() {
crate::cruby::with_rubyvm(|| {
eval("
def test(a, b) = a >= b
test(1, 2); test(1, 2)
");
assert_hir!("test", "
bb0:
v0 = Param 0
v1 = Param 1
v5 = PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_GE)
v6 = GuardType v0, Fixnum
v7 = GuardType v1, Fixnum
v8 = FixnumGe v6, v7
v10 = Return v8
");
});
}
}

View file

@ -4,7 +4,7 @@
use core::ffi::c_void;
use std::collections::HashMap;
use crate::cruby::*;
use crate::{cruby::*, hir_type::{types::{Bottom, Fixnum}, Type}};
/// Ephemeral state for profiling runtime information
struct Profiler {
@ -46,42 +46,57 @@ pub extern "C" fn rb_zjit_profile_insn(opcode: ruby_vminsn_type, ec: EcPtr) {
/// Profile a YARV instruction
fn profile_insn(profiler: &mut Profiler, opcode: ruby_vminsn_type) {
match opcode {
YARVINSN_opt_plus => profile_opt_plus(profiler),
YARVINSN_opt_plus => profile_operands(profiler, 2),
YARVINSN_opt_minus => profile_operands(profiler, 2),
YARVINSN_opt_mult => profile_operands(profiler, 2),
YARVINSN_opt_div => profile_operands(profiler, 2),
YARVINSN_opt_mod => profile_operands(profiler, 2),
YARVINSN_opt_eq => profile_operands(profiler, 2),
YARVINSN_opt_neq => profile_operands(profiler, 2),
YARVINSN_opt_lt => profile_operands(profiler, 2),
YARVINSN_opt_le => profile_operands(profiler, 2),
YARVINSN_opt_gt => profile_operands(profiler, 2),
YARVINSN_opt_ge => profile_operands(profiler, 2),
_ => {}
}
}
/// Profile opt_plus instruction
fn profile_opt_plus(profiler: &mut Profiler) {
let recv = profiler.peek_at_stack(1);
let obj = profiler.peek_at_stack(0);
/// Profile the Type of top-`n` stack operands
fn profile_operands(profiler: &mut Profiler, n: usize) {
let payload = get_or_create_iseq_payload(profiler.iseq);
payload.insns.insert(profiler.insn_idx, InsnProfile::OptPlus {
// TODO: profile the type and union it with past results
recv_is_fixnum: recv.fixnum_p(),
obj_is_fixnum: obj.fixnum_p(),
});
let mut types = if let Some(types) = payload.opnd_types.get(&profiler.insn_idx) {
types.clone()
} else {
vec![Bottom; n]
};
for i in 0..n {
let opnd_type = Type::from_value(profiler.peek_at_stack((n - i - 1) as isize));
types[i] = types[i].union(opnd_type);
}
payload.opnd_types.insert(profiler.insn_idx, types);
}
/// Profiling information for each YARV instruction
pub enum InsnProfile {
// TODO: Change it to { recv: Type, obj: Type } once the type lattice is merged
OptPlus { recv_is_fixnum: bool, obj_is_fixnum: bool },
}
/// This is all the data YJIT stores on an iseq. This will be dynamically allocated by C code
/// This is all the data ZJIT stores on an iseq. This will be dynamically allocated by C code
/// C code should pass an &mut IseqPayload to us when calling into ZJIT.
#[derive(Default)]
#[derive(Default, Debug)]
pub struct IseqPayload {
/// Profiling information for each YARV instruction, indexed by the instruction index
insns: HashMap<usize, InsnProfile>,
/// Type information of YARV instruction operands, indexed by the instruction index
opnd_types: HashMap<usize, Vec<Type>>,
}
impl IseqPayload {
/// Get the instruction profile for a given instruction index
pub fn get_insn_profile(&self, insn_idx: usize) -> Option<&InsnProfile> {
self.insns.get(&insn_idx)
/// Get profiled operand types for a given instruction index
pub fn get_operand_types(&self, insn_idx: usize) -> Option<&[Type]> {
self.opnd_types.get(&insn_idx).map(|types| types.as_slice())
}
pub fn have_two_fixnums(&self, insn_idx: usize) -> bool {
match self.get_operand_types(insn_idx) {
Some([left, right]) => left.is_subtype(Fixnum) && right.is_subtype(Fixnum),
_ => false,
}
}
}

View file

@ -122,7 +122,7 @@ zjit-test: libminiruby.a
RUBY_BUILD_DIR='$(TOP_BUILD_DIR)' \
RUBY_LD_FLAGS='$(LDFLAGS) $(XLDFLAGS) $(MAINLIBS)' \
CARGO_TARGET_DIR='$(ZJIT_CARGO_TARGET_DIR)' \
$(CARGO) nextest run --manifest-path '$(top_srcdir)/zjit/Cargo.toml'
$(CARGO) nextest run --manifest-path '$(top_srcdir)/zjit/Cargo.toml' $(ZJIT_TESTS)
# A library for booting miniruby in tests.
# Why not use libruby-static.a for this?