Skip to content

Commit 9c53957

Browse files
committed
ZJIT: Support opt_newarray_send with PACK_BUFFER
1 parent 1b18294 commit 9c53957

File tree

4 files changed

+132
-1
lines changed

4 files changed

+132
-1
lines changed

test/ruby/test_zjit.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1129,6 +1129,34 @@ def test(x)
11291129
}, insns: [:opt_duparray_send], call_threshold: 1
11301130
end
11311131

1132+
def test_opt_newarray_send_pack_buffer
1133+
assert_compiles '["ABC", "ABC", "ABC", "ABC"]', %q{
1134+
def test(num, buffer)
1135+
[num].pack('C', buffer:)
1136+
end
1137+
buf = ""
1138+
[test(65, buf), test(66, buf), test(67, buf), buf]
1139+
}, insns: [:opt_newarray_send], call_threshold: 1
1140+
end
1141+
1142+
def test_opt_newarray_send_pack_buffer_redefined
1143+
assert_compiles '["b", "A"]', %q{
1144+
class Array
1145+
alias_method :old_pack, :pack
1146+
def pack(fmt, buffer: nil)
1147+
old_pack(fmt, buffer: buffer)
1148+
"b"
1149+
end
1150+
end
1151+
1152+
def test(num, buffer)
1153+
[num].pack('C', buffer:)
1154+
end
1155+
buf = ""
1156+
[test(65, buf), buf]
1157+
}, insns: [:opt_newarray_send], call_threshold: 1
1158+
end
1159+
11321160
def test_opt_newarray_send_hash
11331161
assert_compiles 'Integer', %q{
11341162
def test(x)

zjit/src/codegen.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
481481
&Insn::WriteBarrier { recv, val } => no_output!(gen_write_barrier(asm, opnd!(recv), opnd!(val), function.type_of(val))),
482482
&Insn::IsBlockGiven => gen_is_block_given(jit, asm),
483483
Insn::ArrayInclude { elements, target, state } => gen_array_include(jit, asm, opnds!(elements), opnd!(target), &function.frame_state(*state)),
484+
Insn::ArrayPackBuffer { elements, fmt, buffer, state } => gen_array_pack_buffer(jit, asm, opnds!(elements), opnd!(fmt), opnd!(buffer), &function.frame_state(*state)),
484485
&Insn::DupArrayInclude { ary, target, state } => gen_dup_array_include(jit, asm, ary, opnd!(target), &function.frame_state(state)),
485486
Insn::ArrayHash { elements, state } => gen_opt_newarray_hash(jit, asm, opnds!(elements), &function.frame_state(*state)),
486487
&Insn::IsA { val, class } => gen_is_a(asm, opnd!(val), opnd!(class)),
@@ -1548,6 +1549,34 @@ fn gen_array_include(
15481549
)
15491550
}
15501551

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

zjit/src/hir.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,7 @@ pub enum Insn {
675675
ArrayHash { elements: Vec<InsnId>, state: InsnId },
676676
ArrayMax { elements: Vec<InsnId>, state: InsnId },
677677
ArrayInclude { elements: Vec<InsnId>, target: InsnId, state: InsnId },
678+
ArrayPackBuffer { elements: Vec<InsnId>, fmt: InsnId, buffer: InsnId, state: InsnId },
678679
DupArrayInclude { ary: VALUE, target: InsnId, state: InsnId },
679680
/// Extend `left` with the elements from `right`. `left` and `right` must both be `Array`.
680681
ArrayExtend { left: InsnId, right: InsnId, state: InsnId },
@@ -1122,6 +1123,13 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
11221123
}
11231124
write!(f, " | {target}")
11241125
}
1126+
Insn::ArrayPackBuffer { elements, fmt, buffer, .. } => {
1127+
write!(f, "ArrayPackBuffer ")?;
1128+
for element in elements {
1129+
write!(f, "{element}, ")?;
1130+
}
1131+
write!(f, "fmt: {fmt}, buf: {buffer}")
1132+
}
11251133
Insn::DupArrayInclude { ary, target, .. } => {
11261134
write!(f, "DupArrayInclude {} | {}", ary.print(self.ptr_map), target)
11271135
}
@@ -1990,6 +1998,7 @@ impl Function {
19901998
&ArrayLength { array } => ArrayLength { array: find!(array) },
19911999
&ArrayMax { ref elements, state } => ArrayMax { elements: find_vec!(elements), state: find!(state) },
19922000
&ArrayInclude { ref elements, target, state } => ArrayInclude { elements: find_vec!(elements), target: find!(target), state: find!(state) },
2001+
&ArrayPackBuffer { ref elements, fmt, buffer, state } => ArrayPackBuffer { elements: find_vec!(elements), fmt: find!(fmt), buffer: find!(buffer), state: find!(state) },
19932002
&DupArrayInclude { ary, target, state } => DupArrayInclude { ary, target: find!(target), state: find!(state) },
19942003
&ArrayHash { ref elements, state } => ArrayHash { elements: find_vec!(elements), state },
19952004
&SetGlobal { id, val, state } => SetGlobal { id, val: find!(val), state },
@@ -2144,6 +2153,7 @@ impl Function {
21442153
Insn::FixnumBitCheck { .. } => types::BoolExact,
21452154
Insn::ArrayMax { .. } => types::BasicObject,
21462155
Insn::ArrayInclude { .. } => types::BoolExact,
2156+
Insn::ArrayPackBuffer { .. } => types::String,
21472157
Insn::DupArrayInclude { .. } => types::BoolExact,
21482158
Insn::ArrayHash { .. } => types::Fixnum,
21492159
Insn::GetGlobal { .. } => types::BasicObject,
@@ -3658,6 +3668,12 @@ impl Function {
36583668
worklist.push_back(target);
36593669
worklist.push_back(state);
36603670
}
3671+
&Insn::ArrayPackBuffer { ref elements, fmt, buffer, state } => {
3672+
worklist.extend(elements);
3673+
worklist.push_back(fmt);
3674+
worklist.push_back(buffer);
3675+
worklist.push_back(state);
3676+
}
36613677
&Insn::DupArrayInclude { target, state, .. } => {
36623678
worklist.push_back(target);
36633679
worklist.push_back(state);
@@ -4417,6 +4433,14 @@ impl Function {
44174433
}
44184434
Ok(())
44194435
}
4436+
Insn::ArrayPackBuffer { ref elements, fmt, buffer, .. } => {
4437+
self.assert_subtype(insn_id, fmt, types::BasicObject)?;
4438+
self.assert_subtype(insn_id, buffer, types::BasicObject)?;
4439+
for &element in elements {
4440+
self.assert_subtype(insn_id, element, types::BasicObject)?;
4441+
}
4442+
Ok(())
4443+
}
44204444
// Instructions with a Vec of Ruby objects
44214445
Insn::InvokeBlock { ref args, .. }
44224446
| Insn::NewArray { elements: ref args, .. }
@@ -5266,6 +5290,12 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
52665290
let elements = state.stack_pop_n(count - 1)?;
52675291
(BOP_INCLUDE_P, Insn::ArrayInclude { elements, target, state: exit_id })
52685292
}
5293+
VM_OPT_NEWARRAY_SEND_PACK_BUFFER => {
5294+
let buffer = state.stack_pop()?;
5295+
let fmt = state.stack_pop()?;
5296+
let elements = state.stack_pop_n(count - 2)?;
5297+
(BOP_PACK, Insn::ArrayPackBuffer { elements, fmt, buffer, state: exit_id })
5298+
}
52695299
_ => {
52705300
// Unknown opcode; side-exit into the interpreter
52715301
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 :buf, l0, EP@3
2157-
SideExit UnhandledNewarraySend(PACK_BUFFER)
2157+
PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_PACK)
2158+
v42:String = ArrayPackBuffer v15, 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 :a, l0, SP@7
2185+
v3:BasicObject = GetLocal :b, 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 :buf, l0, EP@3
2201+
SideExit PatchPoint(BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_PACK))
21582202
");
21592203
}
21602204

0 commit comments

Comments
 (0)