Skip to content

Commit 219eb96

Browse files
committed
ZJIT: Implement ToArray and ArrayExtend
- ToArray is used in both `concattoarray` and `splatarray` instructions to convert a value to an array OR put the value in an array - ArrayExtend is used in `concattoarray` to extend `ary1` with `ary2` With this change, ZJIT can actually execute `concattoarray` and `splatarray` instructions.
1 parent 5a7be72 commit 219eb96

4 files changed

Lines changed: 101 additions & 15 deletions

File tree

test/ruby/test_zjit.rb

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,42 @@ def test = [1,2,3]
489489
}
490490
end
491491

492+
def test_array_splat_custom_to_a
493+
assert_compiles '[1, 2, 3]', %q{
494+
class Foo
495+
def to_a
496+
[1, 2, 3]
497+
end
498+
end
499+
def bar(*a) = a
500+
def test = bar(*Foo.new)
501+
test
502+
}, insns: [:splatarray]
503+
end
504+
505+
def test_array_splat_nil
506+
assert_compiles '1', %q{
507+
def foo = 1
508+
def test = foo(*nil)
509+
test
510+
}, insns: [:splatarray]
511+
end
512+
513+
def test_array_splat
514+
assert_compiles '"foo"', %q{
515+
def foo(b) = b
516+
def test(a) = foo(*a)
517+
test("foo")
518+
}, insns: [:splatarray]
519+
end
520+
521+
def test_array_concat_implicit
522+
assert_compiles '[1, 2, 3]', %q{
523+
def test(a) = [1, *a]
524+
test([2, 3])
525+
}, insns: [:concattoarray]
526+
end
527+
492528
def test_new_range_inclusive
493529
assert_compiles '1..5', %q{
494530
def test(a, b) = a..b

zjit/bindgen/src/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ fn main() {
118118
.allowlist_function("rb_obj_is_kind_of")
119119
.allowlist_function("rb_obj_frozen_p")
120120
.allowlist_function("rb_class_inherited_p")
121+
.allowlist_function("rb_Array")
121122

122123
// From ruby/internal/encoding/encoding.h
123124
.allowlist_type("ruby_encoding_consts")
@@ -149,6 +150,7 @@ fn main() {
149150
.allowlist_function("rb_ary_dup")
150151
.allowlist_function("rb_ary_push")
151152
.allowlist_function("rb_ary_unshift_m")
153+
.allowlist_function("rb_ary_concat")
152154

153155
// From internal/array.h
154156
.allowlist_function("rb_ec_ary_new_from_values")

zjit/src/codegen.rs

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,8 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
367367
Insn::AnyToString { val, str, state } => gen_anytostring(asm, opnd!(val), opnd!(str), &function.frame_state(*state))?,
368368
Insn::Defined { op_type, obj, pushval, v } => gen_defined(jit, asm, *op_type, *obj, *pushval, opnd!(v))?,
369369
&Insn::IncrCounter(counter) => return Some(gen_incr_counter(asm, counter)),
370-
Insn::ArrayExtend { .. }
370+
Insn::ToArray { val, state } => gen_to_array(jit, asm, opnd!(val), &function.frame_state(*state))?,
371+
Insn::ArrayExtend { left, right, state } => return gen_array_extend(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state)),
371372
| Insn::ArrayMax { .. }
372373
| Insn::ArrayPush { .. }
373374
| Insn::DefinedIvar { .. }
@@ -379,7 +380,6 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
379380
| Insn::Send { .. }
380381
| Insn::StringIntern { .. }
381382
| Insn::Throw { .. }
382-
| Insn::ToArray { .. }
383383
| Insn::ToNewArray { .. }
384384
| Insn::Const { .. }
385385
=> {
@@ -768,12 +768,9 @@ fn gen_send_without_block(
768768
self_val: Opnd,
769769
args: Vec<Opnd>,
770770
) -> Option<lir::Opnd> {
771-
// Spill locals onto the stack.
772-
// TODO: Don't spill locals eagerly; lazily reify frames
773-
asm_comment!(asm, "spill locals");
774-
for (idx, &insn_id) in state.locals().enumerate() {
775-
asm.mov(Opnd::mem(64, SP, (-local_idx_to_ep_offset(jit.iseq, idx) - 1) * SIZEOF_VALUE_I32), jit.get_opnd(insn_id)?);
776-
}
771+
772+
spill_locals(jit, asm, state);
773+
777774
// Spill the receiver and the arguments onto the stack.
778775
// They need to be on the interpreter stack to let the interpreter access them.
779776
// TODO: Avoid spilling operands that have been spilled before.
@@ -819,13 +816,8 @@ fn gen_send_without_block_direct(
819816

820817
// Spill the virtual stack and the locals of the caller onto the stack
821818
// TODO: Lazily materialize caller frames on side exits or when needed
822-
asm_comment!(asm, "spill locals and stack");
823-
for (idx, &insn_id) in state.locals().enumerate() {
824-
asm.mov(Opnd::mem(64, SP, (-local_idx_to_ep_offset(jit.iseq, idx) - 1) * SIZEOF_VALUE_I32), jit.get_opnd(insn_id)?);
825-
}
826-
for (idx, &insn_id) in state.stack().enumerate() {
827-
asm.mov(Opnd::mem(64, SP, idx as i32 * SIZEOF_VALUE_I32), jit.get_opnd(insn_id)?);
828-
}
819+
spill_locals(jit, asm, state);
820+
spill_stack(jit, asm, state);
829821

830822
// Set up the new frame
831823
// TODO: Lazily materialize caller frames on side exits or when needed
@@ -1042,6 +1034,38 @@ fn gen_anytostring(asm: &mut Assembler, val: lir::Opnd, str: lir::Opnd, state: &
10421034
Some(asm_ccall!(asm, rb_obj_as_string_result, str, val))
10431035
}
10441036

1037+
fn gen_to_array(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, state: &FrameState) -> Option<lir::Opnd> {
1038+
// Save PC
1039+
gen_save_pc(asm, state);
1040+
1041+
// Save SP, dump locals and stack as rb_Array can call user-defined `to_a` method
1042+
gen_save_sp(asm, state.stack().len());
1043+
spill_locals(jit, asm, state);
1044+
spill_stack(jit, asm, state);
1045+
1046+
// ToArray is used in both concattoarray and splatarray instructions
1047+
// It needs to satisfy the following conditions:
1048+
// - Check if the value is already an array. If it is, return it
1049+
// - Check if the value can be converted into an array. If it is, return it
1050+
// - Return an array with just the value in it
1051+
//
1052+
// rb_Array satisfies the above conditions, so we can use it here
1053+
Some(asm_ccall!(asm, rb_Array, val))
1054+
}
1055+
1056+
fn gen_array_extend(jit: &mut JITState, asm: &mut Assembler, left: lir::Opnd, right: lir::Opnd, state: &FrameState) -> Option<()> {
1057+
// Save PC
1058+
gen_save_pc(asm, state);
1059+
1060+
// Save SP, dump locals and stack as rb_ary_concat calls `rb_to_array_type`, which can call user-defined `to_ary` method
1061+
gen_save_sp(asm, state.stack().len());
1062+
spill_locals(jit, asm, state);
1063+
spill_stack(jit, asm, state);
1064+
1065+
asm_ccall!(asm, rb_ary_concat, left, right);
1066+
Some(())
1067+
}
1068+
10451069
/// Evaluate if a value is truthy
10461070
/// Produces a CBool type (0 or 1)
10471071
/// In Ruby, only nil and false are falsy
@@ -1263,6 +1287,28 @@ fn max_num_params(function: &Function) -> usize {
12631287
}).max().unwrap_or(0)
12641288
}
12651289

1290+
/// Spill locals onto the stack.
1291+
/// TODO: Don't spill locals eagerly; lazily reify frames
1292+
fn spill_locals(jit: &mut JITState, asm: &mut Assembler, state: &FrameState) -> Option<()> {
1293+
asm_comment!(asm, "spill locals");
1294+
for (idx, &insn_id) in state.locals().enumerate() {
1295+
let local_mem = Opnd::mem(64, SP, (-local_idx_to_ep_offset(jit.iseq, idx) - 1) * SIZEOF_VALUE_I32);
1296+
asm.mov(local_mem, jit.get_opnd(insn_id)?);
1297+
}
1298+
Some(())
1299+
}
1300+
1301+
/// Spill stack onto the stack.
1302+
/// TODO: Don't spill stack eagerly; lazily reify frames
1303+
fn spill_stack(jit: &mut JITState, asm: &mut Assembler, state: &FrameState) -> Option<()> {
1304+
asm_comment!(asm, "spill stack");
1305+
for (idx, &insn_id) in state.stack().enumerate() {
1306+
let stack_mem = Opnd::mem(64, SP, idx as i32 * SIZEOF_VALUE_I32);
1307+
asm.mov(stack_mem, jit.get_opnd(insn_id)?);
1308+
}
1309+
Some(())
1310+
}
1311+
12661312
#[cfg(target_arch = "x86_64")]
12671313
macro_rules! c_callable {
12681314
($(#[$outer:meta])*

zjit/src/cruby_bindings.inc.rs

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)