Skip to content

Commit 57f2cc2

Browse files
committed
ZJIT: Handle setivar with shape transition
1 parent 19f0df0 commit 57f2cc2

File tree

9 files changed

+242
-28
lines changed

9 files changed

+242
-28
lines changed

jit.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -767,3 +767,9 @@ rb_yarv_str_eql_internal(VALUE str1, VALUE str2)
767767
}
768768

769769
void rb_jit_str_concat_codepoint(VALUE str, VALUE codepoint);
770+
771+
attr_index_t
772+
rb_jit_shape_capacity(shape_id_t shape_id)
773+
{
774+
return RSHAPE_CAPACITY(shape_id);
775+
}

zjit/bindgen/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ fn main() {
100100
.allowlist_function("rb_shape_id_offset")
101101
.allowlist_function("rb_shape_get_iv_index")
102102
.allowlist_function("rb_shape_transition_add_ivar_no_warnings")
103+
.allowlist_function("rb_jit_shape_capacity")
103104
.allowlist_var("rb_invalid_shape_id")
104105
.allowlist_type("shape_id_fl_type")
105106
.allowlist_var("VM_KW_SPECIFIED_BITS_MAX")

zjit/src/codegen.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
350350
&Insn::Const { val: Const::CPtr(val) } => gen_const_cptr(val),
351351
&Insn::Const { val: Const::CInt64(val) } => gen_const_long(val),
352352
&Insn::Const { val: Const::CUInt16(val) } => gen_const_uint16(val),
353+
&Insn::Const { val: Const::CUInt32(val) } => gen_const_uint32(val),
353354
Insn::Const { .. } => panic!("Unexpected Const in gen_insn: {insn}"),
354355
Insn::NewArray { elements, state } => gen_new_array(asm, opnds!(elements), &function.frame_state(*state)),
355356
Insn::NewHash { elements, state } => gen_new_hash(jit, asm, opnds!(elements), &function.frame_state(*state)),
@@ -475,7 +476,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
475476
Insn::LoadEC => gen_load_ec(),
476477
Insn::LoadSelf => gen_load_self(),
477478
&Insn::LoadField { recv, id, offset, return_type: _ } => gen_load_field(asm, opnd!(recv), id, offset),
478-
&Insn::StoreField { recv, id, offset, val } => no_output!(gen_store_field(asm, opnd!(recv), id, offset, opnd!(val))),
479+
&Insn::StoreField { recv, id, offset, val } => no_output!(gen_store_field(asm, opnd!(recv), id, offset, opnd!(val), function.type_of(val))),
479480
&Insn::WriteBarrier { recv, val } => no_output!(gen_write_barrier(asm, opnd!(recv), opnd!(val), function.type_of(val))),
480481
&Insn::IsBlockGiven => gen_is_block_given(jit, asm),
481482
Insn::ArrayInclude { elements, target, state } => gen_array_include(jit, asm, opnds!(elements), opnd!(target), &function.frame_state(*state)),
@@ -1099,10 +1100,10 @@ fn gen_load_field(asm: &mut Assembler, recv: Opnd, id: ID, offset: i32) -> Opnd
10991100
asm.load(Opnd::mem(64, recv, offset))
11001101
}
11011102

1102-
fn gen_store_field(asm: &mut Assembler, recv: Opnd, id: ID, offset: i32, val: Opnd) {
1103+
fn gen_store_field(asm: &mut Assembler, recv: Opnd, id: ID, offset: i32, val: Opnd, val_type: Type) {
11031104
asm_comment!(asm, "Store field id={} offset={}", id.contents_lossy(), offset);
11041105
let recv = asm.load(recv);
1105-
asm.store(Opnd::mem(64, recv, offset), val);
1106+
asm.store(Opnd::mem(val_type.num_bits(), recv, offset), val);
11061107
}
11071108

11081109
fn gen_write_barrier(asm: &mut Assembler, recv: Opnd, val: Opnd, val_type: Type) {
@@ -1159,6 +1160,10 @@ fn gen_const_uint16(val: u16) -> lir::Opnd {
11591160
Opnd::UImm(val as u64)
11601161
}
11611162

1163+
fn gen_const_uint32(val: u32) -> lir::Opnd {
1164+
Opnd::UImm(val as u64)
1165+
}
1166+
11621167
/// Compile a basic block argument
11631168
fn gen_param(asm: &mut Assembler, idx: usize) -> lir::Opnd {
11641169
// Allocate a register or a stack slot

zjit/src/cruby.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -999,8 +999,9 @@ mod manual_defs {
999999
use super::*;
10001000

10011001
pub const SIZEOF_VALUE: usize = 8;
1002+
pub const BITS_PER_BYTE: usize = 8;
10021003
pub const SIZEOF_VALUE_I32: i32 = SIZEOF_VALUE as i32;
1003-
pub const VALUE_BITS: u8 = 8 * SIZEOF_VALUE as u8;
1004+
pub const VALUE_BITS: u8 = BITS_PER_BYTE as u8 * SIZEOF_VALUE as u8;
10041005

10051006
pub const RUBY_LONG_MIN: isize = std::os::raw::c_long::MIN as isize;
10061007
pub const RUBY_LONG_MAX: isize = std::os::raw::c_long::MAX as isize;
@@ -1382,6 +1383,7 @@ pub(crate) mod ids {
13821383
name: thread_ptr
13831384
name: self_ content: b"self"
13841385
name: rb_ivar_get_at_no_ractor_check
1386+
name: _shape_id
13851387
}
13861388

13871389
/// Get an CRuby `ID` to an interned string, e.g. a particular method name.

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: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2947,10 +2947,34 @@ impl Function {
29472947
self.push_insn_id(block, insn_id); continue;
29482948
}
29492949
let mut ivar_index: u16 = 0;
2950+
let mut next_shape_id = recv_type.shape();
29502951
if !unsafe { rb_shape_get_iv_index(recv_type.shape().0, id, &mut ivar_index) } {
2951-
// TODO(max): Shape transition
2952-
self.push_insn(block, Insn::IncrCounter(Counter::setivar_fallback_shape_transition));
2953-
self.push_insn_id(block, insn_id); continue;
2952+
// Current shape does not contain this ivar; do a shape transition.
2953+
let current_shape_id = recv_type.shape();
2954+
let class = recv_type.class();
2955+
// We're only looking at T_OBJECT so ignore all of the imemo stuff.
2956+
assert!(recv_type.flags().is_t_object());
2957+
next_shape_id = ShapeId(unsafe { rb_shape_transition_add_ivar_no_warnings(class, current_shape_id.0, id) });
2958+
let ivar_result = unsafe { rb_shape_get_iv_index(next_shape_id.0, id, &mut ivar_index) };
2959+
assert!(ivar_result, "New shape must have the ivar index");
2960+
// If the VM ran out of shapes, or this class generated too many leaf,
2961+
// it may be de-optimized into OBJ_TOO_COMPLEX_SHAPE (hash-table).
2962+
let new_shape_too_complex = unsafe { rb_jit_shape_too_complex_p(next_shape_id.0) };
2963+
// TODO(max): Is it OK to bail out here after making a shape transition?
2964+
if new_shape_too_complex {
2965+
self.push_insn(block, Insn::IncrCounter(Counter::setivar_fallback_new_shape_too_complex));
2966+
self.push_insn_id(block, insn_id); continue;
2967+
}
2968+
let current_capacity = unsafe { rb_jit_shape_capacity(current_shape_id.0) };
2969+
let next_capacity = unsafe { rb_jit_shape_capacity(next_shape_id.0) };
2970+
// If the new shape has a different capacity, or is TOO_COMPLEX, we'll have to
2971+
// reallocate it.
2972+
let needs_extension = next_capacity != current_capacity;
2973+
if needs_extension {
2974+
self.push_insn(block, Insn::IncrCounter(Counter::setivar_fallback_new_shape_needs_extension));
2975+
self.push_insn_id(block, insn_id); continue;
2976+
}
2977+
// Fall through to emitting the ivar write
29542978
}
29552979
let self_val = self.push_insn(block, Insn::GuardType { val: self_val, guard_type: types::HeapBasicObject, state });
29562980
let self_val = self.push_insn(block, Insn::GuardShape { val: self_val, shape: recv_type.shape(), state });
@@ -2966,6 +2990,13 @@ impl Function {
29662990
};
29672991
self.push_insn(block, Insn::StoreField { recv: ivar_storage, id, offset, val });
29682992
self.push_insn(block, Insn::WriteBarrier { recv: self_val, val });
2993+
if next_shape_id != recv_type.shape() {
2994+
// Write the new shape ID
2995+
assert_eq!(SHAPE_ID_NUM_BITS, 32);
2996+
let shape_id = self.push_insn(block, Insn::Const { val: Const::CUInt32(next_shape_id.0) });
2997+
let shape_id_offset = unsafe { rb_shape_id_offset() };
2998+
self.push_insn(block, Insn::StoreField { recv: self_val, id: ID!(_shape_id), offset: shape_id_offset, val: shape_id });
2999+
}
29693000
}
29703001
_ => { self.push_insn_id(block, insn_id); }
29713002
}

zjit/src/hir/opt_tests.rs

Lines changed: 142 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3483,6 +3483,39 @@ mod hir_opt_tests {
34833483
");
34843484
}
34853485

3486+
#[test]
3487+
fn test_dont_specialize_complex_shape_definedivar() {
3488+
eval(r#"
3489+
class C
3490+
def test = defined?(@a)
3491+
end
3492+
obj = C.new
3493+
(0..1000).each do |i|
3494+
obj.instance_variable_set(:"@v#{i}", i)
3495+
end
3496+
(0..1000).each do |i|
3497+
obj.remove_instance_variable(:"@v#{i}")
3498+
end
3499+
obj.test
3500+
TEST = C.instance_method(:test)
3501+
"#);
3502+
assert_snapshot!(hir_string_proc("TEST"), @r"
3503+
fn test@<compiled>:3:
3504+
bb0():
3505+
EntryPoint interpreter
3506+
v1:BasicObject = LoadSelf
3507+
Jump bb2(v1)
3508+
bb1(v4:BasicObject):
3509+
EntryPoint JIT(0)
3510+
Jump bb2(v4)
3511+
bb2(v6:BasicObject):
3512+
IncrCounter definedivar_fallback_too_complex
3513+
v10:StringExact|NilClass = DefinedIvar v6, :@a
3514+
CheckInterrupts
3515+
Return v10
3516+
");
3517+
}
3518+
34863519
#[test]
34873520
fn test_specialize_monomorphic_setivar_already_in_shape() {
34883521
eval("
@@ -3512,7 +3545,7 @@ mod hir_opt_tests {
35123545
}
35133546

35143547
#[test]
3515-
fn test_dont_specialize_monomorphic_setivar_with_shape_transition() {
3548+
fn test_specialize_monomorphic_setivar_with_shape_transition() {
35163549
eval("
35173550
def test = @foo = 5
35183551
test
@@ -3529,13 +3562,57 @@ mod hir_opt_tests {
35293562
bb2(v6:BasicObject):
35303563
v10:Fixnum[5] = Const Value(5)
35313564
PatchPoint SingleRactorMode
3532-
IncrCounter setivar_fallback_shape_transition
3533-
SetIvar v6, :@foo, v10
3565+
v19:HeapBasicObject = GuardType v6, HeapBasicObject
3566+
v20:HeapBasicObject = GuardShape v19, 0x1000
3567+
StoreField v20, :@foo@0x1001, v10
3568+
WriteBarrier v20, v10
3569+
v23:CUInt32[4194311] = Const CUInt32(4194311)
3570+
StoreField v20, :_shape_id@0x1002, v23
35343571
CheckInterrupts
35353572
Return v10
35363573
");
35373574
}
35383575

3576+
#[test]
3577+
fn test_specialize_multiple_monomorphic_setivar_with_shape_transition() {
3578+
eval("
3579+
def test
3580+
@foo = 1
3581+
@bar = 2
3582+
end
3583+
test
3584+
");
3585+
assert_snapshot!(hir_string("test"), @r"
3586+
fn test@<compiled>:3:
3587+
bb0():
3588+
EntryPoint interpreter
3589+
v1:BasicObject = LoadSelf
3590+
Jump bb2(v1)
3591+
bb1(v4:BasicObject):
3592+
EntryPoint JIT(0)
3593+
Jump bb2(v4)
3594+
bb2(v6:BasicObject):
3595+
v10:Fixnum[1] = Const Value(1)
3596+
PatchPoint SingleRactorMode
3597+
v25:HeapBasicObject = GuardType v6, HeapBasicObject
3598+
v26:HeapBasicObject = GuardShape v25, 0x1000
3599+
StoreField v26, :@foo@0x1001, v10
3600+
WriteBarrier v26, v10
3601+
v29:CUInt32[4194311] = Const CUInt32(4194311)
3602+
StoreField v26, :_shape_id@0x1002, v29
3603+
v16:Fixnum[2] = Const Value(2)
3604+
PatchPoint SingleRactorMode
3605+
v31:HeapBasicObject = GuardType v6, HeapBasicObject
3606+
v32:HeapBasicObject = GuardShape v31, 0x1003
3607+
StoreField v32, :@bar@0x1004, v16
3608+
WriteBarrier v32, v16
3609+
v35:CUInt32[4194312] = Const CUInt32(4194312)
3610+
StoreField v32, :_shape_id@0x1002, v35
3611+
CheckInterrupts
3612+
Return v16
3613+
");
3614+
}
3615+
35393616
#[test]
35403617
fn test_dont_specialize_setivar_with_t_data() {
35413618
eval("
@@ -3610,6 +3687,9 @@ mod hir_opt_tests {
36103687
(0..1000).each do |i|
36113688
obj.instance_variable_set(:"@v#{i}", i)
36123689
end
3690+
(0..1000).each do |i|
3691+
obj.remove_instance_variable(:"@v#{i}")
3692+
end
36133693
obj.test
36143694
TEST = C.instance_method(:test)
36153695
"#);
@@ -3625,7 +3705,7 @@ mod hir_opt_tests {
36253705
bb2(v6:BasicObject):
36263706
v10:Fixnum[5] = Const Value(5)
36273707
PatchPoint SingleRactorMode
3628-
IncrCounter setivar_fallback_shape_transition
3708+
IncrCounter setivar_fallback_too_complex
36293709
SetIvar v6, :@a, v10
36303710
CheckInterrupts
36313711
Return v10
@@ -5416,6 +5496,43 @@ mod hir_opt_tests {
54165496
");
54175497
}
54185498

5499+
#[test]
5500+
fn test_dont_optimize_getivar_with_too_complex_shape() {
5501+
eval(r#"
5502+
class C
5503+
attr_accessor :foo
5504+
end
5505+
obj = C.new
5506+
(0..1000).each do |i|
5507+
obj.instance_variable_set(:"@v#{i}", i)
5508+
end
5509+
(0..1000).each do |i|
5510+
obj.remove_instance_variable(:"@v#{i}")
5511+
end
5512+
def test(o) = o.foo
5513+
test obj
5514+
"#);
5515+
assert_snapshot!(hir_string("test"), @r"
5516+
fn test@<compiled>:12:
5517+
bb0():
5518+
EntryPoint interpreter
5519+
v1:BasicObject = LoadSelf
5520+
v2:BasicObject = GetLocal l0, SP@4
5521+
Jump bb2(v1, v2)
5522+
bb1(v5:BasicObject, v6:BasicObject):
5523+
EntryPoint JIT(0)
5524+
Jump bb2(v5, v6)
5525+
bb2(v8:BasicObject, v9:BasicObject):
5526+
PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010)
5527+
PatchPoint NoSingletonClass(C@0x1000)
5528+
v21:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C]
5529+
IncrCounter getivar_fallback_too_complex
5530+
v22:BasicObject = GetIvar v21, :@foo
5531+
CheckInterrupts
5532+
Return v22
5533+
");
5534+
}
5535+
54195536
#[test]
54205537
fn test_optimize_send_with_block() {
54215538
eval(r#"
@@ -5696,8 +5813,11 @@ mod hir_opt_tests {
56965813
v16:Fixnum[5] = Const Value(5)
56975814
PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010)
56985815
v26:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C]
5699-
IncrCounter setivar_fallback_shape_transition
5700-
SetIvar v26, :@foo, v16
5816+
v29:HeapObject[class_exact:C] = GuardShape v26, 0x1038
5817+
StoreField v29, :@foo@0x1039, v16
5818+
WriteBarrier v29, v16
5819+
v32:CUInt32[4194311] = Const CUInt32(4194311)
5820+
StoreField v29, :_shape_id@0x103a, v32
57015821
CheckInterrupts
57025822
Return v16
57035823
");
@@ -5728,8 +5848,11 @@ mod hir_opt_tests {
57285848
v16:Fixnum[5] = Const Value(5)
57295849
PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010)
57305850
v26:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C]
5731-
IncrCounter setivar_fallback_shape_transition
5732-
SetIvar v26, :@foo, v16
5851+
v29:HeapObject[class_exact:C] = GuardShape v26, 0x1038
5852+
StoreField v29, :@foo@0x1039, v16
5853+
WriteBarrier v29, v16
5854+
v32:CUInt32[4194311] = Const CUInt32(4194311)
5855+
StoreField v29, :_shape_id@0x103a, v32
57335856
CheckInterrupts
57345857
Return v16
57355858
");
@@ -9026,18 +9149,22 @@ mod hir_opt_tests {
90269149
SetLocal l0, EP@3, v27
90279150
v39:BasicObject = GetLocal l0, EP@3
90289151
PatchPoint SingleRactorMode
9029-
IncrCounter setivar_fallback_shape_transition
9030-
SetIvar v14, :@formatted, v39
9031-
v45:Class[VMFrozenCore] = Const Value(VALUE(0x1000))
9032-
PatchPoint MethodRedefined(Class@0x1008, lambda@0x1010, cme:0x1018)
9033-
PatchPoint NoSingletonClass(Class@0x1008)
9034-
v60:BasicObject = CCallWithFrame v45, :RubyVM::FrozenCore.lambda@0x1040, block=0x1048
9152+
v56:HeapBasicObject = GuardType v14, HeapBasicObject
9153+
v57:HeapBasicObject = GuardShape v56, 0x1000
9154+
StoreField v57, :@formatted@0x1001, v39
9155+
WriteBarrier v57, v39
9156+
v60:CUInt32[4194311] = Const CUInt32(4194311)
9157+
StoreField v57, :_shape_id@0x1002, v60
9158+
v45:Class[VMFrozenCore] = Const Value(VALUE(0x1008))
9159+
PatchPoint MethodRedefined(Class@0x1010, lambda@0x1018, cme:0x1020)
9160+
PatchPoint NoSingletonClass(Class@0x1010)
9161+
v65:BasicObject = CCallWithFrame v45, :RubyVM::FrozenCore.lambda@0x1048, block=0x1050
90359162
v48:BasicObject = GetLocal l0, EP@6
90369163
v49:BasicObject = GetLocal l0, EP@5
90379164
v50:BasicObject = GetLocal l0, EP@4
90389165
v51:BasicObject = GetLocal l0, EP@3
90399166
CheckInterrupts
9040-
Return v60
9167+
Return v65
90419168
");
90429169
}
90439170
}

0 commit comments

Comments
 (0)