Ruby 3.5.0dev (2025-08-27 revision 61d26c35bf8c744b4c59a44536bc58a6c4653ab6)
shape.h (61d26c35bf8c744b4c59a44536bc58a6c4653ab6)
1#ifndef RUBY_SHAPE_H
2#define RUBY_SHAPE_H
3
4#include "internal/gc.h"
5
6typedef uint16_t attr_index_t;
7typedef uint32_t shape_id_t;
8#define SHAPE_ID_NUM_BITS 32
9#define SHAPE_ID_OFFSET_NUM_BITS 19
10
11STATIC_ASSERT(shape_id_num_bits, SHAPE_ID_NUM_BITS == sizeof(shape_id_t) * CHAR_BIT);
12
13#define SHAPE_BUFFER_SIZE (1 << SHAPE_ID_OFFSET_NUM_BITS)
14#define SHAPE_ID_OFFSET_MASK (SHAPE_BUFFER_SIZE - 1)
15
16#define SHAPE_ID_HEAP_INDEX_BITS 3
17#define SHAPE_ID_HEAP_INDEX_MAX ((1 << SHAPE_ID_HEAP_INDEX_BITS) - 1)
18
19#define SHAPE_ID_FL_USHIFT (SHAPE_ID_OFFSET_NUM_BITS + SHAPE_ID_HEAP_INDEX_BITS)
20#define SHAPE_ID_HEAP_INDEX_OFFSET SHAPE_ID_FL_USHIFT
21
22// shape_id_t bits:
23// 0-18 SHAPE_ID_OFFSET_MASK
24// index in rb_shape_tree.shape_list. Allow to access `rb_shape_t *`.
25// 19-21 SHAPE_ID_HEAP_INDEX_MASK
26// index in rb_shape_tree.capacities. Allow to access slot size.
27// 22 SHAPE_ID_FL_FROZEN
28// Whether the object is frozen or not.
29// 23 SHAPE_ID_FL_HAS_OBJECT_ID
30// Whether the object has an `SHAPE_OBJ_ID` transition.
31// 24 SHAPE_ID_FL_TOO_COMPLEX
32// The object is backed by a `st_table`.
33
34enum shape_id_fl_type {
35#define RBIMPL_SHAPE_ID_FL(n) (1<<(SHAPE_ID_FL_USHIFT+n))
36
37 SHAPE_ID_HEAP_INDEX_MASK = RBIMPL_SHAPE_ID_FL(0) | RBIMPL_SHAPE_ID_FL(1) | RBIMPL_SHAPE_ID_FL(2),
38
39 SHAPE_ID_FL_FROZEN = RBIMPL_SHAPE_ID_FL(3),
40 SHAPE_ID_FL_HAS_OBJECT_ID = RBIMPL_SHAPE_ID_FL(4),
41 SHAPE_ID_FL_TOO_COMPLEX = RBIMPL_SHAPE_ID_FL(5),
42
43 SHAPE_ID_FL_NON_CANONICAL_MASK = SHAPE_ID_FL_FROZEN | SHAPE_ID_FL_HAS_OBJECT_ID,
44 SHAPE_ID_FLAGS_MASK = SHAPE_ID_HEAP_INDEX_MASK | SHAPE_ID_FL_NON_CANONICAL_MASK | SHAPE_ID_FL_TOO_COMPLEX,
45
46#undef RBIMPL_SHAPE_ID_FL
47};
48
49// This masks allows to check if a shape_id contains any ivar.
50// It rely on ROOT_SHAPE_WITH_OBJ_ID==1.
51enum {
52 SHAPE_ID_HAS_IVAR_MASK = SHAPE_ID_FL_TOO_COMPLEX | (SHAPE_ID_OFFSET_MASK - 1),
53};
54
55// The interpreter doesn't care about frozen status or slot size when reading ivars.
56// So we normalize shape_id by clearing these bits to improve cache hits.
57// JITs however might care about it.
58#define SHAPE_ID_READ_ONLY_MASK (~(SHAPE_ID_FL_FROZEN | SHAPE_ID_HEAP_INDEX_MASK))
59
60typedef uint32_t redblack_id_t;
61
62#define SHAPE_MAX_FIELDS (attr_index_t)(-1)
63#define SHAPE_FLAG_SHIFT ((SIZEOF_VALUE * CHAR_BIT) - SHAPE_ID_NUM_BITS)
64#define SHAPE_FLAG_MASK (((VALUE)-1) >> SHAPE_ID_NUM_BITS)
65
66#define SHAPE_MAX_VARIATIONS 8
67
68#define INVALID_SHAPE_ID ((shape_id_t)-1)
69#define ATTR_INDEX_NOT_SET ((attr_index_t)-1)
70
71#define ROOT_SHAPE_ID 0x0
72#define ROOT_SHAPE_WITH_OBJ_ID 0x1
73#define ROOT_TOO_COMPLEX_SHAPE_ID (ROOT_SHAPE_ID | SHAPE_ID_FL_TOO_COMPLEX)
74#define ROOT_TOO_COMPLEX_WITH_OBJ_ID (ROOT_SHAPE_WITH_OBJ_ID | SHAPE_ID_FL_TOO_COMPLEX | SHAPE_ID_FL_HAS_OBJECT_ID)
75
76typedef struct redblack_node redblack_node_t;
77
78struct rb_shape {
79 VALUE edges; // id_table from ID (ivar) to next shape
80 ID edge_name; // ID (ivar) for transition from parent to rb_shape
81 redblack_node_t *ancestor_index;
82 shape_id_t parent_id;
83 attr_index_t next_field_index; // Fields are either ivars or internal properties like `object_id`
84 attr_index_t capacity; // Total capacity of the object with this shape
85 uint8_t type;
86};
87
88typedef struct rb_shape rb_shape_t;
89
91 ID key;
92 rb_shape_t *value;
93 redblack_id_t l;
94 redblack_id_t r;
95};
96
97enum shape_type {
98 SHAPE_ROOT,
99 SHAPE_IVAR,
100 SHAPE_OBJ_ID,
101};
102
103enum shape_flags {
104 SHAPE_FL_FROZEN = 1 << 0,
105 SHAPE_FL_HAS_OBJECT_ID = 1 << 1,
106 SHAPE_FL_TOO_COMPLEX = 1 << 2,
107
108 SHAPE_FL_NON_CANONICAL_MASK = SHAPE_FL_FROZEN | SHAPE_FL_HAS_OBJECT_ID,
109};
110
111typedef struct {
112 /* object shapes */
113 rb_shape_t *shape_list;
114 rb_shape_t *root_shape;
115 const attr_index_t *capacities;
116 rb_atomic_t next_shape_id;
117
118 redblack_node_t *shape_cache;
119 unsigned int cache_size;
121
122RUBY_SYMBOL_EXPORT_BEGIN
123RUBY_EXTERN rb_shape_tree_t rb_shape_tree;
124RUBY_SYMBOL_EXPORT_END
125
126static inline shape_id_t
127rb_shapes_count(void)
128{
129 return (shape_id_t)RUBY_ATOMIC_LOAD(rb_shape_tree.next_shape_id);
130}
131
133 uint64_t pack;
134 struct {
135 shape_id_t shape_id;
136 attr_index_t index;
137 } unpack;
138};
139
140static inline shape_id_t
141RBASIC_SHAPE_ID(VALUE obj)
142{
144 RUBY_ASSERT(!RB_TYPE_P(obj, T_IMEMO) || IMEMO_TYPE_P(obj, imemo_fields));
145#if RBASIC_SHAPE_ID_FIELD
146 return (shape_id_t)((RBASIC(obj)->shape_id));
147#else
148 return (shape_id_t)((RBASIC(obj)->flags) >> SHAPE_FLAG_SHIFT);
149#endif
150}
151
152// Same as RBASIC_SHAPE_ID but with flags that have no impact
153// on reads removed. e.g. Remove FL_FROZEN.
154static inline shape_id_t
155RBASIC_SHAPE_ID_FOR_READ(VALUE obj)
156{
157 return RBASIC_SHAPE_ID(obj) & SHAPE_ID_READ_ONLY_MASK;
158}
159
160#if RUBY_DEBUG
161bool rb_shape_verify_consistency(VALUE obj, shape_id_t shape_id);
162#endif
163
164static inline void
165RBASIC_SET_SHAPE_ID(VALUE obj, shape_id_t shape_id)
166{
168 RUBY_ASSERT(!RB_TYPE_P(obj, T_IMEMO) || IMEMO_TYPE_P(obj, imemo_fields));
169#if RBASIC_SHAPE_ID_FIELD
170 RBASIC(obj)->shape_id = (VALUE)shape_id;
171#else
172 // Object shapes are occupying top bits
173 RBASIC(obj)->flags &= SHAPE_FLAG_MASK;
174 RBASIC(obj)->flags |= ((VALUE)(shape_id) << SHAPE_FLAG_SHIFT);
175#endif
176 RUBY_ASSERT(rb_shape_verify_consistency(obj, shape_id));
177}
178
179void rb_set_namespaced_class_shape_id(VALUE obj, shape_id_t shape_id);
180
181static inline void
182RB_SET_SHAPE_ID(VALUE obj, shape_id_t shape_id)
183{
184 switch (BUILTIN_TYPE(obj)) {
185 case T_CLASS:
186 case T_MODULE:
187 rb_set_namespaced_class_shape_id(obj, shape_id);
188 break;
189 default:
190 RBASIC_SET_SHAPE_ID(obj, shape_id);
191 break;
192 }
193}
194
195static inline rb_shape_t *
196RSHAPE(shape_id_t shape_id)
197{
198 uint32_t offset = (shape_id & SHAPE_ID_OFFSET_MASK);
199 RUBY_ASSERT(offset != INVALID_SHAPE_ID);
200
201 return &rb_shape_tree.shape_list[offset];
202}
203
204int32_t rb_shape_id_offset(void);
205
206RUBY_FUNC_EXPORTED shape_id_t rb_obj_shape_id(VALUE obj);
207shape_id_t rb_shape_get_next_iv_shape(shape_id_t shape_id, ID id);
208bool rb_shape_get_iv_index(shape_id_t shape_id, ID id, attr_index_t *value);
209bool rb_shape_get_iv_index_with_hint(shape_id_t shape_id, ID id, attr_index_t *value, shape_id_t *shape_id_hint);
210bool rb_shape_find_ivar(shape_id_t shape_id, ID id, shape_id_t *ivar_shape);
211
212typedef int rb_shape_foreach_transition_callback(shape_id_t shape_id, void *data);
213bool rb_shape_foreach_field(shape_id_t shape_id, rb_shape_foreach_transition_callback func, void *data);
214
215shape_id_t rb_shape_transition_frozen(VALUE obj);
216shape_id_t rb_shape_transition_complex(VALUE obj);
217shape_id_t rb_shape_transition_remove_ivar(VALUE obj, ID id, shape_id_t *removed_shape_id);
218shape_id_t rb_shape_transition_add_ivar(VALUE obj, ID id);
219shape_id_t rb_shape_transition_add_ivar_no_warnings(VALUE obj, ID id);
220shape_id_t rb_shape_transition_object_id(VALUE obj);
221shape_id_t rb_shape_transition_heap(VALUE obj, size_t heap_index);
222shape_id_t rb_shape_object_id(shape_id_t original_shape_id);
223
224void rb_shape_free_all(void);
225
226shape_id_t rb_shape_rebuild(shape_id_t initial_shape_id, shape_id_t dest_shape_id);
227void rb_shape_copy_fields(VALUE dest, VALUE *dest_buf, shape_id_t dest_shape_id, VALUE *src_buf, shape_id_t src_shape_id);
228void rb_shape_copy_complex_ivars(VALUE dest, VALUE obj, shape_id_t src_shape_id, st_table *fields_table);
229
230static inline bool
231rb_shape_too_complex_p(shape_id_t shape_id)
232{
233 return shape_id & SHAPE_ID_FL_TOO_COMPLEX;
234}
235
236static inline bool
237rb_shape_obj_too_complex_p(VALUE obj)
238{
239 return !RB_SPECIAL_CONST_P(obj) && rb_shape_too_complex_p(RBASIC_SHAPE_ID(obj));
240}
241
242static inline bool
243rb_shape_has_object_id(shape_id_t shape_id)
244{
245 return shape_id & SHAPE_ID_FL_HAS_OBJECT_ID;
246}
247
248static inline bool
249rb_shape_canonical_p(shape_id_t shape_id)
250{
251 return !(shape_id & SHAPE_ID_FL_NON_CANONICAL_MASK);
252}
253
254static inline uint8_t
255rb_shape_heap_index(shape_id_t shape_id)
256{
257 return (uint8_t)((shape_id & SHAPE_ID_HEAP_INDEX_MASK) >> SHAPE_ID_HEAP_INDEX_OFFSET);
258}
259
260static inline shape_id_t
261rb_shape_root(size_t heap_id)
262{
263 shape_id_t heap_index = (shape_id_t)(heap_id + 1);
264 shape_id_t heap_flags = heap_index << SHAPE_ID_HEAP_INDEX_OFFSET;
265
266 RUBY_ASSERT((heap_flags & SHAPE_ID_HEAP_INDEX_MASK) == heap_flags);
267 RUBY_ASSERT(rb_shape_heap_index(heap_flags) == heap_index);
268
269 return ROOT_SHAPE_ID | heap_flags;
270}
271
272static inline shape_id_t
273RSHAPE_PARENT_RAW_ID(shape_id_t shape_id)
274{
275 return RSHAPE(shape_id)->parent_id;
276}
277
278static inline bool
279RSHAPE_DIRECT_CHILD_P(shape_id_t parent_id, shape_id_t child_id)
280{
281 return (parent_id & SHAPE_ID_FLAGS_MASK) == (child_id & SHAPE_ID_FLAGS_MASK) &&
282 RSHAPE(child_id)->parent_id == (parent_id & SHAPE_ID_OFFSET_MASK);
283}
284
285static inline enum shape_type
286RSHAPE_TYPE(shape_id_t shape_id)
287{
288 return RSHAPE(shape_id)->type;
289}
290
291static inline bool
292RSHAPE_TYPE_P(shape_id_t shape_id, enum shape_type type)
293{
294 return RSHAPE_TYPE(shape_id) == type;
295}
296
297static inline attr_index_t
298RSHAPE_EMBEDDED_CAPACITY(shape_id_t shape_id)
299{
300 uint8_t heap_index = rb_shape_heap_index(shape_id);
301 if (heap_index) {
302 return rb_shape_tree.capacities[heap_index - 1];
303 }
304 return 0;
305}
306
307static inline attr_index_t
308RSHAPE_CAPACITY(shape_id_t shape_id)
309{
310 attr_index_t embedded_capacity = RSHAPE_EMBEDDED_CAPACITY(shape_id);
311
312 if (embedded_capacity > RSHAPE(shape_id)->capacity) {
313 return embedded_capacity;
314 }
315 else {
316 return RSHAPE(shape_id)->capacity;
317 }
318}
319
320static inline attr_index_t
321RSHAPE_LEN(shape_id_t shape_id)
322{
323 return RSHAPE(shape_id)->next_field_index;
324}
325
326static inline attr_index_t
327RSHAPE_INDEX(shape_id_t shape_id)
328{
329 RUBY_ASSERT(RSHAPE_LEN(shape_id) > 0);
330 return RSHAPE_LEN(shape_id) - 1;
331}
332
333static inline ID
334RSHAPE_EDGE_NAME(shape_id_t shape_id)
335{
336 return RSHAPE(shape_id)->edge_name;
337}
338
339static inline uint32_t
340ROBJECT_FIELDS_CAPACITY(VALUE obj)
341{
342 RBIMPL_ASSERT_TYPE(obj, RUBY_T_OBJECT);
343 // Asking for capacity doesn't make sense when the object is using
344 // a hash table for storing instance variables
345 RUBY_ASSERT(!rb_shape_obj_too_complex_p(obj));
346 return RSHAPE_CAPACITY(RBASIC_SHAPE_ID(obj));
347}
348
349static inline st_table *
350ROBJECT_FIELDS_HASH(VALUE obj)
351{
352 RBIMPL_ASSERT_TYPE(obj, RUBY_T_OBJECT);
353 RUBY_ASSERT(rb_shape_obj_too_complex_p(obj));
354 RUBY_ASSERT(FL_TEST_RAW(obj, ROBJECT_HEAP));
355
356 return (st_table *)ROBJECT(obj)->as.heap.fields;
357}
358
359static inline void
360ROBJECT_SET_FIELDS_HASH(VALUE obj, const st_table *tbl)
361{
362 RBIMPL_ASSERT_TYPE(obj, RUBY_T_OBJECT);
363 RUBY_ASSERT(rb_shape_obj_too_complex_p(obj));
364 RUBY_ASSERT(FL_TEST_RAW(obj, ROBJECT_HEAP));
365
366 ROBJECT(obj)->as.heap.fields = (VALUE *)tbl;
367}
368
369static inline uint32_t
370ROBJECT_FIELDS_COUNT(VALUE obj)
371{
372 if (rb_shape_obj_too_complex_p(obj)) {
373 return (uint32_t)rb_st_table_size(ROBJECT_FIELDS_HASH(obj));
374 }
375 else {
376 RBIMPL_ASSERT_TYPE(obj, RUBY_T_OBJECT);
377 RUBY_ASSERT(!rb_shape_obj_too_complex_p(obj));
378 return RSHAPE(RBASIC_SHAPE_ID(obj))->next_field_index;
379 }
380}
381
382static inline uint32_t
383RBASIC_FIELDS_COUNT(VALUE obj)
384{
385 return RSHAPE(rb_obj_shape_id(obj))->next_field_index;
386}
387
388static inline bool
389rb_shape_obj_has_id(VALUE obj)
390{
391 return rb_shape_has_object_id(RBASIC_SHAPE_ID(obj));
392}
393
394static inline bool
395rb_shape_has_ivars(shape_id_t shape_id)
396{
397 return shape_id & SHAPE_ID_HAS_IVAR_MASK;
398}
399
400static inline bool
401rb_shape_obj_has_ivars(VALUE obj)
402{
403 return rb_shape_has_ivars(RBASIC_SHAPE_ID(obj));
404}
405
406static inline bool
407rb_shape_has_fields(shape_id_t shape_id)
408{
409 return shape_id & (SHAPE_ID_OFFSET_MASK | SHAPE_ID_FL_TOO_COMPLEX);
410}
411
412static inline bool
413rb_shape_obj_has_fields(VALUE obj)
414{
415 return rb_shape_has_fields(RBASIC_SHAPE_ID(obj));
416}
417
418static inline bool
419rb_obj_exivar_p(VALUE obj)
420{
421 switch (TYPE(obj)) {
422 case T_NONE:
423 case T_OBJECT:
424 case T_CLASS:
425 case T_MODULE:
426 case T_IMEMO:
427 return false;
428 default:
429 break;
430 }
431 return rb_shape_obj_has_fields(obj);
432}
433
434// For ext/objspace
435RUBY_SYMBOL_EXPORT_BEGIN
436typedef void each_shape_callback(shape_id_t shape_id, void *data);
437void rb_shape_each_shape_id(each_shape_callback callback, void *data);
438size_t rb_shape_memsize(shape_id_t shape);
439size_t rb_shape_edges_count(shape_id_t shape_id);
440size_t rb_shape_depth(shape_id_t shape_id);
441RUBY_SYMBOL_EXPORT_END
442
443#endif
#define RUBY_ASSERT(...)
Asserts that the given expression is truthy if and only if RUBY_DEBUG is truthy.
Definition assert.h:219
std::atomic< unsigned > rb_atomic_t
Type that is eligible for atomic operations.
Definition atomic.h:69
#define RUBY_ATOMIC_LOAD(var)
Atomic load.
Definition atomic.h:175
#define RUBY_EXTERN
Declaration of externally visible global variables.
Definition dllexport.h:45
#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 FL_TEST_RAW
Old name of RB_FL_TEST_RAW.
Definition fl_type.h:131
#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
#define BUILTIN_TYPE
Old name of RB_BUILTIN_TYPE.
Definition value_type.h:85
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.
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