Skip to content

Commit fe47ff0

Browse files
committed
ZJIT: Guard T_* in addition to shape in polymorphic getivar
This is a8f3c34 ("ZJIT: Add missing guard on ivar access on T_{DATA,CLASS,MODULE}") but for the polymorphic implementation in HIR build.
1 parent a2a69b4 commit fe47ff0

File tree

8 files changed

+190
-67
lines changed

8 files changed

+190
-67
lines changed

jit.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ enum jit_bindgen_constants {
4040
// Field offsets for the RString struct
4141
RUBY_OFFSET_RSTRING_LEN = offsetof(struct RString, len),
4242

43+
// Shape constant related to RBasic::flags. (See RBASIC_SET_SHAPE_ID())
44+
RB_SHAPE_FLAG_SHIFT = SHAPE_FLAG_SHIFT,
45+
4346
// Field offsets for rb_execution_context_t
4447
RUBY_OFFSET_EC_CFP = offsetof(rb_execution_context_t, cfp),
4548
RUBY_OFFSET_EC_INTERRUPT_FLAG = offsetof(rb_execution_context_t, interrupt_flag),

yjit/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/codegen.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
617617
&Insn::Const { val: Const::CInt64(val) } => gen_const_long(val),
618618
&Insn::Const { val: Const::CUInt16(val) } => gen_const_uint16(val),
619619
&Insn::Const { val: Const::CUInt32(val) } => gen_const_uint32(val),
620+
&Insn::Const { val: Const::CUInt64(val) } => Opnd::UImm(val),
620621
&Insn::Const { val: Const::CShape(val) } => {
621622
assert_eq!(SHAPE_ID_NUM_BITS, 32);
622623
gen_const_uint32(val.0)

zjit/src/codegen_tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3714,6 +3714,7 @@ fn test_getivar_t_class_then_string() {
37143714
assert_snapshot!(assert_compiles("[STR.test, STR.test]"), @"[1000, 1000]");
37153715
}
37163716

3717+
37173718
#[test]
37183719
fn test_attr_accessor_setivar() {
37193720
eval("

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: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6342,8 +6342,16 @@ impl Function {
63426342
Insn::IntAnd { left, right }
63436343
| Insn::IntOr { left, right } => {
63446344
// TODO: Expand this to other matching C integer sizes when we need them.
6345-
self.assert_subtype(insn_id, left, types::CInt64)?;
6346-
self.assert_subtype(insn_id, right, types::CInt64)
6345+
let left_type = self.type_of(left);
6346+
if left_type.is_subtype(types::CInt64) {
6347+
self.assert_subtype(insn_id, right, types::CInt64)
6348+
} else if left_type.is_subtype(types::CUInt64) {
6349+
self.assert_subtype(insn_id, right, types::CUInt64)
6350+
} else {
6351+
let all_ints = types::CInt64.union(types::CUInt64);
6352+
self.assert_subtype(insn_id, left, all_ints)?;
6353+
self.assert_subtype(insn_id, right, all_ints)
6354+
}
63476355
}
63486356
Insn::BoxBool { val }
63496357
| Insn::IfTrue { val, .. }
@@ -8185,31 +8193,37 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
81858193
}
81868194
if let Some(summary) = fun.polymorphic_summary(&profiles, self_param, exit_state.insn_idx) {
81878195
self_param = fun.push_insn(block, Insn::GuardType { val: self_param, guard_type: types::HeapBasicObject, state: exit_id });
8188-
let shape = fun.load_shape(block, self_param);
8196+
let rbasic_flags = fun.load_rbasic_flags(block, self_param);
81898197
let join_block = insn_idx_to_block.get(&insn_idx).copied().unwrap_or_else(|| fun.new_block(insn_idx));
81908198
let join_param = fun.push_insn(join_block, Insn::Param);
81918199
// Dedup by expected shape so objects with different classes but the same shape can share code
81928200
// TODO(max): De-duplicate further by checking ivar offsets to allow
81938201
// different shapes with the same ivar layout to share code
8194-
let mut seen_shapes = Vec::with_capacity(summary.buckets().len());
8202+
let mut seen_shape_and_flags = Vec::with_capacity(summary.buckets().len());
81958203
for &profiled_type in summary.buckets() {
81968204
// End of the buckets
81978205
if profiled_type.is_empty() { break; }
81988206
// Instance variable lookups on immediate values are always nil; don't bother
81998207
if profiled_type.flags().is_immediate() { continue; }
82008208
let expected_shape = profiled_type.shape();
8209+
let (expected_rbasic_flags, rbasic_flags_mask) = profiled_type.rbasic_flags_and_mask();
82018210
assert!(expected_shape.is_valid());
82028211
// Too-complex shapes use hash tables for ivars;
82038212
// rb_shape_get_iv_index doesn't work for them.
82048213
// Let the fallthrough GetIvar handle these.
82058214
if expected_shape.is_too_complex() { continue; }
8206-
if seen_shapes.contains(&expected_shape) { continue; }
8207-
seen_shapes.push(expected_shape);
8208-
let expected_shape_const = fun.push_insn(block, Insn::Const { val: Const::CShape(expected_shape) });
8209-
let has_shape = fun.push_insn(block, Insn::IsBitEqual { left: shape, right: expected_shape_const });
8215+
if seen_shape_and_flags.contains(&expected_rbasic_flags) { continue; }
8216+
seen_shape_and_flags.push(expected_rbasic_flags);
8217+
let rbasic_flags_mask = fun.push_insn(block, Insn::Const { val: Const::CUInt64(rbasic_flags_mask) });
8218+
// The expected shape can change over run, so we put it
8219+
// as a pointer to keep it stable in snapshot tests.
8220+
let expected_rbasic_flags = fun.push_insn(block, Insn::Const { val: Const::CPtr(ptr::without_provenance(expected_rbasic_flags.to_usize())) });
8221+
let expected_rbasic_flags = fun.push_insn(block, Insn::RefineType { val: expected_rbasic_flags, new_type: types::CUInt64 });
8222+
let masked = fun.push_insn(block, Insn::IntAnd { left: rbasic_flags, right: rbasic_flags_mask});
8223+
let has_shape_and_type = fun.push_insn(block, Insn::IsBitEqual { left: masked, right: expected_rbasic_flags });
82108224
let iftrue_block = fun.new_block(insn_idx);
82118225
let target = BranchEdge { target: iftrue_block, args: vec![] };
8212-
fun.push_insn(block, Insn::IfTrue { val: has_shape, target });
8226+
fun.push_insn(block, Insn::IfTrue { val: has_shape_and_type, target });
82138227
let result = fun.load_ivar(iftrue_block, self_param, profiled_type, id, exit_id);
82148228
fun.push_insn(iftrue_block, Insn::Jump(BranchEdge { target: join_block, args: vec![result] }));
82158229
}

zjit/src/hir/opt_tests.rs

Lines changed: 141 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -7712,29 +7712,35 @@ mod hir_opt_tests {
77127712
bb3(v6:BasicObject):
77137713
PatchPoint SingleRactorMode
77147714
v11:HeapBasicObject = GuardType v6, HeapBasicObject
7715-
v12:CShape = LoadField v11, :_shape_id@0x1000
7716-
v14:CShape[0x1001] = Const CShape(0x1001)
7717-
v15:CBool = IsBitEqual v12, v14
7718-
IfTrue v15, bb5()
7719-
v20:CShape[0x1002] = Const CShape(0x1002)
7720-
v21:CBool = IsBitEqual v12, v20
7721-
IfTrue v21, bb6()
7722-
v25:BasicObject = GetIvar v11, :@foo
7723-
Jump bb4(v25)
7715+
v12:CUInt64 = LoadField v11, :_rbasic_flags@0x1000
7716+
v14:CUInt64[18446744069414584351] = Const CUInt64(18446744069414584351)
7717+
v15:CPtr[CPtr(0x1008)] = Const CPtr(0x1010)
7718+
v16 = RefineType v15, CUInt64
7719+
v17:CInt64 = IntAnd v12, v14
7720+
v18:CBool = IsBitEqual v17, v16
7721+
IfTrue v18, bb5()
7722+
v23:CUInt64[18446744069414584351] = Const CUInt64(18446744069414584351)
7723+
v24:CPtr[CPtr(0x1008)] = Const CPtr(0x1010)
7724+
v25 = RefineType v24, CUInt64
7725+
v26:CInt64 = IntAnd v12, v23
7726+
v27:CBool = IsBitEqual v26, v25
7727+
IfTrue v27, bb6()
7728+
v31:BasicObject = GetIvar v11, :@foo
7729+
Jump bb4(v31)
77247730
bb5():
7725-
v17:CPtr = LoadField v11, :_as_heap@0x1003
7726-
v18:BasicObject = LoadField v17, :@foo@0x1004
7727-
Jump bb4(v18)
7731+
v20:CPtr = LoadField v11, :_as_heap@0x1018
7732+
v21:BasicObject = LoadField v20, :@foo@0x1019
7733+
Jump bb4(v21)
77287734
bb6():
7729-
v23:BasicObject = LoadField v11, :@foo@0x1003
7730-
Jump bb4(v23)
7735+
v29:BasicObject = LoadField v11, :@foo@0x1018
7736+
Jump bb4(v29)
77317737
bb4(v13:BasicObject):
7732-
v28:Fixnum[1] = Const Value(1)
7733-
PatchPoint MethodRedefined(Integer@0x1008, +@0x1010, cme:0x1018)
7734-
v39:Fixnum = GuardType v13, Fixnum
7735-
v40:Fixnum = FixnumAdd v39, v28
7738+
v34:Fixnum[1] = Const Value(1)
7739+
PatchPoint MethodRedefined(Integer@0x1020, +@0x1028, cme:0x1030)
7740+
v45:Fixnum = GuardType v13, Fixnum
7741+
v46:Fixnum = FixnumAdd v45, v34
77367742
CheckInterrupts
7737-
Return v40
7743+
Return v46
77387744
");
77397745
}
77407746

@@ -7782,31 +7788,37 @@ mod hir_opt_tests {
77827788
bb3(v6:BasicObject):
77837789
PatchPoint SingleRactorMode
77847790
v11:HeapBasicObject = GuardType v6, HeapBasicObject
7785-
v12:CShape = LoadField v11, :_shape_id@0x1000
7786-
v14:CShape[0x1001] = Const CShape(0x1001)
7787-
v15:CBool = IsBitEqual v12, v14
7788-
IfTrue v15, bb5()
7789-
v19:CShape[0x1002] = Const CShape(0x1002)
7790-
v20:CBool = IsBitEqual v12, v19
7791-
IfTrue v20, bb6()
7792-
v38:CShape = LoadField v11, :_shape_id@0x1000
7793-
v39:CShape[0x1001] = GuardBitEquals v38, CShape(0x1001)
7794-
v40:BasicObject = LoadField v11, :@foo@0x1003
7795-
Jump bb4(v40)
7791+
v12:CUInt64 = LoadField v11, :_rbasic_flags@0x1000
7792+
v14:CUInt64[18446744069414584351] = Const CUInt64(18446744069414584351)
7793+
v15:CPtr[CPtr(0x1008)] = Const CPtr(0x1010)
7794+
v16 = RefineType v15, CUInt64
7795+
v17:CInt64 = IntAnd v12, v14
7796+
v18:CBool = IsBitEqual v17, v16
7797+
IfTrue v18, bb5()
7798+
v22:CUInt64[18446744069414584351] = Const CUInt64(18446744069414584351)
7799+
v23:CPtr[CPtr(0x1008)] = Const CPtr(0x1010)
7800+
v24 = RefineType v23, CUInt64
7801+
v25:CInt64 = IntAnd v12, v22
7802+
v26:CBool = IsBitEqual v25, v24
7803+
IfTrue v26, bb6()
7804+
v44:CShape = LoadField v11, :_shape_id@0x1018
7805+
v45:CShape[0x1019] = GuardBitEquals v44, CShape(0x1019)
7806+
v46:BasicObject = LoadField v11, :@foo@0x101a
7807+
Jump bb4(v46)
77967808
bb5():
7797-
v17:BasicObject = LoadField v11, :@foo@0x1003
7798-
Jump bb4(v17)
7809+
v20:BasicObject = LoadField v11, :@foo@0x101a
7810+
Jump bb4(v20)
77997811
bb6():
7800-
v22:CPtr = LoadField v11, :_as_heap@0x1003
7801-
v23:BasicObject = LoadField v22, :@foo@0x1004
7802-
Jump bb4(v23)
7812+
v28:CPtr = LoadField v11, :_as_heap@0x101a
7813+
v29:BasicObject = LoadField v28, :@foo@0x101b
7814+
Jump bb4(v29)
78037815
bb4(v13:BasicObject):
7804-
v28:Fixnum[1] = Const Value(1)
7805-
PatchPoint MethodRedefined(Integer@0x1008, +@0x1010, cme:0x1018)
7806-
v43:Fixnum = GuardType v13, Fixnum
7807-
v44:Fixnum = FixnumAdd v43, v28
7816+
v34:Fixnum[1] = Const Value(1)
7817+
PatchPoint MethodRedefined(Integer@0x1020, +@0x1028, cme:0x1030)
7818+
v49:Fixnum = GuardType v13, Fixnum
7819+
v50:Fixnum = FixnumAdd v49, v34
78087820
CheckInterrupts
7809-
Return v44
7821+
Return v50
78107822
");
78117823
}
78127824

@@ -7847,28 +7859,99 @@ mod hir_opt_tests {
78477859
bb3(v6:BasicObject):
78487860
PatchPoint SingleRactorMode
78497861
v11:HeapBasicObject = GuardType v6, HeapBasicObject
7850-
v12:CShape = LoadField v11, :_shape_id@0x1000
7851-
v14:CShape[0x1001] = Const CShape(0x1001)
7852-
v15:CBool = IsBitEqual v12, v14
7853-
IfTrue v15, bb5()
7854-
v19:CShape[0x1002] = Const CShape(0x1002)
7855-
v20:CBool = IsBitEqual v12, v19
7856-
IfTrue v20, bb6()
7857-
v24:BasicObject = GetIvar v11, :@foo
7858-
Jump bb4(v24)
7862+
v12:CUInt64 = LoadField v11, :_rbasic_flags@0x1000
7863+
v14:CUInt64[18446744069414584351] = Const CUInt64(18446744069414584351)
7864+
v15:CPtr[CPtr(0x1008)] = Const CPtr(0x1010)
7865+
v16 = RefineType v15, CUInt64
7866+
v17:CInt64 = IntAnd v12, v14
7867+
v18:CBool = IsBitEqual v17, v16
7868+
IfTrue v18, bb5()
7869+
v22:CUInt64[18446744069414584351] = Const CUInt64(18446744069414584351)
7870+
v23:CPtr[CPtr(0x1008)] = Const CPtr(0x1010)
7871+
v24 = RefineType v23, CUInt64
7872+
v25:CInt64 = IntAnd v12, v22
7873+
v26:CBool = IsBitEqual v25, v24
7874+
IfTrue v26, bb6()
7875+
v30:BasicObject = GetIvar v11, :@foo
7876+
Jump bb4(v30)
78597877
bb5():
7860-
v17:BasicObject = LoadField v11, :@foo@0x1003
7861-
Jump bb4(v17)
7878+
v20:BasicObject = LoadField v11, :@foo@0x1018
7879+
Jump bb4(v20)
78627880
bb6():
7863-
v22:BasicObject = LoadField v11, :@foo@0x1003
7864-
Jump bb4(v22)
7881+
v28:BasicObject = LoadField v11, :@foo@0x1018
7882+
Jump bb4(v28)
78657883
bb4(v13:BasicObject):
7866-
v27:Fixnum[1] = Const Value(1)
7867-
PatchPoint MethodRedefined(Integer@0x1008, +@0x1010, cme:0x1018)
7868-
v38:Fixnum = GuardType v13, Fixnum
7869-
v39:Fixnum = FixnumAdd v38, v27
7884+
v33:Fixnum[1] = Const Value(1)
7885+
PatchPoint MethodRedefined(Integer@0x1020, +@0x1028, cme:0x1030)
7886+
v44:Fixnum = GuardType v13, Fixnum
7887+
v45:Fixnum = FixnumAdd v44, v33
78707888
CheckInterrupts
7871-
Return v39
7889+
Return v45
7890+
");
7891+
}
7892+
7893+
#[test]
7894+
fn test_getivar_polymorphic_t_class_and_typed_data() {
7895+
set_call_threshold(3);
7896+
eval(r#"
7897+
module Reader
7898+
def test = @a
7899+
end
7900+
7901+
class A
7902+
extend Reader
7903+
@a = 0
7904+
end
7905+
7906+
ARGF.instance_eval do
7907+
extend Reader
7908+
@a = :a
7909+
end
7910+
7911+
A.test
7912+
ARGF.test
7913+
"#);
7914+
assert_snapshot!(assert_compiles("[A.test, ARGF.test]"), @"[0, :a]");
7915+
assert_snapshot!(hir_string_proc("Reader.instance_method(:test)"), @"
7916+
fn test@<compiled>:3:
7917+
bb1():
7918+
EntryPoint interpreter
7919+
v1:BasicObject = LoadSelf
7920+
Jump bb3(v1)
7921+
bb2():
7922+
EntryPoint JIT(0)
7923+
v4:BasicObject = LoadArg :self@0
7924+
Jump bb3(v4)
7925+
bb3(v6:BasicObject):
7926+
PatchPoint SingleRactorMode
7927+
v11:HeapBasicObject = GuardType v6, HeapBasicObject
7928+
v12:CUInt64 = LoadField v11, :_rbasic_flags@0x1000
7929+
v14:CUInt64[0xffffffff0000005f] = Const CUInt64(0xffffffff0000005f)
7930+
v15:CPtr[CPtr(0x1001)] = Const CPtr(0x1001)
7931+
v16 = RefineType v15, CUInt64
7932+
v17:CInt64 = IntAnd v12, v14
7933+
v18:CBool = IsBitEqual v17, v16
7934+
IfTrue v18, bb5()
7935+
v23:CUInt64[0xffffffff0000001f] = Const CUInt64(0xffffffff0000001f)
7936+
v24:CPtr[CPtr(0x1002)] = Const CPtr(0x1002)
7937+
v25 = RefineType v24, CUInt64
7938+
v26:CInt64 = IntAnd v12, v23
7939+
v27:CBool = IsBitEqual v26, v25
7940+
IfTrue v27, bb6()
7941+
v33:BasicObject = GetIvar v11, :@a
7942+
Jump bb4(v33)
7943+
bb5():
7944+
v20:RubyValue = LoadField v11, :_fields_obj@0x1003
7945+
v21:BasicObject = LoadField v20, :@a@0x1003
7946+
Jump bb4(v21)
7947+
bb6():
7948+
PatchPoint RootBoxOnly
7949+
v30:RubyValue = LoadField v11, :_fields_obj@0x1004
7950+
v31:BasicObject = LoadField v30, :@a@0x1003
7951+
Jump bb4(v31)
7952+
bb4(v13:BasicObject):
7953+
CheckInterrupts
7954+
Return v13
78727955
");
78737956
}
78747957

zjit/src/profile.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,25 @@ impl ProfiledType {
333333
self.flags
334334
}
335335

336+
/// For ivar access
337+
pub fn rbasic_flags_and_mask(&self) -> (u64, u64) {
338+
let shape_flag_shift = u64::from(RB_SHAPE_FLAG_SHIFT);
339+
let (shape, shape_mask) = (u64::from(self.shape().0) << shape_flag_shift, !0 << shape_flag_shift);
340+
let (builtin_type, type_mask) = if self.flags().is_t_object() {
341+
(RUBY_T_OBJECT, RUBY_T_MASK)
342+
} else if self.class().is_subclass_of(unsafe { rb_cClass }) == ClassRelationship::Subclass {
343+
// Check class first since `Class < Module`
344+
(RUBY_T_CLASS, RUBY_T_MASK)
345+
} else if self.class().is_subclass_of(unsafe { rb_cModule }) == ClassRelationship::Subclass {
346+
(RUBY_T_MODULE, RUBY_T_MASK)
347+
} else if self.flags().is_typed_data() {
348+
(RUBY_T_DATA | RUBY_TYPED_FL_IS_TYPED_DATA, RUBY_T_MASK | RUBY_TYPED_FL_IS_TYPED_DATA)
349+
} else {
350+
(0, 0)
351+
};
352+
(shape | u64::from(builtin_type), shape_mask | u64::from(type_mask))
353+
}
354+
336355
pub fn is_fixnum(&self) -> bool {
337356
self.class == unsafe { rb_cInteger } && self.flags.is_immediate()
338357
}

0 commit comments

Comments
 (0)