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