Skip to content

Commit ecce198

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 75f25e5 commit ecce198

4 files changed

Lines changed: 70 additions & 2 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: 30 additions & 2 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(asm, opnd!(val), &function.frame_state(*state))?,
371+
Insn::ArrayExtend { left, right, state } => return gen_array_extend(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
=> {
@@ -1042,6 +1042,34 @@ fn gen_anytostring(asm: &mut Assembler, val: lir::Opnd, str: lir::Opnd, state: &
10421042
Some(asm_ccall!(asm, rb_obj_as_string_result, str, val))
10431043
}
10441044

1045+
fn gen_to_array(asm: &mut Assembler, val: lir::Opnd, state: &FrameState) -> Option<lir::Opnd> {
1046+
// Save PC
1047+
gen_save_pc(asm, state);
1048+
1049+
// Save SP as rb_Array can call user-defined `to_a` method
1050+
gen_save_sp(asm, state.stack().len());
1051+
1052+
// ToArray is used in both concattoarray and splatarray instructions
1053+
// It needs to satisfy the following conditions:
1054+
// - Check if the value is already an array. If it is, return it
1055+
// - Check if the value can be converted into an array. If it is, return it
1056+
// - Return an array with just the value in it
1057+
//
1058+
// rb_Array satisfies the above conditions, so we can use it here
1059+
Some(asm_ccall!(asm, rb_Array, val))
1060+
}
1061+
1062+
fn gen_array_extend(asm: &mut Assembler, left: lir::Opnd, right: lir::Opnd, state: &FrameState) -> Option<()> {
1063+
// Save PC
1064+
gen_save_pc(asm, state);
1065+
1066+
// Save SP as rb_ary_concat calls `rb_to_array_type`, which can call user-defined `to_ary` method
1067+
gen_save_sp(asm, state.stack().len());
1068+
1069+
asm_ccall!(asm, rb_ary_concat, left, right);
1070+
Some(())
1071+
}
1072+
10451073
/// Evaluate if a value is truthy
10461074
/// Produces a CBool type (0 or 1)
10471075
/// In Ruby, only nil and false are falsy

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)