2022-10-03 11:14:32 -04:00
|
|
|
#include "vm_core.h"
|
|
|
|
#include "vm_sync.h"
|
|
|
|
#include "shape.h"
|
2022-12-06 12:56:51 +01:00
|
|
|
#include "symbol.h"
|
|
|
|
#include "id_table.h"
|
2022-10-03 11:14:32 -04:00
|
|
|
#include "internal/class.h"
|
2023-12-05 16:25:34 -05:00
|
|
|
#include "internal/error.h"
|
2023-02-08 11:56:53 +00:00
|
|
|
#include "internal/gc.h"
|
2023-12-06 07:37:57 -05:00
|
|
|
#include "internal/object.h"
|
2022-10-03 11:14:32 -04:00
|
|
|
#include "internal/symbol.h"
|
|
|
|
#include "internal/variable.h"
|
2022-12-05 16:48:47 -08:00
|
|
|
#include "variable.h"
|
2022-10-03 11:14:32 -04:00
|
|
|
#include <stdbool.h>
|
|
|
|
|
2023-02-07 17:46:42 -08:00
|
|
|
#ifndef _WIN32
|
|
|
|
#include <sys/mman.h>
|
|
|
|
#endif
|
|
|
|
|
2022-11-23 14:01:03 -08:00
|
|
|
#ifndef SHAPE_DEBUG
|
|
|
|
#define SHAPE_DEBUG (VM_CHECK_MODE > 0)
|
|
|
|
#endif
|
|
|
|
|
2023-10-26 13:28:25 -07:00
|
|
|
#define REDBLACK_CACHE_SIZE (SHAPE_BUFFER_SIZE * 32)
|
|
|
|
|
2024-08-16 15:02:30 +09:00
|
|
|
/* This depends on that the allocated memory by Ruby's allocator or
|
|
|
|
* mmap is not located at an odd address. */
|
2023-03-13 15:07:09 -07:00
|
|
|
#define SINGLE_CHILD_TAG 0x1
|
2025-05-19 12:38:49 +02:00
|
|
|
#define TAG_SINGLE_CHILD(x) (VALUE)((uintptr_t)(x) | SINGLE_CHILD_TAG)
|
2023-03-13 15:07:09 -07:00
|
|
|
#define SINGLE_CHILD_MASK (~((uintptr_t)SINGLE_CHILD_TAG))
|
2024-08-16 15:02:30 +09:00
|
|
|
#define SINGLE_CHILD_P(x) ((uintptr_t)(x) & SINGLE_CHILD_TAG)
|
|
|
|
#define SINGLE_CHILD(x) (rb_shape_t *)((uintptr_t)(x) & SINGLE_CHILD_MASK)
|
2023-02-07 17:46:42 -08:00
|
|
|
#define ANCESTOR_CACHE_THRESHOLD 10
|
2023-10-26 11:45:52 +02:00
|
|
|
#define MAX_SHAPE_ID (SHAPE_BUFFER_SIZE - 1)
|
2023-10-26 11:08:05 +02:00
|
|
|
#define ANCESTOR_SEARCH_MAX_DEPTH 2
|
2023-03-13 15:07:09 -07:00
|
|
|
|
2022-11-08 15:35:31 -05:00
|
|
|
static ID id_frozen;
|
2022-11-17 15:57:11 -08:00
|
|
|
static ID id_t_object;
|
2025-04-21 16:16:07 +09:00
|
|
|
ID ruby_internal_object_id; // extern
|
2022-11-08 15:35:31 -05:00
|
|
|
|
2023-02-07 17:46:42 -08:00
|
|
|
#define LEAF 0
|
|
|
|
#define BLACK 0x0
|
|
|
|
#define RED 0x1
|
|
|
|
|
|
|
|
static redblack_node_t *
|
2025-04-29 09:50:47 +02:00
|
|
|
redblack_left(redblack_node_t *node)
|
2023-02-07 17:46:42 -08:00
|
|
|
{
|
|
|
|
if (node->l == LEAF) {
|
|
|
|
return LEAF;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
RUBY_ASSERT(node->l < GET_SHAPE_TREE()->cache_size);
|
2025-04-29 09:50:47 +02:00
|
|
|
redblack_node_t *left = &GET_SHAPE_TREE()->shape_cache[node->l - 1];
|
2023-02-07 17:46:42 -08:00
|
|
|
return left;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static redblack_node_t *
|
2025-04-29 09:50:47 +02:00
|
|
|
redblack_right(redblack_node_t *node)
|
2023-02-07 17:46:42 -08:00
|
|
|
{
|
|
|
|
if (node->r == LEAF) {
|
|
|
|
return LEAF;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
RUBY_ASSERT(node->r < GET_SHAPE_TREE()->cache_size);
|
2025-04-29 09:50:47 +02:00
|
|
|
redblack_node_t *right = &GET_SHAPE_TREE()->shape_cache[node->r - 1];
|
2023-02-07 17:46:42 -08:00
|
|
|
return right;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static redblack_node_t *
|
2025-04-29 09:50:47 +02:00
|
|
|
redblack_find(redblack_node_t *tree, ID key)
|
2023-02-07 17:46:42 -08:00
|
|
|
{
|
2023-10-24 08:46:54 -07:00
|
|
|
if (tree == LEAF) {
|
|
|
|
return LEAF;
|
|
|
|
}
|
|
|
|
else {
|
2023-11-29 10:30:00 -05:00
|
|
|
RUBY_ASSERT(redblack_left(tree) == LEAF || redblack_left(tree)->key < tree->key);
|
|
|
|
RUBY_ASSERT(redblack_right(tree) == LEAF || redblack_right(tree)->key > tree->key);
|
|
|
|
|
2023-02-07 17:46:42 -08:00
|
|
|
if (tree->key == key) {
|
|
|
|
return tree;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (key < tree->key) {
|
|
|
|
return redblack_find(redblack_left(tree), key);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return redblack_find(redblack_right(tree), key);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-16 13:51:21 +08:00
|
|
|
static inline rb_shape_t *
|
2025-04-29 09:50:47 +02:00
|
|
|
redblack_value(redblack_node_t *node)
|
2024-08-16 13:51:21 +08:00
|
|
|
{
|
|
|
|
// Color is stored in the bottom bit of the shape pointer
|
|
|
|
// Mask away the bit so we get the actual pointer back
|
2024-08-16 11:43:35 +09:00
|
|
|
return (rb_shape_t *)((uintptr_t)node->value & ~(uintptr_t)1);
|
2024-08-16 13:51:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef HAVE_MMAP
|
2023-02-07 17:46:42 -08:00
|
|
|
static inline char
|
2025-04-29 09:50:47 +02:00
|
|
|
redblack_color(redblack_node_t *node)
|
2023-02-07 17:46:42 -08:00
|
|
|
{
|
|
|
|
return node && ((uintptr_t)node->value & RED);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline bool
|
2025-04-29 09:50:47 +02:00
|
|
|
redblack_red_p(redblack_node_t *node)
|
2023-02-07 17:46:42 -08:00
|
|
|
{
|
|
|
|
return redblack_color(node) == RED;
|
|
|
|
}
|
|
|
|
|
|
|
|
static redblack_id_t
|
2025-04-29 09:50:47 +02:00
|
|
|
redblack_id_for(redblack_node_t *node)
|
2023-02-07 17:46:42 -08:00
|
|
|
{
|
|
|
|
RUBY_ASSERT(node || node == LEAF);
|
|
|
|
if (node == LEAF) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else {
|
2025-04-29 09:50:47 +02:00
|
|
|
redblack_node_t *redblack_nodes = GET_SHAPE_TREE()->shape_cache;
|
2023-02-07 17:46:42 -08:00
|
|
|
redblack_id_t id = (redblack_id_t)(node - redblack_nodes);
|
|
|
|
return id + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static redblack_node_t *
|
2025-04-29 09:50:47 +02:00
|
|
|
redblack_new(char color, ID key, rb_shape_t *value, redblack_node_t *left, redblack_node_t *right)
|
2023-02-07 17:46:42 -08:00
|
|
|
{
|
2023-10-26 13:28:25 -07:00
|
|
|
if (GET_SHAPE_TREE()->cache_size + 1 >= REDBLACK_CACHE_SIZE) {
|
|
|
|
// We're out of cache, just quit
|
|
|
|
return LEAF;
|
|
|
|
}
|
2023-11-29 10:30:00 -05:00
|
|
|
|
|
|
|
RUBY_ASSERT(left == LEAF || left->key < key);
|
|
|
|
RUBY_ASSERT(right == LEAF || right->key > key);
|
|
|
|
|
2025-04-29 09:50:47 +02:00
|
|
|
redblack_node_t *redblack_nodes = GET_SHAPE_TREE()->shape_cache;
|
|
|
|
redblack_node_t *node = &redblack_nodes[(GET_SHAPE_TREE()->cache_size)++];
|
2023-02-07 17:46:42 -08:00
|
|
|
node->key = key;
|
|
|
|
node->value = (rb_shape_t *)((uintptr_t)value | color);
|
|
|
|
node->l = redblack_id_for(left);
|
|
|
|
node->r = redblack_id_for(right);
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
|
|
|
|
static redblack_node_t *
|
2025-04-29 09:50:47 +02:00
|
|
|
redblack_balance(char color, ID key, rb_shape_t *value, redblack_node_t *left, redblack_node_t *right)
|
2023-02-07 17:46:42 -08:00
|
|
|
{
|
|
|
|
if (color == BLACK) {
|
2023-11-30 10:13:20 -05:00
|
|
|
ID new_key, new_left_key, new_right_key;
|
|
|
|
rb_shape_t *new_value, *new_left_value, *new_right_value;
|
|
|
|
redblack_node_t *new_left_left, *new_left_right, *new_right_left, *new_right_right;
|
2023-02-07 17:46:42 -08:00
|
|
|
|
|
|
|
if (redblack_red_p(left) && redblack_red_p(redblack_left(left))) {
|
2023-11-30 10:13:20 -05:00
|
|
|
new_right_key = key;
|
|
|
|
new_right_value = value;
|
|
|
|
new_right_right = right;
|
2023-02-07 17:46:42 -08:00
|
|
|
|
2023-11-30 10:13:20 -05:00
|
|
|
new_key = left->key;
|
|
|
|
new_value = redblack_value(left);
|
|
|
|
new_right_left = redblack_right(left);
|
2023-02-07 17:46:42 -08:00
|
|
|
|
2023-11-30 10:13:20 -05:00
|
|
|
new_left_key = redblack_left(left)->key;
|
|
|
|
new_left_value = redblack_value(redblack_left(left));
|
2023-02-07 17:46:42 -08:00
|
|
|
|
2023-11-30 10:13:20 -05:00
|
|
|
new_left_left = redblack_left(redblack_left(left));
|
|
|
|
new_left_right = redblack_right(redblack_left(left));
|
2023-02-07 17:46:42 -08:00
|
|
|
}
|
|
|
|
else if (redblack_red_p(left) && redblack_red_p(redblack_right(left))) {
|
2023-11-30 10:13:20 -05:00
|
|
|
new_right_key = key;
|
|
|
|
new_right_value = value;
|
|
|
|
new_right_right = right;
|
|
|
|
|
|
|
|
new_left_key = left->key;
|
|
|
|
new_left_value = redblack_value(left);
|
|
|
|
new_left_left = redblack_left(left);
|
|
|
|
|
|
|
|
new_key = redblack_right(left)->key;
|
|
|
|
new_value = redblack_value(redblack_right(left));
|
|
|
|
new_left_right = redblack_left(redblack_right(left));
|
|
|
|
new_right_left = redblack_right(redblack_right(left));
|
2023-02-07 17:46:42 -08:00
|
|
|
}
|
|
|
|
else if (redblack_red_p(right) && redblack_red_p(redblack_left(right))) {
|
2023-11-30 10:13:20 -05:00
|
|
|
new_left_key = key;
|
|
|
|
new_left_value = value;
|
|
|
|
new_left_left = left;
|
|
|
|
|
|
|
|
new_right_key = right->key;
|
|
|
|
new_right_value = redblack_value(right);
|
|
|
|
new_right_right = redblack_right(right);
|
|
|
|
|
|
|
|
new_key = redblack_left(right)->key;
|
|
|
|
new_value = redblack_value(redblack_left(right));
|
|
|
|
new_left_right = redblack_left(redblack_left(right));
|
|
|
|
new_right_left = redblack_right(redblack_left(right));
|
2023-02-07 17:46:42 -08:00
|
|
|
}
|
|
|
|
else if (redblack_red_p(right) && redblack_red_p(redblack_right(right))) {
|
2023-11-30 10:13:20 -05:00
|
|
|
new_left_key = key;
|
|
|
|
new_left_value = value;
|
|
|
|
new_left_left = left;
|
|
|
|
|
|
|
|
new_key = right->key;
|
|
|
|
new_value = redblack_value(right);
|
|
|
|
new_left_right = redblack_left(right);
|
|
|
|
|
|
|
|
new_right_key = redblack_right(right)->key;
|
|
|
|
new_right_value = redblack_value(redblack_right(right));
|
|
|
|
new_right_left = redblack_left(redblack_right(right));
|
|
|
|
new_right_right = redblack_right(redblack_right(right));
|
2023-02-07 17:46:42 -08:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
return redblack_new(color, key, value, left, right);
|
|
|
|
}
|
2023-12-06 10:16:58 -05:00
|
|
|
|
|
|
|
RUBY_ASSERT(new_left_key < new_key);
|
|
|
|
RUBY_ASSERT(new_right_key > new_key);
|
|
|
|
RUBY_ASSERT(new_left_left == LEAF || new_left_left->key < new_left_key);
|
|
|
|
RUBY_ASSERT(new_left_right == LEAF || new_left_right->key > new_left_key);
|
|
|
|
RUBY_ASSERT(new_left_right == LEAF || new_left_right->key < new_key);
|
|
|
|
RUBY_ASSERT(new_right_left == LEAF || new_right_left->key < new_right_key);
|
|
|
|
RUBY_ASSERT(new_right_left == LEAF || new_right_left->key > new_key);
|
|
|
|
RUBY_ASSERT(new_right_right == LEAF || new_right_right->key > new_right_key);
|
|
|
|
|
2023-02-07 17:46:42 -08:00
|
|
|
return redblack_new(
|
2023-11-30 10:13:20 -05:00
|
|
|
RED, new_key, new_value,
|
|
|
|
redblack_new(BLACK, new_left_key, new_left_value, new_left_left, new_left_right),
|
|
|
|
redblack_new(BLACK, new_right_key, new_right_value, new_right_left, new_right_right));
|
2023-02-07 17:46:42 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
return redblack_new(color, key, value, left, right);
|
|
|
|
}
|
|
|
|
|
|
|
|
static redblack_node_t *
|
2025-04-29 09:50:47 +02:00
|
|
|
redblack_insert_aux(redblack_node_t *tree, ID key, rb_shape_t *value)
|
2023-02-07 17:46:42 -08:00
|
|
|
{
|
|
|
|
if (tree == LEAF) {
|
|
|
|
return redblack_new(RED, key, value, LEAF, LEAF);
|
|
|
|
}
|
|
|
|
else {
|
2023-11-28 13:37:38 -05:00
|
|
|
redblack_node_t *left, *right;
|
2023-02-07 17:46:42 -08:00
|
|
|
if (key < tree->key) {
|
2023-11-28 13:37:38 -05:00
|
|
|
left = redblack_insert_aux(redblack_left(tree), key, value);
|
|
|
|
RUBY_ASSERT(left != LEAF);
|
|
|
|
right = redblack_right(tree);
|
2023-11-29 10:30:00 -05:00
|
|
|
RUBY_ASSERT(right == LEAF || right->key > tree->key);
|
2023-11-28 13:37:38 -05:00
|
|
|
}
|
|
|
|
else if (key > tree->key) {
|
|
|
|
left = redblack_left(tree);
|
2023-11-29 10:30:00 -05:00
|
|
|
RUBY_ASSERT(left == LEAF || left->key < tree->key);
|
2023-11-28 13:37:38 -05:00
|
|
|
right = redblack_insert_aux(redblack_right(tree), key, value);
|
|
|
|
RUBY_ASSERT(right != LEAF);
|
2023-02-07 17:46:42 -08:00
|
|
|
}
|
|
|
|
else {
|
2023-11-28 13:37:38 -05:00
|
|
|
return tree;
|
2023-02-07 17:46:42 -08:00
|
|
|
}
|
2023-11-28 13:37:38 -05:00
|
|
|
|
|
|
|
return redblack_balance(
|
|
|
|
redblack_color(tree),
|
|
|
|
tree->key,
|
|
|
|
redblack_value(tree),
|
|
|
|
left,
|
|
|
|
right
|
|
|
|
);
|
2023-02-07 17:46:42 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static redblack_node_t *
|
2025-04-29 09:50:47 +02:00
|
|
|
redblack_force_black(redblack_node_t *node)
|
2023-02-07 17:46:42 -08:00
|
|
|
{
|
|
|
|
node->value = redblack_value(node);
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
|
|
|
|
static redblack_node_t *
|
2025-04-29 09:50:47 +02:00
|
|
|
redblack_insert(redblack_node_t *tree, ID key, rb_shape_t *value)
|
2023-02-07 17:46:42 -08:00
|
|
|
{
|
2025-04-29 09:50:47 +02:00
|
|
|
redblack_node_t *root = redblack_insert_aux(tree, key, value);
|
2023-02-07 17:46:42 -08:00
|
|
|
|
|
|
|
if (redblack_red_p(root)) {
|
|
|
|
return redblack_force_black(root);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return root;
|
|
|
|
}
|
|
|
|
}
|
2024-04-24 17:13:25 +09:00
|
|
|
#endif
|
2023-02-07 17:46:42 -08:00
|
|
|
|
2023-02-17 13:32:51 +00:00
|
|
|
rb_shape_tree_t *rb_shape_tree_ptr = NULL;
|
2025-05-19 12:38:49 +02:00
|
|
|
static VALUE shape_tree_obj = Qfalse;
|
2023-02-17 13:32:51 +00:00
|
|
|
|
2022-11-08 15:35:31 -05:00
|
|
|
rb_shape_t *
|
2022-10-12 18:27:23 +09:00
|
|
|
rb_shape_get_root_shape(void)
|
|
|
|
{
|
2023-02-17 13:32:51 +00:00
|
|
|
return GET_SHAPE_TREE()->root_shape;
|
2022-10-03 11:14:32 -04:00
|
|
|
}
|
|
|
|
|
2025-05-19 12:38:49 +02:00
|
|
|
static void
|
|
|
|
shape_tree_mark(void *data)
|
|
|
|
{
|
|
|
|
rb_shape_t *cursor = rb_shape_get_root_shape();
|
2025-06-03 22:11:10 +02:00
|
|
|
rb_shape_t *end = RSHAPE(GET_SHAPE_TREE()->next_shape_id - 1);
|
2025-05-19 12:38:49 +02:00
|
|
|
while (cursor < end) {
|
|
|
|
if (cursor->edges && !SINGLE_CHILD_P(cursor->edges)) {
|
2025-06-07 17:30:29 +02:00
|
|
|
rb_gc_mark_movable(cursor->edges);
|
2025-05-19 12:38:49 +02:00
|
|
|
}
|
|
|
|
cursor++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
shape_tree_compact(void *data)
|
|
|
|
{
|
|
|
|
rb_shape_t *cursor = rb_shape_get_root_shape();
|
2025-06-04 13:35:43 +02:00
|
|
|
rb_shape_t *end = RSHAPE(GET_SHAPE_TREE()->next_shape_id - 1);
|
2025-05-19 12:38:49 +02:00
|
|
|
while (cursor < end) {
|
|
|
|
if (cursor->edges && !SINGLE_CHILD_P(cursor->edges)) {
|
|
|
|
cursor->edges = rb_gc_location(cursor->edges);
|
|
|
|
}
|
|
|
|
cursor++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static size_t
|
|
|
|
shape_tree_memsize(const void *data)
|
|
|
|
{
|
|
|
|
return GET_SHAPE_TREE()->cache_size * sizeof(redblack_node_t);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const rb_data_type_t shape_tree_type = {
|
|
|
|
.wrap_struct_name = "VM/shape_tree",
|
|
|
|
.function = {
|
|
|
|
.dmark = shape_tree_mark,
|
|
|
|
.dfree = NULL, // Nothing to free, done at VM exit in rb_shape_free_all,
|
|
|
|
.dsize = shape_tree_memsize,
|
|
|
|
.dcompact = shape_tree_compact,
|
|
|
|
},
|
|
|
|
.flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Shape getters
|
|
|
|
*/
|
|
|
|
|
2025-05-27 11:51:29 +02:00
|
|
|
static inline shape_id_t
|
2025-05-27 15:53:45 +02:00
|
|
|
raw_shape_id(rb_shape_t *shape)
|
2022-10-03 11:14:32 -04:00
|
|
|
{
|
2025-06-07 13:42:46 +02:00
|
|
|
RUBY_ASSERT(shape);
|
2023-02-17 13:32:51 +00:00
|
|
|
return (shape_id_t)(shape - GET_SHAPE_TREE()->shape_list);
|
2022-10-03 11:14:32 -04:00
|
|
|
}
|
|
|
|
|
2025-05-27 15:53:45 +02:00
|
|
|
static inline shape_id_t
|
|
|
|
shape_id(rb_shape_t *shape, shape_id_t previous_shape_id)
|
|
|
|
{
|
2025-06-07 13:42:46 +02:00
|
|
|
RUBY_ASSERT(shape);
|
2025-05-27 15:53:45 +02:00
|
|
|
shape_id_t raw_id = (shape_id_t)(shape - GET_SHAPE_TREE()->shape_list);
|
|
|
|
return raw_id | (previous_shape_id & SHAPE_ID_FLAGS_MASK);
|
|
|
|
}
|
|
|
|
|
|
|
|
#if RUBY_DEBUG
|
|
|
|
static inline bool
|
|
|
|
shape_frozen_p(shape_id_t shape_id)
|
|
|
|
{
|
|
|
|
return shape_id & SHAPE_ID_FL_FROZEN;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2022-12-06 12:56:51 +01:00
|
|
|
void
|
2025-05-27 11:51:29 +02:00
|
|
|
rb_shape_each_shape_id(each_shape_callback callback, void *data)
|
2022-12-06 12:56:51 +01:00
|
|
|
{
|
2025-05-27 11:51:29 +02:00
|
|
|
rb_shape_t *start = rb_shape_get_root_shape();
|
|
|
|
rb_shape_t *cursor = start;
|
2025-05-08 18:47:18 +02:00
|
|
|
rb_shape_t *end = RSHAPE(GET_SHAPE_TREE()->next_shape_id);
|
2022-12-06 12:56:51 +01:00
|
|
|
while (cursor < end) {
|
2025-05-27 11:51:29 +02:00
|
|
|
callback((shape_id_t)(cursor - start), data);
|
2022-12-06 12:56:51 +01:00
|
|
|
cursor += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-22 16:02:10 -05:00
|
|
|
RUBY_FUNC_EXPORTED rb_shape_t *
|
2025-05-09 08:58:07 +02:00
|
|
|
rb_shape_lookup(shape_id_t shape_id)
|
2022-10-03 11:14:32 -04:00
|
|
|
{
|
2025-05-27 15:53:45 +02:00
|
|
|
uint32_t offset = (shape_id & SHAPE_ID_OFFSET_MASK);
|
|
|
|
RUBY_ASSERT(offset != INVALID_SHAPE_ID);
|
2022-10-03 11:14:32 -04:00
|
|
|
|
2025-05-27 15:53:45 +02:00
|
|
|
return &GET_SHAPE_TREE()->shape_list[offset];
|
2022-10-03 11:14:32 -04:00
|
|
|
}
|
|
|
|
|
2023-03-06 21:56:40 -08:00
|
|
|
RUBY_FUNC_EXPORTED shape_id_t
|
2025-05-09 08:58:07 +02:00
|
|
|
rb_obj_shape_id(VALUE obj)
|
2022-10-03 11:14:32 -04:00
|
|
|
{
|
|
|
|
if (RB_SPECIAL_CONST_P(obj)) {
|
2022-11-08 15:35:31 -05:00
|
|
|
return SPECIAL_CONST_SHAPE_ID;
|
2022-10-03 11:14:32 -04:00
|
|
|
}
|
|
|
|
|
2025-05-22 14:01:46 +02:00
|
|
|
if (BUILTIN_TYPE(obj) == T_CLASS || BUILTIN_TYPE(obj) == T_MODULE) {
|
|
|
|
VALUE fields_obj = RCLASS_FIELDS_OBJ(obj);
|
|
|
|
if (fields_obj) {
|
|
|
|
return RBASIC_SHAPE_ID(fields_obj);
|
|
|
|
}
|
|
|
|
return ROOT_SHAPE_ID;
|
|
|
|
}
|
2022-10-03 11:14:32 -04:00
|
|
|
return RBASIC_SHAPE_ID(obj);
|
|
|
|
}
|
|
|
|
|
2022-12-06 12:56:51 +01:00
|
|
|
size_t
|
2025-05-08 18:20:35 +02:00
|
|
|
rb_shape_depth(shape_id_t shape_id)
|
2022-12-05 16:48:47 -08:00
|
|
|
{
|
2022-12-06 12:56:51 +01:00
|
|
|
size_t depth = 1;
|
2025-05-08 18:47:18 +02:00
|
|
|
rb_shape_t *shape = RSHAPE(shape_id);
|
2022-12-05 16:48:47 -08:00
|
|
|
|
|
|
|
while (shape->parent_id != INVALID_SHAPE_ID) {
|
|
|
|
depth++;
|
2025-05-08 21:08:40 +02:00
|
|
|
shape = RSHAPE(shape->parent_id);
|
2022-12-05 16:48:47 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
return depth;
|
|
|
|
}
|
|
|
|
|
2023-03-09 15:50:58 -08:00
|
|
|
static rb_shape_t *
|
|
|
|
shape_alloc(void)
|
|
|
|
{
|
2025-05-19 12:38:49 +02:00
|
|
|
shape_id_t shape_id = (shape_id_t)RUBY_ATOMIC_FETCH_ADD(GET_SHAPE_TREE()->next_shape_id, 1);
|
2023-03-09 15:50:58 -08:00
|
|
|
|
2023-10-19 11:00:54 -07:00
|
|
|
if (shape_id == (MAX_SHAPE_ID + 1)) {
|
2023-03-09 15:50:58 -08:00
|
|
|
// TODO: Make an OutOfShapesError ??
|
2023-05-20 14:00:14 +09:00
|
|
|
rb_bug("Out of shapes");
|
2023-03-09 15:50:58 -08:00
|
|
|
}
|
|
|
|
|
2023-02-17 13:32:51 +00:00
|
|
|
return &GET_SHAPE_TREE()->shape_list[shape_id];
|
2023-03-09 15:50:58 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
static rb_shape_t *
|
|
|
|
rb_shape_alloc_with_parent_id(ID edge_name, shape_id_t parent_id)
|
|
|
|
{
|
2025-04-29 09:50:47 +02:00
|
|
|
rb_shape_t *shape = shape_alloc();
|
2023-03-09 15:50:58 -08:00
|
|
|
|
|
|
|
shape->edge_name = edge_name;
|
2025-04-30 09:42:57 +02:00
|
|
|
shape->next_field_index = 0;
|
2023-03-09 15:50:58 -08:00
|
|
|
shape->parent_id = parent_id;
|
2025-05-19 12:38:49 +02:00
|
|
|
shape->edges = 0;
|
2023-03-09 15:50:58 -08:00
|
|
|
|
|
|
|
return shape;
|
|
|
|
}
|
|
|
|
|
|
|
|
static rb_shape_t *
|
2025-04-29 09:50:47 +02:00
|
|
|
rb_shape_alloc(ID edge_name, rb_shape_t *parent, enum shape_type type)
|
2023-03-09 15:50:58 -08:00
|
|
|
{
|
2025-05-27 15:53:45 +02:00
|
|
|
rb_shape_t *shape = rb_shape_alloc_with_parent_id(edge_name, raw_shape_id(parent));
|
2023-03-22 08:47:29 -07:00
|
|
|
shape->type = (uint8_t)type;
|
|
|
|
shape->capacity = parent->capacity;
|
2023-03-13 15:07:09 -07:00
|
|
|
shape->edges = 0;
|
2023-03-09 15:50:58 -08:00
|
|
|
return shape;
|
|
|
|
}
|
|
|
|
|
2023-10-23 13:53:42 -07:00
|
|
|
#ifdef HAVE_MMAP
|
2023-02-07 17:46:42 -08:00
|
|
|
static redblack_node_t *
|
2025-04-29 09:50:47 +02:00
|
|
|
redblack_cache_ancestors(rb_shape_t *shape)
|
2023-02-07 17:46:42 -08:00
|
|
|
{
|
2023-10-24 11:59:48 -07:00
|
|
|
if (!(shape->ancestor_index || shape->parent_id == INVALID_SHAPE_ID)) {
|
2025-04-29 09:50:47 +02:00
|
|
|
redblack_node_t *parent_index;
|
2023-02-07 17:46:42 -08:00
|
|
|
|
2025-05-08 21:08:40 +02:00
|
|
|
parent_index = redblack_cache_ancestors(RSHAPE(shape->parent_id));
|
2023-02-07 17:46:42 -08:00
|
|
|
|
2023-10-24 11:59:48 -07:00
|
|
|
if (shape->type == SHAPE_IVAR) {
|
|
|
|
shape->ancestor_index = redblack_insert(parent_index, shape->edge_name, shape);
|
2023-11-27 14:05:25 -05:00
|
|
|
|
|
|
|
#if RUBY_DEBUG
|
|
|
|
if (shape->ancestor_index) {
|
|
|
|
redblack_node_t *inserted_node = redblack_find(shape->ancestor_index, shape->edge_name);
|
|
|
|
RUBY_ASSERT(inserted_node);
|
|
|
|
RUBY_ASSERT(redblack_value(inserted_node) == shape);
|
|
|
|
}
|
|
|
|
#endif
|
2023-10-24 11:59:48 -07:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
shape->ancestor_index = parent_index;
|
2023-02-07 17:46:42 -08:00
|
|
|
}
|
|
|
|
}
|
2023-10-24 11:59:48 -07:00
|
|
|
|
|
|
|
return shape->ancestor_index;
|
2023-02-07 17:46:42 -08:00
|
|
|
}
|
2023-10-23 13:53:42 -07:00
|
|
|
#else
|
|
|
|
static redblack_node_t *
|
2025-04-29 09:50:47 +02:00
|
|
|
redblack_cache_ancestors(rb_shape_t *shape)
|
2023-10-23 13:53:42 -07:00
|
|
|
{
|
|
|
|
return LEAF;
|
|
|
|
}
|
|
|
|
#endif
|
2023-02-07 17:46:42 -08:00
|
|
|
|
2025-06-06 12:16:31 +02:00
|
|
|
static attr_index_t
|
|
|
|
shape_grow_capa(attr_index_t current_capa)
|
|
|
|
{
|
2025-06-07 14:44:13 +02:00
|
|
|
const attr_index_t *capacities = GET_SHAPE_TREE()->capacities;
|
2025-06-06 12:16:31 +02:00
|
|
|
|
|
|
|
// First try to use the next size that will be embeddable in a larger object slot.
|
|
|
|
attr_index_t capa;
|
|
|
|
while ((capa = *capacities)) {
|
|
|
|
if (capa > current_capa) {
|
|
|
|
return capa;
|
|
|
|
}
|
|
|
|
capacities++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (attr_index_t)rb_malloc_grow_capa(current_capa, sizeof(VALUE));
|
|
|
|
}
|
|
|
|
|
2023-03-09 15:58:22 -08:00
|
|
|
static rb_shape_t *
|
2025-04-29 09:50:47 +02:00
|
|
|
rb_shape_alloc_new_child(ID id, rb_shape_t *shape, enum shape_type shape_type)
|
2023-03-09 15:58:22 -08:00
|
|
|
{
|
2025-04-29 09:50:47 +02:00
|
|
|
rb_shape_t *new_shape = rb_shape_alloc(id, shape, shape_type);
|
2023-03-09 15:58:22 -08:00
|
|
|
|
|
|
|
switch (shape_type) {
|
2025-04-21 16:16:07 +09:00
|
|
|
case SHAPE_OBJ_ID:
|
2023-03-09 15:58:22 -08:00
|
|
|
case SHAPE_IVAR:
|
2025-04-30 09:42:57 +02:00
|
|
|
if (UNLIKELY(shape->next_field_index >= shape->capacity)) {
|
|
|
|
RUBY_ASSERT(shape->next_field_index == shape->capacity);
|
2025-06-06 12:16:31 +02:00
|
|
|
new_shape->capacity = shape_grow_capa(shape->capacity);
|
2023-11-10 16:17:39 -05:00
|
|
|
}
|
2025-04-30 09:42:57 +02:00
|
|
|
RUBY_ASSERT(new_shape->capacity > shape->next_field_index);
|
|
|
|
new_shape->next_field_index = shape->next_field_index + 1;
|
|
|
|
if (new_shape->next_field_index > ANCESTOR_CACHE_THRESHOLD) {
|
2025-06-06 21:01:24 +02:00
|
|
|
RB_VM_LOCKING() {
|
|
|
|
redblack_cache_ancestors(new_shape);
|
|
|
|
}
|
2023-02-07 17:46:42 -08:00
|
|
|
}
|
2023-03-09 15:58:22 -08:00
|
|
|
break;
|
|
|
|
case SHAPE_ROOT:
|
|
|
|
rb_bug("Unreachable");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return new_shape;
|
|
|
|
}
|
|
|
|
|
2025-05-19 12:38:49 +02:00
|
|
|
#define RUBY_ATOMIC_VALUE_LOAD(x) (VALUE)(RUBY_ATOMIC_PTR_LOAD(x))
|
|
|
|
|
2025-05-05 13:45:29 +02:00
|
|
|
static rb_shape_t *
|
2025-05-19 12:38:49 +02:00
|
|
|
get_next_shape_internal_atomic(rb_shape_t *shape, ID id, enum shape_type shape_type, bool *variation_created, bool new_variations_allowed)
|
2022-10-03 11:14:32 -04:00
|
|
|
{
|
|
|
|
rb_shape_t *res = NULL;
|
2022-11-17 09:47:18 -05:00
|
|
|
|
2022-12-08 17:16:52 -05:00
|
|
|
*variation_created = false;
|
2025-05-19 12:38:49 +02:00
|
|
|
VALUE edges_table;
|
|
|
|
|
|
|
|
retry:
|
|
|
|
edges_table = RUBY_ATOMIC_VALUE_LOAD(shape->edges);
|
|
|
|
|
|
|
|
// If the current shape has children
|
|
|
|
if (edges_table) {
|
|
|
|
// Check if it only has one child
|
|
|
|
if (SINGLE_CHILD_P(edges_table)) {
|
|
|
|
rb_shape_t *child = SINGLE_CHILD(edges_table);
|
|
|
|
// If the one child has a matching edge name, then great,
|
|
|
|
// we found what we want.
|
|
|
|
if (child->edge_name == id) {
|
|
|
|
res = child;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// If it has more than one child, do a hash lookup to find it.
|
|
|
|
VALUE lookup_result;
|
|
|
|
if (rb_managed_id_table_lookup(edges_table, id, &lookup_result)) {
|
|
|
|
res = (rb_shape_t *)lookup_result;
|
|
|
|
}
|
2025-04-28 10:37:54 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-05-19 12:38:49 +02:00
|
|
|
// If we didn't find the shape we're looking for we create it.
|
|
|
|
if (!res) {
|
|
|
|
// If we're not allowed to create a new variation, of if we're out of shapes
|
|
|
|
// we return TOO_COMPLEX_SHAPE.
|
|
|
|
if (!new_variations_allowed || GET_SHAPE_TREE()->next_shape_id > MAX_SHAPE_ID) {
|
2025-06-04 09:05:55 +02:00
|
|
|
res = NULL;
|
2025-05-19 12:38:49 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
VALUE new_edges = 0;
|
|
|
|
|
|
|
|
rb_shape_t *new_shape = rb_shape_alloc_new_child(id, shape, shape_type);
|
|
|
|
|
|
|
|
if (!edges_table) {
|
|
|
|
// If the shape had no edge yet, we can directly set the new child
|
|
|
|
new_edges = TAG_SINGLE_CHILD(new_shape);
|
2023-10-27 16:41:03 +02:00
|
|
|
}
|
|
|
|
else {
|
2025-05-19 12:38:49 +02:00
|
|
|
// If the edge was single child we need to allocate a table.
|
|
|
|
if (SINGLE_CHILD_P(edges_table)) {
|
|
|
|
rb_shape_t *old_child = SINGLE_CHILD(edges_table);
|
|
|
|
new_edges = rb_managed_id_table_new(2);
|
|
|
|
rb_managed_id_table_insert(new_edges, old_child->edge_name, (VALUE)old_child);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
new_edges = rb_managed_id_table_dup(edges_table);
|
2023-03-13 15:07:09 -07:00
|
|
|
}
|
2025-05-19 12:38:49 +02:00
|
|
|
|
|
|
|
rb_managed_id_table_insert(new_edges, new_shape->edge_name, (VALUE)new_shape);
|
|
|
|
*variation_created = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (edges_table != RUBY_ATOMIC_VALUE_CAS(shape->edges, edges_table, new_edges)) {
|
|
|
|
// Another thread updated the table;
|
|
|
|
goto retry;
|
2023-10-27 16:41:03 +02:00
|
|
|
}
|
2025-05-19 12:38:49 +02:00
|
|
|
RB_OBJ_WRITTEN(shape_tree_obj, Qundef, new_edges);
|
|
|
|
res = new_shape;
|
|
|
|
RB_GC_GUARD(new_edges);
|
2023-10-27 16:41:03 +02:00
|
|
|
}
|
2025-05-19 12:38:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
static rb_shape_t *
|
|
|
|
get_next_shape_internal(rb_shape_t *shape, ID id, enum shape_type shape_type, bool *variation_created, bool new_variations_allowed)
|
|
|
|
{
|
|
|
|
if (rb_multi_ractor_p()) {
|
|
|
|
return get_next_shape_internal_atomic(shape, id, shape_type, variation_created, new_variations_allowed);
|
|
|
|
}
|
2022-11-17 09:47:18 -05:00
|
|
|
|
2025-05-19 12:38:49 +02:00
|
|
|
rb_shape_t *res = NULL;
|
|
|
|
*variation_created = false;
|
|
|
|
|
|
|
|
VALUE edges_table = shape->edges;
|
|
|
|
|
|
|
|
// If the current shape has children
|
|
|
|
if (edges_table) {
|
|
|
|
// Check if it only has one child
|
|
|
|
if (SINGLE_CHILD_P(edges_table)) {
|
|
|
|
rb_shape_t *child = SINGLE_CHILD(edges_table);
|
|
|
|
// If the one child has a matching edge name, then great,
|
|
|
|
// we found what we want.
|
|
|
|
if (child->edge_name == id) {
|
|
|
|
res = child;
|
2023-01-05 08:48:19 -05:00
|
|
|
}
|
2025-05-19 12:38:49 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
// If it has more than one child, do a hash lookup to find it.
|
|
|
|
VALUE lookup_result;
|
|
|
|
if (rb_managed_id_table_lookup(edges_table, id, &lookup_result)) {
|
|
|
|
res = (rb_shape_t *)lookup_result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-10-27 16:41:03 +02:00
|
|
|
|
2025-05-19 12:38:49 +02:00
|
|
|
// If we didn't find the shape we're looking for we create it.
|
|
|
|
if (!res) {
|
|
|
|
// If we're not allowed to create a new variation, of if we're out of shapes
|
|
|
|
// we return TOO_COMPLEX_SHAPE.
|
|
|
|
if (!new_variations_allowed || GET_SHAPE_TREE()->next_shape_id > MAX_SHAPE_ID) {
|
2025-06-04 09:05:55 +02:00
|
|
|
res = NULL;
|
2025-05-19 12:38:49 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
rb_shape_t *new_shape = rb_shape_alloc_new_child(id, shape, shape_type);
|
2023-10-27 16:41:03 +02:00
|
|
|
|
2025-05-19 12:38:49 +02:00
|
|
|
if (!edges_table) {
|
|
|
|
// If the shape had no edge yet, we can directly set the new child
|
|
|
|
shape->edges = TAG_SINGLE_CHILD(new_shape);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// If the edge was single child we need to allocate a table.
|
|
|
|
if (SINGLE_CHILD_P(edges_table)) {
|
|
|
|
rb_shape_t *old_child = SINGLE_CHILD(edges_table);
|
|
|
|
VALUE new_edges = rb_managed_id_table_new(2);
|
|
|
|
rb_managed_id_table_insert(new_edges, old_child->edge_name, (VALUE)old_child);
|
|
|
|
RB_OBJ_WRITE(shape_tree_obj, &shape->edges, new_edges);
|
|
|
|
}
|
2025-04-28 10:37:54 +02:00
|
|
|
|
2025-05-19 12:38:49 +02:00
|
|
|
rb_managed_id_table_insert(shape->edges, new_shape->edge_name, (VALUE)new_shape);
|
|
|
|
*variation_created = true;
|
2022-12-08 17:16:52 -05:00
|
|
|
}
|
2025-05-19 12:38:49 +02:00
|
|
|
|
|
|
|
res = new_shape;
|
2022-10-03 11:14:32 -04:00
|
|
|
}
|
2023-10-24 12:37:27 -07:00
|
|
|
}
|
2023-10-27 16:41:03 +02:00
|
|
|
|
2022-10-03 11:14:32 -04:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2022-12-05 16:48:47 -08:00
|
|
|
static rb_shape_t *
|
2023-11-17 14:05:37 -05:00
|
|
|
remove_shape_recursive(rb_shape_t *shape, ID id, rb_shape_t **removed_shape)
|
2022-12-05 16:48:47 -08:00
|
|
|
{
|
|
|
|
if (shape->parent_id == INVALID_SHAPE_ID) {
|
|
|
|
// We've hit the top of the shape tree and couldn't find the
|
|
|
|
// IV we wanted to remove, so return NULL
|
2025-06-04 09:05:55 +02:00
|
|
|
*removed_shape = NULL;
|
2022-12-05 16:48:47 -08:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (shape->type == SHAPE_IVAR && shape->edge_name == id) {
|
2023-11-17 14:05:37 -05:00
|
|
|
*removed_shape = shape;
|
|
|
|
|
2025-05-08 21:08:40 +02:00
|
|
|
return RSHAPE(shape->parent_id);
|
2022-12-05 16:48:47 -08:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
// This isn't the IV we want to remove, keep walking up.
|
2025-05-08 21:08:40 +02:00
|
|
|
rb_shape_t *new_parent = remove_shape_recursive(RSHAPE(shape->parent_id), id, removed_shape);
|
2022-12-05 16:48:47 -08:00
|
|
|
|
|
|
|
// We found a new parent. Create a child of the new parent that
|
|
|
|
// has the same attributes as this shape.
|
|
|
|
if (new_parent) {
|
2023-11-02 10:37:09 +01:00
|
|
|
bool dont_care;
|
2023-11-17 14:05:37 -05:00
|
|
|
rb_shape_t *new_child = get_next_shape_internal(new_parent, shape->edge_name, shape->type, &dont_care, true);
|
2025-06-04 09:05:55 +02:00
|
|
|
RUBY_ASSERT(!new_child || new_child->capacity <= shape->capacity);
|
2022-12-05 16:48:47 -08:00
|
|
|
return new_child;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// We went all the way to the top of the shape tree and couldn't
|
2025-06-04 09:05:55 +02:00
|
|
|
// find an IV to remove so return NULL.
|
2022-12-05 16:48:47 -08:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-06-07 15:26:27 +02:00
|
|
|
static inline shape_id_t transition_complex(shape_id_t shape_id);
|
2025-06-07 10:55:49 +02:00
|
|
|
|
|
|
|
static shape_id_t
|
|
|
|
shape_transition_object_id(shape_id_t original_shape_id)
|
|
|
|
{
|
|
|
|
RUBY_ASSERT(!rb_shape_has_object_id(original_shape_id));
|
|
|
|
|
|
|
|
bool dont_care;
|
|
|
|
rb_shape_t *shape = get_next_shape_internal(RSHAPE(original_shape_id), ruby_internal_object_id, SHAPE_OBJ_ID, &dont_care, true);
|
2025-06-07 15:26:27 +02:00
|
|
|
if (!shape) {
|
|
|
|
shape = RSHAPE(ROOT_SHAPE_WITH_OBJ_ID);
|
|
|
|
}
|
2025-06-07 10:55:49 +02:00
|
|
|
|
|
|
|
RUBY_ASSERT(shape);
|
|
|
|
return shape_id(shape, original_shape_id) | SHAPE_ID_FL_HAS_OBJECT_ID;
|
|
|
|
}
|
|
|
|
|
|
|
|
shape_id_t
|
|
|
|
rb_shape_transition_object_id(VALUE obj)
|
|
|
|
{
|
|
|
|
return shape_transition_object_id(RBASIC_SHAPE_ID(obj));
|
|
|
|
}
|
|
|
|
|
|
|
|
shape_id_t
|
|
|
|
rb_shape_object_id(shape_id_t original_shape_id)
|
|
|
|
{
|
|
|
|
RUBY_ASSERT(rb_shape_has_object_id(original_shape_id));
|
|
|
|
|
|
|
|
rb_shape_t *shape = RSHAPE(original_shape_id);
|
|
|
|
while (shape->type != SHAPE_OBJ_ID) {
|
|
|
|
if (UNLIKELY(shape->parent_id == INVALID_SHAPE_ID)) {
|
|
|
|
rb_bug("Missing object_id in shape tree");
|
|
|
|
}
|
|
|
|
shape = RSHAPE(shape->parent_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
return shape_id(shape, original_shape_id) | SHAPE_ID_FL_HAS_OBJECT_ID;
|
|
|
|
}
|
|
|
|
|
2025-06-04 11:11:24 +02:00
|
|
|
static inline shape_id_t
|
2025-06-06 09:09:36 +02:00
|
|
|
transition_complex(shape_id_t shape_id)
|
2025-06-04 11:11:24 +02:00
|
|
|
{
|
2025-06-07 15:26:27 +02:00
|
|
|
uint8_t heap_index = rb_shape_heap_index(shape_id);
|
2025-06-07 10:55:49 +02:00
|
|
|
shape_id_t next_shape_id;
|
|
|
|
|
|
|
|
if (heap_index) {
|
|
|
|
next_shape_id = rb_shape_root(heap_index - 1) | SHAPE_ID_FL_TOO_COMPLEX;
|
|
|
|
if (rb_shape_has_object_id(shape_id)) {
|
|
|
|
next_shape_id = shape_transition_object_id(next_shape_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (rb_shape_has_object_id(shape_id)) {
|
|
|
|
next_shape_id = ROOT_TOO_COMPLEX_WITH_OBJ_ID | (shape_id & SHAPE_ID_FLAGS_MASK);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
next_shape_id = ROOT_TOO_COMPLEX_SHAPE_ID | (shape_id & SHAPE_ID_FLAGS_MASK);
|
|
|
|
}
|
2025-06-04 11:11:24 +02:00
|
|
|
}
|
|
|
|
|
2025-06-07 10:55:49 +02:00
|
|
|
RUBY_ASSERT(rb_shape_has_object_id(shape_id) == rb_shape_has_object_id(next_shape_id));
|
|
|
|
|
|
|
|
return next_shape_id;
|
|
|
|
}
|
2025-06-04 11:11:24 +02:00
|
|
|
|
2025-05-23 15:55:38 +02:00
|
|
|
shape_id_t
|
|
|
|
rb_shape_transition_remove_ivar(VALUE obj, ID id, shape_id_t *removed_shape_id)
|
2022-12-05 16:48:47 -08:00
|
|
|
{
|
2025-06-04 09:05:55 +02:00
|
|
|
shape_id_t original_shape_id = RBASIC_SHAPE_ID(obj);
|
2025-05-08 20:30:30 +02:00
|
|
|
|
2025-06-04 09:05:55 +02:00
|
|
|
RUBY_ASSERT(!rb_shape_too_complex_p(original_shape_id));
|
|
|
|
RUBY_ASSERT(!shape_frozen_p(original_shape_id));
|
2023-11-01 12:15:12 +01:00
|
|
|
|
2023-11-17 14:05:37 -05:00
|
|
|
rb_shape_t *removed_shape = NULL;
|
2025-06-04 09:05:55 +02:00
|
|
|
rb_shape_t *new_shape = remove_shape_recursive(RSHAPE(original_shape_id), id, &removed_shape);
|
|
|
|
|
|
|
|
if (removed_shape) {
|
2025-05-27 15:53:45 +02:00
|
|
|
*removed_shape_id = raw_shape_id(removed_shape);
|
2022-12-05 16:48:47 -08:00
|
|
|
}
|
2025-06-04 09:05:55 +02:00
|
|
|
|
|
|
|
if (new_shape) {
|
|
|
|
return shape_id(new_shape, original_shape_id);
|
|
|
|
}
|
|
|
|
else if (removed_shape) {
|
|
|
|
// We found the shape to remove, but couldn't create a new variation.
|
|
|
|
// We must transition to TOO_COMPLEX.
|
2025-06-07 10:55:49 +02:00
|
|
|
shape_id_t next_shape_id = transition_complex(original_shape_id);
|
|
|
|
RUBY_ASSERT(rb_shape_has_object_id(next_shape_id) == rb_shape_has_object_id(original_shape_id));
|
|
|
|
return next_shape_id;
|
2025-06-04 09:05:55 +02:00
|
|
|
}
|
|
|
|
return original_shape_id;
|
2022-10-03 11:14:32 -04:00
|
|
|
}
|
|
|
|
|
2025-05-08 19:05:03 +02:00
|
|
|
shape_id_t
|
|
|
|
rb_shape_transition_frozen(VALUE obj)
|
2022-10-03 11:14:32 -04:00
|
|
|
{
|
|
|
|
RUBY_ASSERT(RB_OBJ_FROZEN(obj));
|
|
|
|
|
2025-05-09 08:58:07 +02:00
|
|
|
shape_id_t shape_id = rb_obj_shape_id(obj);
|
2025-05-27 15:53:45 +02:00
|
|
|
return shape_id | SHAPE_ID_FL_FROZEN;
|
2022-10-03 11:14:32 -04:00
|
|
|
}
|
|
|
|
|
2025-05-08 20:16:00 +02:00
|
|
|
shape_id_t
|
|
|
|
rb_shape_transition_complex(VALUE obj)
|
2025-05-05 11:10:08 +02:00
|
|
|
{
|
2025-06-06 09:09:36 +02:00
|
|
|
return transition_complex(RBASIC_SHAPE_ID(obj));
|
2025-04-21 16:16:07 +09:00
|
|
|
}
|
|
|
|
|
2025-06-07 16:48:26 +02:00
|
|
|
shape_id_t
|
|
|
|
rb_shape_transition_heap(VALUE obj, size_t heap_index)
|
|
|
|
{
|
|
|
|
return (RBASIC_SHAPE_ID(obj) & (~SHAPE_ID_HEAP_INDEX_MASK)) | rb_shape_root(heap_index);
|
|
|
|
}
|
|
|
|
|
2022-11-08 15:35:31 -05:00
|
|
|
/*
|
|
|
|
* This function is used for assertions where we don't want to increment
|
|
|
|
* max_iv_count
|
|
|
|
*/
|
2025-05-08 18:36:01 +02:00
|
|
|
static inline rb_shape_t *
|
|
|
|
shape_get_next_iv_shape(rb_shape_t *shape, ID id)
|
2022-11-08 15:35:31 -05:00
|
|
|
{
|
2022-12-06 12:56:51 +01:00
|
|
|
RUBY_ASSERT(!is_instance_id(id) || RTEST(rb_sym2str(ID2SYM(id))));
|
2022-12-08 16:48:48 -05:00
|
|
|
bool dont_care;
|
2023-10-24 12:05:05 -07:00
|
|
|
return get_next_shape_internal(shape, id, SHAPE_IVAR, &dont_care, true);
|
2022-11-08 15:35:31 -05:00
|
|
|
}
|
|
|
|
|
2025-05-08 18:36:01 +02:00
|
|
|
shape_id_t
|
|
|
|
rb_shape_get_next_iv_shape(shape_id_t shape_id, ID id)
|
|
|
|
{
|
2025-05-08 18:47:18 +02:00
|
|
|
rb_shape_t *shape = RSHAPE(shape_id);
|
2025-05-08 18:36:01 +02:00
|
|
|
rb_shape_t *next_shape = shape_get_next_iv_shape(shape, id);
|
2025-05-27 15:53:45 +02:00
|
|
|
return raw_shape_id(next_shape);
|
2025-05-08 18:36:01 +02:00
|
|
|
}
|
|
|
|
|
2025-05-27 12:57:03 +02:00
|
|
|
static bool
|
|
|
|
shape_get_iv_index(rb_shape_t *shape, ID id, attr_index_t *value)
|
|
|
|
{
|
|
|
|
while (shape->parent_id != INVALID_SHAPE_ID) {
|
|
|
|
if (shape->edge_name == id) {
|
|
|
|
enum shape_type shape_type;
|
|
|
|
shape_type = (enum shape_type)shape->type;
|
|
|
|
|
|
|
|
switch (shape_type) {
|
|
|
|
case SHAPE_IVAR:
|
|
|
|
RUBY_ASSERT(shape->next_field_index > 0);
|
|
|
|
*value = shape->next_field_index - 1;
|
|
|
|
return true;
|
|
|
|
case SHAPE_ROOT:
|
|
|
|
return false;
|
|
|
|
case SHAPE_OBJ_ID:
|
|
|
|
rb_bug("Ivar should not exist on transition");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
shape = RSHAPE(shape->parent_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-06-04 17:27:29 +02:00
|
|
|
static inline rb_shape_t *
|
|
|
|
shape_get_next(rb_shape_t *shape, VALUE obj, ID id, bool emit_warnings)
|
2022-10-03 11:14:32 -04:00
|
|
|
{
|
2022-12-08 16:48:48 -05:00
|
|
|
RUBY_ASSERT(!is_instance_id(id) || RTEST(rb_sym2str(ID2SYM(id))));
|
|
|
|
|
2023-11-24 14:31:36 -05:00
|
|
|
#if RUBY_DEBUG
|
|
|
|
attr_index_t index;
|
2025-05-27 12:57:03 +02:00
|
|
|
if (shape_get_iv_index(shape, id, &index)) {
|
2023-11-24 14:31:36 -05:00
|
|
|
rb_bug("rb_shape_get_next: trying to create ivar that already exists at index %u", index);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2025-05-11 18:47:22 +02:00
|
|
|
VALUE klass;
|
2025-05-22 14:01:46 +02:00
|
|
|
if (IMEMO_TYPE_P(obj, imemo_class_fields)) { // HACK
|
|
|
|
klass = CLASS_OF(obj);
|
|
|
|
}
|
|
|
|
else {
|
2025-05-11 18:47:22 +02:00
|
|
|
klass = rb_obj_class(obj);
|
2022-12-08 17:16:52 -05:00
|
|
|
}
|
|
|
|
|
2025-05-11 18:47:22 +02:00
|
|
|
bool allow_new_shape = RCLASS_VARIATION_COUNT(klass) < SHAPE_MAX_VARIATIONS;
|
2022-12-08 17:16:52 -05:00
|
|
|
bool variation_created = false;
|
2023-11-10 16:17:39 -05:00
|
|
|
rb_shape_t *new_shape = get_next_shape_internal(shape, id, SHAPE_IVAR, &variation_created, allow_new_shape);
|
2022-12-08 17:16:52 -05:00
|
|
|
|
2025-06-04 09:05:55 +02:00
|
|
|
if (!new_shape) {
|
|
|
|
// We could create a new variation, transitioning to TOO_COMPLEX.
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2022-11-08 15:35:31 -05:00
|
|
|
// Check if we should update max_iv_count on the object's class
|
2025-05-11 18:47:22 +02:00
|
|
|
if (obj != klass && new_shape->next_field_index > RCLASS_MAX_IV_COUNT(klass)) {
|
|
|
|
RCLASS_SET_MAX_IV_COUNT(klass, new_shape->next_field_index);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (variation_created) {
|
|
|
|
RCLASS_VARIATION_COUNT(klass)++;
|
|
|
|
|
|
|
|
if (emit_warnings && rb_warning_category_enabled_p(RB_WARN_CATEGORY_PERFORMANCE)) {
|
|
|
|
if (RCLASS_VARIATION_COUNT(klass) >= SHAPE_MAX_VARIATIONS) {
|
|
|
|
rb_category_warn(
|
|
|
|
RB_WARN_CATEGORY_PERFORMANCE,
|
|
|
|
"The class %"PRIsVALUE" reached %d shape variations, instance variables accesses will be slower and memory usage increased.\n"
|
|
|
|
"It is recommended to define instance variables in a consistent order, for instance by eagerly defining them all in the #initialize method.",
|
|
|
|
rb_class_path(klass),
|
|
|
|
SHAPE_MAX_VARIATIONS
|
|
|
|
);
|
2023-04-13 12:11:14 +02:00
|
|
|
}
|
2022-12-08 16:48:48 -05:00
|
|
|
}
|
2022-11-08 15:35:31 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return new_shape;
|
|
|
|
}
|
|
|
|
|
2025-05-08 20:47:51 +02:00
|
|
|
shape_id_t
|
|
|
|
rb_shape_transition_add_ivar(VALUE obj, ID id)
|
2024-06-04 17:27:29 +02:00
|
|
|
{
|
2025-06-04 11:11:24 +02:00
|
|
|
shape_id_t original_shape_id = RBASIC_SHAPE_ID(obj);
|
|
|
|
RUBY_ASSERT(!shape_frozen_p(original_shape_id));
|
2025-05-27 15:53:45 +02:00
|
|
|
|
2025-06-07 10:55:49 +02:00
|
|
|
rb_shape_t *next_shape = shape_get_next(RSHAPE(original_shape_id), obj, id, true);
|
|
|
|
if (next_shape) {
|
|
|
|
return shape_id(next_shape, original_shape_id);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return transition_complex(original_shape_id);
|
|
|
|
}
|
2024-06-04 17:27:29 +02:00
|
|
|
}
|
|
|
|
|
2025-05-08 20:47:51 +02:00
|
|
|
shape_id_t
|
|
|
|
rb_shape_transition_add_ivar_no_warnings(VALUE obj, ID id)
|
2024-06-04 17:27:29 +02:00
|
|
|
{
|
2025-06-04 11:11:24 +02:00
|
|
|
shape_id_t original_shape_id = RBASIC_SHAPE_ID(obj);
|
|
|
|
RUBY_ASSERT(!shape_frozen_p(original_shape_id));
|
2025-05-27 15:53:45 +02:00
|
|
|
|
2025-06-07 10:55:49 +02:00
|
|
|
rb_shape_t *next_shape = shape_get_next(RSHAPE(original_shape_id), obj, id, false);
|
|
|
|
if (next_shape) {
|
|
|
|
return shape_id(next_shape, original_shape_id);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return transition_complex(original_shape_id);
|
|
|
|
}
|
2024-06-04 17:27:29 +02:00
|
|
|
}
|
|
|
|
|
2023-10-26 11:08:05 +02:00
|
|
|
// Same as rb_shape_get_iv_index, but uses a provided valid shape id and index
|
|
|
|
// to return a result faster if branches of the shape tree are closely related.
|
|
|
|
bool
|
|
|
|
rb_shape_get_iv_index_with_hint(shape_id_t shape_id, ID id, attr_index_t *value, shape_id_t *shape_id_hint)
|
|
|
|
{
|
|
|
|
attr_index_t index_hint = *value;
|
|
|
|
|
|
|
|
if (*shape_id_hint == INVALID_SHAPE_ID) {
|
|
|
|
*shape_id_hint = shape_id;
|
2025-05-27 12:57:03 +02:00
|
|
|
return rb_shape_get_iv_index(shape_id, id, value);
|
2023-10-26 11:08:05 +02:00
|
|
|
}
|
|
|
|
|
2025-05-27 12:57:03 +02:00
|
|
|
rb_shape_t *shape = RSHAPE(shape_id);
|
|
|
|
rb_shape_t *initial_shape = shape;
|
2025-05-08 18:47:18 +02:00
|
|
|
rb_shape_t *shape_hint = RSHAPE(*shape_id_hint);
|
2023-10-26 11:08:05 +02:00
|
|
|
|
|
|
|
// We assume it's likely shape_id_hint and shape_id have a close common
|
|
|
|
// ancestor, so we check up to ANCESTOR_SEARCH_MAX_DEPTH ancestors before
|
|
|
|
// eventually using the index, as in case of a match it will be faster.
|
|
|
|
// However if the shape doesn't have an index, we walk the entire tree.
|
|
|
|
int depth = INT_MAX;
|
2025-04-30 09:42:57 +02:00
|
|
|
if (shape->ancestor_index && shape->next_field_index >= ANCESTOR_CACHE_THRESHOLD) {
|
2023-10-26 11:08:05 +02:00
|
|
|
depth = ANCESTOR_SEARCH_MAX_DEPTH;
|
|
|
|
}
|
|
|
|
|
2025-04-30 09:42:57 +02:00
|
|
|
while (depth > 0 && shape->next_field_index > index_hint) {
|
|
|
|
while (shape_hint->next_field_index > shape->next_field_index) {
|
2025-05-08 21:08:40 +02:00
|
|
|
shape_hint = RSHAPE(shape_hint->parent_id);
|
2023-10-26 11:08:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (shape_hint == shape) {
|
|
|
|
// We've found a common ancestor so use the index hint
|
|
|
|
*value = index_hint;
|
2025-05-27 15:53:45 +02:00
|
|
|
*shape_id_hint = raw_shape_id(shape);
|
2023-10-26 11:08:05 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (shape->edge_name == id) {
|
|
|
|
// We found the matching id before a common ancestor
|
2025-04-30 09:42:57 +02:00
|
|
|
*value = shape->next_field_index - 1;
|
2025-05-27 15:53:45 +02:00
|
|
|
*shape_id_hint = raw_shape_id(shape);
|
2023-10-26 11:08:05 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2025-05-08 21:08:40 +02:00
|
|
|
shape = RSHAPE(shape->parent_id);
|
2023-10-26 11:08:05 +02:00
|
|
|
depth--;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the original shape had an index but its ancestor doesn't
|
|
|
|
// we switch back to the original one as it will be faster.
|
|
|
|
if (!shape->ancestor_index && initial_shape->ancestor_index) {
|
|
|
|
shape = initial_shape;
|
|
|
|
}
|
|
|
|
*shape_id_hint = shape_id;
|
2025-05-27 12:57:03 +02:00
|
|
|
return shape_get_iv_index(shape, id, value);
|
2022-10-03 11:14:32 -04:00
|
|
|
}
|
|
|
|
|
2023-11-24 14:31:50 -05:00
|
|
|
static bool
|
|
|
|
shape_cache_get_iv_index(rb_shape_t *shape, ID id, attr_index_t *value)
|
|
|
|
{
|
2025-04-30 09:42:57 +02:00
|
|
|
if (shape->ancestor_index && shape->next_field_index >= ANCESTOR_CACHE_THRESHOLD) {
|
2023-11-24 14:31:50 -05:00
|
|
|
redblack_node_t *node = redblack_find(shape->ancestor_index, id);
|
|
|
|
if (node) {
|
|
|
|
rb_shape_t *shape = redblack_value(node);
|
2025-04-30 09:42:57 +02:00
|
|
|
*value = shape->next_field_index - 1;
|
2023-11-24 14:31:50 -05:00
|
|
|
|
|
|
|
#if RUBY_DEBUG
|
|
|
|
attr_index_t shape_tree_index;
|
|
|
|
RUBY_ASSERT(shape_get_iv_index(shape, id, &shape_tree_index));
|
|
|
|
RUBY_ASSERT(shape_tree_index == *value);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Verify the cache is correct by checking that this instance variable
|
2023-11-27 14:04:56 -05:00
|
|
|
* does not exist in the shape tree either. */
|
2023-11-24 14:31:50 -05:00
|
|
|
RUBY_ASSERT(!shape_get_iv_index(shape, id, value));
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
2025-05-27 12:57:03 +02:00
|
|
|
rb_shape_get_iv_index(shape_id_t shape_id, ID id, attr_index_t *value)
|
2023-11-24 14:31:50 -05:00
|
|
|
{
|
|
|
|
// It doesn't make sense to ask for the index of an IV that's stored
|
|
|
|
// on an object that is "too complex" as it uses a hash for storing IVs
|
2025-06-04 09:05:55 +02:00
|
|
|
RUBY_ASSERT(!rb_shape_too_complex_p(shape_id));
|
|
|
|
|
|
|
|
rb_shape_t *shape = RSHAPE(shape_id);
|
2023-11-24 14:31:50 -05:00
|
|
|
|
|
|
|
if (!shape_cache_get_iv_index(shape, id, value)) {
|
If we have a shape cache we should use it
If there is a shape cache, then we should believe the results instead of
doing a linear search for non-existent items
This fixes a case where checking the index of an undefined ivar would
result in an O(n) search. Now we get O(log n).
Benchmark is as follows:
```ruby
N = ARGV[0].to_i
class ManyIVs
class_eval "def initialize;" +
N.times.map { "@a#{_1} = #{_1}" }.join("\n") +
"end"
def check
defined?(@not)
end
end
class Subclass < ManyIVs
def initialize
super
@foo = 123
end
end
def t
s = Process.clock_gettime Process::CLOCK_MONOTONIC
yield
Process.clock_gettime(Process::CLOCK_MONOTONIC) - s
end
def test
a = ManyIVs.new
b = Subclass.new
t { 200000.times { a.check; b.check } }
end
puts "#{N},#{test}"
```
On the master branch:
```
$ for i in (seq 1 3 32); ./miniruby test.rb $i; end
1,0.015619999991031364
4,0.013061000005109236
7,0.013365999999223277
10,0.015474999992875382
13,0.017674999980954453
16,0.020055999979376793
19,0.02260500000556931
22,0.0254080000158865
25,0.02806599999894388
28,0.031244999991031364
31,0.034568000002764165
```
On this branch:
```
$ for i in (seq 1 3 32); ./miniruby test.rb $i; end
1,0.015848999988520518
4,0.013225000002421439
7,0.013049000001046807
10,0.010697999998228624
13,0.010902000009082258
16,0.011448000004747882
19,0.01151199999731034
22,0.011539999977685511
25,0.01173300002119504
28,0.011900000012246892
31,0.012278999987756833
```
2024-03-30 17:29:09 -07:00
|
|
|
// If it wasn't in the ancestor cache, then don't do a linear search
|
2025-04-30 09:42:57 +02:00
|
|
|
if (shape->ancestor_index && shape->next_field_index >= ANCESTOR_CACHE_THRESHOLD) {
|
If we have a shape cache we should use it
If there is a shape cache, then we should believe the results instead of
doing a linear search for non-existent items
This fixes a case where checking the index of an undefined ivar would
result in an O(n) search. Now we get O(log n).
Benchmark is as follows:
```ruby
N = ARGV[0].to_i
class ManyIVs
class_eval "def initialize;" +
N.times.map { "@a#{_1} = #{_1}" }.join("\n") +
"end"
def check
defined?(@not)
end
end
class Subclass < ManyIVs
def initialize
super
@foo = 123
end
end
def t
s = Process.clock_gettime Process::CLOCK_MONOTONIC
yield
Process.clock_gettime(Process::CLOCK_MONOTONIC) - s
end
def test
a = ManyIVs.new
b = Subclass.new
t { 200000.times { a.check; b.check } }
end
puts "#{N},#{test}"
```
On the master branch:
```
$ for i in (seq 1 3 32); ./miniruby test.rb $i; end
1,0.015619999991031364
4,0.013061000005109236
7,0.013365999999223277
10,0.015474999992875382
13,0.017674999980954453
16,0.020055999979376793
19,0.02260500000556931
22,0.0254080000158865
25,0.02806599999894388
28,0.031244999991031364
31,0.034568000002764165
```
On this branch:
```
$ for i in (seq 1 3 32); ./miniruby test.rb $i; end
1,0.015848999988520518
4,0.013225000002421439
7,0.013049000001046807
10,0.010697999998228624
13,0.010902000009082258
16,0.011448000004747882
19,0.01151199999731034
22,0.011539999977685511
25,0.01173300002119504
28,0.011900000012246892
31,0.012278999987756833
```
2024-03-30 17:29:09 -07:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return shape_get_iv_index(shape, id, value);
|
|
|
|
}
|
2023-11-24 14:31:50 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-12-02 12:33:20 -05:00
|
|
|
int32_t
|
|
|
|
rb_shape_id_offset(void)
|
|
|
|
{
|
2022-12-05 16:20:11 -05:00
|
|
|
return sizeof(uintptr_t) - SHAPE_ID_NUM_BITS / sizeof(uintptr_t);
|
2022-12-02 12:33:20 -05:00
|
|
|
}
|
|
|
|
|
2025-04-21 12:01:01 +09:00
|
|
|
// Rebuild a similar shape with the same ivars but starting from
|
|
|
|
// a different SHAPE_T_OBJECT, and don't cary over non-canonical transitions
|
2025-05-27 15:53:45 +02:00
|
|
|
// such as SHAPE_OBJ_ID.
|
2025-06-04 09:05:55 +02:00
|
|
|
static rb_shape_t *
|
|
|
|
shape_rebuild(rb_shape_t *initial_shape, rb_shape_t *dest_shape)
|
2022-11-08 15:35:31 -05:00
|
|
|
{
|
2025-04-29 09:50:47 +02:00
|
|
|
rb_shape_t *midway_shape;
|
2022-11-08 15:35:31 -05:00
|
|
|
|
2025-06-07 14:44:13 +02:00
|
|
|
RUBY_ASSERT(initial_shape->type == SHAPE_ROOT);
|
2022-11-17 15:57:11 -08:00
|
|
|
|
|
|
|
if (dest_shape->type != initial_shape->type) {
|
2025-06-04 09:05:55 +02:00
|
|
|
midway_shape = shape_rebuild(initial_shape, RSHAPE(dest_shape->parent_id));
|
|
|
|
if (UNLIKELY(!midway_shape)) {
|
|
|
|
return NULL;
|
2023-10-30 12:29:59 +01:00
|
|
|
}
|
2022-11-08 15:35:31 -05:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
midway_shape = initial_shape;
|
|
|
|
}
|
|
|
|
|
2022-11-17 15:57:11 -08:00
|
|
|
switch ((enum shape_type)dest_shape->type) {
|
2022-11-17 14:43:46 -05:00
|
|
|
case SHAPE_IVAR:
|
2025-05-08 18:36:01 +02:00
|
|
|
midway_shape = shape_get_next_iv_shape(midway_shape, dest_shape->edge_name);
|
2022-11-17 14:43:46 -05:00
|
|
|
break;
|
2025-04-21 16:16:07 +09:00
|
|
|
case SHAPE_OBJ_ID:
|
2022-11-17 14:43:46 -05:00
|
|
|
case SHAPE_ROOT:
|
|
|
|
break;
|
2022-11-08 15:35:31 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return midway_shape;
|
|
|
|
}
|
|
|
|
|
2025-06-04 13:35:43 +02:00
|
|
|
// Rebuild `dest_shape_id` starting from `initial_shape_id`, and keep only SHAPE_IVAR transitions.
|
|
|
|
// SHAPE_OBJ_ID and frozen status are lost.
|
2025-05-27 09:36:33 +02:00
|
|
|
shape_id_t
|
|
|
|
rb_shape_rebuild(shape_id_t initial_shape_id, shape_id_t dest_shape_id)
|
|
|
|
{
|
2025-06-04 09:05:55 +02:00
|
|
|
RUBY_ASSERT(!rb_shape_too_complex_p(initial_shape_id));
|
|
|
|
RUBY_ASSERT(!rb_shape_too_complex_p(dest_shape_id));
|
|
|
|
|
2025-06-07 10:55:49 +02:00
|
|
|
rb_shape_t *next_shape = shape_rebuild(RSHAPE(initial_shape_id), RSHAPE(dest_shape_id));
|
|
|
|
if (next_shape) {
|
|
|
|
return shape_id(next_shape, initial_shape_id);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return transition_complex(initial_shape_id | (dest_shape_id & SHAPE_ID_FL_HAS_OBJECT_ID));
|
|
|
|
}
|
2025-05-27 09:36:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
rb_shape_copy_fields(VALUE dest, VALUE *dest_buf, shape_id_t dest_shape_id, VALUE src, VALUE *src_buf, shape_id_t src_shape_id)
|
|
|
|
{
|
|
|
|
rb_shape_t *dest_shape = RSHAPE(dest_shape_id);
|
|
|
|
rb_shape_t *src_shape = RSHAPE(src_shape_id);
|
|
|
|
|
|
|
|
if (src_shape->next_field_index == dest_shape->next_field_index) {
|
|
|
|
// Happy path, we can just memcpy the ivptr content
|
|
|
|
MEMCPY(dest_buf, src_buf, VALUE, dest_shape->next_field_index);
|
|
|
|
|
|
|
|
// Fire write barriers
|
|
|
|
for (uint32_t i = 0; i < dest_shape->next_field_index; i++) {
|
|
|
|
RB_OBJ_WRITTEN(dest, Qundef, dest_buf[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
while (src_shape->parent_id != INVALID_SHAPE_ID) {
|
|
|
|
if (src_shape->type == SHAPE_IVAR) {
|
|
|
|
while (dest_shape->edge_name != src_shape->edge_name) {
|
2025-06-04 13:35:43 +02:00
|
|
|
if (UNLIKELY(dest_shape->parent_id == INVALID_SHAPE_ID)) {
|
|
|
|
rb_bug("Lost field %s", rb_id2name(src_shape->edge_name));
|
|
|
|
}
|
2025-05-27 09:36:33 +02:00
|
|
|
dest_shape = RSHAPE(dest_shape->parent_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
RB_OBJ_WRITE(dest, &dest_buf[dest_shape->next_field_index - 1], src_buf[src_shape->next_field_index - 1]);
|
|
|
|
}
|
|
|
|
src_shape = RSHAPE(src_shape->parent_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
rb_shape_copy_complex_ivars(VALUE dest, VALUE obj, shape_id_t src_shape_id, st_table *fields_table)
|
|
|
|
{
|
|
|
|
// obj is TOO_COMPLEX so we can copy its iv_hash
|
|
|
|
st_table *table = st_copy(fields_table);
|
2025-05-27 13:20:58 +02:00
|
|
|
if (rb_shape_has_object_id(src_shape_id)) {
|
2025-05-27 09:36:33 +02:00
|
|
|
st_data_t id = (st_data_t)ruby_internal_object_id;
|
|
|
|
st_delete(table, &id, NULL);
|
|
|
|
}
|
|
|
|
rb_obj_init_too_complex(dest, table);
|
|
|
|
}
|
|
|
|
|
2022-12-06 12:56:51 +01:00
|
|
|
size_t
|
2025-05-08 18:20:35 +02:00
|
|
|
rb_shape_edges_count(shape_id_t shape_id)
|
2022-12-06 12:56:51 +01:00
|
|
|
{
|
2025-05-08 18:47:18 +02:00
|
|
|
rb_shape_t *shape = RSHAPE(shape_id);
|
2022-12-06 12:56:51 +01:00
|
|
|
if (shape->edges) {
|
2023-03-13 15:07:09 -07:00
|
|
|
if (SINGLE_CHILD_P(shape->edges)) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
else {
|
2025-05-19 12:38:49 +02:00
|
|
|
return rb_managed_id_table_size(shape->edges);
|
2023-03-13 15:07:09 -07:00
|
|
|
}
|
2022-12-06 12:56:51 +01:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t
|
2025-05-08 18:20:35 +02:00
|
|
|
rb_shape_memsize(shape_id_t shape_id)
|
2022-12-06 12:56:51 +01:00
|
|
|
{
|
2025-05-08 18:47:18 +02:00
|
|
|
rb_shape_t *shape = RSHAPE(shape_id);
|
2025-05-08 18:20:35 +02:00
|
|
|
|
2022-12-06 12:56:51 +01:00
|
|
|
size_t memsize = sizeof(rb_shape_t);
|
2023-03-13 15:07:09 -07:00
|
|
|
if (shape->edges && !SINGLE_CHILD_P(shape->edges)) {
|
2025-05-19 12:38:49 +02:00
|
|
|
memsize += rb_managed_id_table_size(shape->edges);
|
2022-12-06 12:56:51 +01:00
|
|
|
}
|
|
|
|
return memsize;
|
|
|
|
}
|
|
|
|
|
2025-06-11 14:32:35 +02:00
|
|
|
bool
|
|
|
|
rb_shape_foreach_field(shape_id_t initial_shape_id, rb_shape_foreach_transition_callback func, void *data)
|
|
|
|
{
|
|
|
|
RUBY_ASSERT(!rb_shape_too_complex_p(initial_shape_id));
|
|
|
|
|
|
|
|
rb_shape_t *shape = RSHAPE(initial_shape_id);
|
|
|
|
if (shape->type == SHAPE_ROOT) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
shape_id_t parent_id = shape_id(RSHAPE(shape->parent_id), initial_shape_id);
|
|
|
|
if (rb_shape_foreach_field(parent_id, func, data)) {
|
|
|
|
switch (func(shape_id(shape, initial_shape_id), data)) {
|
|
|
|
case ST_STOP:
|
|
|
|
return false;
|
|
|
|
case ST_CHECK:
|
|
|
|
case ST_CONTINUE:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
rb_bug("unreachable");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2025-06-04 11:11:24 +02:00
|
|
|
#if RUBY_DEBUG
|
|
|
|
bool
|
|
|
|
rb_shape_verify_consistency(VALUE obj, shape_id_t shape_id)
|
|
|
|
{
|
2025-06-07 10:55:49 +02:00
|
|
|
if (shape_id == INVALID_SHAPE_ID) {
|
|
|
|
rb_bug("Can't set INVALID_SHAPE_ID on an object");
|
|
|
|
}
|
|
|
|
|
2025-06-04 11:11:24 +02:00
|
|
|
rb_shape_t *shape = RSHAPE(shape_id);
|
|
|
|
|
|
|
|
bool has_object_id = false;
|
|
|
|
while (shape->parent_id != INVALID_SHAPE_ID) {
|
|
|
|
if (shape->type == SHAPE_OBJ_ID) {
|
|
|
|
has_object_id = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
shape = RSHAPE(shape->parent_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rb_shape_has_object_id(shape_id)) {
|
|
|
|
if (!has_object_id) {
|
|
|
|
rb_p(obj);
|
|
|
|
rb_bug("shape_id claim having obj_id but doesn't shape_id=%u, obj=%s", shape_id, rb_obj_info(obj));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (has_object_id) {
|
|
|
|
rb_p(obj);
|
|
|
|
rb_bug("shape_id claim not having obj_id but it does shape_id=%u, obj=%s", shape_id, rb_obj_info(obj));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-06-07 10:55:49 +02:00
|
|
|
uint8_t flags_heap_index = rb_shape_heap_index(shape_id);
|
2025-06-07 14:44:13 +02:00
|
|
|
if (RB_TYPE_P(obj, T_OBJECT)) {
|
|
|
|
size_t shape_id_slot_size = GET_SHAPE_TREE()->capacities[flags_heap_index - 1] * sizeof(VALUE) + sizeof(struct RBasic);
|
|
|
|
size_t actual_slot_size = rb_gc_obj_slot_size(obj);
|
|
|
|
|
|
|
|
if (shape_id_slot_size != actual_slot_size) {
|
2025-06-11 09:03:26 +02:00
|
|
|
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);
|
2025-06-07 14:44:13 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (flags_heap_index) {
|
|
|
|
rb_bug("shape_id indicate heap_index > 0 but objet is not T_OBJECT: %s", rb_obj_info(obj));
|
|
|
|
}
|
2025-06-06 09:26:35 +02:00
|
|
|
}
|
|
|
|
|
2025-06-04 11:11:24 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2022-11-23 14:01:03 -08:00
|
|
|
#if SHAPE_DEBUG
|
2025-06-04 11:11:24 +02:00
|
|
|
|
2022-10-03 11:14:32 -04:00
|
|
|
/*
|
2025-06-05 16:09:21 +02:00
|
|
|
* Exposing Shape to Ruby via RubyVM::Shape.of(object)
|
2022-10-03 11:14:32 -04:00
|
|
|
*/
|
2022-11-08 15:35:31 -05:00
|
|
|
|
2022-12-08 17:16:52 -05:00
|
|
|
static VALUE
|
2025-04-21 16:16:07 +09:00
|
|
|
shape_too_complex(VALUE self)
|
2022-12-08 17:16:52 -05:00
|
|
|
{
|
2025-04-21 16:16:07 +09:00
|
|
|
shape_id_t shape_id = NUM2INT(rb_struct_getmember(self, rb_intern("id")));
|
2025-06-04 09:05:55 +02:00
|
|
|
return RBOOL(rb_shape_too_complex_p(shape_id));
|
2025-04-21 16:16:07 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
static VALUE
|
|
|
|
shape_frozen(VALUE self)
|
|
|
|
{
|
|
|
|
shape_id_t shape_id = NUM2INT(rb_struct_getmember(self, rb_intern("id")));
|
2025-05-27 15:53:45 +02:00
|
|
|
return RBOOL(shape_id & SHAPE_ID_FL_FROZEN);
|
2025-04-21 16:16:07 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
static VALUE
|
2025-05-27 13:20:58 +02:00
|
|
|
shape_has_object_id_p(VALUE self)
|
2025-04-21 16:16:07 +09:00
|
|
|
{
|
|
|
|
shape_id_t shape_id = NUM2INT(rb_struct_getmember(self, rb_intern("id")));
|
2025-06-04 11:11:24 +02:00
|
|
|
return RBOOL(rb_shape_has_object_id(shape_id));
|
2022-12-08 17:16:52 -05:00
|
|
|
}
|
|
|
|
|
2022-10-12 18:27:23 +09:00
|
|
|
static VALUE
|
|
|
|
parse_key(ID key)
|
|
|
|
{
|
2022-12-06 12:56:51 +01:00
|
|
|
if (is_instance_id(key)) {
|
2022-10-03 11:14:32 -04:00
|
|
|
return ID2SYM(key);
|
|
|
|
}
|
2022-12-06 12:56:51 +01:00
|
|
|
return LONG2NUM(key);
|
2022-10-03 11:14:32 -04:00
|
|
|
}
|
|
|
|
|
2025-04-29 09:50:47 +02:00
|
|
|
static VALUE rb_shape_edge_name(rb_shape_t *shape);
|
2022-12-15 16:38:53 -05:00
|
|
|
|
2022-10-03 11:14:32 -04:00
|
|
|
static VALUE
|
2025-05-27 15:53:45 +02:00
|
|
|
shape_id_t_to_rb_cShape(shape_id_t shape_id)
|
2022-10-12 18:27:23 +09:00
|
|
|
{
|
2022-12-15 16:38:53 -05:00
|
|
|
VALUE rb_cShape = rb_const_get(rb_cRubyVM, rb_intern("Shape"));
|
2025-05-27 15:53:45 +02:00
|
|
|
rb_shape_t *shape = RSHAPE(shape_id);
|
2022-10-03 11:14:32 -04:00
|
|
|
|
2022-12-15 16:38:53 -05:00
|
|
|
VALUE obj = rb_struct_new(rb_cShape,
|
2025-05-27 15:53:45 +02:00
|
|
|
INT2NUM(shape_id),
|
2025-06-06 09:26:35 +02:00
|
|
|
INT2NUM(shape_id & SHAPE_ID_OFFSET_MASK),
|
2022-12-15 16:38:53 -05:00
|
|
|
INT2NUM(shape->parent_id),
|
|
|
|
rb_shape_edge_name(shape),
|
2025-04-30 09:42:57 +02:00
|
|
|
INT2NUM(shape->next_field_index),
|
2025-06-07 15:26:27 +02:00
|
|
|
INT2NUM(rb_shape_heap_index(shape_id)),
|
2022-12-15 16:38:53 -05:00
|
|
|
INT2NUM(shape->type),
|
2025-06-07 14:44:13 +02:00
|
|
|
INT2NUM(RSHAPE_CAPACITY(shape_id)));
|
2022-12-15 16:38:53 -05:00
|
|
|
rb_obj_freeze(obj);
|
|
|
|
return obj;
|
2022-10-03 11:14:32 -04:00
|
|
|
}
|
|
|
|
|
2022-10-12 18:27:23 +09:00
|
|
|
static enum rb_id_table_iterator_result
|
|
|
|
rb_edges_to_hash(ID key, VALUE value, void *ref)
|
2022-10-03 11:14:32 -04:00
|
|
|
{
|
2025-05-27 15:53:45 +02:00
|
|
|
rb_hash_aset(*(VALUE *)ref, parse_key(key), shape_id_t_to_rb_cShape(raw_shape_id((rb_shape_t *)value)));
|
2022-10-03 11:14:32 -04:00
|
|
|
return ID_TABLE_CONTINUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static VALUE
|
|
|
|
rb_shape_edges(VALUE self)
|
|
|
|
{
|
2025-05-19 12:38:49 +02:00
|
|
|
rb_shape_t *shape = RSHAPE(NUM2INT(rb_struct_getmember(self, rb_intern("id"))));
|
2022-10-03 11:14:32 -04:00
|
|
|
|
|
|
|
VALUE hash = rb_hash_new();
|
|
|
|
|
|
|
|
if (shape->edges) {
|
2023-03-13 15:07:09 -07:00
|
|
|
if (SINGLE_CHILD_P(shape->edges)) {
|
2025-04-29 09:50:47 +02:00
|
|
|
rb_shape_t *child = SINGLE_CHILD(shape->edges);
|
2023-03-13 15:07:09 -07:00
|
|
|
rb_edges_to_hash(child->edge_name, (VALUE)child, &hash);
|
|
|
|
}
|
|
|
|
else {
|
2025-05-19 12:38:49 +02:00
|
|
|
VALUE edges = shape->edges;
|
|
|
|
rb_managed_id_table_foreach(edges, rb_edges_to_hash, &hash);
|
|
|
|
RB_GC_GUARD(edges);
|
2023-03-13 15:07:09 -07:00
|
|
|
}
|
2022-10-03 11:14:32 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return hash;
|
|
|
|
}
|
|
|
|
|
|
|
|
static VALUE
|
2025-04-29 09:50:47 +02:00
|
|
|
rb_shape_edge_name(rb_shape_t *shape)
|
2022-10-03 11:14:32 -04:00
|
|
|
{
|
2022-12-06 12:56:51 +01:00
|
|
|
if (shape->edge_name) {
|
|
|
|
if (is_instance_id(shape->edge_name)) {
|
2022-11-08 15:35:31 -05:00
|
|
|
return ID2SYM(shape->edge_name);
|
|
|
|
}
|
2022-12-06 12:56:51 +01:00
|
|
|
return INT2NUM(shape->capacity);
|
2022-10-03 11:14:32 -04:00
|
|
|
}
|
2022-12-06 12:56:51 +01:00
|
|
|
return Qnil;
|
2022-10-03 11:14:32 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
static VALUE
|
|
|
|
rb_shape_export_depth(VALUE self)
|
|
|
|
{
|
2025-05-08 18:20:35 +02:00
|
|
|
shape_id_t shape_id = NUM2INT(rb_struct_getmember(self, rb_intern("id")));
|
|
|
|
return SIZET2NUM(rb_shape_depth(shape_id));
|
2022-10-03 11:14:32 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
static VALUE
|
|
|
|
rb_shape_parent(VALUE self)
|
|
|
|
{
|
2025-04-29 09:50:47 +02:00
|
|
|
rb_shape_t *shape;
|
2025-05-08 18:47:18 +02:00
|
|
|
shape = RSHAPE(NUM2INT(rb_struct_getmember(self, rb_intern("id"))));
|
2022-10-03 13:52:40 -04:00
|
|
|
if (shape->parent_id != INVALID_SHAPE_ID) {
|
2025-05-27 15:53:45 +02:00
|
|
|
return shape_id_t_to_rb_cShape(shape->parent_id);
|
2022-10-03 11:14:32 -04:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
return Qnil;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-12 14:37:02 -07:00
|
|
|
static VALUE
|
2022-10-12 18:27:23 +09:00
|
|
|
rb_shape_debug_shape(VALUE self, VALUE obj)
|
|
|
|
{
|
2025-05-27 15:53:45 +02:00
|
|
|
return shape_id_t_to_rb_cShape(rb_obj_shape_id(obj));
|
2022-10-03 11:14:32 -04:00
|
|
|
}
|
|
|
|
|
2022-10-12 14:37:02 -07:00
|
|
|
static VALUE
|
2022-10-12 18:27:23 +09:00
|
|
|
rb_shape_root_shape(VALUE self)
|
|
|
|
{
|
2025-05-27 15:53:45 +02:00
|
|
|
return shape_id_t_to_rb_cShape(ROOT_SHAPE_ID);
|
2022-10-03 11:14:32 -04:00
|
|
|
}
|
|
|
|
|
2023-10-19 11:00:54 -07:00
|
|
|
static VALUE
|
|
|
|
rb_shape_shapes_available(VALUE self)
|
|
|
|
{
|
|
|
|
return INT2NUM(MAX_SHAPE_ID - (GET_SHAPE_TREE()->next_shape_id - 1));
|
|
|
|
}
|
|
|
|
|
2023-11-21 17:23:56 +01:00
|
|
|
static VALUE
|
|
|
|
rb_shape_exhaust(int argc, VALUE *argv, VALUE self)
|
|
|
|
{
|
|
|
|
rb_check_arity(argc, 0, 1);
|
|
|
|
int offset = argc == 1 ? NUM2INT(argv[0]) : 0;
|
2023-11-22 11:35:42 -05:00
|
|
|
GET_SHAPE_TREE()->next_shape_id = MAX_SHAPE_ID - offset + 1;
|
2023-11-21 17:23:56 +01:00
|
|
|
return Qnil;
|
|
|
|
}
|
|
|
|
|
2025-05-09 08:58:07 +02:00
|
|
|
static VALUE shape_to_h(rb_shape_t *shape);
|
2022-10-03 11:14:32 -04:00
|
|
|
|
|
|
|
static enum rb_id_table_iterator_result collect_keys_and_values(ID key, VALUE value, void *ref)
|
|
|
|
{
|
2025-05-09 08:58:07 +02:00
|
|
|
rb_hash_aset(*(VALUE *)ref, parse_key(key), shape_to_h((rb_shape_t *)value));
|
2022-10-03 11:14:32 -04:00
|
|
|
return ID_TABLE_CONTINUE;
|
|
|
|
}
|
|
|
|
|
2025-05-19 12:38:49 +02:00
|
|
|
static VALUE edges(VALUE edges)
|
2022-10-03 11:14:32 -04:00
|
|
|
{
|
|
|
|
VALUE hash = rb_hash_new();
|
2025-06-10 17:04:07 +02:00
|
|
|
if (edges) {
|
|
|
|
if (SINGLE_CHILD_P(edges)) {
|
|
|
|
rb_shape_t *child = SINGLE_CHILD(edges);
|
|
|
|
collect_keys_and_values(child->edge_name, (VALUE)child, &hash);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
rb_managed_id_table_foreach(edges, collect_keys_and_values, &hash);
|
|
|
|
}
|
2023-03-13 15:07:09 -07:00
|
|
|
}
|
2022-10-03 11:14:32 -04:00
|
|
|
return hash;
|
|
|
|
}
|
|
|
|
|
2025-05-09 08:58:07 +02:00
|
|
|
static VALUE
|
|
|
|
shape_to_h(rb_shape_t *shape)
|
2022-10-12 18:27:23 +09:00
|
|
|
{
|
2022-10-03 11:14:32 -04:00
|
|
|
VALUE rb_shape = rb_hash_new();
|
|
|
|
|
2025-05-27 15:53:45 +02:00
|
|
|
rb_hash_aset(rb_shape, ID2SYM(rb_intern("id")), INT2NUM(raw_shape_id(shape)));
|
|
|
|
rb_hash_aset(rb_shape, ID2SYM(rb_intern("edges")), edges(shape->edges));
|
2022-10-03 11:14:32 -04:00
|
|
|
|
|
|
|
if (shape == rb_shape_get_root_shape()) {
|
|
|
|
rb_hash_aset(rb_shape, ID2SYM(rb_intern("parent_id")), INT2NUM(ROOT_SHAPE_ID));
|
|
|
|
}
|
|
|
|
else {
|
2022-10-03 13:52:40 -04:00
|
|
|
rb_hash_aset(rb_shape, ID2SYM(rb_intern("parent_id")), INT2NUM(shape->parent_id));
|
2022-10-03 11:14:32 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
rb_hash_aset(rb_shape, ID2SYM(rb_intern("edge_name")), rb_id2str(shape->edge_name));
|
|
|
|
return rb_shape;
|
|
|
|
}
|
|
|
|
|
2022-10-12 18:27:23 +09:00
|
|
|
static VALUE
|
|
|
|
shape_transition_tree(VALUE self)
|
|
|
|
{
|
2025-05-09 08:58:07 +02:00
|
|
|
return shape_to_h(rb_shape_get_root_shape());
|
2022-10-03 11:14:32 -04:00
|
|
|
}
|
|
|
|
|
2022-10-03 13:52:40 -04:00
|
|
|
static VALUE
|
|
|
|
rb_shape_find_by_id(VALUE mod, VALUE id)
|
|
|
|
{
|
2022-10-12 08:54:02 -07:00
|
|
|
shape_id_t shape_id = NUM2UINT(id);
|
2023-02-17 13:32:51 +00:00
|
|
|
if (shape_id >= GET_SHAPE_TREE()->next_shape_id) {
|
2022-10-03 13:52:40 -04:00
|
|
|
rb_raise(rb_eArgError, "Shape ID %d is out of bounds\n", shape_id);
|
|
|
|
}
|
2025-05-27 15:53:45 +02:00
|
|
|
return shape_id_t_to_rb_cShape(shape_id);
|
2022-10-03 13:52:40 -04:00
|
|
|
}
|
2022-10-12 14:37:02 -07:00
|
|
|
#endif
|
2022-10-03 13:52:40 -04:00
|
|
|
|
2023-02-17 15:51:16 +00:00
|
|
|
#ifdef HAVE_MMAP
|
2023-02-17 13:32:51 +00:00
|
|
|
#include <sys/mman.h>
|
2023-02-17 15:51:16 +00:00
|
|
|
#endif
|
2023-02-17 13:32:51 +00:00
|
|
|
|
2022-11-08 15:35:31 -05:00
|
|
|
void
|
|
|
|
Init_default_shapes(void)
|
|
|
|
{
|
2024-04-24 15:32:32 -04:00
|
|
|
rb_shape_tree_ptr = xcalloc(1, sizeof(rb_shape_tree_t));
|
2023-02-17 13:32:51 +00:00
|
|
|
|
2025-06-06 12:16:31 +02:00
|
|
|
size_t *heap_sizes = rb_gc_heap_sizes();
|
|
|
|
size_t heaps_count = 0;
|
|
|
|
while (heap_sizes[heaps_count]) {
|
|
|
|
heaps_count++;
|
|
|
|
}
|
|
|
|
attr_index_t *capacities = ALLOC_N(attr_index_t, heaps_count + 1);
|
|
|
|
capacities[heaps_count] = 0;
|
|
|
|
size_t index;
|
|
|
|
for (index = 0; index < heaps_count; index++) {
|
|
|
|
capacities[index] = (heap_sizes[index] - sizeof(struct RBasic)) / sizeof(VALUE);
|
|
|
|
}
|
2025-06-07 14:44:13 +02:00
|
|
|
GET_SHAPE_TREE()->capacities = capacities;
|
2025-06-06 12:16:31 +02:00
|
|
|
|
2023-02-17 13:32:51 +00:00
|
|
|
#ifdef HAVE_MMAP
|
2024-11-19 16:07:22 +08:00
|
|
|
size_t shape_list_mmap_size = rb_size_mul_or_raise(SHAPE_BUFFER_SIZE, sizeof(rb_shape_t), rb_eRuntimeError);
|
|
|
|
rb_shape_tree_ptr->shape_list = (rb_shape_t *)mmap(NULL, shape_list_mmap_size,
|
2023-02-17 13:32:51 +00:00
|
|
|
PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
|
|
|
if (GET_SHAPE_TREE()->shape_list == MAP_FAILED) {
|
|
|
|
GET_SHAPE_TREE()->shape_list = 0;
|
|
|
|
}
|
2024-11-19 16:07:22 +08:00
|
|
|
else {
|
|
|
|
ruby_annotate_mmap(rb_shape_tree_ptr->shape_list, shape_list_mmap_size, "Ruby:Init_default_shapes:shape_list");
|
|
|
|
}
|
2023-02-17 13:32:51 +00:00
|
|
|
#else
|
|
|
|
GET_SHAPE_TREE()->shape_list = xcalloc(SHAPE_BUFFER_SIZE, sizeof(rb_shape_t));
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (!GET_SHAPE_TREE()->shape_list) {
|
|
|
|
rb_memerror();
|
|
|
|
}
|
|
|
|
|
2022-11-08 15:35:31 -05:00
|
|
|
id_frozen = rb_make_internal_id();
|
2022-11-17 15:57:11 -08:00
|
|
|
id_t_object = rb_make_internal_id();
|
2025-04-21 16:16:07 +09:00
|
|
|
ruby_internal_object_id = rb_make_internal_id();
|
2022-11-08 15:35:31 -05:00
|
|
|
|
2023-02-07 17:46:42 -08:00
|
|
|
#ifdef HAVE_MMAP
|
2024-11-19 16:07:22 +08:00
|
|
|
size_t shape_cache_mmap_size = rb_size_mul_or_raise(REDBLACK_CACHE_SIZE, sizeof(redblack_node_t), rb_eRuntimeError);
|
|
|
|
rb_shape_tree_ptr->shape_cache = (redblack_node_t *)mmap(NULL, shape_cache_mmap_size,
|
2023-02-07 17:46:42 -08:00
|
|
|
PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
|
|
|
rb_shape_tree_ptr->cache_size = 0;
|
2024-01-11 16:05:21 -08:00
|
|
|
|
|
|
|
// If mmap fails, then give up on the redblack tree cache.
|
|
|
|
// We set the cache size such that the redblack node allocators think
|
|
|
|
// the cache is full.
|
|
|
|
if (GET_SHAPE_TREE()->shape_cache == MAP_FAILED) {
|
|
|
|
GET_SHAPE_TREE()->shape_cache = 0;
|
|
|
|
GET_SHAPE_TREE()->cache_size = REDBLACK_CACHE_SIZE;
|
|
|
|
}
|
2024-11-19 16:07:22 +08:00
|
|
|
else {
|
|
|
|
ruby_annotate_mmap(rb_shape_tree_ptr->shape_cache, shape_cache_mmap_size, "Ruby:Init_default_shapes:shape_cache");
|
|
|
|
}
|
2023-02-07 17:46:42 -08:00
|
|
|
#endif
|
|
|
|
|
2025-05-19 12:38:49 +02:00
|
|
|
rb_gc_register_address(&shape_tree_obj);
|
|
|
|
shape_tree_obj = TypedData_Wrap_Struct(0, &shape_tree_type, (void *)1);
|
|
|
|
|
2022-11-08 15:35:31 -05:00
|
|
|
// Root shape
|
2023-11-02 09:49:23 -04:00
|
|
|
rb_shape_t *root = rb_shape_alloc_with_parent_id(0, INVALID_SHAPE_ID);
|
|
|
|
root->capacity = 0;
|
2022-11-08 15:35:31 -05:00
|
|
|
root->type = SHAPE_ROOT;
|
2023-02-17 13:32:51 +00:00
|
|
|
GET_SHAPE_TREE()->root_shape = root;
|
2025-05-27 15:53:45 +02:00
|
|
|
RUBY_ASSERT(raw_shape_id(GET_SHAPE_TREE()->root_shape) == ROOT_SHAPE_ID);
|
2022-11-08 15:35:31 -05:00
|
|
|
|
2025-06-04 11:11:24 +02:00
|
|
|
rb_shape_t *root_with_obj_id = rb_shape_alloc_with_parent_id(0, ROOT_SHAPE_ID);
|
|
|
|
root_with_obj_id->type = SHAPE_OBJ_ID;
|
|
|
|
root_with_obj_id->edge_name = ruby_internal_object_id;
|
|
|
|
root_with_obj_id->next_field_index++;
|
|
|
|
RUBY_ASSERT(raw_shape_id(root_with_obj_id) == ROOT_SHAPE_WITH_OBJ_ID);
|
2022-11-08 15:35:31 -05:00
|
|
|
}
|
|
|
|
|
2025-05-08 17:38:39 +02:00
|
|
|
void
|
|
|
|
rb_shape_free_all(void)
|
|
|
|
{
|
2025-06-07 14:44:13 +02:00
|
|
|
xfree((void *)GET_SHAPE_TREE()->capacities);
|
2025-05-08 17:38:39 +02:00
|
|
|
xfree(GET_SHAPE_TREE());
|
|
|
|
}
|
|
|
|
|
2022-10-03 11:14:32 -04:00
|
|
|
void
|
|
|
|
Init_shape(void)
|
|
|
|
{
|
2022-11-23 14:01:03 -08:00
|
|
|
#if SHAPE_DEBUG
|
2024-12-24 21:31:52 +09:00
|
|
|
/* Document-class: RubyVM::Shape
|
|
|
|
* :nodoc: */
|
2022-12-15 16:38:53 -05:00
|
|
|
VALUE rb_cShape = rb_struct_define_under(rb_cRubyVM, "Shape",
|
|
|
|
"id",
|
2025-06-06 09:26:35 +02:00
|
|
|
"raw_id",
|
2022-12-15 16:38:53 -05:00
|
|
|
"parent_id",
|
|
|
|
"edge_name",
|
2025-04-30 09:42:57 +02:00
|
|
|
"next_field_index",
|
Rename size_pool -> heap
Now that we've inlined the eden_heap into the size_pool, we should
rename the size_pool to heap. So that Ruby contains multiple heaps, with
different sized objects.
The term heap as a collection of memory pages is more in memory
management nomenclature, whereas size_pool was a name chosen out of
necessity during the development of the Variable Width Allocation
features of Ruby.
The concept of size pools was introduced in order to facilitate
different sized objects (other than the default 40 bytes). They wrapped
the eden heap and the tomb heap, and some related state, and provided a
reasonably simple way of duplicating all related concerns, to provide
multiple pools that all shared the same structure but held different
objects.
Since then various changes have happend in Ruby's memory layout:
* The concept of tomb heaps has been replaced by a global free pages list,
with each page having it's slot size reconfigured at the point when it
is resurrected
* the eden heap has been inlined into the size pool itself, so that now
the size pool directly controls the free_pages list, the sweeping
page, the compaction cursor and the other state that was previously
being managed by the eden heap.
Now that there is no need for a heap wrapper, we should refer to the
collection of pages containing Ruby objects as a heap again rather than
a size pool
2024-10-03 13:53:49 +01:00
|
|
|
"heap_index",
|
2022-12-15 16:38:53 -05:00
|
|
|
"type",
|
|
|
|
"capacity",
|
|
|
|
NULL);
|
2022-10-03 11:14:32 -04:00
|
|
|
|
|
|
|
rb_define_method(rb_cShape, "parent", rb_shape_parent, 0);
|
|
|
|
rb_define_method(rb_cShape, "edges", rb_shape_edges, 0);
|
|
|
|
rb_define_method(rb_cShape, "depth", rb_shape_export_depth, 0);
|
2025-04-21 16:16:07 +09:00
|
|
|
rb_define_method(rb_cShape, "too_complex?", shape_too_complex, 0);
|
|
|
|
rb_define_method(rb_cShape, "shape_frozen?", shape_frozen, 0);
|
2025-05-27 13:20:58 +02:00
|
|
|
rb_define_method(rb_cShape, "has_object_id?", shape_has_object_id_p, 0);
|
2025-04-21 16:16:07 +09:00
|
|
|
|
2022-10-03 11:14:32 -04:00
|
|
|
rb_define_const(rb_cShape, "SHAPE_ROOT", INT2NUM(SHAPE_ROOT));
|
|
|
|
rb_define_const(rb_cShape, "SHAPE_IVAR", INT2NUM(SHAPE_IVAR));
|
2022-11-18 10:29:41 -08:00
|
|
|
rb_define_const(rb_cShape, "SHAPE_ID_NUM_BITS", INT2NUM(SHAPE_ID_NUM_BITS));
|
2022-10-03 13:52:40 -04:00
|
|
|
rb_define_const(rb_cShape, "SHAPE_FLAG_SHIFT", INT2NUM(SHAPE_FLAG_SHIFT));
|
2022-11-08 15:35:31 -05:00
|
|
|
rb_define_const(rb_cShape, "SPECIAL_CONST_SHAPE_ID", INT2NUM(SPECIAL_CONST_SHAPE_ID));
|
2022-12-08 17:16:52 -05:00
|
|
|
rb_define_const(rb_cShape, "SHAPE_MAX_VARIATIONS", INT2NUM(SHAPE_MAX_VARIATIONS));
|
2023-10-26 13:28:25 -07:00
|
|
|
rb_define_const(rb_cShape, "SIZEOF_RB_SHAPE_T", INT2NUM(sizeof(rb_shape_t)));
|
|
|
|
rb_define_const(rb_cShape, "SIZEOF_REDBLACK_NODE_T", INT2NUM(sizeof(redblack_node_t)));
|
|
|
|
rb_define_const(rb_cShape, "SHAPE_BUFFER_SIZE", INT2NUM(sizeof(rb_shape_t) * SHAPE_BUFFER_SIZE));
|
|
|
|
rb_define_const(rb_cShape, "REDBLACK_CACHE_SIZE", INT2NUM(sizeof(redblack_node_t) * REDBLACK_CACHE_SIZE));
|
2022-10-03 13:52:40 -04:00
|
|
|
|
|
|
|
rb_define_singleton_method(rb_cShape, "transition_tree", shape_transition_tree, 0);
|
|
|
|
rb_define_singleton_method(rb_cShape, "find_by_id", rb_shape_find_by_id, 1);
|
|
|
|
rb_define_singleton_method(rb_cShape, "of", rb_shape_debug_shape, 1);
|
|
|
|
rb_define_singleton_method(rb_cShape, "root_shape", rb_shape_root_shape, 0);
|
2023-10-19 11:00:54 -07:00
|
|
|
rb_define_singleton_method(rb_cShape, "shapes_available", rb_shape_shapes_available, 0);
|
2023-11-21 17:23:56 +01:00
|
|
|
rb_define_singleton_method(rb_cShape, "exhaust_shapes", rb_shape_exhaust, -1);
|
2022-10-12 14:37:02 -07:00
|
|
|
#endif
|
2022-10-03 11:14:32 -04:00
|
|
|
}
|