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