ZJIT: Parse opt_newarray_send into HIR (#13242)

This commit is contained in:
Max Bernstein 2025-05-02 16:01:22 -04:00 committed by GitHub
parent 186022d13f
commit 23000c7339
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
Notes: git 2025-05-02 20:01:39 +00:00
Merged-By: k0kubun <takashikkbn@gmail.com>

View File

@ -141,6 +141,7 @@ impl<'a> std::fmt::Display for InvariantPrinter<'a> {
write!(f, "BOPRedefined(")?;
match klass {
INTEGER_REDEFINED_OP_FLAG => write!(f, "INTEGER_REDEFINED_OP_FLAG")?,
ARRAY_REDEFINED_OP_FLAG => write!(f, "ARRAY_REDEFINED_OP_FLAG")?,
_ => write!(f, "{klass}")?,
}
write!(f, ", ")?;
@ -156,6 +157,7 @@ impl<'a> std::fmt::Display for InvariantPrinter<'a> {
BOP_LE => write!(f, "BOP_LE")?,
BOP_GT => write!(f, "BOP_GT")?,
BOP_GE => write!(f, "BOP_GE")?,
BOP_MAX => write!(f, "BOP_MAX")?,
_ => write!(f, "{bop}")?,
}
write!(f, ")")
@ -310,6 +312,7 @@ pub enum Insn {
NewArray { elements: Vec<InsnId>, state: InsnId },
ArraySet { array: InsnId, idx: usize, val: InsnId },
ArrayDup { val: InsnId, state: InsnId },
ArrayMax { elements: Vec<InsnId>, state: InsnId },
// Check if the value is truthy and "return" a C boolean. In reality, we will likely fuse this
// with IfTrue/IfFalse in the backend to generate jcc.
@ -441,6 +444,15 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
}
Ok(())
}
Insn::ArrayMax { elements, .. } => {
write!(f, "ArrayMax")?;
let mut prefix = " ";
for element in elements {
write!(f, "{prefix}{element}")?;
prefix = ", ";
}
Ok(())
}
Insn::ArraySet { array, idx, val } => { write!(f, "ArraySet {array}, {idx}, {val}") }
Insn::ArrayDup { val, .. } => { write!(f, "ArrayDup {val}") }
Insn::StringCopy { val } => { write!(f, "StringCopy {val}") }
@ -744,12 +756,19 @@ impl Function {
}
};
}
macro_rules! find_vec {
( $x:expr ) => {
{
$x.iter().map(|arg| find!(*arg)).collect()
}
};
}
macro_rules! find_branch_edge {
( $edge:ident ) => {
{
BranchEdge {
target: $edge.target,
args: $edge.args.iter().map(|x| find!(*x)).collect(),
args: find_vec!($edge.args),
}
}
};
@ -757,7 +776,7 @@ impl Function {
let insn_id = self.union_find.borrow_mut().find(insn_id);
use Insn::*;
match &self.insns[insn_id.0] {
result@(PutSelf | Const {..} | Param {..} | NewArray {..} | GetConstantPath {..}
result@(PutSelf | Const {..} | Param {..} | GetConstantPath {..}
| PatchPoint {..}) => result.clone(),
Snapshot { state: FrameState { iseq, insn_idx, pc, stack, locals } } =>
Snapshot {
@ -816,6 +835,8 @@ impl Function {
ArrayDup { val , state } => ArrayDup { val: find!(*val), state: *state },
CCall { cfun, args, name, return_type } => CCall { cfun: *cfun, args: args.iter().map(|arg| find!(*arg)).collect(), name: *name, return_type: *return_type },
Defined { .. } => todo!("find(Defined)"),
NewArray { elements, state } => NewArray { elements: find_vec!(*elements), state: find!(*state) },
ArrayMax { elements, state } => ArrayMax { elements: find_vec!(*elements), state: find!(*state) },
}
}
@ -882,6 +903,7 @@ impl Function {
Insn::PutSelf => types::BasicObject,
Insn::Defined { .. } => types::BasicObject,
Insn::GetConstantPath { .. } => types::BasicObject,
Insn::ArrayMax { .. } => types::BasicObject,
}
}
@ -1309,9 +1331,13 @@ impl Function {
necessary[insn_id.0] = true;
match self.find(insn_id) {
Insn::PutSelf | Insn::Const { .. } | Insn::Param { .. }
| Insn::NewArray { .. } | Insn::PatchPoint(..)
| Insn::GetConstantPath { .. } =>
| Insn::PatchPoint(..) | Insn::GetConstantPath { .. } =>
{}
Insn::ArrayMax { elements, state }
| Insn::NewArray { elements, state } => {
worklist.extend(elements);
worklist.push_back(state);
}
Insn::StringCopy { val }
| Insn::StringIntern { val }
| Insn::Return { val }
@ -1636,6 +1662,7 @@ pub enum CallType {
pub enum ParseError {
StackUnderflow(FrameState),
UnknownOpcode(String),
UnknownNewArraySend(String),
UnhandledCallType(CallType),
}
@ -1755,6 +1782,26 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
elements.reverse();
state.stack_push(fun.push_insn(block, Insn::NewArray { elements, state: exit_id }));
}
YARVINSN_opt_newarray_send => {
let count = get_arg(pc, 0).as_usize();
let method = get_arg(pc, 1).as_u32();
let mut elements = vec![];
for _ in 0..count {
elements.push(state.stack_pop()?);
}
elements.reverse();
let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state.clone() });
let (bop, insn) = match method {
VM_OPT_NEWARRAY_SEND_MAX => (BOP_MAX, Insn::ArrayMax { elements, state: exit_id }),
VM_OPT_NEWARRAY_SEND_MIN => return Err(ParseError::UnknownNewArraySend("min".into())),
VM_OPT_NEWARRAY_SEND_HASH => return Err(ParseError::UnknownNewArraySend("hash".into())),
VM_OPT_NEWARRAY_SEND_PACK => return Err(ParseError::UnknownNewArraySend("pack".into())),
VM_OPT_NEWARRAY_SEND_PACK_BUFFER => return Err(ParseError::UnknownNewArraySend("pack_buffer".into())),
_ => return Err(ParseError::UnknownNewArraySend(format!("{method}"))),
};
fun.push_insn(block, Insn::PatchPoint(Invariant::BOPRedefined { klass: ARRAY_REDEFINED_OP_FLAG, bop }));
state.stack_push(fun.push_insn(block, insn));
}
YARVINSN_duparray => {
let val = fun.push_insn(block, Insn::Const { val: Const::Value(get_arg(pc, 0)) });
let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state.clone() });
@ -2831,6 +2878,35 @@ mod tests {
Return v10
"#]]);
}
#[test]
fn test_opt_newarray_send_max_no_elements() {
eval("
def test = [].max
");
// TODO(max): Rewrite to nil
assert_method_hir("test", expect![[r#"
fn test:
bb0():
PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_MAX)
v3:BasicObject = ArrayMax
Return v3
"#]]);
}
#[test]
fn test_opt_newarray_send_max() {
eval("
def test(a,b) = [a,b].max
");
assert_method_hir("test", expect![[r#"
fn test:
bb0(v0:BasicObject, v1:BasicObject):
PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_MAX)
v5:BasicObject = ArrayMax v0, v1
Return v5
"#]]);
}
}
#[cfg(test)]