From f717edca05fa42e122b7fb8148aa6a728b3aa582 Mon Sep 17 00:00:00 2001
From: Nozomi Hijikata <121233810+nozomemein@users.noreply.github.com>
Date: Wed, 3 Jun 2026 22:42:20 +0900
Subject: [PATCH] ZJIT: Implement Polymorphic DefinedIvar (#16981)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* ZJIT: Implement Polymorphic Definedivar
Closes: https://github.com/Shopify/ruby/issues/980
Build polymorphic shape/type branches for definedivar in HIR. For each profiled T_OBJECT shape, specialize defined?(`@ivar`) to a constant pushval or nil based on the compile-time ivar index check.
DefinedIvar already profiles self on monomorphic shape guard failures for recompilation, so the recompiled HIR can use the collected polymorphic shapes while keeping a generic DefinedIvar fallback for misses and unsupported shapes.
## Benchmark
### lobsters
Summary:
```diff
- dynamic_definedivar_count: 1,294,854 ( 0.7%)
+ dynamic_definedivar_count: 503,058 ( 0.3%)
ratio_in_zjit: 88.4%
```
```
zjit-master: ruby 4.1.0dev (2026-05-23T00:08:49Z master 5855d61ee4) +ZJIT stats +PRISM [arm64-darwin25]
zjit-polymorphic-definedivar: ruby 4.1.0dev (2026-05-23T07:11:22Z zjit-polymorphic-d.. f75a82e7f1) +ZJIT stats +PRISM [arm64-darwin25]
last_commit=ZJIT: Implement Polymorphic Definedivar
-------- ---------------- --------------------------------- ------------------------------------ ----------------------------------------
bench zjit-master (ms) zjit-polymorphic-definedivar (ms) zjit-polymorphic-definedivar 1st itr zjit-master/zjit-polymorphic-definedivar
lobsters 529.9 ± 3.6% 504.2 ± 7.7% 1.148 1.051
-------- ---------------- --------------------------------- ------------------------------------ ----------------------------------------
```
Full stats details below:
before patch
```
Top-2 not optimized method types for send (100.0% of total 65,120):
null: 44,877 (68.9%)
optimized: 20,243 (31.1%)
Top-3 not optimized method types for send_without_block (100.0% of total 716,678):
optimized_send: 711,905 (99.3%)
optimized_block_call: 4,299 ( 0.6%)
zsuper: 474 ( 0.1%)
Top-1 not optimized method types for super (100.0% of total 3,678):
attrset: 3,678 (100.0%)
Top-1 instructions with uncategorized fallback reason (100.0% of total 65,868):
opt_send_without_block: 65,868 (100.0%)
Top-20 send fallback reasons (99.5% of total 22,386,632):
invokeblock_not_specialized: 5,946,612 (26.6%)
one_or_more_complex_arg_pass: 4,376,419 (19.5%)
send_without_block_polymorphic: 3,748,855 (16.7%)
send_without_block_no_profiles: 2,830,116 (12.6%)
send_without_block_megamorphic: 1,108,467 ( 5.0%)
sendforward_not_specialized: 971,957 ( 4.3%)
send_polymorphic: 806,862 ( 3.6%)
send_without_block_not_optimized_method_type_optimized: 716,204 ( 3.2%)
super_polymorphic: 337,181 ( 1.5%)
too_many_args_for_lir: 333,633 ( 1.5%)
send_not_optimized_need_permission: 238,842 ( 1.1%)
send_without_block_not_optimized_need_permission: 180,946 ( 0.8%)
send_no_profiles: 177,008 ( 0.8%)
argc_param_mismatch: 124,061 ( 0.6%)
super_complex_args_pass: 89,280 ( 0.4%)
invokesuperforward_not_specialized: 80,729 ( 0.4%)
uncategorized: 65,868 ( 0.3%)
send_not_optimized_method_type: 65,120 ( 0.3%)
super_from_block: 43,185 ( 0.2%)
obj_to_string_not_string: 42,731 ( 0.2%)
Top-4 setivar fallback reasons (100.0% of total 4,182,333):
not_monomorphic: 3,969,254 (94.9%)
not_t_object: 119,505 ( 2.9%)
complex: 93,131 ( 2.2%)
new_shape_needs_extension: 443 ( 0.0%)
Top-2 getivar fallback reasons (100.0% of total 11,716,846):
not_monomorphic: 11,537,525 (98.5%)
complex: 179,321 ( 1.5%)
Top-3 definedivar fallback reasons (100.0% of total 1,294,854):
not_monomorphic: 1,289,423 (99.6%)
complex: 5,122 ( 0.4%)
not_t_object: 309 ( 0.0%)
Top-5 invokeblock handler (100.0% of total 6,968,323):
monomorphic_ifunc: 4,143,418 (59.5%)
monomorphic_other: 1,444,525 (20.7%)
monomorphic_iseq: 865,749 (12.4%)
polymorphic: 508,677 ( 7.3%)
megamorphic: 5,954 ( 0.1%)
Top-6 getblockparamproxy handler (100.0% of total 3,321,873):
polymorphic: 2,370,458 (71.4%)
nil: 492,185 (14.8%)
iseq: 242,627 ( 7.3%)
no_profiles: 168,826 ( 5.1%)
proc: 40,245 ( 1.2%)
megamorphic: 7,532 ( 0.2%)
Top-7 popular complex argument-parameter features not optimized (100.0% of total 4,677,994):
caller_blockarg: 3,092,361 (66.1%)
param_forwardable: 697,044 (14.9%)
param_rest: 409,311 ( 8.7%)
caller_kwarg: 193,462 ( 4.1%)
param_kwrest: 143,181 ( 3.1%)
caller_kw_splat: 85,992 ( 1.8%)
caller_splat: 56,643 ( 1.2%)
Top-3 compile error reasons (100.0% of total 196,397):
exception_handler: 192,000 (97.8%)
native_stack_too_large: 4,342 ( 2.2%)
validation_mismatched_block_arity: 55 ( 0.0%)
Top-2 unhandled HIR insns (100.0% of total 229,881):
throw: 194,104 (84.4%)
invokebuiltin: 35,777 (15.6%)
Top-19 side exit reasons (100.0% of total 8,637,525):
guard_type_failure: 7,692,359 (89.1%)
unhandled_hir_insn: 229,881 ( 2.7%)
compile_error: 196,397 ( 2.3%)
patchpoint_method_redefined: 118,680 ( 1.4%)
block_param_proxy_fallback_miss: 112,551 ( 1.3%)
no_profile_send: 93,334 ( 1.1%)
block_param_proxy_not_nil: 75,195 ( 0.9%)
patchpoint_stable_constant_names: 55,819 ( 0.6%)
patchpoint_no_singleton_class: 19,352 ( 0.2%)
unhandled_block_arg: 14,541 ( 0.2%)
block_param_proxy_not_iseq_or_ifunc: 13,401 ( 0.2%)
fixnum_lshift_overflow: 10,165 ( 0.1%)
guard_less_failure: 3,120 ( 0.0%)
guard_shape_failure: 1,302 ( 0.0%)
guard_greater_eq_failure: 941 ( 0.0%)
guard_super_method_entry: 407 ( 0.0%)
interrupt: 49 ( 0.0%)
obj_to_string_fallback: 30 ( 0.0%)
guard_not_shared_failure: 1 ( 0.0%)
send_count: 182,469,185
dynamic_send_count: 22,386,632 (12.3%)
optimized_send_count: 160,082,553 (87.7%)
dynamic_setivar_count: 4,182,333 ( 2.3%)
dynamic_getivar_count: 11,716,846 ( 6.4%)
dynamic_definedivar_count: 1,294,854 ( 0.7%)
iseq_optimized_send_count: 54,154,353 (29.7%)
inline_cfunc_optimized_send_count: 74,812,159 (41.0%)
inline_iseq_optimized_send_count: 6,346,705 ( 3.5%)
non_variadic_cfunc_optimized_send_count: 13,789,249 ( 7.6%)
variadic_cfunc_optimized_send_count: 10,980,087 ( 6.0%)
compiled_iseq_count: 6,166
compiled_side_exit_count: 80,485
failed_iseq_count: 3
compile_time: 2,462ms
compile_side_exit_time: 113ms
compile_side_exit_time_ratio: 4.6%
compile_hir_time: 798ms
compile_hir_build_time: 238ms
compile_hir_strength_reduce_time: 322ms
compile_hir_canonicalize_time: 49ms
compile_hir_fold_constants_time: 37ms
compile_hir_clean_cfg_time: 26ms
compile_hir_eliminate_dead_code_time: 30ms
compile_lir_time: 1,525ms
profile_time: 39ms
gc_time: 33ms
invalidation_time: 14ms
vm_write_jit_frame_count: 137,433,269
vm_write_sp_count: 137,433,269
vm_write_locals_count: 131,272,119
vm_write_stack_count: 131,272,119
vm_write_to_parent_iseq_local_count: 688,492
guard_type_count: 145,711,992
guard_type_exit_ratio: 5.3%
guard_shape_count: 36,890,099
guard_shape_exit_ratio: 0.0%
load_field_count: 210,155,590
store_field_count: 15,758,742
side_exit_size: 13,563,708
code_region_bytes: 33,505,280
side_exit_size_ratio: 40.5%
zjit_alloc_bytes: 22,783,024
total_mem_bytes: 56,288,304
side_exit_count: 8,637,525
total_insn_count: 995,180,861
vm_insn_count: 115,515,525
zjit_insn_count: 879,665,336
ratio_in_zjit: 88.4%
```
after patch
```
Top-2 not optimized method types for send (100.0% of total 65,119):
null: 44,877 (68.9%)
optimized: 20,242 (31.1%)
Top-3 not optimized method types for send_without_block (100.0% of total 716,636):
optimized_send: 711,869 (99.3%)
optimized_block_call: 4,293 ( 0.6%)
zsuper: 474 ( 0.1%)
Top-1 not optimized method types for super (100.0% of total 3,676):
attrset: 3,676 (100.0%)
Top-1 instructions with uncategorized fallback reason (100.0% of total 65,866):
opt_send_without_block: 65,866 (100.0%)
Top-20 send fallback reasons (99.5% of total 22,409,237):
invokeblock_not_specialized: 5,946,405 (26.5%)
one_or_more_complex_arg_pass: 4,376,457 (19.5%)
send_without_block_polymorphic: 3,775,975 (16.9%)
send_without_block_no_profiles: 2,830,029 (12.6%)
send_without_block_megamorphic: 1,104,527 ( 4.9%)
sendforward_not_specialized: 971,924 ( 4.3%)
send_polymorphic: 806,852 ( 3.6%)
send_without_block_not_optimized_method_type_optimized: 716,162 ( 3.2%)
super_polymorphic: 337,178 ( 1.5%)
too_many_args_for_lir: 333,625 ( 1.5%)
send_not_optimized_need_permission: 238,842 ( 1.1%)
send_without_block_not_optimized_need_permission: 180,949 ( 0.8%)
send_no_profiles: 176,799 ( 0.8%)
argc_param_mismatch: 124,064 ( 0.6%)
super_complex_args_pass: 89,280 ( 0.4%)
invokesuperforward_not_specialized: 80,723 ( 0.4%)
uncategorized: 65,866 ( 0.3%)
send_not_optimized_method_type: 65,119 ( 0.3%)
super_from_block: 43,182 ( 0.2%)
obj_to_string_not_string: 42,725 ( 0.2%)
Top-4 setivar fallback reasons (100.0% of total 4,182,216):
not_monomorphic: 3,969,159 (94.9%)
not_t_object: 119,484 ( 2.9%)
complex: 93,131 ( 2.2%)
new_shape_needs_extension: 442 ( 0.0%)
Top-2 getivar fallback reasons (100.0% of total 11,716,471):
not_monomorphic: 11,537,150 (98.5%)
complex: 179,321 ( 1.5%)
Top-3 definedivar fallback reasons (100.0% of total 503,058):
not_monomorphic: 498,173 (99.0%)
complex: 4,576 ( 0.9%)
not_t_object: 309 ( 0.1%)
Top-5 invokeblock handler (100.0% of total 6,968,099):
monomorphic_ifunc: 4,143,324 (59.5%)
monomorphic_other: 1,444,457 (20.7%)
monomorphic_iseq: 865,705 (12.4%)
polymorphic: 508,659 ( 7.3%)
megamorphic: 5,954 ( 0.1%)
Top-6 getblockparamproxy handler (100.0% of total 3,321,803):
polymorphic: 2,370,425 (71.4%)
nil: 492,172 (14.8%)
iseq: 242,810 ( 7.3%)
no_profiles: 168,625 ( 5.1%)
proc: 40,239 ( 1.2%)
megamorphic: 7,532 ( 0.2%)
Top-7 popular complex argument-parameter features not optimized (100.0% of total 4,678,011):
caller_blockarg: 3,092,480 (66.1%)
param_forwardable: 697,011 (14.9%)
param_rest: 409,285 ( 8.7%)
caller_kwarg: 193,450 ( 4.1%)
param_kwrest: 143,179 ( 3.1%)
caller_kw_splat: 85,971 ( 1.8%)
caller_splat: 56,635 ( 1.2%)
Top-3 compile error reasons (100.0% of total 196,386):
exception_handler: 191,989 (97.8%)
native_stack_too_large: 4,342 ( 2.2%)
validation_mismatched_block_arity: 55 ( 0.0%)
Top-2 unhandled HIR insns (100.0% of total 229,876):
throw: 194,101 (84.4%)
invokebuiltin: 35,775 (15.6%)
Top-19 side exit reasons (100.0% of total 8,638,646):
guard_type_failure: 7,693,566 (89.1%)
unhandled_hir_insn: 229,876 ( 2.7%)
compile_error: 196,386 ( 2.3%)
patchpoint_method_redefined: 118,676 ( 1.4%)
block_param_proxy_fallback_miss: 112,548 ( 1.3%)
no_profile_send: 93,330 ( 1.1%)
block_param_proxy_not_nil: 75,195 ( 0.9%)
patchpoint_stable_constant_names: 55,792 ( 0.6%)
patchpoint_no_singleton_class: 19,326 ( 0.2%)
unhandled_block_arg: 14,540 ( 0.2%)
block_param_proxy_not_iseq_or_ifunc: 13,401 ( 0.2%)
fixnum_lshift_overflow: 10,165 ( 0.1%)
guard_less_failure: 3,119 ( 0.0%)
guard_shape_failure: 1,302 ( 0.0%)
guard_greater_eq_failure: 941 ( 0.0%)
guard_super_method_entry: 407 ( 0.0%)
interrupt: 45 ( 0.0%)
obj_to_string_fallback: 30 ( 0.0%)
guard_not_shared_failure: 1 ( 0.0%)
send_count: 182,477,537
dynamic_send_count: 22,409,237 (12.3%)
optimized_send_count: 160,068,300 (87.7%)
dynamic_setivar_count: 4,182,216 ( 2.3%)
dynamic_getivar_count: 11,716,471 ( 6.4%)
dynamic_definedivar_count: 503,058 ( 0.3%)
iseq_optimized_send_count: 54,138,292 (29.7%)
inline_cfunc_optimized_send_count: 74,815,196 (41.0%)
inline_iseq_optimized_send_count: 6,346,484 ( 3.5%)
non_variadic_cfunc_optimized_send_count: 13,788,597 ( 7.6%)
variadic_cfunc_optimized_send_count: 10,979,731 ( 6.0%)
compiled_iseq_count: 6,167
compiled_side_exit_count: 80,471
failed_iseq_count: 3
compile_time: 2,711ms
compile_side_exit_time: 125ms
compile_side_exit_time_ratio: 4.6%
compile_hir_time: 870ms
compile_hir_build_time: 266ms
compile_hir_strength_reduce_time: 347ms
compile_hir_canonicalize_time: 52ms
compile_hir_fold_constants_time: 40ms
compile_hir_clean_cfg_time: 27ms
compile_hir_eliminate_dead_code_time: 32ms
compile_lir_time: 1,680ms
profile_time: 48ms
gc_time: 32ms
invalidation_time: 15ms
vm_write_jit_frame_count: 137,425,789
vm_write_sp_count: 137,425,789
vm_write_locals_count: 131,264,963
vm_write_stack_count: 131,264,963
vm_write_to_parent_iseq_local_count: 688,457
guard_type_count: 146,412,455
guard_type_exit_ratio: 5.3%
guard_shape_count: 36,937,934
guard_shape_exit_ratio: 0.0%
load_field_count: 211,394,883
store_field_count: 15,778,119
side_exit_size: 13,563,624
code_region_bytes: 33,505,280
side_exit_size_ratio: 40.5%
zjit_alloc_bytes: 22,870,223
total_mem_bytes: 56,375,503
side_exit_count: 8,638,646
total_insn_count: 995,144,390
vm_insn_count: 115,472,694
zjit_insn_count: 879,671,696
ratio_in_zjit: 88.4%
```
* ZJIT: Add TODO for deduplicating DefinedIvar specialization
* ZJIT: Add more DefinedIvar HIR tests
Cover unsupported receiver profiles and a mixed polymorphic profile with a generic DefinedIvar fallback.
* ZJIT: Add more DefinedIvar polymorphic HIR test
Cover polymorphic definedivar profiles with:
- complex path
- non-T_OBJECT path
---
zjit/src/hir.rs | 65 +++++++++-
zjit/src/hir/opt_tests.rs | 267 +++++++++++++++++++++++++++++++++++++-
2 files changed, 322 insertions(+), 10 deletions(-)
diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs
index f5363aebe261bb..02e3759cd69d9e 100644
--- a/zjit/src/hir.rs
+++ b/zjit/src/hir.rs
@@ -4497,8 +4497,6 @@ impl Function {
self.push_insn_id(block, insn_id); continue;
}
if self.policy.no_side_exits {
- // TODO: Support polymorphic DefinedIvar shape-specialized paths.
- // https://github.com/Shopify/ruby/issues/980
// On the final version, keep the DefinedIvar fallback instead of another shape guard.
self.push_insn_id(block, insn_id); continue;
}
@@ -7155,7 +7153,68 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result {
// (ID id, IVC ic, VALUE pushval)
let id = ID(get_arg(pc, 0).as_u64());
let pushval = get_arg(pc, 2);
- state.stack_push(fun.push_insn(block, Insn::DefinedIvar { self_val: self_param, id, pushval, state: exit_id }));
+ if let Some(summary) = fun.polymorphic_summary(&profiles, self_param, exit_state.insn_idx) {
+ self_param = fun.push_insn(block, Insn::GuardType { val: self_param, guard_type: types::HeapBasicObject, state: exit_id });
+ let rbasic_flags = fun.load_rbasic_flags(block, self_param);
+ let join_block = insn_idx_to_block.get(&insn_idx).copied().unwrap_or_else(|| fun.new_block(insn_idx));
+ let join_param = fun.push_insn(join_block, Insn::Param);
+ // Dedup by expected shape and type so objects with different classes
+ // but the same shape can share code.
+ let mut seen_shape_and_flags = Vec::with_capacity(summary.buckets().len());
+ for &profiled_type in summary.buckets() {
+ // End of the buckets
+ if profiled_type.is_empty() { break; }
+ // Runtime immediates cannot pass the HeapBasicObject guard, so don't
+ // generate unreachable shape branches for profiled immediate buckets.
+ if profiled_type.flags().is_immediate() { continue; }
+ // Class/module/T_DATA ivars use different storage rules.
+ // Let the fallthrough DefinedIvar handle these.
+ if !profiled_type.flags().is_t_object() { continue; }
+ let expected_shape = profiled_type.shape();
+ let (expected_rbasic_flags, rbasic_flags_mask) = profiled_type.rbasic_flags_and_mask();
+ assert!(expected_shape.is_valid());
+ // Too-complex shapes use hash tables for ivars;
+ // rb_shape_get_iv_index doesn't work for them.
+ // Let the fallthrough DefinedIvar handle these.
+ if expected_shape.is_complex() { continue; }
+ if seen_shape_and_flags.contains(&expected_rbasic_flags) { continue; }
+ seen_shape_and_flags.push(expected_rbasic_flags);
+ let rbasic_flags_mask = fun.push_insn(block, Insn::Const { val: Const::CUInt64(rbasic_flags_mask) });
+ // The expected shape can change over run, so we put it
+ // as a pointer to keep it stable in snapshot tests.
+ let expected_rbasic_flags = fun.push_insn(block, Insn::Const { val: Const::CPtr(ptr::without_provenance(expected_rbasic_flags.to_usize())) });
+ let expected_rbasic_flags = fun.push_insn(block, Insn::RefineType { val: expected_rbasic_flags, new_type: types::CUInt64 });
+ let masked = fun.push_insn(block, Insn::IntAnd { left: rbasic_flags, right: rbasic_flags_mask});
+ let has_shape_and_type = fun.push_insn(block, Insn::IsBitEqual { left: masked, right: expected_rbasic_flags });
+ let iftrue_block = fun.new_block(insn_idx);
+ let target = BranchEdge { target: iftrue_block, args: vec![] };
+ let fall_through = fun.new_block(insn_idx);
+
+ fun.push_insn(block, Insn::CondBranch { val: has_shape_and_type,
+ if_true: target,
+ if_false: BranchEdge { target: fall_through, args: vec![] }
+ });
+
+ block = fall_through;
+ let mut ivar_index: attr_index_t = 0;
+ let result = if unsafe { rb_shape_get_iv_index(expected_shape.0, id, &mut ivar_index) } {
+ fun.push_insn(iftrue_block, Insn::Const { val: Const::Value(pushval) })
+ } else {
+ fun.push_insn(iftrue_block, Insn::Const { val: Const::Value(Qnil) })
+ };
+ fun.push_insn(iftrue_block, Insn::Jump(BranchEdge { target: join_block, args: vec![result] }));
+ }
+ // In the fallthrough case, do a generic interpreter definedivar and then join.
+ let result = fun.push_insn(block, Insn::DefinedIvar { self_val: self_param, id, pushval, state: exit_id });
+ fun.push_insn(block, Insn::Jump(BranchEdge { target: join_block, args: vec![result] }));
+ state.stack_push(join_param);
+ block = join_block;
+ } else {
+ // TODO: Handle monomorphic definedivar specialization here too, including the
+ // no_side_exits policy, so optimize_getivar doesn't need a separate DefinedIvar
+ // path. Unlike GetIvar, DefinedIvar isn't emitted by later lowering passes.
+ state.stack_push(fun.push_insn(block, Insn::DefinedIvar { self_val: self_param, id, pushval, state: exit_id }));
+ }
}
YARVINSN_checkkeyword => {
// When a keyword is unspecified past index 32, a hash will be used instead.
diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs
index 3212390ecf948f..74b951d2e59410 100644
--- a/zjit/src/hir/opt_tests.rs
+++ b/zjit/src/hir/opt_tests.rs
@@ -5638,7 +5638,40 @@ mod hir_opt_tests {
}
#[test]
- fn test_dont_specialize_definedivar_with_t_data() {
+ fn test_dont_specialize_definedivar_with_immediate() {
+ eval("
+ module M
+ def test = defined?(@a)
+ end
+
+ class Integer
+ include M
+ end
+
+ 1.test
+ 2.test
+ TEST = M.instance_method(:test)
+ ");
+ assert_snapshot!(hir_string_proc("TEST"), @"
+ fn test@:3:
+ bb1():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ Jump bb3(v1)
+ bb2():
+ EntryPoint JIT(0)
+ v4:BasicObject = LoadArg :self@0
+ Jump bb3(v4)
+ bb3(v6:BasicObject):
+ v10:StringExact|NilClass = DefinedIvar v6, :@a
+ CheckInterrupts
+ Return v10
+ ");
+ }
+
+ #[test]
+ fn test_dont_specialize_definedivar_with_t_struct() {
+ // Range is T_STRUCT (not T_OBJECT): falls back to DefinedIvar.
eval("
class C < Range
def test = defined?(@a)
@@ -5666,7 +5699,7 @@ mod hir_opt_tests {
}
#[test]
- fn test_dont_specialize_polymorphic_definedivar() {
+ fn test_optimize_definedivar_polymorphic() {
set_call_threshold(3);
eval("
class C
@@ -5691,9 +5724,206 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- v10:StringExact|NilClass = DefinedIvar v6, :@a
+ v10:HeapBasicObject = GuardType v6, HeapBasicObject
+ v11:CUInt64 = LoadField v10, :RBASIC_FLAGS@0x1000
+ v13:CUInt64[0xffffffff0000001f] = Const CUInt64(0xffffffff0000001f)
+ v14:CPtr[CPtr(0x1001)] = Const CPtr(0x1001)
+ v15 = RefineType v14, CUInt64
+ v16:CInt64 = IntAnd v11, v13
+ v17:CBool = IsBitEqual v16, v15
+ CondBranch v17, bb5(), bb6()
+ bb5():
+ v19:NilClass = Const Value(nil)
+ Jump bb4(v19)
+ bb6():
+ v21:CUInt64[0xffffffff0000001f] = Const CUInt64(0xffffffff0000001f)
+ v22:CPtr[CPtr(0x1002)] = Const CPtr(0x1002)
+ v23 = RefineType v22, CUInt64
+ v24:CInt64 = IntAnd v11, v21
+ v25:CBool = IsBitEqual v24, v23
+ CondBranch v25, bb7(), bb8()
+ bb7():
+ v27:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
+ Jump bb4(v27)
+ bb8():
+ v29:StringExact|NilClass = DefinedIvar v10, :@a
+ Jump bb4(v29)
+ bb4(v12:StringExact|NilClass):
CheckInterrupts
- Return v10
+ Return v12
+ ");
+ }
+
+ #[test]
+ fn test_optimize_definedivar_polymorphic_with_immediate() {
+ set_call_threshold(3);
+ eval(r#"
+ module M
+ def test = defined?(@a)
+ end
+
+ class C
+ include M
+ end
+
+ class Integer
+ include M
+ end
+
+ obj = C.new
+ obj.instance_variable_set(:@a, 1)
+
+ obj.test
+ 1.test
+ TEST = M.instance_method(:test)
+ "#);
+ assert_snapshot!(hir_string_proc("TEST"), @"
+ fn test@:3:
+ bb1():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ Jump bb3(v1)
+ bb2():
+ EntryPoint JIT(0)
+ v4:BasicObject = LoadArg :self@0
+ Jump bb3(v4)
+ bb3(v6:BasicObject):
+ v10:HeapBasicObject = GuardType v6, HeapBasicObject
+ v11:CUInt64 = LoadField v10, :RBASIC_FLAGS@0x1000
+ v13:CUInt64[0xffffffff0000001f] = Const CUInt64(0xffffffff0000001f)
+ v14:CPtr[CPtr(0x1001)] = Const CPtr(0x1001)
+ v15 = RefineType v14, CUInt64
+ v16:CInt64 = IntAnd v11, v13
+ v17:CBool = IsBitEqual v16, v15
+ CondBranch v17, bb5(), bb6()
+ bb5():
+ v19:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
+ Jump bb4(v19)
+ bb6():
+ v21:StringExact|NilClass = DefinedIvar v10, :@a
+ Jump bb4(v21)
+ bb4(v12:StringExact|NilClass):
+ CheckInterrupts
+ Return v12
+ ");
+ }
+
+ #[test]
+ fn test_optimize_definedivar_polymorphic_with_t_struct() {
+ set_call_threshold(3);
+ eval(r#"
+ module M
+ def test = defined?(@a)
+ end
+
+ class C
+ include M
+ end
+
+ class D < Range
+ include M
+ end
+
+ obj = C.new
+ obj.instance_variable_set(:@a, 1)
+
+ range = D.new 0, 1
+ range.instance_variable_set(:@a, 1)
+
+ obj.test
+ range.test
+ TEST = M.instance_method(:test)
+ "#);
+ assert_snapshot!(hir_string_proc("TEST"), @"
+ fn test@:3:
+ bb1():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ Jump bb3(v1)
+ bb2():
+ EntryPoint JIT(0)
+ v4:BasicObject = LoadArg :self@0
+ Jump bb3(v4)
+ bb3(v6:BasicObject):
+ v10:HeapBasicObject = GuardType v6, HeapBasicObject
+ v11:CUInt64 = LoadField v10, :RBASIC_FLAGS@0x1000
+ v13:CUInt64[0xffffffff0000001f] = Const CUInt64(0xffffffff0000001f)
+ v14:CPtr[CPtr(0x1001)] = Const CPtr(0x1001)
+ v15 = RefineType v14, CUInt64
+ v16:CInt64 = IntAnd v11, v13
+ v17:CBool = IsBitEqual v16, v15
+ CondBranch v17, bb5(), bb6()
+ bb5():
+ v19:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
+ Jump bb4(v19)
+ bb6():
+ v21:StringExact|NilClass = DefinedIvar v10, :@a
+ Jump bb4(v21)
+ bb4(v12:StringExact|NilClass):
+ CheckInterrupts
+ Return v12
+ ");
+ }
+
+ #[test]
+ fn test_optimize_definedivar_polymorphic_with_complex_shape() {
+ set_call_threshold(3);
+ eval(r#"
+ module M
+ def test = defined?(@a)
+ end
+
+ class C
+ include M
+ end
+
+ class D
+ include M
+ end
+
+ obj = C.new
+ obj.instance_variable_set(:@a, 1)
+
+ complex = D.new
+ (0..1000).each do |i|
+ complex.instance_variable_set(:"@v#{i}", i)
+ end
+ (0..1000).each do |i|
+ complex.remove_instance_variable(:"@v#{i}")
+ end
+
+ obj.test
+ complex.test
+ TEST = M.instance_method(:test)
+ "#);
+ assert_snapshot!(hir_string_proc("TEST"), @"
+ fn test@:3:
+ bb1():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ Jump bb3(v1)
+ bb2():
+ EntryPoint JIT(0)
+ v4:BasicObject = LoadArg :self@0
+ Jump bb3(v4)
+ bb3(v6:BasicObject):
+ v10:HeapBasicObject = GuardType v6, HeapBasicObject
+ v11:CUInt64 = LoadField v10, :RBASIC_FLAGS@0x1000
+ v13:CUInt64[0xffffffff0000001f] = Const CUInt64(0xffffffff0000001f)
+ v14:CPtr[CPtr(0x1001)] = Const CPtr(0x1001)
+ v15 = RefineType v14, CUInt64
+ v16:CInt64 = IntAnd v11, v13
+ v17:CBool = IsBitEqual v16, v15
+ CondBranch v17, bb5(), bb6()
+ bb5():
+ v19:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
+ Jump bb4(v19)
+ bb6():
+ v21:StringExact|NilClass = DefinedIvar v10, :@a
+ Jump bb4(v21)
+ bb4(v12:StringExact|NilClass):
+ CheckInterrupts
+ Return v12
");
}
@@ -8010,7 +8240,7 @@ mod hir_opt_tests {
fn test_definedivar_shape_guard_recompile() {
// Call with one shape to compile, then call with a different shape to
// trigger shape guard exits and recompilation. On the recompiled version,
- // DefinedIvar stays as a C call fallback to avoid more shape guard exits.
+ // DefinedIvar uses polymorphic fast paths plus a C call fallback.
eval("
class C
def initialize(extra = false)
@@ -8038,9 +8268,32 @@ mod hir_opt_tests {
v4:HeapBasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:HeapBasicObject):
- v10:StringExact|NilClass = DefinedIvar v6, :@foo
+ v11:CUInt64 = LoadField v6, :RBASIC_FLAGS@0x1000
+ v13:CUInt64[0xffffffff0000001f] = Const CUInt64(0xffffffff0000001f)
+ v14:CPtr[CPtr(0x1001)] = Const CPtr(0x1001)
+ v15 = RefineType v14, CUInt64
+ v16:CInt64 = IntAnd v11, v13
+ v17:CBool = IsBitEqual v16, v15
+ CondBranch v17, bb5(), bb6()
+ bb5():
+ v19:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
+ Jump bb4(v19)
+ bb6():
+ v21:CUInt64[0xffffffff0000001f] = Const CUInt64(0xffffffff0000001f)
+ v22:CPtr[CPtr(0x1010)] = Const CPtr(0x1010)
+ v23 = RefineType v22, CUInt64
+ v24:CInt64 = IntAnd v11, v21
+ v25:CBool = IsBitEqual v24, v23
+ CondBranch v25, bb7(), bb8()
+ bb7():
+ v27:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
+ Jump bb4(v27)
+ bb8():
+ v29:StringExact|NilClass = DefinedIvar v6, :@foo
+ Jump bb4(v29)
+ bb4(v12:StringExact|NilClass):
CheckInterrupts
- Return v10
+ Return v12
");
}