ZJIT: Support opt_newarray_send with PACK_BUFFER

This commit is contained in:
Max Bernstein authored and Max Bernstein committed 2025-12-09 08:52:05 -05:00
1 parent c8441e8db5
commit cb9510f539
4 files changed
+132 -1
Notes: git 2025-12-09 13:52:35 +00:00

No files matched your search

+28
View File
@@ -1129,6 +1129,34 @@ class TestZJIT < Test::Unit::TestCase
}, insns: [:opt_duparray_send], call_threshold: 1
end
def test_opt_newarray_send_pack_buffer
assert_compiles '["ABC", "ABC", "ABC", "ABC"]', %q{
def test(num, buffer)
[num].pack('C', buffer:)
end
buf = ""
[test(65, buf), test(66, buf), test(67, buf), buf]
}, insns: [:opt_newarray_send], call_threshold: 1
end
def test_opt_newarray_send_pack_buffer_redefined
assert_compiles '["b", "A"]', %q{
class Array
alias_method :old_pack, :pack
def pack(fmt, buffer: nil)
old_pack(fmt, buffer: buffer)
"b"
end
end
def test(num, buffer)
[num].pack('C', buffer:)
end
buf = ""
[test(65, buf), buf]
}, insns: [:opt_newarray_send], call_threshold: 1
end
def test_opt_newarray_send_hash
assert_compiles 'Integer', %q{
def test(x)
+29
View File
@@ -481,6 +481,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
&Insn::WriteBarrier { recv, val } => no_output!(gen_write_barrier(asm, opnd!(recv), opnd!(val), function.type_of(val))),
&Insn::IsBlockGiven => gen_is_block_given(jit, asm),
Insn::ArrayInclude { elements, target, state } => gen_array_include(jit, asm, opnds!(elements), opnd!(target), &function.frame_state(*state)),
Insn::ArrayPackBuffer { elements, fmt, buffer, state } => gen_array_pack_buffer(jit, asm, opnds!(elements), opnd!(fmt), opnd!(buffer), &function.frame_state(*state)),
&Insn::DupArrayInclude { ary, target, state } => gen_dup_array_include(jit, asm, ary, opnd!(target), &function.frame_state(state)),
Insn::ArrayHash { elements, state } => gen_opt_newarray_hash(jit, asm, opnds!(elements), &function.frame_state(*state)),
&Insn::IsA { val, class } => gen_is_a(asm, opnd!(val), opnd!(class)),
@@ -1548,6 +1549,34 @@ fn gen_array_include(
)
}
fn gen_array_pack_buffer(
jit: &JITState,
asm: &mut Assembler,
elements: Vec<Opnd>,
fmt: Opnd,
buffer: Opnd,
state: &FrameState,
) -> lir::Opnd {
gen_prepare_non_leaf_call(jit, asm, state);
let array_len: c_long = elements.len().try_into().expect("Unable to fit length of elements into c_long");
// After gen_prepare_non_leaf_call, the elements are spilled to the Ruby stack.
// The elements are at the bottom of the virtual stack, followed by the fmt, followed by the buffer.
// Get a pointer to the first element on the Ruby stack.
let stack_bottom = state.stack().len() - elements.len() - 2;
let elements_ptr = asm.lea(Opnd::mem(64, SP, stack_bottom as i32 * SIZEOF_VALUE_I32));
unsafe extern "C" {
fn rb_vm_opt_newarray_pack_buffer(ec: EcPtr, num: c_long, elts: *const VALUE, fmt: VALUE, buffer: VALUE) -> VALUE;
}
asm_ccall!(
asm,
rb_vm_opt_newarray_pack_buffer,
EC, array_len.into(), elements_ptr, fmt, buffer
)
}
fn gen_dup_array_include(
jit: &JITState,
asm: &mut Assembler,
+30
View File
@@ -675,6 +675,7 @@ pub enum Insn {
ArrayHash { elements: Vec<InsnId>, state: InsnId },
ArrayMax { elements: Vec<InsnId>, state: InsnId },
ArrayInclude { elements: Vec<InsnId>, target: InsnId, state: InsnId },
ArrayPackBuffer { elements: Vec<InsnId>, fmt: InsnId, buffer: InsnId, state: InsnId },
DupArrayInclude { ary: VALUE, target: InsnId, state: InsnId },
/// Extend `left` with the elements from `right`. `left` and `right` must both be `Array`.
ArrayExtend { left: InsnId, right: InsnId, state: InsnId },
@@ -1122,6 +1123,13 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
}
write!(f, " | {target}")
}
Insn::ArrayPackBuffer { elements, fmt, buffer, .. } => {
write!(f, "ArrayPackBuffer ")?;
for element in elements {
write!(f, "{element}, ")?;
}
write!(f, "fmt: {fmt}, buf: {buffer}")
}
Insn::DupArrayInclude { ary, target, .. } => {
write!(f, "DupArrayInclude {} | {}", ary.print(self.ptr_map), target)
}
@@ -1990,6 +1998,7 @@ impl Function {
&ArrayLength { array } => ArrayLength { array: find!(array) },
&ArrayMax { ref elements, state } => ArrayMax { elements: find_vec!(elements), state: find!(state) },
&ArrayInclude { ref elements, target, state } => ArrayInclude { elements: find_vec!(elements), target: find!(target), state: find!(state) },
&ArrayPackBuffer { ref elements, fmt, buffer, state } => ArrayPackBuffer { elements: find_vec!(elements), fmt: find!(fmt), buffer: find!(buffer), state: find!(state) },
&DupArrayInclude { ary, target, state } => DupArrayInclude { ary, target: find!(target), state: find!(state) },
&ArrayHash { ref elements, state } => ArrayHash { elements: find_vec!(elements), state },
&SetGlobal { id, val, state } => SetGlobal { id, val: find!(val), state },
@@ -2144,6 +2153,7 @@ impl Function {
Insn::FixnumBitCheck { .. } => types::BoolExact,
Insn::ArrayMax { .. } => types::BasicObject,
Insn::ArrayInclude { .. } => types::BoolExact,
Insn::ArrayPackBuffer { .. } => types::String,
Insn::DupArrayInclude { .. } => types::BoolExact,
Insn::ArrayHash { .. } => types::Fixnum,
Insn::GetGlobal { .. } => types::BasicObject,
@@ -3658,6 +3668,12 @@ impl Function {
worklist.push_back(target);
worklist.push_back(state);
}
&Insn::ArrayPackBuffer { ref elements, fmt, buffer, state } => {
worklist.extend(elements);
worklist.push_back(fmt);
worklist.push_back(buffer);
worklist.push_back(state);
}
&Insn::DupArrayInclude { target, state, .. } => {
worklist.push_back(target);
worklist.push_back(state);
@@ -4417,6 +4433,14 @@ impl Function {
}
Ok(())
}
Insn::ArrayPackBuffer { ref elements, fmt, buffer, .. } => {
self.assert_subtype(insn_id, fmt, types::BasicObject)?;
self.assert_subtype(insn_id, buffer, types::BasicObject)?;
for &element in elements {
self.assert_subtype(insn_id, element, types::BasicObject)?;
}
Ok(())
}
// Instructions with a Vec of Ruby objects
Insn::InvokeBlock { ref args, .. }
| Insn::NewArray { elements: ref args, .. }
@@ -5266,6 +5290,12 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
let elements = state.stack_pop_n(count - 1)?;
(BOP_INCLUDE_P, Insn::ArrayInclude { elements, target, state: exit_id })
}
VM_OPT_NEWARRAY_SEND_PACK_BUFFER => {
let buffer = state.stack_pop()?;
let fmt = state.stack_pop()?;
let elements = state.stack_pop_n(count - 2)?;
(BOP_PACK, Insn::ArrayPackBuffer { elements, fmt, buffer, state: exit_id })
}
_ => {
// Unknown opcode; side-exit into the interpreter
fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledNewarraySend(method) });
+45 -1
View File
@@ -2154,7 +2154,51 @@ pub mod hir_build_tests {
v36:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
v37:StringExact = StringCopy v36
v39:BasicObject = GetLocal :buf, l0, EP@3
SideExit UnhandledNewarraySend(PACK_BUFFER)
PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_PACK)
v42:String = ArrayPackBuffer v15, v16, fmt: v37, buf: v39
PatchPoint NoEPEscape(test)
CheckInterrupts
Return v30
");
}
#[test]
fn test_opt_newarray_send_pack_buffer_redefined() {
eval(r#"
class Array
def pack(fmt, buffer: nil) = 5
end
def test(a,b)
sum = a+b
buf = ""
[a,b].pack 'C', buffer: buf
buf
end
"#);
assert_contains_opcode("test", YARVINSN_opt_newarray_send);
assert_snapshot!(hir_string("test"), @r"
fn test@<compiled>:6:
bb0():
EntryPoint interpreter
v1:BasicObject = LoadSelf
v2:BasicObject = GetLocal :a, l0, SP@7
v3:BasicObject = GetLocal :b, l0, SP@6
v4:NilClass = Const Value(nil)
v5:NilClass = Const Value(nil)
Jump bb2(v1, v2, v3, v4, v5)
bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject):
EntryPoint JIT(0)
v11:NilClass = Const Value(nil)
v12:NilClass = Const Value(nil)
Jump bb2(v8, v9, v10, v11, v12)
bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass):
v25:BasicObject = SendWithoutBlock v15, :+, v16
v29:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
v30:StringExact = StringCopy v29
v36:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
v37:StringExact = StringCopy v36
v39:BasicObject = GetLocal :buf, l0, EP@3
SideExit PatchPoint(BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_PACK))
");
}