Ruby 4.1.0dev (2026-08-28 revision 5eb9a6925b805a17dced976d5b741afe5086aab1)
shape.c (5eb9a6925b805a17dced976d5b741afe5086aab1)
1#include "vm_core.h"
2#include "vm_sync.h"
3#include "shape.h"
4#include "symbol.h"
5#include "id_table.h"
6#include "internal/class.h"
7#include "internal/error.h"
8#include "internal/gc.h"
9#include "internal/object.h"
10#include "internal/symbol.h"
11#include "internal/variable.h"
12#include "variable.h"
13#include <stdbool.h>
14
15#ifndef _WIN32
16#include <sys/mman.h>
17#endif
18
19#ifndef SHAPE_DEBUG
20#define SHAPE_DEBUG (VM_CHECK_MODE > 0)
21#endif
22
23#define REDBLACK_CACHE_SIZE (SHAPE_BUFFER_SIZE * 32)
24
25/* This depends on that the allocated memory by Ruby's allocator or
26 * mmap is not located at an odd address. */
27#define SINGLE_CHILD_TAG 0x1
28#define TAG_SINGLE_CHILD(x) (VALUE)((uintptr_t)(x) | SINGLE_CHILD_TAG)
29#define SINGLE_CHILD_MASK (~((uintptr_t)SINGLE_CHILD_TAG))
30#define SINGLE_CHILD_P(x) ((uintptr_t)(x) & SINGLE_CHILD_TAG)
31#define SINGLE_CHILD(x) (rb_shape_t *)((uintptr_t)(x) & SINGLE_CHILD_MASK)
32#define ANCESTOR_CACHE_THRESHOLD 10
33#define MAX_SHAPE_ID (INVALID_SHAPE_ID - 1)
34#define ANCESTOR_SEARCH_MAX_DEPTH 2
35
36// Should be on its own cache line
37static RUBY_ALIGNAS(128) rb_atomic_t redblack_cache_size;
38
39struct redblack_node {
40 ID key;
41 rb_shape_t *value;
42 redblack_id_t l;
43 redblack_id_t r;
44};
45typedef struct redblack_node redblack_node_t;
46
47static redblack_node_t *redblack_cache;
48
49#define LEAF 0
50#define BLACK 0x0
51#define RED 0x1
52
53static inline redblack_node_t *
54redblack_node(redblack_id_t id)
55{
56 return id ? &redblack_cache[id - 1] : LEAF;
57}
58
59static redblack_node_t *
60redblack_left(redblack_node_t *node)
61{
62 if (node->l == LEAF) {
63 return LEAF;
64 }
65 else {
66 RUBY_ASSERT(node->l < redblack_cache_size);
67 redblack_node_t *left = redblack_node(node->l);
68 return left;
69 }
70}
71
72static redblack_node_t *
73redblack_right(redblack_node_t *node)
74{
75 if (node->r == LEAF) {
76 return LEAF;
77 }
78 else {
79 RUBY_ASSERT(node->r < redblack_cache_size);
80 redblack_node_t *right = redblack_node(node->r);
81 return right;
82 }
83}
84
85static redblack_node_t *
86redblack_find0(redblack_node_t *tree, ID key)
87{
88 if (tree == LEAF) {
89 return LEAF;
90 }
91 else {
92 RUBY_ASSERT(redblack_left(tree) == LEAF || redblack_left(tree)->key < tree->key);
93 RUBY_ASSERT(redblack_right(tree) == LEAF || redblack_right(tree)->key > tree->key);
94
95 if (tree->key == key) {
96 return tree;
97 }
98 else {
99 if (key < tree->key) {
100 return redblack_find0(redblack_left(tree), key);
101 }
102 else {
103 return redblack_find0(redblack_right(tree), key);
104 }
105 }
106 }
107}
108
109static redblack_node_t *
110redblack_find(redblack_id_t tree_id, ID key)
111{
112 return redblack_find0(redblack_node(tree_id), key);
113}
114
115static inline rb_shape_t *
116redblack_value(redblack_node_t *node)
117{
118 // Color is stored in the bottom bit of the shape pointer
119 // Mask away the bit so we get the actual pointer back
120 return (rb_shape_t *)((uintptr_t)node->value & ~(uintptr_t)1);
121}
122
123#ifdef HAVE_MMAP
124static inline char
125redblack_color(redblack_node_t *node)
126{
127 return node && ((uintptr_t)node->value & RED);
128}
129
130static inline bool
131redblack_red_p(redblack_node_t *node)
132{
133 return redblack_color(node) == RED;
134}
135
136static redblack_id_t
137redblack_id_for(redblack_node_t *node)
138{
139 RUBY_ASSERT(node || node == LEAF);
140 if (node == LEAF) {
141 return 0;
142 }
143 else {
144 redblack_node_t *redblack_nodes = redblack_cache;
145 redblack_id_t id = (redblack_id_t)(node - redblack_nodes);
146 return id + 1;
147 }
148}
149
150static redblack_node_t *
151redblack_new(char color, ID key, rb_shape_t *value, redblack_node_t *left, redblack_node_t *right)
152{
153 if (redblack_cache_size + 1 >= REDBLACK_CACHE_SIZE) {
154 // We're out of cache, just quit
155 return LEAF;
156 }
157
158 RUBY_ASSERT(left == LEAF || left->key < key);
159 RUBY_ASSERT(right == LEAF || right->key > key);
160
161 redblack_node_t *redblack_nodes = redblack_cache;
162 redblack_node_t *node = &redblack_nodes[RUBY_ATOMIC_FETCH_ADD(redblack_cache_size, 1)];
163 node->key = key;
164 node->value = (rb_shape_t *)((uintptr_t)value | color);
165 node->l = redblack_id_for(left);
166 node->r = redblack_id_for(right);
167 return node;
168}
169
170static redblack_node_t *
171redblack_balance(char color, ID key, rb_shape_t *value, redblack_node_t *left, redblack_node_t *right)
172{
173 if (color == BLACK) {
174 ID new_key, new_left_key, new_right_key;
175 rb_shape_t *new_value, *new_left_value, *new_right_value;
176 redblack_node_t *new_left_left, *new_left_right, *new_right_left, *new_right_right;
177
178 if (redblack_red_p(left) && redblack_red_p(redblack_left(left))) {
179 new_right_key = key;
180 new_right_value = value;
181 new_right_right = right;
182
183 new_key = left->key;
184 new_value = redblack_value(left);
185 new_right_left = redblack_right(left);
186
187 new_left_key = redblack_left(left)->key;
188 new_left_value = redblack_value(redblack_left(left));
189
190 new_left_left = redblack_left(redblack_left(left));
191 new_left_right = redblack_right(redblack_left(left));
192 }
193 else if (redblack_red_p(left) && redblack_red_p(redblack_right(left))) {
194 new_right_key = key;
195 new_right_value = value;
196 new_right_right = right;
197
198 new_left_key = left->key;
199 new_left_value = redblack_value(left);
200 new_left_left = redblack_left(left);
201
202 new_key = redblack_right(left)->key;
203 new_value = redblack_value(redblack_right(left));
204 new_left_right = redblack_left(redblack_right(left));
205 new_right_left = redblack_right(redblack_right(left));
206 }
207 else if (redblack_red_p(right) && redblack_red_p(redblack_left(right))) {
208 new_left_key = key;
209 new_left_value = value;
210 new_left_left = left;
211
212 new_right_key = right->key;
213 new_right_value = redblack_value(right);
214 new_right_right = redblack_right(right);
215
216 new_key = redblack_left(right)->key;
217 new_value = redblack_value(redblack_left(right));
218 new_left_right = redblack_left(redblack_left(right));
219 new_right_left = redblack_right(redblack_left(right));
220 }
221 else if (redblack_red_p(right) && redblack_red_p(redblack_right(right))) {
222 new_left_key = key;
223 new_left_value = value;
224 new_left_left = left;
225
226 new_key = right->key;
227 new_value = redblack_value(right);
228 new_left_right = redblack_left(right);
229
230 new_right_key = redblack_right(right)->key;
231 new_right_value = redblack_value(redblack_right(right));
232 new_right_left = redblack_left(redblack_right(right));
233 new_right_right = redblack_right(redblack_right(right));
234 }
235 else {
236 return redblack_new(color, key, value, left, right);
237 }
238
239 RUBY_ASSERT(new_left_key < new_key);
240 RUBY_ASSERT(new_right_key > new_key);
241 RUBY_ASSERT(new_left_left == LEAF || new_left_left->key < new_left_key);
242 RUBY_ASSERT(new_left_right == LEAF || new_left_right->key > new_left_key);
243 RUBY_ASSERT(new_left_right == LEAF || new_left_right->key < new_key);
244 RUBY_ASSERT(new_right_left == LEAF || new_right_left->key < new_right_key);
245 RUBY_ASSERT(new_right_left == LEAF || new_right_left->key > new_key);
246 RUBY_ASSERT(new_right_right == LEAF || new_right_right->key > new_right_key);
247
248 return redblack_new(
249 RED, new_key, new_value,
250 redblack_new(BLACK, new_left_key, new_left_value, new_left_left, new_left_right),
251 redblack_new(BLACK, new_right_key, new_right_value, new_right_left, new_right_right));
252 }
253
254 return redblack_new(color, key, value, left, right);
255}
256
257static redblack_node_t *
258redblack_insert_aux(redblack_node_t *tree, ID key, rb_shape_t *value)
259{
260 if (tree == LEAF) {
261 return redblack_new(RED, key, value, LEAF, LEAF);
262 }
263 else {
264 redblack_node_t *left, *right;
265 if (key < tree->key) {
266 left = redblack_insert_aux(redblack_left(tree), key, value);
267 RUBY_ASSERT(left != LEAF);
268 right = redblack_right(tree);
269 RUBY_ASSERT(right == LEAF || right->key > tree->key);
270 }
271 else if (key > tree->key) {
272 left = redblack_left(tree);
273 RUBY_ASSERT(left == LEAF || left->key < tree->key);
274 right = redblack_insert_aux(redblack_right(tree), key, value);
275 RUBY_ASSERT(right != LEAF);
276 }
277 else {
278 return tree;
279 }
280
281 return redblack_balance(
282 redblack_color(tree),
283 tree->key,
284 redblack_value(tree),
285 left,
286 right
287 );
288 }
289}
290
291static redblack_node_t *
292redblack_force_black(redblack_node_t *node)
293{
294 node->value = redblack_value(node);
295 return node;
296}
297
298static redblack_id_t
299redblack_insert(redblack_node_t *tree, ID key, rb_shape_t *value)
300{
301 redblack_node_t *root = redblack_insert_aux(tree, key, value);
302
303 if (redblack_red_p(root)) {
304 return redblack_id_for(redblack_force_black(root));
305 }
306 else {
307 return redblack_id_for(root);
308 }
309}
310#endif
311
312static VALUE shape_tree_obj = Qfalse;
313rb_shape_tree_t rb_shape_tree = { 0 };
314
315// Should be on its own cache line
316static RUBY_ALIGNAS(128) rb_atomic_t shape_next_id;
317
318static rb_shape_t *
319rb_shape_get_root_shape(void)
320{
321 return rb_shape_tree.shape_list;
322}
323
324static void
325shape_tree_mark_and_move(void *data)
326{
327 rb_shape_t *cursor = rb_shape_get_root_shape();
328 rb_shape_t *end = RSHAPE(shape_next_id - 1);
329 while (cursor <= end) {
330 if (cursor->edges && !SINGLE_CHILD_P(cursor->edges)) {
331 rb_gc_mark_and_move(&cursor->edges);
332 }
333 cursor++;
334 }
335}
336
337size_t
338rb_shapes_cache_size(void)
339{
340 return redblack_cache ? redblack_cache_size : 0;
341}
342
343size_t
344rb_shapes_count(void)
345{
346 return (size_t)RUBY_ATOMIC_LOAD(shape_next_id);
347}
348
349static size_t
350shape_tree_memsize(const void *data)
351{
352 if (redblack_cache) {
353 return redblack_cache_size * sizeof(redblack_node_t);
354 }
355 return 0;
356}
357
358static const rb_data_type_t shape_tree_type = {
359 .wrap_struct_name = "VM/shape_tree",
360 .function = {
361 .dmark = shape_tree_mark_and_move,
362 .dfree = NULL, // Nothing to free, done at VM exit in rb_shape_free_all,
363 .dsize = shape_tree_memsize,
364 .dcompact = shape_tree_mark_and_move,
365 },
366 .flags = RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_WB_PROTECTED,
367};
368
369
370/*
371 * Shape getters
372 */
373
374static inline shape_id_t
375SHAPE_OFFSET(rb_shape_t *shape)
376{
377 RUBY_ASSERT(shape);
378 return (shape_id_t)(shape - rb_shape_tree.shape_list);
379}
380
381static inline shape_id_t
382SHAPE_ID(rb_shape_t *shape, shape_id_t previous_shape_id)
383{
384 RUBY_ASSERT(shape);
385 shape_id_t offset = (shape_id_t)(shape - rb_shape_tree.shape_list);
386 return offset | RSHAPE_FLAGS(previous_shape_id);
387}
388
389void
390rb_shape_each_shape_id(each_shape_callback callback, void *data)
391{
392 rb_shape_t *start = rb_shape_get_root_shape();
393 rb_shape_t *cursor = start;
394 rb_shape_t *end = RSHAPE(RUBY_ATOMIC_LOAD(shape_next_id));
395 while (cursor < end) {
396 callback((shape_id_t)(cursor - start), data);
397 cursor += 1;
398 }
399}
400
401RUBY_FUNC_EXPORTED shape_id_t
402rb_obj_shape_id(VALUE obj)
403{
404 if (RB_SPECIAL_CONST_P(obj)) {
405 rb_bug("rb_obj_shape_id: called on a special constant");
406 }
407
408 if (BUILTIN_TYPE(obj) == T_CLASS || BUILTIN_TYPE(obj) == T_MODULE) {
409 VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj);
410 shape_id_t base = ROOT_SHAPE_ID;
411 if (fields_obj) {
412 // Remove the layout and capacity from the fields object. We want to
413 // combine the shape tree state of the fields object with the layout
414 // and object slot capacity of the class / module object.
415 base = RBASIC_SHAPE_ID(fields_obj) & ~(SHAPE_ID_LAYOUT_MASK | SHAPE_ID_CAPACITY_MASK);
416 }
417 shape_id_t shape_id = RBASIC_SHAPE_ID(obj);
418 return rb_shape_layout(shape_id) | (shape_id & SHAPE_ID_CAPACITY_MASK) | base;
419 }
420 return RBASIC_SHAPE_ID(obj);
421}
422
423size_t
424rb_shape_depth(shape_id_t shape_id)
425{
426 size_t depth = 1;
427 rb_shape_t *shape = RSHAPE(shape_id);
428
429 while (shape->parent_offset != INVALID_SHAPE_ID) {
430 depth++;
431 shape = RSHAPE(shape->parent_offset);
432 }
433
434 return depth;
435}
436
437static rb_shape_t *
438shape_alloc(void)
439{
440 shape_id_t current, new_id;
441
442 do {
443 current = RUBY_ATOMIC_LOAD(shape_next_id);
444 if (current > MAX_SHAPE_ID) {
445 return NULL; // Out of shapes
446 }
447 new_id = current + 1;
448 } while (current != RUBY_ATOMIC_CAS(shape_next_id, current, new_id));
449
450 return &rb_shape_tree.shape_list[current];
451}
452
453static rb_shape_t *
454rb_shape_alloc_with_parent_offset(ID edge_name, shape_id_t parent_offset)
455{
456 rb_shape_t *shape = shape_alloc();
457 if (!shape) return NULL;
458
459 shape->edge_name = edge_name;
460 shape->next_field_index = 0;
461 shape->parent_offset = parent_offset;
462 shape->edges = 0;
463
464 return shape;
465}
466
467static rb_shape_t *
468rb_shape_alloc(ID edge_name, rb_shape_t *parent, enum shape_type type)
469{
470 rb_shape_t *shape = rb_shape_alloc_with_parent_offset(edge_name, SHAPE_OFFSET(parent));
471 if (!shape) return NULL;
472
473 shape->type = (uint8_t)type;
474 shape->capacity = parent->capacity;
475 shape->edges = 0;
476 return shape;
477}
478
479#ifdef HAVE_MMAP
480static redblack_node_t *
481redblack_cache_ancestors(rb_shape_t *shape)
482{
483 if (!(shape->ancestor_index || shape->parent_offset == INVALID_SHAPE_ID)) {
484 redblack_node_t *parent_index_node = redblack_cache_ancestors(RSHAPE(shape->parent_offset));
485
486 if (shape->type == SHAPE_IVAR) {
487 shape->ancestor_index = redblack_insert(parent_index_node, shape->edge_name, shape);
488
489#if RUBY_DEBUG
490 if (shape->ancestor_index) {
491 redblack_node_t *inserted_node = redblack_find(shape->ancestor_index, shape->edge_name);
492 RUBY_ASSERT(inserted_node);
493 RUBY_ASSERT(redblack_value(inserted_node) == shape);
494 }
495#endif
496 }
497 else {
498 shape->ancestor_index = redblack_id_for(parent_index_node);
499 }
500 }
501
502 return redblack_node(shape->ancestor_index);
503}
504#else
505static redblack_node_t *
506redblack_cache_ancestors(rb_shape_t *shape)
507{
508 return LEAF;
509}
510#endif
511
512static attr_index_t
513shape_grow_capa(attr_index_t current_capa)
514{
515 size_t next_size = rb_obj_embedded_size(current_capa + 1);
516 if (UNLIKELY(!rb_gc_size_allocatable_p(next_size))) {
517 return rb_shape_max_capacity();
518 }
519
520 attr_index_t next_capa = rb_shape_capacity_for_slot_size(rb_gc_size_slot_size(next_size));
521 RUBY_ASSERT(next_capa > current_capa);
522 return next_capa;
523}
524
525static rb_shape_t *
526rb_shape_alloc_new_child(ID id, rb_shape_t *shape, enum shape_type shape_type)
527{
528 rb_shape_t *new_shape = rb_shape_alloc(id, shape, shape_type);
529 if (!new_shape) return NULL;
530
531 switch (shape_type) {
532 case SHAPE_OBJ_ID:
533 case SHAPE_IVAR:
534 if (UNLIKELY(shape->next_field_index >= shape->capacity)) {
535 RUBY_ASSERT(shape->next_field_index == shape->capacity);
536 new_shape->capacity = shape_grow_capa(shape->capacity);
537 }
538
539 RUBY_ASSERT(new_shape->capacity > shape->next_field_index);
540 new_shape->next_field_index = shape->next_field_index + 1;
541 if (new_shape->next_field_index > ANCESTOR_CACHE_THRESHOLD) {
542 RB_VM_LOCKING() {
543 redblack_cache_ancestors(new_shape);
544 }
545 }
546 break;
547 case SHAPE_ROOT:
548 rb_bug("Unreachable");
549 break;
550 }
551
552 return new_shape;
553}
554
555static rb_shape_t *
556get_next_shape_internal_atomic(rb_shape_t *shape, ID id, enum shape_type shape_type, bool *variation_created, bool new_variations_allowed)
557{
558 rb_shape_t *res = NULL;
559
560 *variation_created = false;
561 VALUE edges_table;
562
563retry:
564 edges_table = RUBY_ATOMIC_VALUE_LOAD(shape->edges);
565
566 // If the current shape has children
567 if (edges_table) {
568 // Check if it only has one child
569 if (SINGLE_CHILD_P(edges_table)) {
570 rb_shape_t *child = SINGLE_CHILD(edges_table);
571 // If the one child has a matching edge name, then great,
572 // we found what we want.
573 if (child->edge_name == id) {
574 res = child;
575 }
576 }
577 else {
578 // If it has more than one child, do a hash lookup to find it.
579 VALUE lookup_result;
580 if (rb_managed_id_table_lookup(edges_table, id, &lookup_result)) {
581 res = (rb_shape_t *)lookup_result;
582 }
583 }
584 }
585
586 // If we didn't find the shape we're looking for and we're allowed more variations we create it.
587 if (!res && new_variations_allowed) {
588 VALUE new_edges = 0;
589
590 rb_shape_t *new_shape = rb_shape_alloc_new_child(id, shape, shape_type);
591
592 // If we're out of shapes, return NULL
593 if (new_shape) {
594 if (!edges_table) {
595 // If the shape had no edge yet, we can directly set the new child
596 new_edges = TAG_SINGLE_CHILD(new_shape);
597 }
598 else {
599 // If the edge was single child we need to allocate a table.
600 if (SINGLE_CHILD_P(edges_table)) {
601 rb_shape_t *old_child = SINGLE_CHILD(edges_table);
602 new_edges = rb_managed_id_table_new(2);
603 rb_managed_id_table_insert(new_edges, old_child->edge_name, (VALUE)old_child);
604 }
605 else {
606 new_edges = rb_managed_id_table_dup(edges_table);
607 }
608
609 rb_managed_id_table_insert(new_edges, new_shape->edge_name, (VALUE)new_shape);
610 *variation_created = true;
611 }
612
613 if (edges_table != RUBY_ATOMIC_VALUE_CAS(shape->edges, edges_table, new_edges)) {
614 // Another thread updated the table;
615 goto retry;
616 }
617 RB_OBJ_WRITTEN(shape_tree_obj, Qundef, new_edges);
618 res = new_shape;
619 RB_GC_GUARD(new_edges);
620 }
621 }
622
623 return res;
624}
625
626static rb_shape_t *
627get_next_shape_internal(rb_shape_t *shape, ID id, enum shape_type shape_type, bool *variation_created, bool new_variations_allowed)
628{
629 if (rb_multi_ractor_p()) {
630 return get_next_shape_internal_atomic(shape, id, shape_type, variation_created, new_variations_allowed);
631 }
632
633 rb_shape_t *res = NULL;
634 *variation_created = false;
635
636 VALUE edges_table = shape->edges;
637
638 // If the current shape has children
639 if (edges_table) {
640 // Check if it only has one child
641 if (SINGLE_CHILD_P(edges_table)) {
642 rb_shape_t *child = SINGLE_CHILD(edges_table);
643 // If the one child has a matching edge name, then great,
644 // we found what we want.
645 if (child->edge_name == id) {
646 res = child;
647 }
648 }
649 else {
650 // If it has more than one child, do a hash lookup to find it.
651 VALUE lookup_result;
652 if (rb_managed_id_table_lookup(edges_table, id, &lookup_result)) {
653 res = (rb_shape_t *)lookup_result;
654 }
655 }
656 }
657
658 // If we didn't find the shape we're looking for we create it.
659 if (!res) {
660 // If we're not allowed to create a new variation, of if we're out of shapes
661 // we return COMPLEX_SHAPE.
662 if (!new_variations_allowed || rb_shapes_count() > MAX_SHAPE_ID) {
663 res = NULL;
664 }
665 else {
666 rb_shape_t *new_shape = rb_shape_alloc_new_child(id, shape, shape_type);
667
668 if (!edges_table) {
669 // If the shape had no edge yet, we can directly set the new child
670 shape->edges = TAG_SINGLE_CHILD(new_shape);
671 }
672 else {
673 // If the edge was single child we need to allocate a table.
674 if (SINGLE_CHILD_P(edges_table)) {
675 rb_shape_t *old_child = SINGLE_CHILD(edges_table);
676 VALUE new_edges = rb_managed_id_table_new(2);
677 rb_managed_id_table_insert(new_edges, old_child->edge_name, (VALUE)old_child);
678 RB_OBJ_WRITE(shape_tree_obj, &shape->edges, new_edges);
679 }
680
681 rb_managed_id_table_insert(shape->edges, new_shape->edge_name, (VALUE)new_shape);
682 *variation_created = true;
683 }
684
685 res = new_shape;
686 }
687 }
688
689 return res;
690}
691
692shape_id_t
693rb_shape_transition_object_id(shape_id_t original_shape_id)
694{
695 RUBY_ASSERT(!rb_shape_has_object_id(original_shape_id));
696
697 rb_shape_t *original_shape = RSHAPE(original_shape_id);
698
699 bool dont_care;
700 rb_shape_t *shape = NULL;
701 if (LIKELY(original_shape->next_field_index < rb_shape_max_capacity())) {
702 shape = get_next_shape_internal(original_shape, rb_shape_tree.id_object_id, SHAPE_OBJ_ID, &dont_care, true);
703 }
704 if (!shape) {
705 return rb_shape_layout(original_shape_id) | ROOT_COMPLEX_WITH_OBJ_ID | RSHAPE_FLAGS(original_shape_id);
706 }
707
708 RUBY_ASSERT(shape);
709 return SHAPE_ID(shape, original_shape_id) | SHAPE_ID_FL_HAS_OBJECT_ID;
710}
711
712shape_id_t
713rb_shape_object_id(shape_id_t original_shape_id)
714{
715 RUBY_ASSERT(rb_shape_has_object_id(original_shape_id));
716
717 rb_shape_t *shape = RSHAPE(original_shape_id);
718 while (shape->type != SHAPE_OBJ_ID) {
719 if (UNLIKELY(shape->parent_offset == INVALID_SHAPE_ID)) {
720 rb_bug("Missing object_id in shape tree");
721 }
722 shape = RSHAPE(shape->parent_offset);
723 }
724
725 return SHAPE_ID(shape, original_shape_id) | SHAPE_ID_FL_HAS_OBJECT_ID;
726}
727
728static bool
729shape_get_iv_index(rb_shape_t *shape, ID id, attr_index_t *value)
730{
731 while (shape->parent_offset != INVALID_SHAPE_ID) {
732 if (shape->edge_name == id) {
733 enum shape_type shape_type;
734 shape_type = (enum shape_type)shape->type;
735
736 switch (shape_type) {
737 case SHAPE_IVAR:
738 RUBY_ASSERT(shape->next_field_index > 0);
739 *value = shape->next_field_index - 1;
740 return true;
741 case SHAPE_ROOT:
742 return false;
743 case SHAPE_OBJ_ID:
744 rb_bug("Ivar should not exist on transition");
745 }
746 }
747
748 shape = RSHAPE(shape->parent_offset);
749 }
750
751 return false;
752}
753
754static inline rb_shape_t *
755shape_get_next(rb_shape_t *shape, enum shape_type shape_type, VALUE klass, ID id, bool emit_warnings)
756{
757 RUBY_ASSERT(!is_instance_id(id) || RTEST(rb_sym2str(ID2SYM(id))));
758
759#if RUBY_DEBUG
760 attr_index_t index;
761 if (shape_get_iv_index(shape, id, &index)) {
762 rb_bug("rb_shape_get_next: trying to create ivar that already exists at index %u", index);
763 }
764#endif
765
766 RUBY_ASSERT(SHAPE_ID_CAPACITY_MAX > 0);
767 RUBY_ASSERT(rb_shape_max_capacity() > 0);
768 if (UNLIKELY(shape->next_field_index >= rb_shape_max_capacity())) {
769 return NULL;
770 }
771
772 bool allow_new_shape = RCLASS_VARIATION_COUNT(klass) < SHAPE_MAX_VARIATIONS;
773 bool variation_created = false;
774 rb_shape_t *new_shape = get_next_shape_internal(shape, id, shape_type, &variation_created, allow_new_shape);
775
776 if (!new_shape) {
777 // We could create a new variation, transitioning to COMPLEX.
778 return NULL;
779 }
780
781 // Check if we should update max_iv_count on the object's class
782 if (new_shape->next_field_index > RCLASS_MAX_IV_COUNT(klass) && !RCLASS_EXPECT_NO_IVAR(klass)) {
783 RCLASS_SET_MAX_IV_COUNT(klass, new_shape->next_field_index);
784 }
785
786 if (variation_created) {
787 RCLASS_VARIATION_COUNT(klass)++;
788
789 if (emit_warnings && rb_warning_category_enabled_p(RB_WARN_CATEGORY_PERFORMANCE)) {
790 if (RCLASS_VARIATION_COUNT(klass) >= SHAPE_MAX_VARIATIONS) {
793 "The class %"PRIsVALUE" reached %d shape variations, instance variables accesses will be slower and memory usage increased.\n"
794 "It is recommended to define instance variables in a consistent order, for instance by eagerly defining them all in the #initialize method.",
795 rb_class_path(klass),
796 SHAPE_MAX_VARIATIONS
797 );
798 }
799 }
800 }
801
802 return new_shape;
803}
804
805static VALUE
806obj_get_owner_class(VALUE obj)
807{
808 VALUE klass;
809 if (IMEMO_TYPE_P(obj, imemo_fields)) {
810 VALUE owner = rb_imemo_fields_owner(obj);
811 switch (BUILTIN_TYPE(owner)) {
812 case T_CLASS:
813 case T_MODULE:
814 klass = rb_singleton_class(owner);
815 break;
816 default:
817 klass = rb_obj_class(owner);
818 break;
819 }
820 }
821 else {
822 klass = rb_obj_class(obj);
823 }
824 return klass;
825}
826
827static rb_shape_t *
828remove_shape_recursive(VALUE obj, rb_shape_t *shape, ID id, rb_shape_t **removed_shape)
829{
830 if (shape->parent_offset == INVALID_SHAPE_ID) {
831 // We've hit the top of the shape tree and couldn't find the
832 // IV we wanted to remove, so return NULL
833 *removed_shape = NULL;
834 return NULL;
835 }
836 else {
837 if (shape->type == SHAPE_IVAR && shape->edge_name == id) {
838 *removed_shape = shape;
839
840 return RSHAPE(shape->parent_offset);
841 }
842 else {
843 // This isn't the IV we want to remove, keep walking up.
844 rb_shape_t *new_parent = remove_shape_recursive(obj, RSHAPE(shape->parent_offset), id, removed_shape);
845
846 // We found a new parent. Create a child of the new parent that
847 // has the same attributes as this shape.
848 if (new_parent) {
849 VALUE klass = obj_get_owner_class(obj);
850 rb_shape_t *new_child = shape_get_next(new_parent, shape->type, klass, shape->edge_name, true);
851 RUBY_ASSERT(!new_child || new_child->capacity <= shape->capacity);
852 return new_child;
853 }
854 else {
855 // We went all the way to the top of the shape tree and couldn't
856 // find an IV to remove so return NULL.
857 return NULL;
858 }
859 }
860 }
861}
862
863shape_id_t
864rb_obj_shape_transition_remove_ivar(VALUE obj, ID id, shape_id_t *removed_shape_id)
865{
866 shape_id_t original_shape_id = RBASIC_SHAPE_ID(obj);
867 RUBY_ASSERT(!rb_shape_frozen_p(original_shape_id));
868
869 if (rb_shape_complex_p(original_shape_id)) {
870 return original_shape_id;
871 }
872
873 rb_shape_t *removed_shape = NULL;
874 rb_shape_t *new_shape = remove_shape_recursive(obj, RSHAPE(original_shape_id), id, &removed_shape);
875
876 if (removed_shape) {
877 *removed_shape_id = SHAPE_OFFSET(removed_shape);
878 }
879
880 if (new_shape) {
881 return SHAPE_ID(new_shape, original_shape_id);
882 }
883 else if (removed_shape) {
884 // We found the shape to remove, but couldn't create a new variation.
885 // We must transition to COMPLEX.
886 shape_id_t next_shape_id = rb_shape_transition_complex(original_shape_id);
887 RUBY_ASSERT(rb_shape_has_object_id(next_shape_id) == rb_shape_has_object_id(original_shape_id));
888 return next_shape_id;
889 }
890 return original_shape_id;
891}
892
893shape_id_t
894rb_obj_shape_transition_add_ivar(VALUE obj, ID id)
895{
896 shape_id_t original_shape_id = RBASIC_SHAPE_ID(obj);
897 RUBY_ASSERT(!rb_shape_frozen_p(original_shape_id));
898
899 VALUE klass = obj_get_owner_class(obj);
900 rb_shape_t *next_shape = shape_get_next(RSHAPE(original_shape_id), SHAPE_IVAR, klass, id, true);
901 if (next_shape) {
902 return SHAPE_ID(next_shape, original_shape_id);
903 }
904 else {
905 return rb_shape_transition_complex(original_shape_id);
906 }
907}
908
909shape_id_t
910rb_shape_transition_add_ivar_no_warnings(shape_id_t original_shape_id, ID id, VALUE klass)
911{
912 RUBY_ASSERT(!rb_shape_frozen_p(original_shape_id));
913
914 rb_shape_t *next_shape = shape_get_next(RSHAPE(original_shape_id), SHAPE_IVAR, klass, id, false);
915 if (next_shape) {
916 return SHAPE_ID(next_shape, original_shape_id);
917 }
918 else {
919 return rb_shape_transition_complex(original_shape_id);
920 }
921}
922
923// Same as rb_shape_get_iv_index, but uses a provided valid shape id and index
924// to return a result faster if branches of the shape tree are closely related.
925bool
926rb_shape_get_iv_index_with_hint(shape_id_t shape_id, ID id, attr_index_t *value, shape_id_t *shape_id_hint)
927{
928 attr_index_t index_hint = *value;
929
930 if (*shape_id_hint == INVALID_SHAPE_ID) {
931 *shape_id_hint = shape_id;
932 return rb_shape_get_iv_index(shape_id, id, value);
933 }
934
935 rb_shape_t *shape = RSHAPE(shape_id);
936 rb_shape_t *initial_shape = shape;
937 rb_shape_t *shape_hint = RSHAPE(*shape_id_hint);
938
939 // We assume it's likely shape_id_hint and shape_id have a close common
940 // ancestor, so we check up to ANCESTOR_SEARCH_MAX_DEPTH ancestors before
941 // eventually using the index, as in case of a match it will be faster.
942 // However if the shape doesn't have an index, we walk the entire tree.
943 int depth = INT_MAX;
944 if (shape->ancestor_index && shape->next_field_index >= ANCESTOR_CACHE_THRESHOLD) {
945 depth = ANCESTOR_SEARCH_MAX_DEPTH;
946 }
947
948 while (depth > 0 && shape->next_field_index > index_hint) {
949 while (shape_hint->next_field_index > shape->next_field_index) {
950 shape_hint = RSHAPE(shape_hint->parent_offset);
951 }
952
953 if (shape_hint == shape) {
954 // We've found a common ancestor so use the index hint
955 *value = index_hint;
956 *shape_id_hint = SHAPE_OFFSET(shape);
957 return true;
958 }
959 if (shape->edge_name == id) {
960 // We found the matching id before a common ancestor
961 *value = shape->next_field_index - 1;
962 *shape_id_hint = SHAPE_OFFSET(shape);
963 return true;
964 }
965
966 shape = RSHAPE(shape->parent_offset);
967 depth--;
968 }
969
970 // If the original shape had an index but its ancestor doesn't
971 // we switch back to the original one as it will be faster.
972 if (!shape->ancestor_index && initial_shape->ancestor_index) {
973 shape = initial_shape;
974 }
975 *shape_id_hint = shape_id;
976 return shape_get_iv_index(shape, id, value);
977}
978
979static bool
980shape_cache_find_ivar(rb_shape_t *shape, ID id, rb_shape_t **ivar_shape)
981{
982 if (shape->ancestor_index && shape->next_field_index >= ANCESTOR_CACHE_THRESHOLD) {
983 redblack_node_t *node = redblack_find(shape->ancestor_index, id);
984 if (node) {
985 *ivar_shape = redblack_value(node);
986
987 return true;
988 }
989 }
990
991 return false;
992}
993
994static bool
995shape_find_ivar(rb_shape_t *shape, ID id, rb_shape_t **ivar_shape)
996{
997 while (shape->parent_offset != INVALID_SHAPE_ID) {
998 if (shape->edge_name == id) {
999 RUBY_ASSERT(shape->type == SHAPE_IVAR);
1000 *ivar_shape = shape;
1001 return true;
1002 }
1003
1004 shape = RSHAPE(shape->parent_offset);
1005 }
1006
1007 return false;
1008}
1009
1010bool
1011rb_shape_find_ivar(shape_id_t current_shape_id, ID id, shape_id_t *ivar_shape_id)
1012{
1013 RUBY_ASSERT(!rb_shape_complex_p(current_shape_id));
1014
1015 rb_shape_t *shape = RSHAPE(current_shape_id);
1016 rb_shape_t *ivar_shape;
1017
1018 if (!shape_cache_find_ivar(shape, id, &ivar_shape)) {
1019 // If it wasn't in the ancestor cache, then don't do a linear search
1020 if (shape->ancestor_index && shape->next_field_index >= ANCESTOR_CACHE_THRESHOLD) {
1021 return false;
1022 }
1023 else {
1024 if (!shape_find_ivar(shape, id, &ivar_shape)) {
1025 return false;
1026 }
1027 }
1028 }
1029
1030 *ivar_shape_id = SHAPE_ID(ivar_shape, current_shape_id);
1031
1032 return true;
1033}
1034
1035bool
1036rb_shape_get_iv_index(shape_id_t shape_id, ID id, attr_index_t *value)
1037{
1038 // It doesn't make sense to ask for the index of an IV that's stored
1039 // on an object that is "too complex" as it uses a hash for storing IVs
1040 RUBY_ASSERT(!rb_shape_complex_p(shape_id));
1041
1042 shape_id_t ivar_shape_id;
1043 if (rb_shape_find_ivar(shape_id, id, &ivar_shape_id)) {
1044 *value = RSHAPE_INDEX(ivar_shape_id);
1045 return true;
1046 }
1047 return false;
1048}
1049
1050int32_t
1051rb_shape_id_offset(void)
1052{
1053 return sizeof(uintptr_t) - SHAPE_ID_NUM_BITS / sizeof(uintptr_t);
1054}
1055
1056// Rebuild a similar shape with the same ivars but without "non-canonical"
1057// edges such as SHAPE_OBJ_ID.
1058static rb_shape_t *
1059shape_rebuild(rb_shape_t *initial_shape, rb_shape_t *dest_shape)
1060{
1061 rb_shape_t *midway_shape;
1062
1063 RUBY_ASSERT(initial_shape->type == SHAPE_ROOT);
1064
1065 if (dest_shape->type != initial_shape->type) {
1066 midway_shape = shape_rebuild(initial_shape, RSHAPE(dest_shape->parent_offset));
1067 if (UNLIKELY(!midway_shape)) {
1068 return NULL;
1069 }
1070 }
1071 else {
1072 midway_shape = initial_shape;
1073 }
1074
1075 switch ((enum shape_type)dest_shape->type) {
1076 case SHAPE_IVAR: {
1077 bool dont_care;
1078 midway_shape = get_next_shape_internal(midway_shape, dest_shape->edge_name, SHAPE_IVAR, &dont_care, true);
1079 break;
1080 }
1081 case SHAPE_OBJ_ID:
1082 case SHAPE_ROOT:
1083 break;
1084 }
1085
1086 return midway_shape;
1087}
1088
1089// Rebuild `dest_shape_id` starting from `initial_shape_id`, and keep only SHAPE_IVAR transitions.
1090// SHAPE_OBJ_ID and frozen status are lost.
1091shape_id_t
1092rb_shape_rebuild(shape_id_t initial_shape_id, shape_id_t dest_shape_id)
1093{
1094 RUBY_ASSERT(RSHAPE_TYPE_P(initial_shape_id, SHAPE_ROOT));
1095
1096 if (RB_UNLIKELY(rb_shape_complex_p(initial_shape_id))) {
1097 // The class has been marked as too complex.
1098 return initial_shape_id;
1099 }
1100
1101 if (RB_UNLIKELY(rb_shape_complex_p(dest_shape_id))) {
1102 return rb_shape_transition_complex(initial_shape_id);
1103 }
1104
1105 shape_id_t next_shape_id;
1106 // The shape has a SHAPE_OBJ_ID edge, it needs to be rebuilt.
1107 if (dest_shape_id & SHAPE_ID_FL_HAS_OBJECT_ID) {
1108 rb_shape_t *next_shape = shape_rebuild(RSHAPE(initial_shape_id), RSHAPE(dest_shape_id));
1109 if (next_shape) {
1110 next_shape_id = SHAPE_ID(next_shape, initial_shape_id & ~SHAPE_ID_FL_NON_CANONICAL_MASK);
1111 }
1112 else {
1113 return rb_shape_transition_complex(initial_shape_id | (dest_shape_id & ~SHAPE_ID_FL_NON_CANONICAL_MASK));
1114 }
1115 }
1116 else {
1117 // Happy path, we have nothing to do other than change the flags.
1118 next_shape_id = RSHAPE_OFFSET(dest_shape_id) | RSHAPE_FLAGS(initial_shape_id);
1119 }
1120 return next_shape_id;
1121}
1122
1123void
1124rb_shape_copy_fields(VALUE dest, VALUE *dest_buf, shape_id_t dest_shape_id, VALUE *src_buf, shape_id_t src_shape_id)
1125{
1126 rb_shape_t *dest_shape = RSHAPE(dest_shape_id);
1127 rb_shape_t *src_shape = RSHAPE(src_shape_id);
1128
1129 if (src_shape->next_field_index == dest_shape->next_field_index) {
1130 // Happy path, we can just memcpy the ivptr content
1131 MEMCPY(dest_buf, src_buf, VALUE, dest_shape->next_field_index);
1132
1133 // Fire write barriers
1134 for (uint32_t i = 0; i < dest_shape->next_field_index; i++) {
1135 RB_OBJ_WRITTEN(dest, Qundef, dest_buf[i]);
1136 }
1137 }
1138 else {
1139 while (src_shape->parent_offset != INVALID_SHAPE_ID) {
1140 if (src_shape->type == SHAPE_IVAR) {
1141 while (dest_shape->edge_name != src_shape->edge_name) {
1142 if (UNLIKELY(dest_shape->parent_offset == INVALID_SHAPE_ID)) {
1143 rb_bug("Lost field %s", rb_id2name(src_shape->edge_name));
1144 }
1145 dest_shape = RSHAPE(dest_shape->parent_offset);
1146 }
1147
1148 RB_OBJ_WRITE(dest, &dest_buf[dest_shape->next_field_index - 1], src_buf[src_shape->next_field_index - 1]);
1149 }
1150 src_shape = RSHAPE(src_shape->parent_offset);
1151 }
1152 }
1153}
1154
1155size_t
1156rb_shape_edges_count(shape_id_t shape_id)
1157{
1158 rb_shape_t *shape = RSHAPE(shape_id);
1159 if (shape->edges) {
1160 if (SINGLE_CHILD_P(shape->edges)) {
1161 return 1;
1162 }
1163 else {
1164 return rb_managed_id_table_size(shape->edges);
1165 }
1166 }
1167 return 0;
1168}
1169
1170size_t
1171rb_shape_memsize(shape_id_t shape_id)
1172{
1173 rb_shape_t *shape = RSHAPE(shape_id);
1174
1175 size_t memsize = sizeof(rb_shape_t);
1176 if (shape->edges && !SINGLE_CHILD_P(shape->edges)) {
1177 memsize += rb_managed_id_table_size(shape->edges);
1178 }
1179 return memsize;
1180}
1181
1182bool
1183rb_shape_foreach_field(shape_id_t initial_shape_id, rb_shape_foreach_transition_callback func, void *data)
1184{
1185 RUBY_ASSERT(!rb_shape_complex_p(initial_shape_id));
1186
1187 rb_shape_t *shape = RSHAPE(initial_shape_id);
1188 if (shape->type == SHAPE_ROOT) {
1189 return true;
1190 }
1191
1192 shape_id_t parent_offset = SHAPE_ID(RSHAPE(shape->parent_offset), initial_shape_id);
1193 if (rb_shape_foreach_field(parent_offset, func, data)) {
1194 switch (func(SHAPE_ID(shape, initial_shape_id), data)) {
1195 case ST_STOP:
1196 return false;
1197 case ST_CHECK:
1198 case ST_CONTINUE:
1199 break;
1200 default:
1201 rb_bug("unreachable");
1202 }
1203 }
1204 return true;
1205}
1206
1207#if RUBY_DEBUG
1208/*
1209 * Get the layout of this object. The "layout" indicates what strategy
1210 * we should use for fetching instance variables from `obj`. It's based
1211 * on the C struct layout for each particular object.
1212 *
1213 * TODO: make Struct have a similar layout to RDATA
1214 */
1215static shape_id_t
1216rb_shape_expected_layout(VALUE obj)
1217{
1218 switch (BUILTIN_TYPE(obj)) {
1219 case T_OBJECT: {
1220 return SHAPE_ID_LAYOUT_ROBJECT;
1221 }
1222
1223 case T_CLASS:
1224 case T_MODULE:
1225 if (FL_TEST_RAW(obj, RCLASS_BOXABLE)) {
1226 return SHAPE_ID_LAYOUT_OTHER;
1227 }
1228 return SHAPE_ID_LAYOUT_RCLASS;
1229
1230 case T_STRUCT:
1231 case T_DATA:
1232 return SHAPE_ID_LAYOUT_EXTENDED;
1233
1234 case T_IMEMO:
1235 if (IMEMO_TYPE_P(obj, imemo_fields)) {
1236 return SHAPE_ID_LAYOUT_ROBJECT;
1237 }
1238 return SHAPE_ID_LAYOUT_OTHER;
1239
1240 default:
1241 return SHAPE_ID_LAYOUT_OTHER;
1242 }
1243}
1244
1245static const char *
1246shape_layout_name(shape_id_t shape_id)
1247{
1248 switch (rb_shape_layout(shape_id)) {
1249 case SHAPE_ID_LAYOUT_ROBJECT:
1250 return "robject";
1251 case SHAPE_ID_LAYOUT_RCLASS:
1252 return "rclass";
1253 case SHAPE_ID_LAYOUT_EXTENDED:
1254 return "extended (or RData)";
1255 case SHAPE_ID_LAYOUT_OTHER:
1256 return "other";
1257 default:
1258 return "invalid";
1259 }
1260}
1261
1262bool
1263rb_shape_verify_capacity_consistency_p(VALUE obj)
1264{
1265 switch (BUILTIN_TYPE(obj)) {
1266 case T_IMEMO:
1267 return IMEMO_TYPE_P(obj, imemo_fields);
1268 case T_STRING:
1269 return !FL_TEST_RAW(obj, FL_USER19); // STR_FAKESTR
1270 case T_ARRAY:
1271 return !FL_TEST_RAW(obj, RARRAY_FAKEARY);
1272 default:
1273 return true;
1274 }
1275}
1276
1277bool
1278rb_shape_verify_consistency(VALUE obj, shape_id_t shape_id)
1279{
1280 if (shape_id == INVALID_SHAPE_ID) {
1281 rb_bug("Can't set INVALID_SHAPE_ID on an object");
1282 }
1283
1284 shape_id_t actual_layout = rb_shape_layout(rb_obj_shape_id(obj));
1285 shape_id_t expected_layout = rb_shape_expected_layout(obj);
1286 if (actual_layout != expected_layout) {
1287 if (!(RB_TYPE_P(obj, T_OBJECT) && actual_layout == SHAPE_ID_LAYOUT_EXTENDED)) {
1288 rb_bug("shape_id layout mismatch: expected=%s actual=%s shape_id=%u obj=%s",
1289 shape_layout_name(expected_layout), shape_layout_name(actual_layout), shape_id, rb_obj_info(obj));
1290 }
1291 }
1292
1293 if (shape_id == ROOT_SHAPE_ID) {
1294 return true;
1295 }
1296
1297 rb_shape_t *shape = RSHAPE(shape_id);
1298
1299 // Make sure SHAPE_ID_HAS_IVAR_MASK is valid.
1300 if (rb_shape_complex_p(shape_id)) {
1301 RUBY_ASSERT(shape_id & SHAPE_ID_HAS_IVAR_MASK);
1302
1303 // Ensure complex object don't appear as embedded
1304 if (RB_TYPE_P(obj, T_OBJECT)) {
1305 RUBY_ASSERT(rb_obj_shape_extended_p(obj));
1306 }
1307 else if (IMEMO_TYPE_P(obj, imemo_fields)) {
1308 RUBY_ASSERT(rb_obj_shape_embedded_p(obj));
1309 }
1310 }
1311 else {
1312 bool has_object_id = false;
1313 while (shape->parent_offset != INVALID_SHAPE_ID) {
1314 if (shape->type == SHAPE_OBJ_ID) {
1315 has_object_id = true;
1316 break;
1317 }
1318 shape = RSHAPE(shape->parent_offset);
1319 }
1320
1321 if (rb_shape_has_object_id(shape_id)) {
1322 if (!has_object_id) {
1323 rb_bug("shape_id claim having obj_id but doesn't shape_id=%u, obj=%s", shape_id, rb_obj_info(obj));
1324 }
1325 }
1326 else {
1327 if (has_object_id) {
1328 rb_bug("shape_id claim not having obj_id but it does shape_id=%u, obj=%s", shape_id, rb_obj_info(obj));
1329 }
1330 }
1331
1332 attr_index_t ivar_count = RSHAPE_LEN(shape_id);
1333 if (has_object_id) {
1334 ivar_count--;
1335 }
1336 if (ivar_count) {
1337 RUBY_ASSERT(shape_id & SHAPE_ID_HAS_IVAR_MASK);
1338 }
1339 else {
1340 RUBY_ASSERT(!(shape_id & SHAPE_ID_HAS_IVAR_MASK));
1341 }
1342 }
1343
1344 if (rb_shape_verify_capacity_consistency_p(obj)) {
1345 attr_index_t shape_id_capacity = rb_shape_embedded_capacity(shape_id);
1346
1347 size_t shape_id_slot_size = shape_id_capacity * sizeof(VALUE) + sizeof(struct RBasic);
1348 size_t actual_slot_size = rb_gc_obj_slot_size(obj);
1349
1350 if (shape_id_capacity == SHAPE_ID_CAPACITY_MAX) {
1351 if (actual_slot_size < SHAPE_ID_CAPACITY_MAX) {
1352 rb_bug("shape_id_capacity is SHAPE_ID_CAPACITY_MAX, but actual slot size is only %zu", actual_slot_size);
1353 }
1354 }
1355 else {
1356 if (shape_id_slot_size != actual_slot_size) {
1357 rb_bug("shape_id capacity flags mismatch: shape_id_slot_size=%zu, gc_slot_size=%zu\n", shape_id_slot_size, actual_slot_size);
1358 }
1359 }
1360 }
1361
1362 return true;
1363}
1364#endif
1365
1366#if SHAPE_DEBUG
1367
1368/*
1369 * Exposing Shape to Ruby via RubyVM::Shape.of(object)
1370 */
1371
1372static VALUE
1373shape_complex(VALUE self)
1374{
1375 shape_id_t shape_id = NUM2INT(rb_struct_getmember(self, rb_intern("id")));
1376 return RBOOL(rb_shape_complex_p(shape_id));
1377}
1378
1379static VALUE
1380shape_frozen(VALUE self)
1381{
1382 shape_id_t shape_id = NUM2INT(rb_struct_getmember(self, rb_intern("id")));
1383 return RBOOL(shape_id & SHAPE_ID_FL_FROZEN);
1384}
1385
1386static VALUE
1387shape_has_object_id_p(VALUE self)
1388{
1389 shape_id_t shape_id = NUM2INT(rb_struct_getmember(self, rb_intern("id")));
1390 return RBOOL(rb_shape_has_object_id(shape_id));
1391}
1392
1393static VALUE
1394shape_layout(VALUE self)
1395{
1396 shape_id_t shape_id = NUM2UINT(rb_struct_getmember(self, rb_intern("id")));
1397
1398 switch (rb_shape_layout(shape_id)) {
1399 case SHAPE_ID_LAYOUT_ROBJECT:
1400 return ID2SYM(rb_intern("robject"));
1401 case SHAPE_ID_LAYOUT_RCLASS:
1402 return ID2SYM(rb_intern("rclass"));
1403 case SHAPE_ID_LAYOUT_EXTENDED:
1404 return ID2SYM(rb_intern("extended_or_rdata"));
1405 case SHAPE_ID_LAYOUT_OTHER:
1406 return ID2SYM(rb_intern("other"));
1407 default:
1408 rb_bug("unknown shape layout: %u", rb_shape_layout(shape_id));
1409 }
1410}
1411
1412static VALUE
1413parse_key(ID key)
1414{
1415 if (is_instance_id(key)) {
1416 return ID2SYM(key);
1417 }
1418 return LONG2NUM(key);
1419}
1420
1421static VALUE rb_shape_edge_name(rb_shape_t *shape);
1422
1423static VALUE
1424shape_id_t_to_rb_cShape(shape_id_t shape_id)
1425{
1426 VALUE rb_cShape = rb_const_get(rb_cRubyVM, rb_intern("Shape"));
1427 rb_shape_t *shape = RSHAPE(shape_id);
1428
1429 VALUE obj = rb_struct_new(rb_cShape,
1430 INT2NUM(shape_id),
1431 INT2NUM(RSHAPE_OFFSET(shape_id)),
1432 INT2NUM(shape->parent_offset),
1433 rb_shape_edge_name(shape),
1434 INT2NUM(shape->next_field_index),
1435 INT2NUM(rb_shape_embedded_capacity(shape_id)),
1436 INT2NUM(shape->type),
1437 INT2NUM(RSHAPE_CAPACITY(shape_id)));
1438 rb_obj_freeze(obj);
1439 return obj;
1440}
1441
1442static enum rb_id_table_iterator_result
1443rb_edges_to_hash(ID key, VALUE value, void *ref)
1444{
1445 rb_hash_aset(*(VALUE *)ref, parse_key(key), shape_id_t_to_rb_cShape(SHAPE_OFFSET((rb_shape_t *)value)));
1446 return ID_TABLE_CONTINUE;
1447}
1448
1449static VALUE
1450rb_shape_edges(VALUE self)
1451{
1452 rb_shape_t *shape = RSHAPE(NUM2INT(rb_struct_getmember(self, rb_intern("id"))));
1453
1454 VALUE hash = rb_hash_new();
1455
1456 if (shape->edges) {
1457 if (SINGLE_CHILD_P(shape->edges)) {
1458 rb_shape_t *child = SINGLE_CHILD(shape->edges);
1459 rb_edges_to_hash(child->edge_name, (VALUE)child, &hash);
1460 }
1461 else {
1462 VALUE edges = shape->edges;
1463 rb_managed_id_table_foreach(edges, rb_edges_to_hash, &hash);
1464 RB_GC_GUARD(edges);
1465 }
1466 }
1467
1468 return hash;
1469}
1470
1471static VALUE
1472rb_shape_edge_name(rb_shape_t *shape)
1473{
1474 if (shape->edge_name) {
1475 if (is_instance_id(shape->edge_name)) {
1476 return ID2SYM(shape->edge_name);
1477 }
1478 return INT2NUM(shape->capacity);
1479 }
1480 return Qnil;
1481}
1482
1483static VALUE
1484rb_shape_export_depth(VALUE self)
1485{
1486 shape_id_t shape_id = NUM2INT(rb_struct_getmember(self, rb_intern("id")));
1487 return SIZET2NUM(rb_shape_depth(shape_id));
1488}
1489
1490static VALUE
1491rb_shape_parent(VALUE self)
1492{
1493 rb_shape_t *shape;
1494 shape = RSHAPE(NUM2INT(rb_struct_getmember(self, rb_intern("id"))));
1495 if (shape->parent_offset != INVALID_SHAPE_ID) {
1496 return shape_id_t_to_rb_cShape(shape->parent_offset);
1497 }
1498 else {
1499 return Qnil;
1500 }
1501}
1502
1503static VALUE
1504rb_shape_debug_shape(VALUE self, VALUE obj)
1505{
1506 if (RB_SPECIAL_CONST_P(obj)) {
1507 rb_raise(rb_eArgError, "Can't get shape of special constant");
1508 }
1509 return shape_id_t_to_rb_cShape(rb_obj_shape_id(obj));
1510}
1511
1512static VALUE
1513rb_shape_root_shape(VALUE self)
1514{
1515 return shape_id_t_to_rb_cShape(ROOT_SHAPE_ID);
1516}
1517
1518static VALUE
1519rb_shape_shapes_available(VALUE self)
1520{
1521 return ULL2NUM(MAX_SHAPE_ID - (rb_shapes_count() - 1));
1522}
1523
1524static VALUE
1525rb_shape_exhaust(int argc, VALUE *argv, VALUE self)
1526{
1527 rb_check_arity(argc, 0, 1);
1528 int offset = argc == 1 ? NUM2INT(argv[0]) : 0;
1529 RUBY_ATOMIC_SET(shape_next_id, MAX_SHAPE_ID - offset + 1);
1530 return Qnil;
1531}
1532
1533static VALUE
1534rb_shape_class_max_iv_count(VALUE self, VALUE klass)
1535{
1536 return INT2NUM(RCLASS_MAX_IV_COUNT(klass));
1537}
1538
1539static VALUE shape_to_h(rb_shape_t *shape);
1540
1541static enum rb_id_table_iterator_result collect_keys_and_values(ID key, VALUE value, void *ref)
1542{
1543 rb_hash_aset(*(VALUE *)ref, parse_key(key), shape_to_h((rb_shape_t *)value));
1544 return ID_TABLE_CONTINUE;
1545}
1546
1547static VALUE edges(VALUE edges)
1548{
1549 VALUE hash = rb_hash_new();
1550 if (edges) {
1551 if (SINGLE_CHILD_P(edges)) {
1552 rb_shape_t *child = SINGLE_CHILD(edges);
1553 collect_keys_and_values(child->edge_name, (VALUE)child, &hash);
1554 }
1555 else {
1556 rb_managed_id_table_foreach(edges, collect_keys_and_values, &hash);
1557 }
1558 }
1559 return hash;
1560}
1561
1562static VALUE
1563shape_to_h(rb_shape_t *shape)
1564{
1565 VALUE rb_shape = rb_hash_new();
1566
1567 rb_hash_aset(rb_shape, ID2SYM(rb_intern("id")), INT2NUM(SHAPE_OFFSET(shape)));
1568 rb_hash_aset(rb_shape, ID2SYM(rb_intern("edges")), edges(shape->edges));
1569
1570 if (shape == rb_shape_get_root_shape()) {
1571 rb_hash_aset(rb_shape, ID2SYM(rb_intern("parent_offset")), INT2NUM(ROOT_SHAPE_ID));
1572 }
1573 else {
1574 rb_hash_aset(rb_shape, ID2SYM(rb_intern("parent_offset")), INT2NUM(shape->parent_offset));
1575 }
1576
1577 rb_hash_aset(rb_shape, ID2SYM(rb_intern("edge_name")), rb_id2str(shape->edge_name));
1578 return rb_shape;
1579}
1580
1581static VALUE
1582shape_transition_tree(VALUE self)
1583{
1584 return shape_to_h(rb_shape_get_root_shape());
1585}
1586
1587static VALUE
1588rb_shape_find_by_id(VALUE mod, VALUE id)
1589{
1590 shape_id_t shape_id = NUM2UINT(id);
1591 if (shape_id >= rb_shapes_count()) {
1592 rb_raise(rb_eArgError, "Shape ID %d is out of bounds\n", shape_id);
1593 }
1594 return shape_id_t_to_rb_cShape(shape_id);
1595}
1596#endif
1597
1598#ifdef HAVE_MMAP
1599#include <sys/mman.h>
1600#endif
1601
1602void
1603Init_default_shapes(void)
1604{
1605 attr_index_t max_capacity = (attr_index_t)((rb_gc_max_allocation_size() - sizeof(struct RBasic)) / sizeof(VALUE));
1606 if (max_capacity > SHAPE_ID_CAPACITY_MAX) max_capacity = SHAPE_ID_CAPACITY_MAX;
1607 rb_shape_tree.max_capacity = max_capacity;
1608
1609#ifdef HAVE_MMAP
1610 size_t shape_list_mmap_size = rb_size_mul_or_raise(SHAPE_BUFFER_SIZE, sizeof(rb_shape_t), rb_eRuntimeError);
1611 rb_shape_tree.shape_list = (rb_shape_t *)mmap(NULL, shape_list_mmap_size,
1612 PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
1613 if (rb_shape_tree.shape_list == MAP_FAILED) {
1614 rb_shape_tree.shape_list = 0;
1615 }
1616 else {
1617 ruby_annotate_mmap(rb_shape_tree.shape_list, shape_list_mmap_size, "Ruby:Init_default_shapes:shape_list");
1618 }
1619#else
1620 rb_shape_tree.shape_list = xcalloc(SHAPE_BUFFER_SIZE, sizeof(rb_shape_t));
1621#endif
1622
1623 if (!rb_shape_tree.shape_list) {
1624 rb_memerror();
1625 }
1626
1627 rb_shape_tree.id_object_id = rb_make_internal_id();
1628
1629#ifdef HAVE_MMAP
1630 size_t shape_cache_mmap_size = rb_size_mul_or_raise(REDBLACK_CACHE_SIZE, sizeof(redblack_node_t), rb_eRuntimeError);
1631 redblack_cache = (redblack_node_t *)mmap(NULL, shape_cache_mmap_size,
1632 PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
1633 redblack_cache_size = 0;
1634
1635 // If mmap fails, then give up on the redblack tree cache.
1636 // We set the cache size such that the redblack node allocators think
1637 // the cache is full.
1638 if (redblack_cache == MAP_FAILED) {
1639 redblack_cache = NULL;
1640 redblack_cache_size = REDBLACK_CACHE_SIZE;
1641 }
1642 else {
1643 ruby_annotate_mmap(redblack_cache, shape_cache_mmap_size, "Ruby:Init_default_shapes:shape_cache");
1644 }
1645#endif
1646
1647 rb_gc_register_address(&shape_tree_obj);
1648
1649 // The shape tree's data lives in the `rb_shape_tree` global variable, so there's no struct to wrap here.
1650 // We can't use NULL, because then the GC would skip the mark/compact callbacks,
1651 // so instead we pass a fake non-NULL pointer that will never be dereferenced.
1652 shape_tree_obj = TypedData_Wrap_Struct(0, &shape_tree_type, (void *)1);
1653
1654 // Root shape
1655 rb_shape_t *root = rb_shape_alloc_with_parent_offset(0, INVALID_SHAPE_ID);
1656 root->capacity = 0;
1657 root->type = SHAPE_ROOT;
1658 RUBY_ASSERT(SHAPE_OFFSET(root) == ROOT_SHAPE_ID);
1659 RUBY_ASSERT(!(SHAPE_OFFSET(root) & SHAPE_ID_HAS_IVAR_MASK));
1660
1661 bool dontcare;
1662 rb_shape_t *root_with_obj_id = get_next_shape_internal(root, rb_shape_tree.id_object_id, SHAPE_OBJ_ID, &dontcare, true);
1663 RUBY_ASSERT(root_with_obj_id);
1664 RUBY_ASSERT(SHAPE_OFFSET(root_with_obj_id) == ROOT_SHAPE_WITH_OBJ_ID);
1665 RUBY_ASSERT(root_with_obj_id->type == SHAPE_OBJ_ID);
1666 RUBY_ASSERT(root_with_obj_id->edge_name == rb_shape_tree.id_object_id);
1667 RUBY_ASSERT(root_with_obj_id->next_field_index == 1);
1668 RUBY_ASSERT(!(SHAPE_OFFSET(root_with_obj_id) & SHAPE_ID_HAS_IVAR_MASK));
1669 (void)root_with_obj_id;
1670}
1671
1672void
1673Init_shape(void)
1674{
1675#if SHAPE_DEBUG
1676 /* Document-class: RubyVM::Shape
1677 * :nodoc: */
1678 VALUE rb_cShape = rb_struct_define_under(rb_cRubyVM, "Shape",
1679 "id",
1680 "offset",
1681 "parent_offset",
1682 "edge_name",
1683 "next_field_index",
1684 "embedded_capacity",
1685 "type",
1686 "capacity",
1687 NULL);
1688
1689 rb_define_method(rb_cShape, "parent", rb_shape_parent, 0);
1690 rb_define_method(rb_cShape, "edges", rb_shape_edges, 0);
1691 rb_define_method(rb_cShape, "depth", rb_shape_export_depth, 0);
1692 rb_define_method(rb_cShape, "complex?", shape_complex, 0);
1693 rb_define_method(rb_cShape, "shape_frozen?", shape_frozen, 0);
1694 rb_define_method(rb_cShape, "has_object_id?", shape_has_object_id_p, 0);
1695 rb_define_method(rb_cShape, "layout", shape_layout, 0);
1696
1697 rb_define_const(rb_cShape, "SHAPE_ROOT", INT2NUM(SHAPE_ROOT));
1698 rb_define_const(rb_cShape, "SHAPE_IVAR", INT2NUM(SHAPE_IVAR));
1699 rb_define_const(rb_cShape, "SHAPE_ID_NUM_BITS", INT2NUM(SHAPE_ID_NUM_BITS));
1700 rb_define_const(rb_cShape, "SHAPE_FLAG_SHIFT", INT2NUM(SHAPE_FLAG_SHIFT));
1701 rb_define_const(rb_cShape, "SHAPE_MAX_VARIATIONS", INT2NUM(SHAPE_MAX_VARIATIONS));
1702 rb_define_const(rb_cShape, "SHAPE_MAX_FIELDS", INT2NUM(rb_shape_max_capacity()));
1703 rb_define_const(rb_cShape, "SIZEOF_RB_SHAPE_T", INT2NUM(sizeof(rb_shape_t)));
1704 rb_define_const(rb_cShape, "SIZEOF_REDBLACK_NODE_T", INT2NUM(sizeof(redblack_node_t)));
1705 rb_define_const(rb_cShape, "SHAPE_BUFFER_SIZE", INT2NUM(sizeof(rb_shape_t) * SHAPE_BUFFER_SIZE));
1706 rb_define_const(rb_cShape, "REDBLACK_CACHE_SIZE", INT2NUM(sizeof(redblack_node_t) * REDBLACK_CACHE_SIZE));
1707
1708 rb_define_singleton_method(rb_cShape, "transition_tree", shape_transition_tree, 0);
1709 rb_define_singleton_method(rb_cShape, "find_by_id", rb_shape_find_by_id, 1);
1710 rb_define_singleton_method(rb_cShape, "of", rb_shape_debug_shape, 1);
1711 rb_define_singleton_method(rb_cShape, "root_shape", rb_shape_root_shape, 0);
1712 rb_define_singleton_method(rb_cShape, "shapes_available", rb_shape_shapes_available, 0);
1713 rb_define_singleton_method(rb_cShape, "exhaust_shapes", rb_shape_exhaust, -1);
1714 rb_define_singleton_method(rb_cShape, "class_max_iv_count", rb_shape_class_max_iv_count, 1);
1715#endif
1716}
#define RUBY_ASSERT(...)
Asserts that the given expression is truthy if and only if RUBY_DEBUG is truthy.
Definition assert.h:219
#define RUBY_ATOMIC_VALUE_CAS(var, oldval, newval)
Identical to RUBY_ATOMIC_CAS, except it expects its arguments are VALUE.
Definition atomic.h:406
#define RUBY_ATOMIC_CAS(var, oldval, newval)
Atomic compare-and-swap.
Definition atomic.h:165
std::atomic< unsigned > rb_atomic_t
Type that is eligible for atomic operations.
Definition atomic.h:69
#define RUBY_ATOMIC_FETCH_ADD(var, val)
Atomically replaces the value pointed by var with the result of addition of val to the old value of v...
Definition atomic.h:118
#define RUBY_ATOMIC_LOAD(var)
Atomic load.
Definition atomic.h:175
#define RUBY_ATOMIC_SET(var, val)
Identical to RUBY_ATOMIC_EXCHANGE, except for the return type.
Definition atomic.h:185
#define RUBY_ALIGNAS
Wraps (or simulates) alignas.
Definition stdalign.h:27
#define rb_define_method(klass, mid, func, arity)
Defines klass#mid.
#define rb_define_singleton_method(klass, mid, func, arity)
Defines klass.mid.
VALUE rb_singleton_class(VALUE obj)
Finds or creates the singleton class of the passed object.
Definition class.c:2865
#define T_STRING
Old name of RUBY_T_STRING.
Definition value_type.h:78
#define Qundef
Old name of RUBY_Qundef.
#define T_IMEMO
Old name of RUBY_T_IMEMO.
Definition value_type.h:67
#define ID2SYM
Old name of RB_ID2SYM.
Definition symbol.h:44
#define T_STRUCT
Old name of RUBY_T_STRUCT.
Definition value_type.h:79
#define FL_USER19
Old name of RUBY_FL_USER19.
Definition fl_type.h:88
#define T_DATA
Old name of RUBY_T_DATA.
Definition value_type.h:60
#define SIZET2NUM
Old name of RB_SIZE2NUM.
Definition size_t.h:62
#define T_MODULE
Old name of RUBY_T_MODULE.
Definition value_type.h:70
#define NUM2UINT
Old name of RB_NUM2UINT.
Definition int.h:45
#define FL_TEST_RAW
Old name of RB_FL_TEST_RAW.
Definition fl_type.h:128
#define LONG2NUM
Old name of RB_LONG2NUM.
Definition long.h:50
#define ULL2NUM
Old name of RB_ULL2NUM.
Definition long_long.h:31
#define NUM2INT
Old name of RB_NUM2INT.
Definition int.h:44
#define INT2NUM
Old name of RB_INT2NUM.
Definition int.h:43
#define Qnil
Old name of RUBY_Qnil.
#define Qfalse
Old name of RUBY_Qfalse.
#define T_ARRAY
Old name of RUBY_T_ARRAY.
Definition value_type.h:56
#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
#define xcalloc
Old name of ruby_xcalloc.
Definition xmalloc.h:55
void rb_category_warn(rb_warning_category_t category, const char *fmt,...)
Identical to rb_category_warning(), except it reports unless $VERBOSE is nil.
Definition error.c:478
VALUE rb_eRuntimeError
RuntimeError exception.
Definition error.c:1429
@ RB_WARN_CATEGORY_PERFORMANCE
Warning is for performance issues (not enabled by -w).
Definition error.h:54
VALUE rb_obj_class(VALUE obj)
Queries the class of an object.
Definition object.c:234
VALUE rb_obj_freeze(VALUE obj)
Just calls rb_obj_freeze_inline() inside.
Definition object.c:1308
#define RB_OBJ_WRITTEN(old, oldv, young)
Identical to RB_OBJ_WRITE(), except it doesn't write any values, but only a WB declaration.
Definition gc.h:468
#define RB_OBJ_WRITE(old, slot, young)
Declaration of a "back" pointer.
Definition gc.h:456
static int rb_check_arity(int argc, int min, int max)
Ensures that the passed integer is in the passed range.
Definition error.h:284
VALUE rb_struct_define_under(VALUE space, const char *name,...)
Identical to rb_struct_define(), except it defines the class under the specified namespace instead of...
Definition struct.c:512
VALUE rb_struct_new(VALUE klass,...)
Creates an instance of the given struct.
Definition struct.c:874
VALUE rb_struct_getmember(VALUE self, ID key)
Identical to rb_struct_aref(), except it takes ID instead of VALUE.
Definition struct.c:233
VALUE rb_const_get(VALUE space, ID name)
Identical to rb_const_defined(), except it returns the actual defined value.
Definition variable.c:3408
VALUE rb_class_path(VALUE mod)
Identical to rb_mod_name(), except it returns #<Class: ...> style inspection for anonymous modules.
Definition variable.c:394
VALUE rb_sym2str(VALUE symbol)
Obtain a frozen string representation of a symbol (not including the leading colon).
Definition symbol.c:1148
#define MEMCPY(p1, p2, type, n)
Handy macro to call memcpy.
Definition memory.h:372
#define RB_GC_GUARD(v)
Prevents premature destruction of local objects.
Definition memory.h:167
VALUE type(ANYARGS)
ANYARGS-ed function type.
#define TypedData_Wrap_Struct(klass, data_type, sval)
Converts sval, a pointer to your struct, into a Ruby object.
Definition rtypeddata.h:557
static bool RB_SPECIAL_CONST_P(VALUE obj)
Checks if the given object is of enum ruby_special_consts.
#define RTEST
This is an old name of RB_TEST.
C99 shim for <stdbool.h>
Ruby object's base components.
Definition rbasic.h:69
This is the struct that holds necessary info for a struct.
Definition rtypeddata.h:238
const char * wrap_struct_name
Name of structs of this kind.
Definition rtypeddata.h:245
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