Skip to content

Commit bec22ac

Browse files
committed
ZJIT: Support opt_newarray_send with PACK_BUFFER
1 parent ece0617 commit bec22ac

File tree

4 files changed

+133
-2
lines changed

4 files changed

+133
-2
lines changed

test/ruby/test_zjit.rb

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1094,7 +1094,35 @@ def test(x)
10941094
end
10951095
[test(1), test("n")]
10961096
}, insns: [:opt_duparray_send], call_threshold: 1
1097-
end
1097+
end
1098+
1099+
def test_opt_newarray_send_pack_buffer
1100+
assert_compiles '["ABC", "ABC", "ABC", "ABC"]', %q{
1101+
def test(num, buffer)
1102+
[num].pack('C', buffer:)
1103+
end
1104+
buf = ""
1105+
[test(65, buf), test(66, buf), test(67, buf), buf]
1106+
}, insns: [:opt_newarray_send], call_threshold: 1
1107+
end
1108+
1109+
def test_opt_newarray_send_pack_buffer_redefined
1110+
assert_compiles '["b", "A"]', %q{
1111+
class Array
1112+
alias_method :old_pack, :pack
1113+
def pack(fmt, buffer: nil)
1114+
old_pack(fmt, buffer: buffer)
1115+
"b"
1116+
end
1117+
end
1118+
1119+
def test(num, buffer)
1120+
[num].pack('C', buffer:)
1121+
end
1122+
buf = ""
1123+
[test(65, buf), buf]
1124+
}, insns: [:opt_newarray_send], call_threshold: 1
1125+
end
10981126

10991127
def test_opt_newarray_send_hash
11001128
assert_compiles 'Integer', %q{

zjit/src/codegen.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
479479
&Insn::WriteBarrier { recv, val } => no_output!(gen_write_barrier(asm, opnd!(recv), opnd!(val), function.type_of(val))),
480480
&Insn::IsBlockGiven => gen_is_block_given(jit, asm),
481481
Insn::ArrayInclude { elements, target, state } => gen_array_include(jit, asm, opnds!(elements), opnd!(target), &function.frame_state(*state)),
482+
Insn::ArrayPackBuffer { elements, fmt, buffer, state } => gen_array_pack_buffer(jit, asm, opnds!(elements), opnd!(fmt), opnd!(buffer), &function.frame_state(*state)),
482483
&Insn::DupArrayInclude { ary, target, state } => gen_dup_array_include(jit, asm, ary, opnd!(target), &function.frame_state(state)),
483484
Insn::ArrayHash { elements, state } => gen_opt_newarray_hash(jit, asm, opnds!(elements), &function.frame_state(*state)),
484485
&Insn::IsA { val, class } => gen_is_a(asm, opnd!(val), opnd!(class)),
@@ -1543,6 +1544,34 @@ fn gen_array_include(
15431544
)
15441545
}
15451546

1547+
fn gen_array_pack_buffer(
1548+
jit: &JITState,
1549+
asm: &mut Assembler,
1550+
elements: Vec<Opnd>,
1551+
fmt: Opnd,
1552+
buffer: Opnd,
1553+
state: &FrameState,
1554+
) -> lir::Opnd {
1555+
gen_prepare_non_leaf_call(jit, asm, state);
1556+
1557+
let array_len: c_long = elements.len().try_into().expect("Unable to fit length of elements into c_long");
1558+
1559+
// After gen_prepare_non_leaf_call, the elements are spilled to the Ruby stack.
1560+
// The elements are at the bottom of the virtual stack, followed by the fmt, followed by the buffer.
1561+
// Get a pointer to the first element on the Ruby stack.
1562+
let stack_bottom = state.stack().len() - elements.len() - 2;
1563+
let elements_ptr = asm.lea(Opnd::mem(64, SP, stack_bottom as i32 * SIZEOF_VALUE_I32));
1564+
1565+
unsafe extern "C" {
1566+
fn rb_vm_opt_newarray_pack_buffer(ec: EcPtr, num: c_long, elts: *const VALUE, fmt: VALUE, buffer: VALUE) -> VALUE;
1567+
}
1568+
asm_ccall!(
1569+
asm,
1570+
rb_vm_opt_newarray_pack_buffer,
1571+
EC, array_len.into(), elements_ptr, fmt, buffer
1572+
)
1573+
}
1574+
15461575
fn gen_dup_array_include(
15471576
jit: &JITState,
15481577
asm: &mut Assembler,

zjit/src/hir.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,7 @@ pub enum Insn {
674674
ArrayHash { elements: Vec<InsnId>, state: InsnId },
675675
ArrayMax { elements: Vec<InsnId>, state: InsnId },
676676
ArrayInclude { elements: Vec<InsnId>, target: InsnId, state: InsnId },
677+
ArrayPackBuffer { elements: Vec<InsnId>, fmt: InsnId, buffer: InsnId, state: InsnId },
677678
DupArrayInclude { ary: VALUE, target: InsnId, state: InsnId },
678679
/// Extend `left` with the elements from `right`. `left` and `right` must both be `Array`.
679680
ArrayExtend { left: InsnId, right: InsnId, state: InsnId },
@@ -1097,6 +1098,13 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
10971098
}
10981099
write!(f, " | {target}")
10991100
}
1101+
Insn::ArrayPackBuffer { elements, fmt, buffer, .. } => {
1102+
write!(f, "ArrayPackBuffer")?;
1103+
for element in elements {
1104+
write!(f, "{element}, ")?;
1105+
}
1106+
write!(f, "fmt: {fmt}, buf: {buffer}")
1107+
}
11001108
Insn::DupArrayInclude { ary, target, .. } => {
11011109
write!(f, "DupArrayInclude {} | {}", ary.print(self.ptr_map), target)
11021110
}
@@ -1956,6 +1964,7 @@ impl Function {
19561964
&ArrayLength { array } => ArrayLength { array: find!(array) },
19571965
&ArrayMax { ref elements, state } => ArrayMax { elements: find_vec!(elements), state: find!(state) },
19581966
&ArrayInclude { ref elements, target, state } => ArrayInclude { elements: find_vec!(elements), target: find!(target), state: find!(state) },
1967+
&ArrayPackBuffer { ref elements, fmt, buffer, state } => ArrayPackBuffer { elements: find_vec!(elements), fmt: find!(fmt), buffer: find!(buffer), state: find!(state) },
19591968
&DupArrayInclude { ary, target, state } => DupArrayInclude { ary, target: find!(target), state: find!(state) },
19601969
&ArrayHash { ref elements, state } => ArrayHash { elements: find_vec!(elements), state },
19611970
&SetGlobal { id, val, state } => SetGlobal { id, val: find!(val), state },
@@ -2110,6 +2119,7 @@ impl Function {
21102119
Insn::FixnumBitCheck { .. } => types::BoolExact,
21112120
Insn::ArrayMax { .. } => types::BasicObject,
21122121
Insn::ArrayInclude { .. } => types::BoolExact,
2122+
Insn::ArrayPackBuffer { .. } => types::String,
21132123
Insn::DupArrayInclude { .. } => types::BoolExact,
21142124
Insn::ArrayHash { .. } => types::Fixnum,
21152125
Insn::GetGlobal { .. } => types::BasicObject,
@@ -3593,6 +3603,12 @@ impl Function {
35933603
worklist.push_back(target);
35943604
worklist.push_back(state);
35953605
}
3606+
&Insn::ArrayPackBuffer { ref elements, fmt, buffer, state } => {
3607+
worklist.extend(elements);
3608+
worklist.push_back(fmt);
3609+
worklist.push_back(buffer);
3610+
worklist.push_back(state);
3611+
}
35963612
&Insn::DupArrayInclude { target, state, .. } => {
35973613
worklist.push_back(target);
35983614
worklist.push_back(state);
@@ -4352,6 +4368,14 @@ impl Function {
43524368
}
43534369
Ok(())
43544370
}
4371+
Insn::ArrayPackBuffer { ref elements, fmt, buffer, .. } => {
4372+
self.assert_subtype(insn_id, fmt, types::BasicObject)?;
4373+
self.assert_subtype(insn_id, buffer, types::BasicObject)?;
4374+
for &element in elements {
4375+
self.assert_subtype(insn_id, element, types::BasicObject)?;
4376+
}
4377+
Ok(())
4378+
}
43554379
// Instructions with a Vec of Ruby objects
43564380
Insn::InvokeBlock { ref args, .. }
43574381
| Insn::NewArray { elements: ref args, .. }
@@ -5201,6 +5225,12 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
52015225
let elements = state.stack_pop_n(count - 1)?;
52025226
(BOP_INCLUDE_P, Insn::ArrayInclude { elements, target, state: exit_id })
52035227
}
5228+
VM_OPT_NEWARRAY_SEND_PACK_BUFFER => {
5229+
let buffer = state.stack_pop()?;
5230+
let fmt = state.stack_pop()?;
5231+
let elements = state.stack_pop_n(count - 2)?;
5232+
(BOP_PACK, Insn::ArrayPackBuffer { elements, fmt, buffer, state: exit_id })
5233+
}
52045234
_ => {
52055235
// Unknown opcode; side-exit into the interpreter
52065236
fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledNewarraySend(method) });

zjit/src/hir/tests.rs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2154,7 +2154,51 @@ pub mod hir_build_tests {
21542154
v36:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
21552155
v37:StringExact = StringCopy v36
21562156
v39:BasicObject = GetLocal l0, EP@3
2157-
SideExit UnhandledNewarraySend(PACK_BUFFER)
2157+
PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_PACK)
2158+
v42:String = ArrayPackBufferv15, v16, fmt: v37, buf: v39
2159+
PatchPoint NoEPEscape(test)
2160+
CheckInterrupts
2161+
Return v30
2162+
");
2163+
}
2164+
2165+
#[test]
2166+
fn test_opt_newarray_send_pack_buffer_redefined() {
2167+
eval(r#"
2168+
class Array
2169+
def pack(fmt, buffer: nil) = 5
2170+
end
2171+
def test(a,b)
2172+
sum = a+b
2173+
buf = ""
2174+
[a,b].pack 'C', buffer: buf
2175+
buf
2176+
end
2177+
"#);
2178+
assert_contains_opcode("test", YARVINSN_opt_newarray_send);
2179+
assert_snapshot!(hir_string("test"), @r"
2180+
fn test@<compiled>:6:
2181+
bb0():
2182+
EntryPoint interpreter
2183+
v1:BasicObject = LoadSelf
2184+
v2:BasicObject = GetLocal l0, SP@7
2185+
v3:BasicObject = GetLocal l0, SP@6
2186+
v4:NilClass = Const Value(nil)
2187+
v5:NilClass = Const Value(nil)
2188+
Jump bb2(v1, v2, v3, v4, v5)
2189+
bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject):
2190+
EntryPoint JIT(0)
2191+
v11:NilClass = Const Value(nil)
2192+
v12:NilClass = Const Value(nil)
2193+
Jump bb2(v8, v9, v10, v11, v12)
2194+
bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass):
2195+
v25:BasicObject = SendWithoutBlock v15, :+, v16
2196+
v29:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
2197+
v30:StringExact = StringCopy v29
2198+
v36:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
2199+
v37:StringExact = StringCopy v36
2200+
v39:BasicObject = GetLocal l0, EP@3
2201+
SideExit PatchPoint(BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_PACK))
21582202
");
21592203
}
21602204

0 commit comments

Comments
 (0)