Skip to content

Commit 2afc8f2

Browse files
committed
ZJIT: Optimize Array#[]= with splice semantics (index, length)
Add inlined implementation for the three-argument form of Array#[]= (arr[index, len] = val) which splices elements into the array. Guards for exact Array type, Fixnum index and length, and that the array is not frozen or shared. Falls back to the standard method for Array subclasses.
1 parent e730ac4 commit 2afc8f2

8 files changed

Lines changed: 188 additions & 0 deletions

File tree

array.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2715,6 +2715,13 @@ rb_jit_fixnum_inc(rb_execution_context_t *ec, VALUE self, VALUE num)
27152715
return LONG2FIX(FIX2LONG(num) + 1);
27162716
}
27172717

2718+
// Splice a value into an array at the given index and length.
2719+
VALUE
2720+
rb_jit_ary_aset_by_rb_ary_splice(VALUE ary, long beg, long len, VALUE val)
2721+
{
2722+
return ary_aset_by_rb_ary_splice(ary, beg, len, val);
2723+
}
2724+
27182725
// Push a value onto an array and return the value.
27192726
VALUE
27202727
rb_jit_ary_push(rb_execution_context_t *ec, VALUE self, VALUE ary, VALUE val)

jit.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -791,6 +791,7 @@ rb_yarv_str_eql_internal(VALUE str1, VALUE str2)
791791
}
792792

793793
void rb_jit_str_concat_codepoint(VALUE str, VALUE codepoint);
794+
VALUE rb_jit_ary_aset_by_rb_ary_splice(VALUE ary, long beg, long len, VALUE val);
794795

795796
attr_index_t
796797
rb_jit_shape_capacity(shape_id_t shape_id)

zjit/bindgen/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ fn main() {
283283
.allowlist_function("rb_jit_get_page_size")
284284
.allowlist_function("rb_jit_array_len")
285285
.allowlist_function("rb_jit_fix_div_fix")
286+
.allowlist_function("rb_jit_ary_aset_by_rb_ary_splice")
286287
.allowlist_function("rb_jit_iseq_builtin_attrs")
287288
.allowlist_function("rb_jit_str_concat_codepoint")
288289
.allowlist_function("rb_zjit_iseq_inspect")

zjit/src/codegen_tests.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2781,6 +2781,72 @@ fn test_array_fixnum_aset_array_subclass() {
27812781
assert_snapshot!(inspect("arr = MyArray.new; test(arr, 0); arr[0]"), @"7");
27822782
}
27832783

2784+
#[test]
2785+
fn test_array_splice_aset() {
2786+
eval("
2787+
def test(arr, idx, len)
2788+
arr[idx, len] = [4, 5, 6]
2789+
end
2790+
test([1,2,3], 0, 2)
2791+
");
2792+
assert_snapshot!(inspect("arr = [1,2,3]; test(arr, 0, 2); arr"), @"[4, 5, 6, 3]");
2793+
}
2794+
2795+
#[test]
2796+
fn test_array_splice_aset_returns_value() {
2797+
eval("
2798+
def test(arr, idx, len, val)
2799+
arr[idx, len] = val
2800+
end
2801+
test([1,2,3], 0, 2, [4,5,6])
2802+
");
2803+
assert_snapshot!(inspect("test([1,2,3], 0, 2, [4,5,6])"), @"[4, 5, 6]");
2804+
}
2805+
2806+
#[test]
2807+
fn test_array_splice_aset_frozen() {
2808+
assert_snapshot!(inspect("
2809+
def test(arr, idx, len, val)
2810+
arr[idx, len] = val
2811+
end
2812+
arr = [1,2,3]
2813+
test(arr, 0, 2, [4,5])
2814+
test(arr, 0, 2, [4,5])
2815+
arr.freeze
2816+
begin
2817+
test(arr, 0, 2, [4,5])
2818+
rescue => e
2819+
e.class
2820+
end
2821+
"), @"FrozenError");
2822+
}
2823+
2824+
#[test]
2825+
fn test_array_splice_aset_shared() {
2826+
assert_snapshot!(inspect("
2827+
def test(arr, idx, len, val)
2828+
arr[idx, len] = val
2829+
end
2830+
arr = (0..50).to_a
2831+
test(arr, 0, 1, [-1])
2832+
shared = arr[10, 20]
2833+
test(shared, 0, 1, [999])
2834+
[arr[10], shared[0], arr[0]]
2835+
"), @"[10, 999, -1]");
2836+
}
2837+
2838+
#[test]
2839+
fn test_array_splice_aset_array_subclass() {
2840+
eval("
2841+
class MySpliceArray < Array; end
2842+
def test(arr, idx, len, val)
2843+
arr[idx, len] = val
2844+
end
2845+
test(MySpliceArray.new([1,2,3]), 0, 2, [4,5])
2846+
");
2847+
assert_snapshot!(inspect("arr = MySpliceArray.new([1,2,3]); test(arr, 0, 2, [4,5]); arr.to_a"), @"[4, 5, 3]");
2848+
}
2849+
27842850
#[test]
27852851
fn test_array_aset_non_fixnum_index() {
27862852
assert_snapshot!(inspect(r#"

zjit/src/cruby.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1416,6 +1416,7 @@ pub(crate) mod ids {
14161416
name: freeze
14171417
name: minusat content: b"-@"
14181418
name: aref content: b"[]"
1419+
name: aset content: b"[]="
14191420
name: len
14201421
name: _as_heap
14211422
name: thread_ptr

zjit/src/cruby_bindings.inc.rs

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

zjit/src/cruby_methods.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,29 @@ fn inline_array_aset(fun: &mut hir::Function, block: hir::BlockId, recv: hir::In
379379
fun.push_insn(block, hir::Insn::WriteBarrier { recv, val });
380380
return Some(val);
381381
}
382+
} else if let &[index, len, val] = args {
383+
if fun.likely_a(recv, types::ArrayExact, state)
384+
&& fun.likely_a(index, types::Fixnum, state)
385+
&& fun.likely_a(len, types::Fixnum, state)
386+
{
387+
let recv = fun.coerce_to(block, recv, types::ArrayExact, state);
388+
let index = fun.coerce_to(block, index, types::Fixnum, state);
389+
let len = fun.coerce_to(block, len, types::Fixnum, state);
390+
fun.guard_not_frozen(block, recv, state);
391+
fun.guard_not_shared(block, recv, state);
392+
393+
let index = fun.push_insn(block, hir::Insn::UnboxFixnum { val: index });
394+
let len = fun.push_insn(block, hir::Insn::UnboxFixnum { val: len });
395+
let _ = fun.push_insn(block, hir::Insn::CCall {
396+
cfunc: rb_jit_ary_aset_by_rb_ary_splice as _,
397+
recv,
398+
args: vec![index, len, val],
399+
return_type: types::BasicObject,
400+
elidable: false,
401+
name: ID!(aset),
402+
});
403+
return Some(val);
404+
}
382405
}
383406
None
384407
}

zjit/src/hir/opt_tests.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8383,6 +8383,89 @@ mod hir_opt_tests {
83838383
");
83848384
}
83858385

8386+
#[test]
8387+
fn test_optimize_array_splice_aset_profiled() {
8388+
eval("
8389+
def test(arr, index, len, val)
8390+
arr[index, len] = val
8391+
end
8392+
test([1,2,3], 0, 2, [4,5,6])
8393+
");
8394+
assert_snapshot!(hir_string("test"), @"
8395+
fn test@<compiled>:3:
8396+
bb1():
8397+
EntryPoint interpreter
8398+
v1:BasicObject = LoadSelf
8399+
v2:BasicObject = GetLocal :arr, l0, SP@7
8400+
v3:BasicObject = GetLocal :index, l0, SP@6
8401+
v4:BasicObject = GetLocal :len, l0, SP@5
8402+
v5:BasicObject = GetLocal :val, l0, SP@4
8403+
Jump bb3(v1, v2, v3, v4, v5)
8404+
bb2():
8405+
EntryPoint JIT(0)
8406+
v8:BasicObject = LoadArg :self@0
8407+
v9:BasicObject = LoadArg :arr@1
8408+
v10:BasicObject = LoadArg :index@2
8409+
v11:BasicObject = LoadArg :len@3
8410+
v12:BasicObject = LoadArg :val@4
8411+
Jump bb3(v8, v9, v10, v11, v12)
8412+
bb3(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:BasicObject, v18:BasicObject):
8413+
PatchPoint NoSingletonClass(Array@0x1000)
8414+
PatchPoint MethodRedefined(Array@0x1000, []=@0x1008, cme:0x1010)
8415+
v39:ArrayExact = GuardType v15, ArrayExact
8416+
v40:Fixnum = GuardType v16, Fixnum
8417+
v41:Fixnum = GuardType v17, Fixnum
8418+
v42:CUInt64 = LoadField v39, :_rbasic_flags@0x1038
8419+
v43:CUInt64 = GuardNoBitsSet v42, RUBY_FL_FREEZE=CUInt64(2048)
8420+
v44:CUInt64 = LoadField v39, :_rbasic_flags@0x1038
8421+
v45:CUInt64 = GuardNoBitsSet v44, RUBY_ELTS_SHARED=CUInt64(4096)
8422+
v46:CInt64 = UnboxFixnum v40
8423+
v47:CInt64 = UnboxFixnum v41
8424+
v48:BasicObject = CCall v39, :[]=@0x1040, v46, v47, v18
8425+
IncrCounter inline_cfunc_optimized_send_count
8426+
CheckInterrupts
8427+
Return v18
8428+
");
8429+
}
8430+
8431+
#[test]
8432+
fn test_optimize_array_splice_aset_array_subclass() {
8433+
eval("
8434+
class MySpliceArray2 < Array; end
8435+
def test(arr, index, len, val)
8436+
arr[index, len] = val
8437+
end
8438+
a = MySpliceArray2.new([1,2,3])
8439+
test(a, 0, 2, [4,5])
8440+
");
8441+
assert_snapshot!(hir_string("test"), @"
8442+
fn test@<compiled>:4:
8443+
bb1():
8444+
EntryPoint interpreter
8445+
v1:BasicObject = LoadSelf
8446+
v2:BasicObject = GetLocal :arr, l0, SP@7
8447+
v3:BasicObject = GetLocal :index, l0, SP@6
8448+
v4:BasicObject = GetLocal :len, l0, SP@5
8449+
v5:BasicObject = GetLocal :val, l0, SP@4
8450+
Jump bb3(v1, v2, v3, v4, v5)
8451+
bb2():
8452+
EntryPoint JIT(0)
8453+
v8:BasicObject = LoadArg :self@0
8454+
v9:BasicObject = LoadArg :arr@1
8455+
v10:BasicObject = LoadArg :index@2
8456+
v11:BasicObject = LoadArg :len@3
8457+
v12:BasicObject = LoadArg :val@4
8458+
Jump bb3(v8, v9, v10, v11, v12)
8459+
bb3(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:BasicObject, v18:BasicObject):
8460+
PatchPoint NoSingletonClass(MySpliceArray2@0x1000)
8461+
PatchPoint MethodRedefined(MySpliceArray2@0x1000, []=@0x1008, cme:0x1010)
8462+
v39:ArraySubclass[class_exact:MySpliceArray2] = GuardType v15, ArraySubclass[class_exact:MySpliceArray2]
8463+
v40:BasicObject = CCallVariadic v39, :Array#[]=@0x1038, v16, v17, v18
8464+
CheckInterrupts
8465+
Return v18
8466+
");
8467+
}
8468+
83868469
#[test]
83878470
fn test_optimize_array_ltlt() {
83888471
eval("

0 commit comments

Comments
 (0)