Skip to content

Commit e77b7b6

Browse files
authored
ZJIT: Specialize Struct class .new (ruby#18835)
We can use `ObjectAllocClass` for Structs because they are basically a `__members__` ivar lookup for sizing plus the default allocator. Requires ruby#18888 to make sure the constructor does not raise.
1 parent bf4ab04 commit e77b7b6

6 files changed

Lines changed: 47 additions & 0 deletions

File tree

‎struct.c‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,13 @@ struct_alloc(VALUE klass)
859859
}
860860
}
861861

862+
// Whether `klass` allocates its instances with struct_alloc.
863+
bool
864+
rb_zjit_class_has_struct_allocator(VALUE klass)
865+
{
866+
return rb_get_alloc_func(klass) == struct_alloc;
867+
}
868+
862869
VALUE
863870
rb_struct_alloc(VALUE klass, VALUE values)
864871
{

‎zjit.c‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,9 @@ rb_zjit_can_load_superclass_p(VALUE klass)
356356

357357
rb_alloc_func_t rb_zjit_class_get_alloc_func(VALUE klass);
358358

359+
// Defined in struct.c, where struct_alloc is visible.
360+
bool rb_zjit_class_has_struct_allocator(VALUE klass);
361+
359362
VALUE rb_class_allocate_instance(VALUE klass);
360363

361364
bool

‎zjit/bindgen/src/main.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,7 @@ fn main() {
407407
.allowlist_function("rb_zjit_class_initialized_p")
408408
.allowlist_function("rb_zjit_can_load_superclass_p")
409409
.allowlist_function("rb_zjit_class_has_default_allocator")
410+
.allowlist_function("rb_zjit_class_has_struct_allocator")
410411
.allowlist_function("rb_zjit_class_get_alloc_func")
411412
.allowlist_function("rb_get_ec_cfp")
412413
.allowlist_function("rb_get_cfp_iseq")

‎zjit/src/cruby.rs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1648,6 +1648,10 @@ pub fn class_has_leaf_allocator(class: VALUE) -> bool {
16481648
if class == unsafe { rb_cString } { return true; }
16491649
// rb_reg_s_alloc
16501650
if class == unsafe { rb_cRegexp } { return true; }
1651+
// struct_alloc, used by every Struct subclass, is leaf: it reads the hidden __members__ ivar
1652+
// and allocates, without calling into Ruby. It does modify the class's __members__ ivar once
1653+
// to cache the members, but without a Ractor check.
1654+
if unsafe { rb_zjit_class_has_struct_allocator(class) } { return true; }
16511655
// rb_class_allocate_instance
16521656
unsafe { rb_zjit_class_has_default_allocator(class) }
16531657
}

‎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/opt_tests.rs‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12365,6 +12365,37 @@ mod hir_opt_tests {
1236512365
");
1236612366
}
1236712367

12368+
#[test]
12369+
fn test_specialize_struct_new_generates_object_alloc_class() {
12370+
eval(r#"
12371+
C = Struct.new
12372+
def test = C.new
12373+
test
12374+
"#);
12375+
assert_snapshot!(hir_string("test"), @"
12376+
fn test@<compiled>:3:
12377+
bb1():
12378+
EntryPoint interpreter
12379+
v1:BasicObject = LoadSelf
12380+
Jump bb3(v1)
12381+
bb2():
12382+
EntryPoint JIT(0)
12383+
v4:BasicObject = LoadArg :self@0
12384+
Jump bb3(v4)
12385+
bb3(v6:BasicObject):
12386+
v10:NilClass = Const Value(nil)
12387+
PatchPoint StableConstantNames(0x1000, C)
12388+
v13:ClassSubclass[C@0x1008] = Const Value(VALUE(0x1008))
12389+
PatchPoint MethodRedefined(C@0x1008, new@0x1009, cme:0x1010)
12390+
v42:ObjectSubclass[class_exact:C] = ObjectAllocClass C:VALUE(0x1008)
12391+
PatchPoint NoSingletonClass(C@0x1008)
12392+
PatchPoint MethodRedefined(C@0x1008, initialize@0x1038, cme:0x1040)
12393+
v47:BasicObject = CCallVariadic v42, :Struct#initialize@0x1068
12394+
CheckInterrupts
12395+
Return v42
12396+
");
12397+
}
12398+
1236812399
#[test]
1236912400
fn test_inline_struct_aref_embedded() {
1237012401
eval(r#"

0 commit comments

Comments
 (0)