mirror of
https://github.com/ruby/ruby.git
synced 2026-10-28 16:36:20 +08:00
ZJIT: Support opt_newarray_send with PACK_BUFFER
This commit is contained in:
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
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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))
|
||||
");
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user