Ruby 4.1.0dev (2026-08-15 revision 3349f4107d268658fdf8cc6b979fbb1c923e597f)
shape.h (3349f4107d268658fdf8cc6b979fbb1c923e597f)
1#ifndef RUBY_SHAPE_H
2#define RUBY_SHAPE_H
3
4#include "internal/gc.h"
5#include "internal/imemo.h"
6
7typedef uint8_t attr_index_t;
8typedef uint32_t shape_id_t;
9#define SHAPE_ID_NUM_BITS 32
10#define SHAPE_ID_OFFSET_NUM_BITS 19
11
12STATIC_ASSERT(shape_id_num_bits, SHAPE_ID_NUM_BITS == sizeof(shape_id_t) * CHAR_BIT);
13
14#define SHAPE_BUFFER_SIZE (1 << SHAPE_ID_OFFSET_NUM_BITS)
15#define SHAPE_ID_OFFSET_MASK (SHAPE_BUFFER_SIZE - 1)
16
17#define SHAPE_ID_CAPACITY_BITS 7
18#define SHAPE_ID_CAPACITY_MAX ((1U << SHAPE_ID_CAPACITY_BITS) - 1)
19
20#define SHAPE_ID_CAPACITY_OFFSET SHAPE_ID_OFFSET_NUM_BITS
21#define SHAPE_ID_FL_USHIFT (SHAPE_ID_OFFSET_NUM_BITS + SHAPE_ID_CAPACITY_BITS)
22
23// shape_id_t bits:
24// 0-18 SHAPE_ID_OFFSET_MASK
25// index in rb_shape_tree.shape_list. Allow to access `rb_shape_t *`.
26// This is the part that describe how fields are laid out in memory.
27// 19-25 SHAPE_ID_CAPACITY_MASK
28// Embedded field capacity for T_OBJECT objects.
29// 26 SHAPE_ID_FL_COMPLEX
30// The object is backed by a `st_table`.
31// 27 SHAPE_ID_FL_FROZEN
32// Whether the object is frozen or not.
33// 28 SHAPE_ID_FL_HAS_OBJECT_ID
34// Whether the object has an `SHAPE_OBJ_ID` transition.
35// 29-30 SHAPE_ID_LAYOUT_MASK
36// The object's physical field layout.
37
38STATIC_ASSERT(robject_rdata_fields_offset, offsetof(struct RObject, as.extended) == offsetof(struct RTypedData, fields_obj));
39
40enum shape_id_fl_type {
41#define RBIMPL_SHAPE_ID_FL(n) (1<<(SHAPE_ID_FL_USHIFT+n))
42
43 SHAPE_ID_CAPACITY_MASK = ((1 << SHAPE_ID_CAPACITY_BITS) - 1) << SHAPE_ID_CAPACITY_OFFSET,
44
45 SHAPE_ID_FL_COMPLEX = RBIMPL_SHAPE_ID_FL(0),
46 SHAPE_ID_FL_FROZEN = RBIMPL_SHAPE_ID_FL(1),
47 SHAPE_ID_FL_HAS_OBJECT_ID = RBIMPL_SHAPE_ID_FL(2),
48
49 // Means IVs are found at an offset from the object's addr, or in a
50 // malloc allocated side table
51 SHAPE_ID_LAYOUT_ROBJECT = 0,
52
53 // Means this object is a class/module that is NOT RCLASS_BOXABLE, and IV's
54 // are found in the fields_obj found on the rclass struct
55 SHAPE_ID_LAYOUT_RCLASS = RBIMPL_SHAPE_ID_FL(3),
56
57 // Means this object is an extened RObject or a RTypedData and IVs are found in the
58 // fields_obj found on the RObject/RTypedData struct at offset `sizeof(VALUE) * 2`.
59 SHAPE_ID_LAYOUT_EXTENDED = RBIMPL_SHAPE_ID_FL(4),
60 SHAPE_ID_LAYOUT_RDATA = SHAPE_ID_LAYOUT_EXTENDED,
61
62 // Means this is a complicated object: boxable classes, structs, objects
63 // that store IVs on the geniv table
64 SHAPE_ID_LAYOUT_OTHER = SHAPE_ID_LAYOUT_RCLASS | SHAPE_ID_LAYOUT_EXTENDED,
65
66 SHAPE_ID_LAYOUT_MASK = SHAPE_ID_LAYOUT_OTHER,
67
68 SHAPE_ID_FL_NON_CANONICAL_MASK = SHAPE_ID_FL_FROZEN | SHAPE_ID_FL_HAS_OBJECT_ID,
69 SHAPE_ID_FLAGS_MASK = SHAPE_ID_CAPACITY_MASK | SHAPE_ID_FL_NON_CANONICAL_MASK | SHAPE_ID_FL_COMPLEX | SHAPE_ID_LAYOUT_MASK,
70
71 // These parts of the shape id are specific to the object.
72 // Typically, when replicating a shape transition from an object to
73 // its IMEMO/fields, these bits should be stripped.
74 // All other bits are shared between an IMEMO/fields and its owner.
75 SHAPE_ID_FL_PRIVATE_MASK = SHAPE_ID_LAYOUT_MASK|SHAPE_ID_CAPACITY_MASK,
76#undef RBIMPL_SHAPE_ID_FL
77};
78
79// This mask allows to check if a shape_id contains any ivar.
80// It relies on ROOT_SHAPE_WITH_OBJ_ID==1.
81enum shape_id_mask {
82 SHAPE_ID_HAS_IVAR_MASK = SHAPE_ID_FL_COMPLEX | (SHAPE_ID_OFFSET_MASK - 1),
83};
84
85// The interpreter doesn't care about frozen status, embedded capacity, or object id, and
86// has its own checks for physical field layout when reading ivars.
87// So we normalize shape_id by clearing these bits to improve cache hits.
88// JITs however might care about some of it.
89#define SHAPE_ID_READ_ONLY_MASK (~(SHAPE_ID_FL_FROZEN | SHAPE_ID_CAPACITY_MASK | SHAPE_ID_FL_HAS_OBJECT_ID | SHAPE_ID_LAYOUT_MASK))
90// For write it's the same idea, but here we do care about frozen status.
91#define SHAPE_ID_WRITE_MASK (~(SHAPE_ID_CAPACITY_MASK | SHAPE_ID_FL_HAS_OBJECT_ID | SHAPE_ID_LAYOUT_MASK))
92
93typedef uint32_t redblack_id_t;
94
95#define SHAPE_FLAG_SHIFT ((SIZEOF_VALUE * CHAR_BIT) - SHAPE_ID_NUM_BITS)
96#define SHAPE_FLAG_MASK (((VALUE)-1) >> SHAPE_ID_NUM_BITS)
97
98#define SHAPE_MAX_VARIATIONS 8
99
100#define INVALID_SHAPE_ID (SHAPE_BUFFER_SIZE - 1)
101#define ATTR_INDEX_NOT_SET ((attr_index_t)-1)
102
103#define ROOT_SHAPE_ID 0x0
104#define ROOT_SHAPE_WITH_OBJ_ID 0x1
105#define ROOT_COMPLEX_SHAPE_ID (ROOT_SHAPE_ID | SHAPE_ID_FL_COMPLEX)
106#define ROOT_COMPLEX_WITH_OBJ_ID (ROOT_SHAPE_WITH_OBJ_ID | SHAPE_ID_FL_COMPLEX | SHAPE_ID_FL_HAS_OBJECT_ID)
107
108enum shape_type {
109 SHAPE_ROOT,
110 SHAPE_IVAR,
111 SHAPE_OBJ_ID
112};
113
114struct rb_shape {
115 VALUE edges; // id_table from ID (ivar) to next shape
116 ID edge_name; // ID (ivar) for transition from parent to rb_shape
117 redblack_id_t ancestor_index;
118 shape_id_t parent_offset;
119 attr_index_t next_field_index; // Fields are either ivars or internal properties like `object_id`
120 attr_index_t capacity; // Total capacity of the object with this shape
121 enum shape_type type : 8;
122};
123
124typedef struct rb_shape rb_shape_t;
125
126enum shape_flags {
127 SHAPE_FL_FROZEN = 1 << 0,
128 SHAPE_FL_HAS_OBJECT_ID = 1 << 1,
129 SHAPE_FL_COMPLEX = 1 << 2,
130
131 SHAPE_FL_NON_CANONICAL_MASK = SHAPE_FL_FROZEN | SHAPE_FL_HAS_OBJECT_ID,
132};
133
134typedef struct {
135 rb_shape_t *shape_list;
136 attr_index_t max_capacity;
137 ID id_object_id;
139
140RUBY_SYMBOL_EXPORT_BEGIN
141RUBY_EXTERN rb_shape_tree_t rb_shape_tree;
142RUBY_SYMBOL_EXPORT_END
143
144size_t rb_shapes_cache_size(void);
145size_t rb_shapes_count(void);
146
147static inline attr_index_t
148rb_shape_max_capacity(void)
149{
150 return rb_shape_tree.max_capacity;
151}
152
153static inline shape_id_t
154RBASIC_SHAPE_ID(VALUE obj)
155{
157 RUBY_ASSERT(!RB_TYPE_P(obj, T_IMEMO) || IMEMO_TYPE_P(obj, imemo_fields));
158#if RBASIC_SHAPE_ID_FIELD
159 return (shape_id_t)((RBASIC(obj)->shape_id));
160#else
161 return (shape_id_t)((RBASIC(obj)->flags) >> SHAPE_FLAG_SHIFT);
162#endif
163}
164
165// Same as RBASIC_SHAPE_ID but with flags that have no impact
166// on reads removed. e.g. Remove FL_FROZEN.
167static inline shape_id_t
168RBASIC_SHAPE_ID_FOR_READ(VALUE obj)
169{
170 return RBASIC_SHAPE_ID(obj) & SHAPE_ID_READ_ONLY_MASK;
171}
172
173#if RUBY_DEBUG
174bool rb_shape_verify_consistency(VALUE obj, shape_id_t shape_id);
175#endif
176
177static inline void
178RBASIC_SET_FULL_SHAPE_ID_NO_CHECKS(VALUE obj, shape_id_t shape_id)
179{
180#if RBASIC_SHAPE_ID_FIELD
181 RBASIC(obj)->shape_id = (VALUE)shape_id;
182#else
183 // Object shapes are occupying top bits
184 RBASIC(obj)->flags &= SHAPE_FLAG_MASK;
185 RBASIC(obj)->flags |= ((VALUE)(shape_id) << SHAPE_FLAG_SHIFT);
186#endif
187}
188
189static inline shape_id_t
190rb_shape_layout(shape_id_t shape_id)
191{
192 return shape_id & SHAPE_ID_LAYOUT_MASK;
193}
194
195static inline bool
196rb_shape_embedded_p(shape_id_t shape_id)
197{
198 return rb_shape_layout(shape_id) == SHAPE_ID_LAYOUT_ROBJECT;
199}
200
201static inline bool
202rb_shape_extended_p(shape_id_t shape_id)
203{
204 return rb_shape_layout(shape_id) == SHAPE_ID_LAYOUT_EXTENDED;
205}
206
207static inline bool
208rb_obj_shape_embedded_p(VALUE obj)
209{
210 return rb_shape_embedded_p(RBASIC_SHAPE_ID(obj));
211}
212
213static inline bool
214rb_obj_shape_extended_p(VALUE obj)
215{
216 return rb_shape_extended_p(RBASIC_SHAPE_ID(obj));
217}
218
219// Assigns the entire shape_id.
220// shape_id_t is composed of two parts:
221// - The layout and capacity part, which never changes except on GC compaction.
222// - All the other bits that regularly change.
223// In the overwhelming majority of cases, you want to use RBASIC_SET_SHAPE_ID
224// which preserves the object's layout and capacity bits.
225// In rare cases you may want to set all bits.
226static inline void
227RBASIC_SET_FULL_SHAPE_ID(VALUE obj, shape_id_t shape_id)
228{
230 RUBY_ASSERT(!RB_TYPE_P(obj, T_IMEMO) || IMEMO_TYPE_P(obj, imemo_fields));
231
232 RBASIC_SET_FULL_SHAPE_ID_NO_CHECKS(obj, shape_id);
233
234 RUBY_ASSERT(rb_shape_verify_consistency(obj, shape_id));
235}
236
237static inline shape_id_t rb_shape_transition_layout(shape_id_t, shape_id_t);
238
239static inline void
240RBASIC_SET_SHAPE_ID_WITH_LAYOUT(VALUE obj, shape_id_t target_shape_id, shape_id_t layout)
241{
242 RUBY_ASSERT((layout & SHAPE_ID_LAYOUT_MASK) == layout);
243 shape_id_t current_shape_id = RBASIC_SHAPE_ID(obj);
244 current_shape_id = rb_shape_transition_layout(current_shape_id, layout);
245 current_shape_id = (current_shape_id & SHAPE_ID_FL_PRIVATE_MASK) | (target_shape_id & ~SHAPE_ID_FL_PRIVATE_MASK);
246 RBASIC_SET_FULL_SHAPE_ID(obj, current_shape_id);
247}
248
249static inline void
250RBASIC_SET_SHAPE_ID(VALUE obj, shape_id_t shape_id)
251{
253
254 RBASIC_SET_FULL_SHAPE_ID(obj, (
255 (shape_id & ~SHAPE_ID_FL_PRIVATE_MASK) |
256 (RBASIC_SHAPE_ID(obj) & SHAPE_ID_FL_PRIVATE_MASK)
257 ));
258}
259
260static inline shape_id_t
261RSHAPE_FLAGS(shape_id_t shape_id)
262{
263 return shape_id & SHAPE_ID_FLAGS_MASK;
264}
265
266static inline shape_id_t
267RSHAPE_OFFSET(shape_id_t shape_id)
268{
269 return shape_id & SHAPE_ID_OFFSET_MASK;
270}
271
272static inline rb_shape_t *
273RSHAPE(shape_id_t shape_id)
274{
275 shape_id_t offset = RSHAPE_OFFSET(shape_id);
276 RUBY_ASSERT(offset != INVALID_SHAPE_ID);
277 return &rb_shape_tree.shape_list[offset];
278}
279
280int32_t rb_shape_id_offset(void);
281
282RUBY_FUNC_EXPORTED shape_id_t rb_obj_shape_id(VALUE obj);
283bool rb_shape_get_iv_index(shape_id_t shape_id, ID id, attr_index_t *value);
284bool rb_shape_get_iv_index_with_hint(shape_id_t shape_id, ID id, attr_index_t *value, shape_id_t *shape_id_hint);
285bool rb_shape_find_ivar(shape_id_t shape_id, ID id, shape_id_t *ivar_shape);
286
287typedef int rb_shape_foreach_transition_callback(shape_id_t shape_id, void *data);
288bool rb_shape_foreach_field(shape_id_t shape_id, rb_shape_foreach_transition_callback func, void *data);
289
290shape_id_t rb_shape_transition_add_ivar_no_warnings(shape_id_t shape_id, ID id, VALUE klass);
291
292shape_id_t rb_shape_object_id(shape_id_t original_shape_id);
293shape_id_t rb_shape_rebuild(shape_id_t initial_shape_id, shape_id_t dest_shape_id);
294void rb_shape_copy_fields(VALUE dest, VALUE *dest_buf, shape_id_t dest_shape_id, VALUE *src_buf, shape_id_t src_shape_id);
295
296static inline bool
297rb_shape_frozen_p(shape_id_t shape_id)
298{
299 return shape_id & SHAPE_ID_FL_FROZEN;
300}
301
302static inline bool
303rb_shape_complex_p(shape_id_t shape_id)
304{
305 return shape_id & SHAPE_ID_FL_COMPLEX;
306}
307
308static inline bool
309rb_obj_shape_complex_p(VALUE obj)
310{
311 return !RB_SPECIAL_CONST_P(obj) && rb_shape_complex_p(RBASIC_SHAPE_ID(obj));
312}
313
314static inline bool
315rb_shape_has_object_id(shape_id_t shape_id)
316{
317 return shape_id & SHAPE_ID_FL_HAS_OBJECT_ID;
318}
319
320static inline bool
321rb_shape_canonical_p(shape_id_t shape_id)
322{
323 return !(shape_id & SHAPE_ID_FL_NON_CANONICAL_MASK);
324}
325
326static inline attr_index_t
327rb_shape_embedded_capacity(shape_id_t shape_id)
328{
329 return (attr_index_t)((shape_id & SHAPE_ID_CAPACITY_MASK) >> SHAPE_ID_CAPACITY_OFFSET);
330}
331
332static inline size_t
333rb_shape_slot_size(shape_id_t shape_id)
334{
335 return sizeof(struct RBasic) + (rb_shape_embedded_capacity(shape_id) * sizeof(VALUE));
336}
337
338static inline size_t
339rb_obj_shape_slot_size(VALUE obj)
340{
341 RUBY_ASSERT(!RB_TYPE_P(obj, T_IMEMO) || IMEMO_TYPE_P(obj, imemo_fields));
342
343 shape_id_t shape_id = RBASIC_SHAPE_ID(obj);
344 size_t slot_size = rb_shape_slot_size(shape_id);
345
346 if (rb_shape_embedded_capacity(shape_id) == SHAPE_ID_CAPACITY_MAX) {
347 size_t gc_slot_size = rb_gc_obj_slot_size(obj);
348 RUBY_ASSERT(gc_slot_size >= slot_size);
349 return gc_slot_size;
350 }
351
352 return slot_size;
353}
354
355static inline attr_index_t
356rb_shape_capacity_for_slot_size(size_t slot_size)
357{
358 size_t capacity = (slot_size - sizeof(struct RBasic)) / sizeof(VALUE);
359 if (capacity > SHAPE_ID_CAPACITY_MAX) {
360 capacity = SHAPE_ID_CAPACITY_MAX;
361 }
362 return (attr_index_t)capacity;
363}
364
365static inline shape_id_t
366RSHAPE_PARENT_OFFSET(shape_id_t shape_id)
367{
368 return RSHAPE(shape_id)->parent_offset;
369}
370
371static inline bool
372RSHAPE_DIRECT_CHILD_P(shape_id_t parent_offset, shape_id_t child_id)
373{
374 return RSHAPE_PARENT_OFFSET(child_id) == RSHAPE_OFFSET(parent_offset);
375}
376
377static inline enum shape_type
378RSHAPE_TYPE(shape_id_t shape_id)
379{
380 return RSHAPE(shape_id)->type;
381}
382
383static inline bool
384RSHAPE_TYPE_P(shape_id_t shape_id, enum shape_type type)
385{
386 return RSHAPE_TYPE(shape_id) == type;
387}
388
389static inline attr_index_t
390RSHAPE_CAPACITY(shape_id_t shape_id)
391{
392 attr_index_t embedded_capacity = rb_shape_embedded_capacity(shape_id);
393
394 if (embedded_capacity > RSHAPE(shape_id)->capacity) {
395 return embedded_capacity;
396 }
397 else {
398 return RSHAPE(shape_id)->capacity;
399 }
400}
401
402static inline attr_index_t
403RSHAPE_LEN(shape_id_t shape_id)
404{
405 return RSHAPE(shape_id)->next_field_index;
406}
407
408static inline attr_index_t
409RSHAPE_INDEX(shape_id_t shape_id)
410{
411 RUBY_ASSERT(RSHAPE_LEN(shape_id) > 0);
412 return RSHAPE_LEN(shape_id) - 1;
413}
414
415static inline ID
416RSHAPE_EDGE_NAME(shape_id_t shape_id)
417{
418 return RSHAPE(shape_id)->edge_name;
419}
420
421static inline VALUE *
422rb_imemo_fields_ptr(VALUE fields_obj)
423{
424 if (!fields_obj) {
425 return NULL;
426 }
427
428 RUBY_ASSERT(rb_obj_shape_embedded_p(fields_obj));
429 return IMEMO_OBJ_FIELDS(fields_obj)->as.embed.fields;
430}
431
432static inline uint32_t
433RBASIC_FIELDS_COUNT(VALUE obj)
434{
435 return RSHAPE(RBASIC_SHAPE_ID(obj))->next_field_index;
436}
437
438static inline bool
439rb_obj_shape_has_id(VALUE obj)
440{
441 return rb_shape_has_object_id(RBASIC_SHAPE_ID(obj));
442}
443
444static inline bool
445rb_shape_has_ivars(shape_id_t shape_id)
446{
447 return shape_id & SHAPE_ID_HAS_IVAR_MASK;
448}
449
450static inline bool
451rb_obj_shape_has_ivars(VALUE obj)
452{
453 return rb_shape_has_ivars(RBASIC_SHAPE_ID(obj));
454}
455
456static inline bool
457rb_shape_has_fields(shape_id_t shape_id)
458{
459 return shape_id & (SHAPE_ID_OFFSET_MASK | SHAPE_ID_FL_COMPLEX);
460}
461
462static inline bool
463rb_obj_shape_has_fields(VALUE obj)
464{
465 return rb_shape_has_fields(RBASIC_SHAPE_ID(obj));
466}
467
468static inline bool
469rb_obj_gen_fields_p(VALUE obj)
470{
471 switch (TYPE(obj)) {
472 case T_NONE:
473 case T_OBJECT:
474 case T_CLASS:
475 case T_MODULE:
476 case T_IMEMO:
477 return false;
478 default:
479 break;
480 }
481 return rb_obj_shape_has_fields(obj);
482}
483
484static inline shape_id_t
485rb_shape_transition_layout(shape_id_t shape_id, shape_id_t layout)
486{
487 return (shape_id & (~SHAPE_ID_LAYOUT_MASK)) | layout;
488}
489
490static inline shape_id_t
491rb_shape_transition_robject(shape_id_t shape_id)
492{
493 return rb_shape_transition_layout(shape_id, SHAPE_ID_LAYOUT_ROBJECT);
494}
495
496static inline shape_id_t
497rb_shape_transition_extended(shape_id_t shape_id)
498{
499 return rb_shape_transition_layout(shape_id, SHAPE_ID_LAYOUT_EXTENDED);
500}
501
502static inline shape_id_t
503rb_shape_transition_frozen(shape_id_t shape_id)
504{
505 return shape_id | SHAPE_ID_FL_FROZEN;
506}
507
508static inline shape_id_t
509rb_shape_transition_complex(shape_id_t shape_id)
510{
511 shape_id_t next_shape_id = rb_shape_layout(shape_id) | ROOT_COMPLEX_SHAPE_ID;
512
513 if (rb_shape_has_object_id(shape_id)) {
514 next_shape_id = rb_shape_layout(shape_id) | ROOT_COMPLEX_WITH_OBJ_ID;
515 }
516
517 next_shape_id |= shape_id & SHAPE_ID_CAPACITY_MASK;
518
519 RUBY_ASSERT(rb_shape_has_object_id(shape_id) == rb_shape_has_object_id(next_shape_id));
520
521 return next_shape_id;
522}
523
524static inline shape_id_t
525rb_shape_transition_offset(shape_id_t shape_id, shape_id_t offset)
526{
527 offset = RSHAPE_OFFSET(offset);
528 RUBY_ASSERT(RSHAPE_OFFSET(shape_id) == offset || RSHAPE_DIRECT_CHILD_P(shape_id, offset));
529 return RSHAPE_FLAGS(shape_id) | offset;
530}
531
532static inline shape_id_t
533rb_shape_transition_capacity(shape_id_t shape_id, size_t capacity)
534{
535 RUBY_ASSERT(capacity <= SHAPE_ID_CAPACITY_MAX);
536
537 shape_id_t capacity_flags = (shape_id_t)capacity << SHAPE_ID_CAPACITY_OFFSET;
538 return (shape_id & (~SHAPE_ID_CAPACITY_MASK)) | capacity_flags;
539}
540
541static inline shape_id_t
542rb_shape_transition_slot_size(shape_id_t shape_id, size_t slot_size)
543{
544 return rb_shape_transition_capacity(shape_id, rb_shape_capacity_for_slot_size(slot_size));
545}
546
547shape_id_t rb_shape_transition_object_id(shape_id_t shape_id);
548
549static inline shape_id_t
550rb_obj_shape_transition_frozen(VALUE obj)
551{
553 return rb_shape_transition_frozen(RBASIC_SHAPE_ID(obj));
554}
555
556static inline shape_id_t
557rb_obj_shape_transition_complex(VALUE obj)
558{
559 return rb_shape_transition_complex(RBASIC_SHAPE_ID(obj));
560}
561
562static inline shape_id_t
563rb_obj_shape_transition_capacity(VALUE obj, size_t capacity)
564{
565 return rb_shape_transition_capacity(RBASIC_SHAPE_ID(obj), capacity);
566}
567
568static inline shape_id_t
569rb_obj_shape_transition_slot_size(VALUE obj, size_t slot_size)
570{
571 return rb_shape_transition_slot_size(RBASIC_SHAPE_ID(obj), slot_size);
572}
573
574static inline shape_id_t
575rb_obj_shape_transition_object_id(VALUE obj)
576{
577 return rb_shape_transition_object_id(RBASIC_SHAPE_ID(obj));
578}
579
580shape_id_t rb_obj_shape_transition_remove_ivar(VALUE obj, ID id, shape_id_t *removed_shape_id);
581shape_id_t rb_obj_shape_transition_add_ivar(VALUE obj, ID id);
582
583// For ext/objspace
584RUBY_SYMBOL_EXPORT_BEGIN
585typedef void each_shape_callback(shape_id_t shape_id, void *data);
586void rb_shape_each_shape_id(each_shape_callback callback, void *data);
587size_t rb_shape_memsize(shape_id_t shape);
588size_t rb_shape_edges_count(shape_id_t shape_id);
589size_t rb_shape_depth(shape_id_t shape_id);
590RUBY_SYMBOL_EXPORT_END
591
592// Inline cache helpers
593
594typedef struct {
595 attr_index_t index;
596 shape_id_t shape_offset;
598
599union rb_getivar_cache {
600 uint64_t pack;
601 rb_getivar_cache unpack;
602};
603STATIC_ASSERT(rb_getivar_cache_size, sizeof(union rb_getivar_cache) <= sizeof(uint64_t));
604
605#define IVAR_CACHE_INIT ((uint64_t)-1)
606#define ATTR_INDEX_T_NUM_BITS (sizeof(attr_index_t) * CHAR_BIT)
607
608static inline rb_getivar_cache
609rb_getivar_cache_unpack(uint64_t packed)
610{
611 union rb_getivar_cache cache = {
612 .pack = packed,
613 };
614
615 // Because caches may initialized with all bits set (IVAR_CACHE_INIT), and `shape_offset` if 32bits,
616 // we need to remove any potential extra bits set in the "padding".
617 cache.unpack.shape_offset &= SHAPE_ID_OFFSET_MASK;
618 return cache.unpack;
619}
620
621static inline uint64_t
622rb_getivar_cache_pack(shape_id_t shape_offset, attr_index_t index)
623{
624 RUBY_ASSERT(shape_offset == RSHAPE_OFFSET(shape_offset));
625 RUBY_ASSERT(shape_offset != INVALID_SHAPE_ID);
626
627 union rb_getivar_cache cache = {
628 .unpack = {
629 .shape_offset = shape_offset,
630 .index = index,
631 },
632 };
633 return cache.pack;
634}
635
636typedef struct {
637 attr_index_t index;
638 shape_id_t source_shape_offset;
639 shape_id_t dest_shape_offset;
641
642static inline rb_setivar_cache
643rb_setivar_cache_unpack(uint64_t packed)
644{
645 rb_setivar_cache cache = {
646 .index = (attr_index_t)packed,
647 .source_shape_offset = RSHAPE_OFFSET((shape_id_t)(packed >> ATTR_INDEX_T_NUM_BITS)),
648 .dest_shape_offset = RSHAPE_OFFSET((shape_id_t)(packed >> (ATTR_INDEX_T_NUM_BITS + SHAPE_ID_OFFSET_NUM_BITS))),
649 };
650 return cache;
651}
652
653static inline uint64_t
654rb_setivar_cache_pack(shape_id_t shape_offset, shape_id_t dest_shape_offset, attr_index_t index)
655{
656 RUBY_ASSERT(shape_offset == RSHAPE_OFFSET(shape_offset));
657 RUBY_ASSERT(dest_shape_offset == RSHAPE_OFFSET(dest_shape_offset));
658 RUBY_ASSERT(shape_offset == dest_shape_offset || RSHAPE_DIRECT_CHILD_P(shape_offset, dest_shape_offset));
659
660 uint64_t packed_cache = (uint64_t)dest_shape_offset << (ATTR_INDEX_T_NUM_BITS + SHAPE_ID_OFFSET_NUM_BITS);
661 packed_cache |= (uint64_t)shape_offset << ATTR_INDEX_T_NUM_BITS;
662 packed_cache |= (uint64_t)index;
663 return packed_cache;
664}
665
666ALWAYS_INLINE(static shape_id_t rb_setivar_cache_revalidate(shape_id_t shape_id, shape_id_t fields_shape_id, rb_setivar_cache cache));
667static shape_id_t
668rb_setivar_cache_revalidate(shape_id_t shape_id, shape_id_t fields_shape_id, rb_setivar_cache cache)
669{
670 RUBY_ASSERT(shape_id != INVALID_SHAPE_ID);
671 RUBY_ASSERT(cache.dest_shape_offset == INVALID_SHAPE_ID || cache.dest_shape_offset == RSHAPE_OFFSET(cache.dest_shape_offset));
672
673 shape_id_t normalized_shape_id = shape_id & SHAPE_ID_WRITE_MASK;
674 if (UNLIKELY(normalized_shape_id != cache.source_shape_offset)) {
675 return INVALID_SHAPE_ID;
676 }
677
678 if (UNLIKELY(cache.index >= RSHAPE_CAPACITY(fields_shape_id))) {
679 // That's still a hit in term of layout, but the object will need to be resized,
680 // so unfortunately we'll have to go through the slow path regardless...
681 return INVALID_SHAPE_ID;
682 }
683
684 // Cache hit case
685 RUBY_ASSERT(cache.source_shape_offset == cache.dest_shape_offset || RSHAPE_DIRECT_CHILD_P(shape_id, cache.dest_shape_offset));
686 RUBY_ASSERT(cache.index < RSHAPE_CAPACITY(shape_id));
687 RUBY_ASSERT(!rb_shape_frozen_p(shape_id));
688 RUBY_ASSERT(!rb_shape_complex_p(shape_id));
689
690 // We use the cached offset, but combined with the current shape flags.
691 return rb_shape_transition_offset(shape_id, cache.dest_shape_offset);
692}
693
694static inline st_table *
695rb_imemo_fields_complex_tbl(VALUE fields_obj)
696{
697 if (!fields_obj) {
698 return NULL;
699 }
700
701 RUBY_ASSERT(IMEMO_TYPE_P(fields_obj, imemo_fields));
702
703 // Some codepaths unconditionally access the fields_ptr, and assume it can be used as st_table if the
704 // shape is complex.
705 RUBY_ASSERT((st_table *)rb_imemo_fields_ptr(fields_obj) == &IMEMO_OBJ_FIELDS(fields_obj)->as.complex.table);
706
707 return &IMEMO_OBJ_FIELDS(fields_obj)->as.complex.table;
708}
709
710static inline VALUE
711ROBJECT_FIELDS_OBJ(VALUE obj)
712{
713 RBIMPL_ASSERT_TYPE(obj, RUBY_T_OBJECT);
714
715 return rb_obj_shape_embedded_p(obj) ? obj : ROBJECT(obj)->as.extended;
716}
717
718static inline VALUE *
719ROBJECT_EMBEDDED_FIELDS(VALUE obj)
720{
721 return ROBJECT(obj)->as.ary;
722}
723
724static inline VALUE *
725ROBJECT_FIELDS(VALUE obj)
726{
727 RBIMPL_ASSERT_TYPE(obj, RUBY_T_OBJECT);
728
729 return ROBJECT_EMBEDDED_FIELDS(ROBJECT_FIELDS_OBJ(obj));
730}
731
732#endif
#define RUBY_ASSERT(...)
Asserts that the given expression is truthy if and only if RUBY_DEBUG is truthy.
Definition assert.h:219
#define RUBY_EXTERN
Declaration of externally visible global variables.
Definition dllexport.h:45
static bool RB_OBJ_FROZEN(VALUE obj)
Checks if an object is frozen.
Definition fl_type.h:711
#define TYPE(_)
Old name of rb_type.
Definition value_type.h:108
#define T_IMEMO
Old name of RUBY_T_IMEMO.
Definition value_type.h:67
#define T_NONE
Old name of RUBY_T_NONE.
Definition value_type.h:74
#define T_MODULE
Old name of RUBY_T_MODULE.
Definition value_type.h:70
#define T_OBJECT
Old name of RUBY_T_OBJECT.
Definition value_type.h:75
#define T_CLASS
Old name of RUBY_T_CLASS.
Definition value_type.h:58
VALUE type(ANYARGS)
ANYARGS-ed function type.
#define RBASIC(obj)
Convenient casting macro.
Definition rbasic.h:40
#define ROBJECT(obj)
Convenient casting macro.
Definition robject.h:43
static bool RB_SPECIAL_CONST_P(VALUE obj)
Checks if the given object is of enum ruby_special_consts.
Ruby object's base components.
Definition rbasic.h:69
Ruby's ordinal objects.
Definition robject.h:56
VALUE extended
When an object slot is too small or too complex to store instance variables inline,...
Definition robject.h:78
"Typed" user data.
Definition rtypeddata.h:393
Definition st.h:79
uintptr_t ID
Type that represents a Ruby identifier such as a variable name.
Definition value.h:52
uintptr_t VALUE
Type that represents a Ruby object.
Definition value.h:40
static bool RB_TYPE_P(VALUE obj, enum ruby_value_type t)
Queries if the given object is of given type.
Definition value_type.h:376
@ RUBY_T_OBJECT
Definition value_type.h:116