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