Skip to content

Commit a81c3eb

Browse files
tekknolagiXrXr
andauthored
ZJIT: Use LoadField for TypedData ivars (ruby#16259)
Drops C calls to `rb_ivar_get_at_no_ractor_check` out of the stats completely. Co-authored-by: Alan Wu <XrXr@users.noreply.github.com>
1 parent 3b6562f commit a81c3eb

9 files changed

Lines changed: 147 additions & 2 deletions

File tree

depend

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7809,6 +7809,7 @@ jit.$(OBJEXT): {$(VPATH)}internal/error.h
78097809
jit.$(OBJEXT): {$(VPATH)}internal/eval.h
78107810
jit.$(OBJEXT): {$(VPATH)}internal/event.h
78117811
jit.$(OBJEXT): {$(VPATH)}internal/fl_type.h
7812+
jit.$(OBJEXT): {$(VPATH)}internal/core/rtypeddata.h
78127813
jit.$(OBJEXT): {$(VPATH)}internal/gc.h
78137814
jit.$(OBJEXT): {$(VPATH)}internal/glob.h
78147815
jit.$(OBJEXT): {$(VPATH)}internal/globals.h

jit.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "internal/string.h"
1919
#include "internal/class.h"
2020
#include "internal/imemo.h"
21+
#include "ruby/internal/core/rtypeddata.h"
2122

2223
enum jit_bindgen_constants {
2324
// Field offsets for the RObject struct
@@ -27,6 +28,9 @@ enum jit_bindgen_constants {
2728
// Field offset for prime classext's fields_obj from a class pointer
2829
RCLASS_OFFSET_PRIME_FIELDS_OBJ = offsetof(struct RClass_and_rb_classext_t, classext.fields_obj),
2930

31+
// Field offset for fields_obj in RTypedData
32+
RTYPEDDATA_OFFSET_FIELDS_OBJ = offsetof(struct RTypedData, fields_obj),
33+
3034
// Field offsets for the RString struct
3135
RUBY_OFFSET_RSTRING_LEN = offsetof(struct RString, len),
3236

@@ -541,6 +545,13 @@ rb_jit_class_fields_embedded_p(VALUE klass)
541545
return !fields_obj || !FL_TEST_RAW(fields_obj, OBJ_FIELD_HEAP);
542546
}
543547

548+
bool
549+
rb_jit_typed_data_fields_embedded_p(VALUE obj)
550+
{
551+
VALUE fields_obj = RTYPEDDATA(obj)->fields_obj;
552+
return !fields_obj || !FL_TEST_RAW(fields_obj, OBJ_FIELD_HEAP);
553+
}
554+
544555
// Acquire the VM lock and then signal all other Ruby threads (ractors) to
545556
// contend for the VM lock, putting them to sleep. ZJIT and YJIT use this to
546557
// evict threads running inside generated code so among other things, it can

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/bindgen/src/main.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ fn main() {
8181
.allowlist_type("RBasic")
8282

8383
.allowlist_type("ruby_rstring_flags")
84+
.allowlist_type("rbimpl_typeddata_flags")
8485

8586
// This function prints info about a value and is useful for debugging
8687
.allowlist_function("rb_raw_obj_info")
@@ -310,6 +311,8 @@ fn main() {
310311
.allowlist_function("rb_jit_shape_too_complex_p")
311312
.allowlist_function("rb_jit_multi_ractor_p")
312313
.allowlist_function("rb_jit_class_fields_embedded_p")
314+
.allowlist_function("rb_jit_typed_data_p")
315+
.allowlist_function("rb_jit_typed_data_fields_embedded_p")
313316
.allowlist_function("rb_jit_vm_lock_then_barrier")
314317
.allowlist_function("rb_jit_vm_unlock")
315318
.allowlist_function("rb_jit_for_each_iseq")

zjit/src/cruby.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ use std::fmt::{Debug, Display, Formatter};
9494
use std::os::raw::{c_char, c_int, c_uint};
9595
use std::panic::{catch_unwind, UnwindSafe};
9696

97+
use crate::cast::IntoUsize as _;
98+
9799
// We check that we can do this with the configure script and a couple of
98100
// static asserts. u64 and not usize to play nice with lowering to x86.
99101
pub type size_t = u64;
@@ -602,6 +604,16 @@ impl VALUE {
602604
unsafe { rb_jit_class_fields_embedded_p(self) }
603605
}
604606

607+
pub fn typed_data_p(self) -> bool {
608+
!self.special_const_p() &&
609+
self.builtin_type() == RUBY_T_DATA &&
610+
0 != (self.builtin_flags() & RUBY_TYPED_FL_IS_TYPED_DATA.to_usize())
611+
}
612+
613+
pub fn typed_data_fields_embedded_p(self) -> bool {
614+
unsafe { rb_jit_typed_data_fields_embedded_p(self) }
615+
}
616+
605617
pub fn as_fixnum(self) -> i64 {
606618
assert!(self.fixnum_p());
607619
(self.0 as i64) >> 1

zjit/src/cruby_bindings.inc.rs

Lines changed: 9 additions & 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: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3949,8 +3949,34 @@ impl Function {
39493949
return_type: types::BasicObject,
39503950
elidable: true })
39513951
}
3952+
} else if recv_type.flags().is_typed_data() {
3953+
// Typed T_DATA: load from fields_obj at fixed offset in RTypedData
3954+
let fields_obj = self.push_insn(block, Insn::LoadField {
3955+
recv: self_val, id: ID!(_fields_obj),
3956+
offset: RTYPEDDATA_OFFSET_FIELDS_OBJ as i32,
3957+
return_type: types::RubyValue,
3958+
});
3959+
if recv_type.flags().is_fields_embedded() {
3960+
let offset = ROBJECT_OFFSET_AS_ARY as i32
3961+
+ (SIZEOF_VALUE * ivar_index.to_usize()) as i32;
3962+
self.push_insn(block, Insn::LoadField {
3963+
recv: fields_obj, id, offset,
3964+
return_type: types::BasicObject,
3965+
})
3966+
} else {
3967+
let ptr = self.push_insn(block, Insn::LoadField {
3968+
recv: fields_obj, id: ID!(_as_heap),
3969+
offset: ROBJECT_OFFSET_AS_HEAP_FIELDS as i32,
3970+
return_type: types::CPtr,
3971+
});
3972+
let offset = SIZEOF_VALUE_I32 * ivar_index as i32;
3973+
self.push_insn(block, Insn::LoadField {
3974+
recv: ptr, id, offset,
3975+
return_type: types::BasicObject,
3976+
})
3977+
}
39523978
} else if !recv_type.flags().is_t_object() {
3953-
// Non-T_OBJECT, non-class/module (e.g. T_DATA): fall back to C call
3979+
// Non-T_OBJECT, non-class/module, non-typed-data: fall back to C call
39543980
// NOTE: it's fine to use rb_ivar_get_at_no_ractor_check because
39553981
// getinstancevariable does assume_single_ractor_mode()
39563982
let ivar_index_insn = self.push_insn(block, Insn::Const { val: Const::CUInt16(ivar_index as u16) });

zjit/src/hir/opt_tests.rs

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7253,7 +7253,8 @@ mod hir_opt_tests {
72537253
}
72547254

72557255
#[test]
7256-
fn test_optimize_getivar_on_t_data() {
7256+
fn test_optimize_getivar_on_t_struct() {
7257+
// Range is T_STRUCT (not T_DATA): falls back to CCall
72577258
eval("
72587259
class C < Range
72597260
def test = @a
@@ -7285,6 +7286,78 @@ mod hir_opt_tests {
72857286
");
72867287
}
72877288

7289+
#[test]
7290+
fn test_optimize_getivar_on_typed_data() {
7291+
// Thread is typed T_DATA: uses LoadField chain via RTypedData fields_obj
7292+
eval("
7293+
class C < Thread
7294+
def test = @a
7295+
end
7296+
obj = C.new { }
7297+
obj.join
7298+
obj.instance_variable_set(:@a, 1)
7299+
obj.test
7300+
TEST = C.instance_method(:test)
7301+
");
7302+
assert_snapshot!(hir_string_proc("TEST"), @r"
7303+
fn test@<compiled>:3:
7304+
bb1():
7305+
EntryPoint interpreter
7306+
v1:BasicObject = LoadSelf
7307+
Jump bb3(v1)
7308+
bb2():
7309+
EntryPoint JIT(0)
7310+
v4:BasicObject = LoadArg :self@0
7311+
Jump bb3(v4)
7312+
bb3(v6:BasicObject):
7313+
PatchPoint SingleRactorMode
7314+
v17:HeapBasicObject = GuardType v6, HeapBasicObject
7315+
v18:CShape = LoadField v17, :_shape_id@0x1000
7316+
v19:CShape[0x1001] = GuardBitEquals v18, CShape(0x1001)
7317+
v20:RubyValue = LoadField v17, :_fields_obj@0x1002
7318+
v21:BasicObject = LoadField v20, :@a@0x1002
7319+
CheckInterrupts
7320+
Return v21
7321+
");
7322+
}
7323+
7324+
#[test]
7325+
fn test_optimize_getivar_on_typed_data_heap_fields() {
7326+
// Typed T_DATA with enough ivars to force heap field storage
7327+
eval("
7328+
class C < Thread
7329+
def test = @var1000
7330+
end
7331+
obj = C.new { }
7332+
obj.join
7333+
1000.times { |i| obj.instance_variable_set(:\"@var#{i}\", 1) }
7334+
obj.instance_variable_set(:@var1000, 42)
7335+
obj.test
7336+
TEST = C.instance_method(:test)
7337+
");
7338+
assert_snapshot!(hir_string_proc("TEST"), @r"
7339+
fn test@<compiled>:3:
7340+
bb1():
7341+
EntryPoint interpreter
7342+
v1:BasicObject = LoadSelf
7343+
Jump bb3(v1)
7344+
bb2():
7345+
EntryPoint JIT(0)
7346+
v4:BasicObject = LoadArg :self@0
7347+
Jump bb3(v4)
7348+
bb3(v6:BasicObject):
7349+
PatchPoint SingleRactorMode
7350+
v17:HeapBasicObject = GuardType v6, HeapBasicObject
7351+
v18:CShape = LoadField v17, :_shape_id@0x1000
7352+
v19:CShape[0x1001] = GuardBitEquals v18, CShape(0x1001)
7353+
v20:RubyValue = LoadField v17, :_fields_obj@0x1002
7354+
v21:CPtr = LoadField v20, :_as_heap@0x1002
7355+
v22:BasicObject = LoadField v21, :@var1000@0x1003
7356+
CheckInterrupts
7357+
Return v22
7358+
");
7359+
}
7360+
72887361
#[test]
72897362
fn test_optimize_getivar_on_module_multi_ractor() {
72907363
eval("

zjit/src/profile.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,8 @@ impl Flags {
207207
const IS_FIELDS_EMBEDDED: u32 = 1 << 5;
208208
/// Object is a T_CLASS or T_MODULE
209209
const IS_T_CLASS_OR_MODULE: u32 = 1 << 6;
210+
/// Object is a typed T_DATA (RTYPEDDATA_P)
211+
const IS_TYPED_DATA: u32 = 1 << 7;
210212

211213
pub fn none() -> Self { Self(Self::NONE) }
212214

@@ -218,6 +220,7 @@ impl Flags {
218220
pub fn is_object_profiling(self) -> bool { (self.0 & Self::IS_OBJECT_PROFILING) != 0 }
219221
pub fn is_fields_embedded(self) -> bool { (self.0 & Self::IS_FIELDS_EMBEDDED) != 0 }
220222
pub fn is_t_class_or_module(self) -> bool { (self.0 & Self::IS_T_CLASS_OR_MODULE) != 0 }
223+
pub fn is_typed_data(self) -> bool { (self.0 & Self::IS_TYPED_DATA) != 0 }
221224
}
222225

223226
/// opt_send_without_block/opt_plus/... should store:
@@ -300,6 +303,12 @@ impl ProfiledType {
300303
flags.0 |= Flags::IS_FIELDS_EMBEDDED;
301304
}
302305
}
306+
if obj.typed_data_p() {
307+
flags.0 |= Flags::IS_TYPED_DATA;
308+
if obj.typed_data_fields_embedded_p() {
309+
flags.0 |= Flags::IS_FIELDS_EMBEDDED;
310+
}
311+
}
303312
Self { class: obj.class_of(), shape: obj.shape_id_of(), flags }
304313
}
305314

0 commit comments

Comments
 (0)