Skip to content

Commit 094b1e0

Browse files
committed
Turn rb_classext_t.fields into a T_OBJECT
This aims to solve two problems. First, it solves the problem of namspaced classes having a single `shape_id`. Now each namespaced classext has a `T_OBJECT` that can hold the namespace specific shape. Second, it open the door to later make class instance variable writes atomics, hence be able to read class variables without locking the VM. In the future, in multi-ractor mode, we can do the write on a copy of the `fields_obj` and then atomically swap it. Considerations: - T_OBJECT isn't ideal, it probably should be a `T_IMEMO` but this require a very large refatoring. - Right now the `RClass` shape_id is always synchronized, but with namespace we should likely mark classes that have multiple namespace with `INVALID_SHAPE`.
1 parent f18883b commit 094b1e0

8 files changed

Lines changed: 157 additions & 235 deletions

File tree

class.c

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -317,13 +317,8 @@ rb_class_duplicate_classext(rb_classext_t *orig, VALUE klass, const rb_namespace
317317

318318
RCLASSEXT_M_TBL(ext) = duplicate_classext_m_tbl(RCLASSEXT_M_TBL(orig), klass, dup_iclass);
319319

320-
// TODO: consider shapes for performance
321-
if (RCLASSEXT_FIELDS(orig)) {
322-
RCLASSEXT_FIELDS(ext) = (VALUE *)st_copy((st_table *)RCLASSEXT_FIELDS(orig));
323-
rb_autoload_copy_table_for_namespace((st_table *)RCLASSEXT_FIELDS(ext), ns);
324-
}
325-
else {
326-
RCLASSEXT_FIELDS(ext) = (VALUE *)st_init_numtable();
320+
if (orig->fields_obj) {
321+
ext->fields_obj = rb_obj_clone(orig->fields_obj);
327322
}
328323

329324
if (RCLASSEXT_SHARED_CONST_TBL(orig)) {

gc.c

Lines changed: 5 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1212,7 +1212,6 @@ rb_data_free(void *objspace, VALUE obj)
12121212

12131213
struct classext_foreach_args {
12141214
VALUE klass;
1215-
bool obj_too_complex;
12161215
rb_objspace_t *objspace; // used for update_*
12171216
};
12181217

@@ -1224,12 +1223,6 @@ classext_free(rb_classext_t *ext, bool is_prime, VALUE namespace, void *arg)
12241223

12251224
rb_id_table_free(RCLASSEXT_M_TBL(ext));
12261225
rb_cc_tbl_free(RCLASSEXT_CC_TBL(ext), args->klass);
1227-
if (args->obj_too_complex) {
1228-
st_free_table((st_table *)RCLASSEXT_FIELDS(ext));
1229-
}
1230-
else {
1231-
xfree(RCLASSEXT_FIELDS(ext));
1232-
}
12331226
if (!RCLASSEXT_SHARED_CONST_TBL(ext) && (tbl = RCLASSEXT_CONST_TBL(ext)) != NULL) {
12341227
rb_free_const_table(tbl);
12351228
}
@@ -1302,8 +1295,6 @@ rb_gc_obj_free(void *objspace, VALUE obj)
13021295
case T_MODULE:
13031296
case T_CLASS:
13041297
args.klass = obj;
1305-
args.obj_too_complex = rb_shape_obj_too_complex_p(obj) ? true : false;
1306-
13071298
rb_class_classext_foreach(obj, classext_free, (void *)&args);
13081299
if (RCLASS(obj)->ns_classext_tbl) {
13091300
st_free_table(RCLASS(obj)->ns_classext_tbl);
@@ -1495,6 +1486,8 @@ internal_object_p(VALUE obj)
14951486
return rb_singleton_class_internal_p(obj);
14961487
}
14971488
return 0;
1489+
case T_OBJECT:
1490+
if (FL_TEST_RAW(obj, ROBJECT_HIDDEN)) break;
14981491
default:
14991492
if (!RBASIC(obj)->klass) break;
15001493
return 0;
@@ -2274,20 +2267,6 @@ classext_memsize(rb_classext_t *ext, bool prime, VALUE namespace, void *arg)
22742267
*size += s;
22752268
}
22762269

2277-
static void
2278-
classext_fields_hash_memsize(rb_classext_t *ext, bool prime, VALUE namespace, void *arg)
2279-
{
2280-
size_t *size = (size_t *)arg;
2281-
size_t count;
2282-
RB_VM_LOCK_ENTER();
2283-
{
2284-
count = rb_st_table_size((st_table *)RCLASSEXT_FIELDS(ext));
2285-
}
2286-
RB_VM_LOCK_LEAVE();
2287-
// class IV sizes are allocated as powers of two
2288-
*size += SIZEOF_VALUE << bit_length(count);
2289-
}
2290-
22912270
static void
22922271
classext_superclasses_memsize(rb_classext_t *ext, bool prime, VALUE namespace, void *arg)
22932272
{
@@ -2326,15 +2305,6 @@ rb_obj_memsize_of(VALUE obj)
23262305
case T_MODULE:
23272306
case T_CLASS:
23282307
rb_class_classext_foreach(obj, classext_memsize, (void *)&size);
2329-
2330-
if (rb_shape_obj_too_complex_p(obj)) {
2331-
rb_class_classext_foreach(obj, classext_fields_hash_memsize, (void *)&size);
2332-
}
2333-
else {
2334-
// class IV sizes are allocated as powers of two
2335-
size += SIZEOF_VALUE << bit_length(RCLASS_FIELDS_COUNT(obj));
2336-
}
2337-
23382308
rb_class_classext_foreach(obj, classext_superclasses_memsize, (void *)&size);
23392309
break;
23402310
case T_ICLASS:
@@ -3107,10 +3077,7 @@ gc_mark_classext_module(rb_classext_t *ext, bool prime, VALUE namespace, void *a
31073077
gc_mark_internal(RCLASSEXT_SUPER(ext));
31083078
}
31093079
mark_m_tbl(objspace, RCLASSEXT_M_TBL(ext));
3110-
if (rb_shape_obj_too_complex_p(obj)) {
3111-
gc_mark_tbl_no_pin((st_table *)RCLASSEXT_FIELDS(ext));
3112-
// for the case ELSE is written in rb_gc_mark_children() because it's per RClass, not classext
3113-
}
3080+
gc_mark_internal(RCLASSEXT_FIELDS_OBJ(ext));
31143081
if (!RCLASSEXT_SHARED_CONST_TBL(ext) && RCLASSEXT_CONST_TBL(ext)) {
31153082
mark_const_tbl(objspace, RCLASSEXT_CONST_TBL(ext));
31163083
}
@@ -3191,11 +3158,7 @@ rb_gc_mark_children(void *objspace, VALUE obj)
31913158
foreach_args.obj = obj;
31923159
rb_class_classext_foreach(obj, gc_mark_classext_module, (void *)&foreach_args);
31933160

3194-
if (!rb_shape_obj_too_complex_p(obj)) {
3195-
for (attr_index_t i = 0; i < RCLASS_FIELDS_COUNT(obj); i++) {
3196-
gc_mark_internal(RCLASS_PRIME_FIELDS(obj)[i]);
3197-
}
3198-
}
3161+
gc_mark_internal(RCLASS_PRIME_FIELDS_OBJ(obj));
31993162
break;
32003163

32013164
case T_ICLASS:
@@ -3827,7 +3790,6 @@ static void
38273790
update_classext(rb_classext_t *ext, bool is_prime, VALUE namespace, void *arg)
38283791
{
38293792
struct classext_foreach_args *args = (struct classext_foreach_args *)arg;
3830-
VALUE klass = args->klass;
38313793
rb_objspace_t *objspace = args->objspace;
38323794

38333795
if (RCLASSEXT_SUPER(ext)) {
@@ -3836,16 +3798,7 @@ update_classext(rb_classext_t *ext, bool is_prime, VALUE namespace, void *arg)
38363798

38373799
update_m_tbl(objspace, RCLASSEXT_M_TBL(ext));
38383800

3839-
if (args->obj_too_complex) {
3840-
gc_ref_update_table_values_only((st_table *)RCLASSEXT_FIELDS(ext));
3841-
}
3842-
else {
3843-
// Classext is not copied in this case
3844-
for (attr_index_t i = 0; i < RCLASS_FIELDS_COUNT(klass); i++) {
3845-
UPDATE_IF_MOVED(objspace, RCLASSEXT_FIELDS(RCLASS_EXT_PRIME(klass))[i]);
3846-
}
3847-
}
3848-
3801+
UPDATE_IF_MOVED(objspace, ext->fields_obj);
38493802
if (!RCLASSEXT_SHARED_CONST_TBL(ext)) {
38503803
update_const_tbl(objspace, RCLASSEXT_CONST_TBL(ext));
38513804
}
@@ -4202,7 +4155,6 @@ rb_gc_update_object_references(void *objspace, VALUE obj)
42024155
// Continue to the shared T_CLASS/T_MODULE
42034156
case T_MODULE:
42044157
args.klass = obj;
4205-
args.obj_too_complex = rb_shape_obj_too_complex_p(obj);
42064158
args.objspace = objspace;
42074159
rb_class_classext_foreach(obj, update_classext, (void *)&args);
42084160
break;

include/ruby/internal/core/robject.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@ enum ruby_robject_flags {
7171
* 3rd parties must not be aware that there even is more than one way to
7272
* store instance variables. Might better be hidden.
7373
*/
74-
ROBJECT_EMBED = RUBY_FL_USER1
74+
ROBJECT_EMBED = RUBY_FL_USER1,
75+
76+
ROBJECT_HIDDEN = RUBY_FL_USER2, // HACK
7577
};
7678

7779
struct st_table;

internal/class.h

Lines changed: 44 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
*/
1111
#include "id.h"
1212
#include "id_table.h" /* for struct rb_id_table */
13+
#include "internal/object.h" /* for rb_class_allocate_instance */
1314
#include "internal/namespace.h" /* for rb_current_namespace */
1415
#include "internal/serial.h" /* for rb_serial_t */
1516
#include "internal/static_assert.h"
@@ -79,7 +80,7 @@ struct rb_cvar_class_tbl_entry {
7980
struct rb_classext_struct {
8081
const rb_namespace_t *ns;
8182
VALUE super;
82-
VALUE *fields; // Fields are either ivar or other internal properties stored inline
83+
VALUE fields_obj; // Fields are either ivar or other internal properties stored inline
8384
struct rb_id_table *m_tbl;
8485
struct rb_id_table *const_tbl;
8586
struct rb_id_table *callable_m_tbl;
@@ -176,7 +177,8 @@ static inline rb_classext_t * RCLASS_EXT_WRITABLE(VALUE obj);
176177

177178
#define RCLASSEXT_NS(ext) (ext->ns)
178179
#define RCLASSEXT_SUPER(ext) (ext->super)
179-
#define RCLASSEXT_FIELDS(ext) (ext->fields)
180+
#define RCLASSEXT_FIELDS(ext) (ext->fields_obj ? ROBJECT_FIELDS(ext->fields_obj) : NULL)
181+
#define RCLASSEXT_FIELDS_OBJ(ext) (ext->fields_obj)
180182
#define RCLASSEXT_M_TBL(ext) (ext->m_tbl)
181183
#define RCLASSEXT_CONST_TBL(ext) (ext->const_tbl)
182184
#define RCLASSEXT_CALLABLE_M_TBL(ext) (ext->callable_m_tbl)
@@ -209,7 +211,7 @@ static inline void RCLASSEXT_SET_INCLUDER(rb_classext_t *ext, VALUE klass, VALUE
209211
#define RCLASS_PRIME_NS(c) (RCLASS_EXT_PRIME(c)->ns)
210212
// To invalidate CC by inserting&invalidating method entry into tables containing the target cme
211213
// See clear_method_cache_by_id_in_class()
212-
#define RCLASS_PRIME_FIELDS(c) (RCLASS_EXT_PRIME(c)->fields)
214+
#define RCLASS_PRIME_FIELDS_OBJ(c) (RCLASS_EXT_PRIME(c)->fields_obj)
213215
#define RCLASS_PRIME_M_TBL(c) (RCLASS_EXT_PRIME(c)->m_tbl)
214216
#define RCLASS_PRIME_CONST_TBL(c) (RCLASS_EXT_PRIME(c)->const_tbl)
215217
#define RCLASS_PRIME_CALLABLE_M_TBL(c) (RCLASS_EXT_PRIME(c)->callable_m_tbl)
@@ -255,9 +257,7 @@ static inline void RCLASSEXT_SET_INCLUDER(rb_classext_t *ext, VALUE klass, VALUE
255257

256258
static inline void RCLASS_SET_SUPER(VALUE klass, VALUE super);
257259
static inline void RCLASS_WRITE_SUPER(VALUE klass, VALUE super);
258-
static inline st_table * RCLASS_FIELDS_HASH(VALUE obj);
259260
static inline st_table * RCLASS_WRITABLE_FIELDS_HASH(VALUE obj);
260-
static inline uint32_t RCLASS_FIELDS_COUNT(VALUE obj);
261261
static inline void RCLASS_SET_FIELDS_HASH(VALUE obj, const st_table *table);
262262
static inline void RCLASS_WRITE_FIELDS_HASH(VALUE obj, const st_table *table);
263263
// TODO: rename RCLASS_SET_M_TBL_WORKAROUND (and _WRITE_) to RCLASS_SET_M_TBL with write barrier
@@ -531,58 +531,67 @@ RCLASS_WRITE_SUPER(VALUE klass, VALUE super)
531531
RB_OBJ_WRITE(klass, &RCLASSEXT_SUPER(RCLASS_EXT_WRITABLE(klass)), super);
532532
}
533533

534-
static inline st_table *
535-
RCLASS_FIELDS_HASH(VALUE obj)
534+
static inline VALUE
535+
RCLASS_FIELDS_OBJ(VALUE obj)
536536
{
537537
RUBY_ASSERT(RB_TYPE_P(obj, RUBY_T_CLASS) || RB_TYPE_P(obj, RUBY_T_MODULE));
538-
RUBY_ASSERT(rb_shape_obj_too_complex_p(obj));
539-
return (st_table *)RCLASSEXT_FIELDS(RCLASS_EXT_READABLE(obj));
538+
return RCLASSEXT_FIELDS_OBJ(RCLASS_EXT_READABLE(obj));
539+
}
540+
541+
static inline VALUE
542+
rb_allocate_fields_obj(VALUE klass)
543+
{
544+
VALUE fields_obj = rb_class_allocate_instance(rb_singleton_class(klass));
545+
FL_SET_RAW(fields_obj, ROBJECT_HIDDEN); // HACK
546+
return fields_obj;
547+
}
548+
549+
static inline VALUE
550+
RCLASS_ENSURE_FIELDS_OBJ(VALUE obj)
551+
{
552+
RUBY_ASSERT(RB_TYPE_P(obj, RUBY_T_CLASS) || RB_TYPE_P(obj, RUBY_T_MODULE));
553+
rb_classext_t *ext = RCLASS_EXT_READABLE(obj);
554+
if (!ext->fields_obj) {
555+
RB_OBJ_WRITE(obj, &ext->fields_obj, rb_allocate_fields_obj(obj));
556+
}
557+
return ext->fields_obj;
540558
}
541559

542-
static inline st_table *
543-
RCLASS_WRITABLE_FIELDS_HASH(VALUE obj)
560+
static inline VALUE
561+
RCLASS_WRITABLE_FIELDS_OBJ(VALUE obj)
544562
{
545563
RUBY_ASSERT(RB_TYPE_P(obj, RUBY_T_CLASS) || RB_TYPE_P(obj, RUBY_T_MODULE));
546-
RUBY_ASSERT(rb_shape_obj_too_complex_p(obj));
547-
return (st_table *)RCLASSEXT_FIELDS(RCLASS_EXT_WRITABLE(obj));
564+
return RCLASSEXT_FIELDS_OBJ(RCLASS_EXT_WRITABLE(obj));
548565
}
549566

550567
static inline void
551-
RCLASS_SET_FIELDS_HASH(VALUE obj, const st_table *tbl)
568+
RCLASSEXT_SET_FIELDS_HASH(VALUE obj, rb_classext_t *ext, const st_table *tbl)
552569
{
553570
RUBY_ASSERT(RB_TYPE_P(obj, RUBY_T_CLASS) || RB_TYPE_P(obj, RUBY_T_MODULE));
554571
RUBY_ASSERT(rb_shape_obj_too_complex_p(obj));
555-
RCLASSEXT_FIELDS(RCLASS_EXT_PRIME(obj)) = (VALUE *)tbl;
572+
573+
if (!ext->fields_obj) {
574+
// FIXME: We can trigger GC here and `*tbl` may not be marked
575+
RB_OBJ_WRITE(obj, &ext->fields_obj, rb_allocate_fields_obj(obj));
576+
}
577+
ROBJECT_SET_FIELDS_HASH(ext->fields_obj, tbl);
556578
}
557579

558580
static inline void
559-
RCLASS_WRITE_FIELDS_HASH(VALUE obj, const st_table *tbl)
581+
RCLASS_SET_FIELDS_HASH(VALUE obj, const st_table *tbl)
560582
{
561583
RUBY_ASSERT(RB_TYPE_P(obj, RUBY_T_CLASS) || RB_TYPE_P(obj, RUBY_T_MODULE));
562584
RUBY_ASSERT(rb_shape_obj_too_complex_p(obj));
563-
RCLASSEXT_FIELDS(RCLASS_EXT_WRITABLE(obj)) = (VALUE *)tbl;
585+
586+
RCLASSEXT_SET_FIELDS_HASH(obj, RCLASS_EXT_PRIME(obj), tbl);
564587
}
565588

566-
static inline uint32_t
567-
RCLASS_FIELDS_COUNT(VALUE obj)
589+
static inline void
590+
RCLASS_WRITE_FIELDS_HASH(VALUE obj, const st_table *tbl)
568591
{
569592
RUBY_ASSERT(RB_TYPE_P(obj, RUBY_T_CLASS) || RB_TYPE_P(obj, RUBY_T_MODULE));
570-
if (rb_shape_obj_too_complex_p(obj)) {
571-
uint32_t count;
572-
573-
// "Too complex" classes could have their IV hash mutated in
574-
// parallel, so lets lock around getting the hash size.
575-
RB_VM_LOCK_ENTER();
576-
{
577-
count = (uint32_t)rb_st_table_size(RCLASS_FIELDS_HASH(obj));
578-
}
579-
RB_VM_LOCK_LEAVE();
580-
581-
return count;
582-
}
583-
else {
584-
return RSHAPE(RCLASS_SHAPE_ID(obj))->next_field_index;
585-
}
593+
RUBY_ASSERT(rb_shape_obj_too_complex_p(obj));
594+
RCLASSEXT_SET_FIELDS_HASH(obj, RCLASS_EXT_WRITABLE(obj), tbl);
586595
}
587596

588597
#define RCLASS_SET_M_TBL_EVEN_WHEN_PROMOTED(klass, table) RCLASS_SET_M_TBL_WORKAROUND(klass, table, false)

shape.c

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -359,15 +359,27 @@ rb_obj_shape_id(VALUE obj)
359359
}
360360

361361
#if SHAPE_IN_BASIC_FLAGS
362+
if (BUILTIN_TYPE(obj) == T_CLASS || BUILTIN_TYPE(obj) == T_MODULE) {
363+
VALUE fields_obj = RCLASS_FIELDS_OBJ(obj);
364+
if (fields_obj) {
365+
return ROBJECT_SHAPE_ID(fields_obj);
366+
}
367+
return ROOT_SHAPE_ID;
368+
}
362369
return RBASIC_SHAPE_ID(obj);
363370
#else
364371
switch (BUILTIN_TYPE(obj)) {
365372
case T_OBJECT:
366373
return ROBJECT_SHAPE_ID(obj);
367374
break;
368375
case T_CLASS:
369-
case T_MODULE:
370-
return RCLASS_SHAPE_ID(obj);
376+
case T_MODULE: {
377+
VALUE fields_obj = RCLASS_FIELDS_OBJ(obj);
378+
if (fields_obj) {
379+
ROBJECT_SHAPE_ID(fields_obj);
380+
}
381+
return ROOT_SHAPE_ID;
382+
}
371383
default:
372384
return rb_generic_shape_id(obj);
373385
}
@@ -635,6 +647,19 @@ remove_shape_recursive(rb_shape_t *shape, ID id, rb_shape_t **removed_shape)
635647
bool
636648
rb_shape_transition_remove_ivar(VALUE obj, ID id, VALUE *removed)
637649
{
650+
switch(BUILTIN_TYPE(obj)) {
651+
case T_CLASS:
652+
case T_MODULE: {
653+
VALUE fields_obj = RCLASS_PRIME_FIELDS_OBJ(obj);
654+
if (fields_obj) {
655+
return rb_shape_transition_remove_ivar(fields_obj, id, removed);
656+
}
657+
return false;
658+
}
659+
default:
660+
break;
661+
}
662+
638663
rb_shape_t *shape = rb_obj_shape(obj);
639664

640665
if (UNLIKELY(rb_shape_too_complex_p(shape))) {
@@ -656,7 +681,7 @@ rb_shape_transition_remove_ivar(VALUE obj, ID id, VALUE *removed)
656681
switch(BUILTIN_TYPE(obj)) {
657682
case T_CLASS:
658683
case T_MODULE:
659-
fields = RCLASS_PRIME_FIELDS(obj);
684+
rb_bug("Unreacheable");
660685
break;
661686
case T_OBJECT:
662687
fields = ROBJECT_FIELDS(obj);

shape.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ get_shape_id_from_flags(VALUE obj)
9595
{
9696
RUBY_ASSERT(!RB_SPECIAL_CONST_P(obj));
9797
RUBY_ASSERT(!RB_TYPE_P(obj, T_IMEMO));
98+
RUBY_ASSERT(!RB_TYPE_P(obj, T_CLASS) && !RB_TYPE_P(obj, T_MODULE));
99+
98100
return (shape_id_t)((RBASIC(obj)->flags) >> SHAPE_FLAG_SHIFT);
99101
}
100102

0 commit comments

Comments
 (0)