Skip to content

Commit 3a9bf4a

Browse files
authored
ZJIT: Optimize frozen array aref (ruby#13666)
If we have a frozen array `[..., a, ...]` and a compile-time fixnum index `i`, we can do the array load at compile-time.
1 parent 67346a7 commit 3a9bf4a

File tree

5 files changed

+91
-8
lines changed

5 files changed

+91
-8
lines changed

jit.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,3 +421,10 @@ rb_assert_cme_handle(VALUE handle)
421421
RUBY_ASSERT_ALWAYS(!rb_objspace_garbage_object_p(handle));
422422
RUBY_ASSERT_ALWAYS(IMEMO_TYPE_P(handle, imemo_ment));
423423
}
424+
425+
// YJIT and ZJIT need this function to never allocate and never raise
426+
VALUE
427+
rb_yarv_ary_entry_internal(VALUE ary, long offset)
428+
{
429+
return rb_ary_entry_internal(ary, offset);
430+
}

yjit.c

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -533,13 +533,6 @@ rb_str_neq_internal(VALUE str1, VALUE str2)
533533
return rb_str_eql_internal(str1, str2) == Qtrue ? Qfalse : Qtrue;
534534
}
535535

536-
// YJIT needs this function to never allocate and never raise
537-
VALUE
538-
rb_yarv_ary_entry_internal(VALUE ary, long offset)
539-
{
540-
return rb_ary_entry_internal(ary, offset);
541-
}
542-
543536
extern VALUE rb_ary_unshift_m(int argc, VALUE *argv, VALUE ary);
544537

545538
VALUE

yjit/src/cruby_bindings.inc.rs

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

zjit/src/cruby_bindings.inc.rs

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

zjit/src/hir.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ impl<'a> std::fmt::Display for InvariantPrinter<'a> {
206206
BOP_FREEZE => write!(f, "BOP_FREEZE")?,
207207
BOP_UMINUS => write!(f, "BOP_UMINUS")?,
208208
BOP_MAX => write!(f, "BOP_MAX")?,
209+
BOP_AREF => write!(f, "BOP_AREF")?,
209210
_ => write!(f, "{bop}")?,
210211
}
211212
write!(f, ")")
@@ -1317,6 +1318,25 @@ impl Function {
13171318
}
13181319
}
13191320

1321+
fn try_rewrite_aref(&mut self, block: BlockId, orig_insn_id: InsnId, self_val: InsnId, idx_val: InsnId) {
1322+
let self_type = self.type_of(self_val);
1323+
let idx_type = self.type_of(idx_val);
1324+
if self_type.is_subtype(types::ArrayExact) {
1325+
if let Some(array_obj) = self_type.ruby_object() {
1326+
if array_obj.is_frozen() {
1327+
if let Some(idx) = idx_type.fixnum_value() {
1328+
self.push_insn(block, Insn::PatchPoint(Invariant::BOPRedefined { klass: ARRAY_REDEFINED_OP_FLAG, bop: BOP_AREF }));
1329+
let val = unsafe { rb_yarv_ary_entry_internal(array_obj, idx) };
1330+
let const_insn = self.push_insn(block, Insn::Const { val: Const::Value(val) });
1331+
self.make_equal_to(orig_insn_id, const_insn);
1332+
return;
1333+
}
1334+
}
1335+
}
1336+
}
1337+
self.push_insn_id(block, orig_insn_id);
1338+
}
1339+
13201340
/// Rewrite SendWithoutBlock opcodes into SendWithoutBlockDirect opcodes if we know the target
13211341
/// ISEQ statically. This removes run-time method lookups and opens the door for inlining.
13221342
fn optimize_direct_sends(&mut self) {
@@ -1351,6 +1371,8 @@ impl Function {
13511371
self.try_rewrite_freeze(block, insn_id, self_val),
13521372
Insn::SendWithoutBlock { self_val, call_info: CallInfo { method_name }, args, .. } if method_name == "-@" && args.len() == 0 =>
13531373
self.try_rewrite_uminus(block, insn_id, self_val),
1374+
Insn::SendWithoutBlock { self_val, call_info: CallInfo { method_name }, args, .. } if method_name == "[]" && args.len() == 1 =>
1375+
self.try_rewrite_aref(block, insn_id, self_val, args[0]),
13541376
Insn::SendWithoutBlock { mut self_val, call_info, cd, args, state } => {
13551377
let frame_state = self.frame_state(state);
13561378
let (klass, guard_equal_to) = if let Some(klass) = self.type_of(self_val).runtime_exact_ruby_class() {
@@ -6068,4 +6090,64 @@ mod opt_tests {
60686090
SideExit
60696091
"#]]);
60706092
}
6093+
6094+
#[test]
6095+
fn test_eliminate_load_from_frozen_array_in_bounds() {
6096+
eval(r##"
6097+
def test = [4,5,6].freeze[1]
6098+
"##);
6099+
assert_optimized_method_hir("test", expect![[r#"
6100+
fn test:
6101+
bb0(v0:BasicObject):
6102+
PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE)
6103+
PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_AREF)
6104+
v11:Fixnum[5] = Const Value(5)
6105+
Return v11
6106+
"#]]);
6107+
}
6108+
6109+
#[test]
6110+
fn test_eliminate_load_from_frozen_array_negative() {
6111+
eval(r##"
6112+
def test = [4,5,6].freeze[-3]
6113+
"##);
6114+
assert_optimized_method_hir("test", expect![[r#"
6115+
fn test:
6116+
bb0(v0:BasicObject):
6117+
PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE)
6118+
PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_AREF)
6119+
v11:Fixnum[4] = Const Value(4)
6120+
Return v11
6121+
"#]]);
6122+
}
6123+
6124+
#[test]
6125+
fn test_eliminate_load_from_frozen_array_negative_out_of_bounds() {
6126+
eval(r##"
6127+
def test = [4,5,6].freeze[-10]
6128+
"##);
6129+
assert_optimized_method_hir("test", expect![[r#"
6130+
fn test:
6131+
bb0(v0:BasicObject):
6132+
PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE)
6133+
PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_AREF)
6134+
v11:NilClassExact = Const Value(nil)
6135+
Return v11
6136+
"#]]);
6137+
}
6138+
6139+
#[test]
6140+
fn test_eliminate_load_from_frozen_array_out_of_bounds() {
6141+
eval(r##"
6142+
def test = [4,5,6].freeze[10]
6143+
"##);
6144+
assert_optimized_method_hir("test", expect![[r#"
6145+
fn test:
6146+
bb0(v0:BasicObject):
6147+
PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE)
6148+
PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_AREF)
6149+
v11:NilClassExact = Const Value(nil)
6150+
Return v11
6151+
"#]]);
6152+
}
60716153
}

0 commit comments

Comments
 (0)