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