ZJIT: Get rid of CallInfo

This commit is contained in:
Max Bernstein 2025-07-30 12:16:51 -04:00 committed by Max Bernstein
parent 096d48d7db
commit 0f7ee8e7a4
3 changed files with 69 additions and 84 deletions

View file

@ -10,7 +10,7 @@ use crate::state::ZJITState;
use crate::stats::{counter_ptr, Counter};
use crate::{asm::CodeBlock, cruby::*, options::debug, virtualmem::CodePtr};
use crate::backend::lir::{self, asm_comment, asm_ccall, Assembler, Opnd, SideExitContext, Target, CFP, C_ARG_OPNDS, C_RET_OPND, EC, NATIVE_STACK_PTR, NATIVE_BASE_PTR, SP};
use crate::hir::{iseq_to_hir, Block, BlockId, BranchEdge, CallInfo, Invariant, RangeType, SideExitReason, SideExitReason::*, SpecialObjectType, SELF_PARAM_IDX};
use crate::hir::{iseq_to_hir, Block, BlockId, BranchEdge, Invariant, RangeType, SideExitReason, SideExitReason::*, SpecialObjectType, SELF_PARAM_IDX};
use crate::hir::{Const, FrameState, Function, Insn, InsnId};
use crate::hir_type::{types, Type};
use crate::options::get_option;
@ -331,10 +331,10 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
Insn::Jump(branch) => return gen_jump(jit, asm, branch),
Insn::IfTrue { val, target } => return gen_if_true(jit, asm, opnd!(val), target),
Insn::IfFalse { val, target } => return gen_if_false(jit, asm, opnd!(val), target),
Insn::SendWithoutBlock { call_info, cd, state, self_val, args, .. } => gen_send_without_block(jit, asm, call_info, *cd, &function.frame_state(*state), opnd!(self_val), opnds!(args))?,
Insn::SendWithoutBlock { cd, state, self_val, args, .. } => gen_send_without_block(jit, asm, *cd, &function.frame_state(*state), opnd!(self_val), opnds!(args))?,
// Give up SendWithoutBlockDirect for 6+ args since asm.ccall() doesn't support it.
Insn::SendWithoutBlockDirect { call_info, cd, state, self_val, args, .. } if args.len() + 1 > C_ARG_OPNDS.len() => // +1 for self
gen_send_without_block(jit, asm, call_info, *cd, &function.frame_state(*state), opnd!(self_val), opnds!(args))?,
Insn::SendWithoutBlockDirect { cd, state, self_val, args, .. } if args.len() + 1 > C_ARG_OPNDS.len() => // +1 for self
gen_send_without_block(jit, asm, *cd, &function.frame_state(*state), opnd!(self_val), opnds!(args))?,
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))?,
Insn::InvokeBuiltin { bf, args, state } => gen_invokebuiltin(asm, &function.frame_state(*state), bf, opnds!(args))?,
Insn::Return { val } => return Some(gen_return(asm, opnd!(val))?),
@ -763,7 +763,6 @@ fn gen_if_false(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, branch:
fn gen_send_without_block(
jit: &mut JITState,
asm: &mut Assembler,
call_info: &CallInfo,
cd: *const rb_call_data,
state: &FrameState,
self_val: Opnd,
@ -789,7 +788,7 @@ fn gen_send_without_block(
gen_save_pc(asm, state);
gen_save_sp(asm, 1 + args.len()); // +1 for receiver
asm_comment!(asm, "call #{} with dynamic dispatch", call_info.method_name);
asm_comment!(asm, "call #{} with dynamic dispatch", ruby_call_method_name(cd));
unsafe extern "C" {
fn rb_vm_opt_send_without_block(ec: EcPtr, cfp: CfpPtr, cd: VALUE) -> VALUE;
}

View file

@ -750,6 +750,16 @@ pub fn ruby_sym_to_rust_string(v: VALUE) -> String {
ruby_str_to_rust_string(ruby_str)
}
pub fn ruby_call_method_id(cd: *const rb_call_data) -> ID {
let call_info = unsafe { rb_get_call_data_ci(cd) };
unsafe { rb_vm_ci_mid(call_info) }
}
pub fn ruby_call_method_name(cd: *const rb_call_data) -> String {
let mid = ruby_call_method_id(cd);
mid.contents_lossy().to_string()
}
/// A location in Rust code for integrating with debugging facilities defined in C.
/// Use the [src_loc!] macro to crate an instance.
pub struct SourceLocation {
@ -1211,6 +1221,21 @@ pub(crate) mod ids {
name: to_s
name: compile
name: eval
name: plus content: b"+"
name: minus content: b"-"
name: mult content: b"*"
name: div content: b"/"
name: modulo content: b"%"
name: neq content: b"!="
name: lt content: b"<"
name: le content: b"<="
name: gt content: b">"
name: ge content: b">="
name: and content: b"&"
name: or content: b"|"
name: freeze
name: minusat content: b"-@"
name: aref content: b"[]"
}
/// Get an CRuby `ID` to an interned string, e.g. a particular method name.

View file

@ -107,11 +107,6 @@ impl std::fmt::Display for BranchEdge {
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct CallInfo {
pub method_name: String,
}
/// Invalidation reasons
#[derive(Debug, Clone, Copy)]
pub enum Invariant {
@ -515,11 +510,10 @@ pub enum Insn {
/// Send without block with dynamic dispatch
/// Ignoring keyword arguments etc for now
SendWithoutBlock { self_val: InsnId, call_info: CallInfo, cd: *const rb_call_data, args: Vec<InsnId>, state: InsnId },
Send { self_val: InsnId, call_info: CallInfo, cd: *const rb_call_data, blockiseq: IseqPtr, args: Vec<InsnId>, state: InsnId },
SendWithoutBlock { self_val: InsnId, cd: *const rb_call_data, args: Vec<InsnId>, state: InsnId },
Send { self_val: InsnId, cd: *const rb_call_data, blockiseq: IseqPtr, args: Vec<InsnId>, state: InsnId },
SendWithoutBlockDirect {
self_val: InsnId,
call_info: CallInfo,
cd: *const rb_call_data,
cme: *const rb_callable_method_entry_t,
iseq: IseqPtr,
@ -551,7 +545,7 @@ pub enum Insn {
FixnumOr { left: InsnId, right: InsnId },
// Distinct from `SendWithoutBlock` with `mid:to_s` because does not have a patch point for String to_s being redefined
ObjToString { val: InsnId, call_info: CallInfo, cd: *const rb_call_data, state: InsnId },
ObjToString { val: InsnId, cd: *const rb_call_data, state: InsnId },
AnyToString { val: InsnId, str: InsnId, state: InsnId },
/// Side-exit if val doesn't have the expected type.
@ -680,25 +674,25 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
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::SendWithoutBlock { self_val, call_info, args, .. } => {
write!(f, "SendWithoutBlock {self_val}, :{}", call_info.method_name)?;
Insn::SendWithoutBlock { self_val, cd, args, .. } => {
write!(f, "SendWithoutBlock {self_val}, :{}", ruby_call_method_name(*cd))?;
for arg in args {
write!(f, ", {arg}")?;
}
Ok(())
}
Insn::SendWithoutBlockDirect { self_val, call_info, iseq, args, .. } => {
write!(f, "SendWithoutBlockDirect {self_val}, :{} ({:?})", call_info.method_name, self.ptr_map.map_ptr(iseq))?;
Insn::SendWithoutBlockDirect { self_val, cd, iseq, args, .. } => {
write!(f, "SendWithoutBlockDirect {self_val}, :{} ({:?})", ruby_call_method_name(*cd), self.ptr_map.map_ptr(iseq))?;
for arg in args {
write!(f, ", {arg}")?;
}
Ok(())
}
Insn::Send { self_val, call_info, args, blockiseq, .. } => {
Insn::Send { self_val, cd, args, blockiseq, .. } => {
// For tests, we want to check HIR snippets textually. Addresses change
// between runs, making tests fail. Instead, pick an arbitrary hex value to
// use as a "pointer" so we can check the rest of the HIR.
write!(f, "Send {self_val}, {:p}, :{}", self.ptr_map.map_ptr(blockiseq), call_info.method_name)?;
write!(f, "Send {self_val}, {:p}, :{}", self.ptr_map.map_ptr(blockiseq), ruby_call_method_name(*cd))?;
for arg in args {
write!(f, ", {arg}")?;
}
@ -1134,9 +1128,8 @@ impl Function {
&FixnumLe { left, right } => FixnumLe { left: find!(left), right: find!(right) },
&FixnumAnd { left, right } => FixnumAnd { left: find!(left), right: find!(right) },
&FixnumOr { left, right } => FixnumOr { left: find!(left), right: find!(right) },
&ObjToString { val, ref call_info, cd, state } => ObjToString {
&ObjToString { val, cd, state } => ObjToString {
val: find!(val),
call_info: call_info.clone(),
cd: cd,
state,
},
@ -1145,25 +1138,22 @@ impl Function {
str: find!(str),
state,
},
&SendWithoutBlock { self_val, ref call_info, cd, ref args, state } => SendWithoutBlock {
&SendWithoutBlock { self_val, cd, ref args, state } => SendWithoutBlock {
self_val: find!(self_val),
call_info: call_info.clone(),
cd: cd,
args: find_vec!(args),
state,
},
&SendWithoutBlockDirect { self_val, ref call_info, cd, cme, iseq, ref args, state } => SendWithoutBlockDirect {
&SendWithoutBlockDirect { self_val, cd, cme, iseq, ref args, state } => SendWithoutBlockDirect {
self_val: find!(self_val),
call_info: call_info.clone(),
cd: cd,
cme: cme,
iseq: iseq,
args: find_vec!(args),
state,
},
&Send { self_val, ref call_info, cd, blockiseq, ref args, state } => Send {
&Send { self_val, cd, blockiseq, ref args, state } => Send {
self_val: find!(self_val),
call_info: call_info.clone(),
cd: cd,
blockiseq: blockiseq,
args: find_vec!(args),
@ -1477,39 +1467,39 @@ impl Function {
assert!(self.blocks[block.0].insns.is_empty());
for insn_id in old_insns {
match self.find(insn_id) {
Insn::SendWithoutBlock { self_val, call_info: CallInfo { method_name }, args, state, .. } if method_name == "+" && args.len() == 1 =>
Insn::SendWithoutBlock { self_val, args, state, cd, .. } if ruby_call_method_id(cd) == ID!(plus) && args.len() == 1 =>
self.try_rewrite_fixnum_op(block, insn_id, &|left, right| Insn::FixnumAdd { left, right, state }, BOP_PLUS, self_val, args[0], state),
Insn::SendWithoutBlock { self_val, call_info: CallInfo { method_name }, args, state, .. } if method_name == "-" && args.len() == 1 =>
Insn::SendWithoutBlock { self_val, args, state, cd, .. } if ruby_call_method_id(cd) == ID!(minus) && args.len() == 1 =>
self.try_rewrite_fixnum_op(block, insn_id, &|left, right| Insn::FixnumSub { left, right, state }, BOP_MINUS, self_val, args[0], state),
Insn::SendWithoutBlock { self_val, call_info: CallInfo { method_name }, args, state, .. } if method_name == "*" && args.len() == 1 =>
Insn::SendWithoutBlock { self_val, args, state, cd, .. } if ruby_call_method_id(cd) == ID!(mult) && args.len() == 1 =>
self.try_rewrite_fixnum_op(block, insn_id, &|left, right| Insn::FixnumMult { left, right, state }, BOP_MULT, self_val, args[0], state),
Insn::SendWithoutBlock { self_val, call_info: CallInfo { method_name }, args, state, .. } if method_name == "/" && args.len() == 1 =>
Insn::SendWithoutBlock { self_val, args, state, cd, .. } if ruby_call_method_id(cd) == ID!(div) && args.len() == 1 =>
self.try_rewrite_fixnum_op(block, insn_id, &|left, right| Insn::FixnumDiv { left, right, state }, BOP_DIV, self_val, args[0], state),
Insn::SendWithoutBlock { self_val, call_info: CallInfo { method_name }, args, state, .. } if method_name == "%" && args.len() == 1 =>
Insn::SendWithoutBlock { self_val, args, state, cd, .. } if ruby_call_method_id(cd) == ID!(modulo) && args.len() == 1 =>
self.try_rewrite_fixnum_op(block, insn_id, &|left, right| Insn::FixnumMod { left, right, state }, BOP_MOD, self_val, args[0], state),
Insn::SendWithoutBlock { self_val, call_info: CallInfo { method_name }, args, state, .. } if method_name == "==" && args.len() == 1 =>
Insn::SendWithoutBlock { self_val, args, state, cd, .. } if ruby_call_method_id(cd) == ID!(eq) && args.len() == 1 =>
self.try_rewrite_fixnum_op(block, insn_id, &|left, right| Insn::FixnumEq { left, right }, BOP_EQ, self_val, args[0], state),
Insn::SendWithoutBlock { self_val, call_info: CallInfo { method_name }, args, state, .. } if method_name == "!=" && args.len() == 1 =>
Insn::SendWithoutBlock { self_val, args, state, cd, .. } if ruby_call_method_id(cd) == ID!(neq) && args.len() == 1 =>
self.try_rewrite_fixnum_op(block, insn_id, &|left, right| Insn::FixnumNeq { left, right }, BOP_NEQ, self_val, args[0], state),
Insn::SendWithoutBlock { self_val, call_info: CallInfo { method_name }, args, state, .. } if method_name == "<" && args.len() == 1 =>
Insn::SendWithoutBlock { self_val, args, state, cd, .. } if ruby_call_method_id(cd) == ID!(lt) && args.len() == 1 =>
self.try_rewrite_fixnum_op(block, insn_id, &|left, right| Insn::FixnumLt { left, right }, BOP_LT, self_val, args[0], state),
Insn::SendWithoutBlock { self_val, call_info: CallInfo { method_name }, args, state, .. } if method_name == "<=" && args.len() == 1 =>
Insn::SendWithoutBlock { self_val, args, state, cd, .. } if ruby_call_method_id(cd) == ID!(le) && args.len() == 1 =>
self.try_rewrite_fixnum_op(block, insn_id, &|left, right| Insn::FixnumLe { left, right }, BOP_LE, self_val, args[0], state),
Insn::SendWithoutBlock { self_val, call_info: CallInfo { method_name }, args, state, .. } if method_name == ">" && args.len() == 1 =>
Insn::SendWithoutBlock { self_val, args, state, cd, .. } if ruby_call_method_id(cd) == ID!(gt) && args.len() == 1 =>
self.try_rewrite_fixnum_op(block, insn_id, &|left, right| Insn::FixnumGt { left, right }, BOP_GT, self_val, args[0], state),
Insn::SendWithoutBlock { self_val, call_info: CallInfo { method_name }, args, state, .. } if method_name == ">=" && args.len() == 1 =>
Insn::SendWithoutBlock { self_val, args, state, cd, .. } if ruby_call_method_id(cd) == ID!(ge) && args.len() == 1 =>
self.try_rewrite_fixnum_op(block, insn_id, &|left, right| Insn::FixnumGe { left, right }, BOP_GE, self_val, args[0], state),
Insn::SendWithoutBlock { self_val, call_info: CallInfo { method_name }, args, state, .. } if method_name == "&" && args.len() == 1 =>
Insn::SendWithoutBlock { self_val, args, state, cd, .. } if ruby_call_method_id(cd) == ID!(and) && args.len() == 1 =>
self.try_rewrite_fixnum_op(block, insn_id, &|left, right| Insn::FixnumAnd { left, right }, BOP_AND, self_val, args[0], state),
Insn::SendWithoutBlock { self_val, call_info: CallInfo { method_name }, args, state, .. } if method_name == "|" && args.len() == 1 =>
Insn::SendWithoutBlock { self_val, args, state, cd, .. } if ruby_call_method_id(cd) == ID!(or) && args.len() == 1 =>
self.try_rewrite_fixnum_op(block, insn_id, &|left, right| Insn::FixnumOr { left, right }, BOP_OR, self_val, args[0], state),
Insn::SendWithoutBlock { self_val, call_info: CallInfo { method_name }, args, state, .. } if method_name == "freeze" && args.len() == 0 =>
Insn::SendWithoutBlock { self_val, args, state, cd, .. } if ruby_call_method_id(cd) == ID!(freeze) && args.len() == 0 =>
self.try_rewrite_freeze(block, insn_id, self_val, state),
Insn::SendWithoutBlock { self_val, call_info: CallInfo { method_name }, args, state, .. } if method_name == "-@" && args.len() == 0 =>
Insn::SendWithoutBlock { self_val, args, state, cd, .. } if ruby_call_method_id(cd) == ID!(minusat) && args.len() == 0 =>
self.try_rewrite_uminus(block, insn_id, self_val, state),
Insn::SendWithoutBlock { self_val, call_info: CallInfo { method_name }, args, state, .. } if method_name == "[]" && args.len() == 1 =>
Insn::SendWithoutBlock { self_val, args, state, cd, .. } if ruby_call_method_id(cd) == ID!(aref) && args.len() == 1 =>
self.try_rewrite_aref(block, insn_id, self_val, args[0], state),
Insn::SendWithoutBlock { mut self_val, call_info, cd, args, state } => {
Insn::SendWithoutBlock { mut self_val, cd, args, state } => {
let frame_state = self.frame_state(state);
let (klass, guard_equal_to) = if let Some(klass) = self.type_of(self_val).runtime_exact_ruby_class() {
// If we know the class statically, use it to fold the lookup at compile-time.
@ -1546,7 +1536,7 @@ impl Function {
if let Some(expected) = guard_equal_to {
self_val = self.push_insn(block, Insn::GuardBitEquals { val: self_val, expected, state });
}
let send_direct = self.push_insn(block, Insn::SendWithoutBlockDirect { self_val, call_info, cd, cme, iseq, args, state });
let send_direct = self.push_insn(block, Insn::SendWithoutBlockDirect { self_val, cd, cme, iseq, args, state });
self.make_equal_to(insn_id, send_direct);
}
Insn::GetConstantPath { ic, state, .. } => {
@ -1569,12 +1559,12 @@ impl Function {
self.insn_types[replacement.0] = self.infer_type(replacement);
self.make_equal_to(insn_id, replacement);
}
Insn::ObjToString { val, call_info, cd, state, .. } => {
Insn::ObjToString { val, cd, state, .. } => {
if self.is_a(val, types::String) {
// behaves differently from `SendWithoutBlock` with `mid:to_s` because ObjToString should not have a patch point for String to_s being redefined
self.make_equal_to(insn_id, val);
} else {
let replacement = self.push_insn(block, Insn::SendWithoutBlock { self_val: val, call_info, cd, args: vec![], state });
let replacement = self.push_insn(block, Insn::SendWithoutBlock { self_val: val, cd, args: vec![], state });
self.make_equal_to(insn_id, replacement)
}
}
@ -2934,18 +2924,13 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
}
let argc = unsafe { vm_ci_argc((*cd).ci) };
let method_name = unsafe {
let mid = rb_vm_ci_mid(call_info);
mid.contents_lossy().into_owned()
};
assert_eq!(1, argc, "opt_aref_with should only be emitted for argc=1");
let aref_arg = fun.push_insn(block, Insn::Const { val: Const::Value(get_arg(pc, 0)) });
let args = vec![aref_arg];
let recv = state.stack_pop()?;
let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
let send = fun.push_insn(block, Insn::SendWithoutBlock { self_val: recv, call_info: CallInfo { method_name }, cd, args, state: exit_id });
let send = fun.push_insn(block, Insn::SendWithoutBlock { self_val: recv, cd, args, state: exit_id });
state.stack_push(send);
}
YARVINSN_opt_neq => {
@ -2960,11 +2945,6 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
}
let argc = unsafe { vm_ci_argc((*cd).ci) };
let method_name = unsafe {
let mid = rb_vm_ci_mid(call_info);
mid.contents_lossy().into_owned()
};
let mut args = vec![];
for _ in 0..argc {
args.push(state.stack_pop()?);
@ -2973,7 +2953,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
let recv = state.stack_pop()?;
let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
let send = fun.push_insn(block, Insn::SendWithoutBlock { self_val: recv, call_info: CallInfo { method_name }, cd, args, state: exit_id });
let send = fun.push_insn(block, Insn::SendWithoutBlock { self_val: recv, cd, args, state: exit_id });
state.stack_push(send);
}
YARVINSN_opt_hash_freeze |
@ -2994,14 +2974,9 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
assert_eq!(0, argc, "{name} should not have args");
let args = vec![];
let method_name = unsafe {
let mid = rb_vm_ci_mid(call_info);
mid.contents_lossy().into_owned()
};
let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
let recv = fun.push_insn(block, Insn::Const { val: Const::Value(get_arg(pc, 0)) });
let send = fun.push_insn(block, Insn::SendWithoutBlock { self_val: recv, call_info: CallInfo { method_name }, cd, args, state: exit_id });
let send = fun.push_insn(block, Insn::SendWithoutBlock { self_val: recv, cd, args, state: exit_id });
state.stack_push(send);
}
@ -3051,11 +3026,6 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
}
let argc = unsafe { vm_ci_argc((*cd).ci) };
let method_name = unsafe {
let mid = rb_vm_ci_mid(call_info);
mid.contents_lossy().into_owned()
};
let mut args = vec![];
for _ in 0..argc {
args.push(state.stack_pop()?);
@ -3064,7 +3034,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
let recv = state.stack_pop()?;
let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
let send = fun.push_insn(block, Insn::SendWithoutBlock { self_val: recv, call_info: CallInfo { method_name }, cd, args, state: exit_id });
let send = fun.push_insn(block, Insn::SendWithoutBlock { self_val: recv, cd, args, state: exit_id });
state.stack_push(send);
}
YARVINSN_send => {
@ -3079,10 +3049,6 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
}
let argc = unsafe { vm_ci_argc((*cd).ci) };
let method_name = unsafe {
let mid = rb_vm_ci_mid(call_info);
mid.contents_lossy().into_owned()
};
let mut args = vec![];
for _ in 0..argc {
args.push(state.stack_pop()?);
@ -3091,7 +3057,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
let recv = state.stack_pop()?;
let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
let send = fun.push_insn(block, Insn::Send { self_val: recv, call_info: CallInfo { method_name }, cd, blockiseq, args, state: exit_id });
let send = fun.push_insn(block, Insn::Send { self_val: recv, cd, blockiseq, args, state: exit_id });
state.stack_push(send);
}
YARVINSN_getglobal => {
@ -3177,14 +3143,9 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
let argc = unsafe { vm_ci_argc((*cd).ci) };
assert_eq!(0, argc, "objtostring should not have args");
let method_name: String = unsafe {
let mid = rb_vm_ci_mid(call_info);
mid.contents_lossy().into_owned()
};
let recv = state.stack_pop()?;
let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
let objtostring = fun.push_insn(block, Insn::ObjToString { val: recv, call_info: CallInfo { method_name }, cd, state: exit_id });
let objtostring = fun.push_insn(block, Insn::ObjToString { val: recv, cd, state: exit_id });
state.stack_push(objtostring)
}
YARVINSN_anytostring => {