2022-10-03 18:14:32 +03:00
|
|
|
#include "vm_core.h"
|
|
|
|
#include "vm_sync.h"
|
|
|
|
#include "shape.h"
|
2022-12-06 14:56:51 +03:00
|
|
|
#include "symbol.h"
|
|
|
|
#include "id_table.h"
|
2022-10-03 18:14:32 +03:00
|
|
|
#include "internal/class.h"
|
2023-02-08 14:56:53 +03:00
|
|
|
#include "internal/gc.h"
|
2022-10-03 18:14:32 +03:00
|
|
|
#include "internal/symbol.h"
|
|
|
|
#include "internal/variable.h"
|
2023-04-13 13:11:14 +03:00
|
|
|
#include "internal/error.h"
|
2022-12-06 03:48:47 +03:00
|
|
|
#include "variable.h"
|
2022-10-03 18:14:32 +03:00
|
|
|
#include <stdbool.h>
|
|
|
|
|
2023-02-08 04:46:42 +03:00
|
|
|
#ifndef _WIN32
|
|
|
|
#include <sys/mman.h>
|
|
|
|
#endif
|
|
|
|
|
2022-11-24 01:01:03 +03:00
|
|
|
#ifndef SHAPE_DEBUG
|
|
|
|
#define SHAPE_DEBUG (VM_CHECK_MODE > 0)
|
|
|
|
#endif
|
|
|
|
|
2023-10-26 12:45:52 +03:00
|
|
|
#if SIZEOF_SHAPE_T == 4
|
|
|
|
#define SHAPE_BUFFER_SIZE 0x80000
|
|
|
|
#else
|
|
|
|
#define SHAPE_BUFFER_SIZE 0x8000
|
|
|
|
#endif
|
|
|
|
|
2023-03-14 01:07:09 +03:00
|
|
|
#define SINGLE_CHILD_TAG 0x1
|
|
|
|
#define TAG_SINGLE_CHILD(x) (struct rb_id_table *)((uintptr_t)x | SINGLE_CHILD_TAG)
|
|
|
|
#define SINGLE_CHILD_MASK (~((uintptr_t)SINGLE_CHILD_TAG))
|
|
|
|
#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-08 04:46:42 +03:00
|
|
|
#define ANCESTOR_CACHE_THRESHOLD 10
|
2023-10-26 12:45:52 +03:00
|
|
|
#define MAX_SHAPE_ID (SHAPE_BUFFER_SIZE - 1)
|
2023-03-14 01:07:09 +03:00
|
|
|
|
2022-11-08 23:35:31 +03:00
|
|
|
static ID id_frozen;
|
2022-11-18 02:57:11 +03:00
|
|
|
static ID id_t_object;
|
2022-11-08 23:35:31 +03:00
|
|
|
static ID size_pool_edge_names[SIZE_POOL_COUNT];
|
|
|
|
|
2023-02-08 04:46:42 +03:00
|
|
|
#define LEAF 0
|
|
|
|
#define BLACK 0x0
|
|
|
|
#define RED 0x1
|
|
|
|
|
|
|
|
static redblack_node_t *
|
|
|
|
redblack_left(redblack_node_t * node)
|
|
|
|
{
|
|
|
|
if (node->l == LEAF) {
|
|
|
|
return LEAF;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
RUBY_ASSERT(node->l < GET_SHAPE_TREE()->cache_size);
|
|
|
|
redblack_node_t * left = &GET_SHAPE_TREE()->shape_cache[node->l - 1];
|
|
|
|
return left;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static redblack_node_t *
|
|
|
|
redblack_right(redblack_node_t * node)
|
|
|
|
{
|
|
|
|
if (node->r == LEAF) {
|
|
|
|
return LEAF;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
RUBY_ASSERT(node->r < GET_SHAPE_TREE()->cache_size);
|
|
|
|
redblack_node_t * right = &GET_SHAPE_TREE()->shape_cache[node->r - 1];
|
|
|
|
return right;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static redblack_node_t *
|
|
|
|
redblack_find(redblack_node_t * tree, ID key)
|
|
|
|
{
|
2023-10-24 18:46:54 +03:00
|
|
|
if (tree == LEAF) {
|
|
|
|
return LEAF;
|
|
|
|
}
|
|
|
|
else {
|
2023-02-08 04:46:42 +03: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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline char
|
|
|
|
redblack_color(redblack_node_t * node)
|
|
|
|
{
|
|
|
|
return node && ((uintptr_t)node->value & RED);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline bool
|
|
|
|
redblack_red_p(redblack_node_t * node)
|
|
|
|
{
|
|
|
|
return redblack_color(node) == RED;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline rb_shape_t *
|
|
|
|
redblack_value(redblack_node_t * node)
|
|
|
|
{
|
|
|
|
// Color is stored in the bottom bit of the shape pointer
|
|
|
|
// Mask away the bit so we get the actual pointer back
|
|
|
|
return (rb_shape_t *)((uintptr_t)node->value & (((uintptr_t)-1) - 1));
|
|
|
|
}
|
|
|
|
|
|
|
|
static redblack_id_t
|
|
|
|
redblack_id_for(redblack_node_t * node)
|
|
|
|
{
|
|
|
|
RUBY_ASSERT(node || node == LEAF);
|
|
|
|
if (node == LEAF) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
redblack_node_t * redblack_nodes = GET_SHAPE_TREE()->shape_cache;
|
|
|
|
redblack_id_t id = (redblack_id_t)(node - redblack_nodes);
|
|
|
|
return id + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static redblack_node_t *
|
|
|
|
redblack_new(char color, ID key, rb_shape_t * value, redblack_node_t * left, redblack_node_t * right)
|
|
|
|
{
|
|
|
|
redblack_node_t * redblack_nodes = GET_SHAPE_TREE()->shape_cache;
|
|
|
|
redblack_node_t * node = &redblack_nodes[(GET_SHAPE_TREE()->cache_size)++];
|
|
|
|
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 *
|
|
|
|
redblack_balance(char color, ID key, rb_shape_t * value, redblack_node_t * left, redblack_node_t * right)
|
|
|
|
{
|
|
|
|
if (color == BLACK) {
|
|
|
|
ID z, y, x;
|
|
|
|
rb_shape_t * z_, * y_, * x_;
|
|
|
|
redblack_node_t * a, * b, * c, * d;
|
|
|
|
|
|
|
|
if (redblack_red_p(left) && redblack_red_p(redblack_left(left))) {
|
|
|
|
z = key;
|
|
|
|
z_ = value;
|
|
|
|
d = right;
|
|
|
|
|
|
|
|
y = left->key;
|
|
|
|
y_ = redblack_value(left);
|
|
|
|
c = redblack_right(left);
|
|
|
|
|
|
|
|
x = redblack_left(left)->key;
|
|
|
|
x_ = redblack_value(redblack_left(left));
|
|
|
|
|
|
|
|
a = redblack_left(redblack_left(left));
|
|
|
|
b = redblack_right(redblack_left(left));
|
|
|
|
}
|
|
|
|
else if (redblack_red_p(left) && redblack_red_p(redblack_right(left))) {
|
|
|
|
z = key;
|
|
|
|
z_ = value;
|
|
|
|
d = right;
|
|
|
|
|
|
|
|
x = left->key;
|
|
|
|
x_ = redblack_value(left);
|
|
|
|
a = redblack_left(left);
|
|
|
|
|
|
|
|
y = redblack_right(left)->key;
|
|
|
|
y_ = redblack_value(redblack_right(left));
|
|
|
|
b = redblack_left(redblack_right(left));
|
|
|
|
c = redblack_right(redblack_right(left));
|
|
|
|
}
|
|
|
|
else if (redblack_red_p(right) && redblack_red_p(redblack_left(right))) {
|
|
|
|
x = key;
|
|
|
|
x_ = value;
|
|
|
|
a = left;
|
|
|
|
|
|
|
|
z = right->key;
|
|
|
|
z_ = redblack_value(right);
|
|
|
|
d = redblack_right(right);
|
|
|
|
|
|
|
|
y = redblack_left(right)->key;
|
|
|
|
y_ = redblack_value(redblack_left(right));
|
|
|
|
b = redblack_left(redblack_left(right));
|
|
|
|
c = redblack_right(redblack_left(right));
|
|
|
|
}
|
|
|
|
else if (redblack_red_p(right) && redblack_red_p(redblack_right(right))) {
|
|
|
|
x = key;
|
|
|
|
x_ = value;
|
|
|
|
a = left;
|
|
|
|
|
|
|
|
y = right->key;
|
|
|
|
y_ = redblack_value(right);
|
|
|
|
b = redblack_left(right);
|
|
|
|
|
|
|
|
z = redblack_right(right)->key;
|
|
|
|
z_ = redblack_value(redblack_right(right));
|
|
|
|
c = redblack_left(redblack_right(right));
|
|
|
|
d = redblack_right(redblack_right(right));
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return redblack_new(color, key, value, left, right);
|
|
|
|
}
|
|
|
|
return redblack_new(
|
|
|
|
RED, y, y_,
|
|
|
|
redblack_new(BLACK, x, x_, a, b),
|
|
|
|
redblack_new(BLACK, z, z_, c, d));
|
|
|
|
}
|
|
|
|
|
|
|
|
return redblack_new(color, key, value, left, right);
|
|
|
|
}
|
|
|
|
|
|
|
|
static redblack_node_t *
|
|
|
|
redblack_insert_aux(redblack_node_t * tree, ID key, rb_shape_t * value)
|
|
|
|
{
|
|
|
|
if (tree == LEAF) {
|
|
|
|
return redblack_new(RED, key, value, LEAF, LEAF);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (key < tree->key) {
|
|
|
|
return redblack_balance(redblack_color(tree),
|
|
|
|
tree->key,
|
|
|
|
redblack_value(tree),
|
|
|
|
redblack_insert_aux(redblack_left(tree), key, value),
|
|
|
|
redblack_right(tree));
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (key > tree->key) {
|
|
|
|
return redblack_balance(redblack_color(tree),
|
|
|
|
tree->key,
|
|
|
|
redblack_value(tree),
|
|
|
|
redblack_left(tree),
|
|
|
|
redblack_insert_aux(redblack_right(tree), key, value));
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return tree;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static redblack_node_t *
|
|
|
|
redblack_force_black(redblack_node_t * node)
|
|
|
|
{
|
|
|
|
node->value = redblack_value(node);
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
|
|
|
|
static redblack_node_t *
|
|
|
|
redblack_insert(redblack_node_t * tree, ID key, rb_shape_t * value)
|
|
|
|
{
|
|
|
|
redblack_node_t * root = redblack_insert_aux(tree, key, value);
|
|
|
|
|
|
|
|
if (redblack_red_p(root)) {
|
|
|
|
return redblack_force_black(root);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return root;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-17 16:32:51 +03:00
|
|
|
rb_shape_tree_t *rb_shape_tree_ptr = NULL;
|
|
|
|
|
2022-10-03 18:14:32 +03:00
|
|
|
/*
|
|
|
|
* Shape getters
|
|
|
|
*/
|
2022-11-08 23:35:31 +03:00
|
|
|
rb_shape_t *
|
2022-10-12 12:27:23 +03:00
|
|
|
rb_shape_get_root_shape(void)
|
|
|
|
{
|
2023-02-17 16:32:51 +03:00
|
|
|
return GET_SHAPE_TREE()->root_shape;
|
2022-10-03 18:14:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
shape_id_t
|
|
|
|
rb_shape_id(rb_shape_t * shape)
|
|
|
|
{
|
2023-02-17 16:32:51 +03:00
|
|
|
return (shape_id_t)(shape - GET_SHAPE_TREE()->shape_list);
|
2022-10-03 18:14:32 +03:00
|
|
|
}
|
|
|
|
|
2022-12-06 14:56:51 +03:00
|
|
|
void
|
|
|
|
rb_shape_each_shape(each_shape_callback callback, void *data)
|
|
|
|
{
|
|
|
|
rb_shape_t *cursor = rb_shape_get_root_shape();
|
2023-02-17 16:32:51 +03:00
|
|
|
rb_shape_t *end = rb_shape_get_shape_by_id(GET_SHAPE_TREE()->next_shape_id);
|
2022-12-06 14:56:51 +03:00
|
|
|
while (cursor < end) {
|
|
|
|
callback(cursor, data);
|
|
|
|
cursor += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-07 08:56:40 +03:00
|
|
|
RUBY_FUNC_EXPORTED rb_shape_t*
|
2022-10-03 18:14:32 +03:00
|
|
|
rb_shape_get_shape_by_id(shape_id_t shape_id)
|
|
|
|
{
|
|
|
|
RUBY_ASSERT(shape_id != INVALID_SHAPE_ID);
|
|
|
|
|
2023-02-17 16:32:51 +03:00
|
|
|
rb_shape_t *shape = &GET_SHAPE_TREE()->shape_list[shape_id];
|
2022-10-03 18:14:32 +03:00
|
|
|
return shape;
|
|
|
|
}
|
|
|
|
|
2022-11-10 19:36:24 +03:00
|
|
|
rb_shape_t *
|
|
|
|
rb_shape_get_parent(rb_shape_t * shape)
|
|
|
|
{
|
|
|
|
return rb_shape_get_shape_by_id(shape->parent_id);
|
|
|
|
}
|
|
|
|
|
2022-10-03 18:14:32 +03:00
|
|
|
#if !SHAPE_IN_BASIC_FLAGS
|
|
|
|
shape_id_t rb_generic_shape_id(VALUE obj);
|
|
|
|
#endif
|
|
|
|
|
2023-03-07 08:56:40 +03:00
|
|
|
RUBY_FUNC_EXPORTED shape_id_t
|
2022-10-03 18:14:32 +03:00
|
|
|
rb_shape_get_shape_id(VALUE obj)
|
|
|
|
{
|
|
|
|
if (RB_SPECIAL_CONST_P(obj)) {
|
2022-11-08 23:35:31 +03:00
|
|
|
return SPECIAL_CONST_SHAPE_ID;
|
2022-10-03 18:14:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
#if SHAPE_IN_BASIC_FLAGS
|
|
|
|
return RBASIC_SHAPE_ID(obj);
|
|
|
|
#else
|
|
|
|
switch (BUILTIN_TYPE(obj)) {
|
|
|
|
case T_OBJECT:
|
2022-12-09 01:16:52 +03:00
|
|
|
return ROBJECT_SHAPE_ID(obj);
|
|
|
|
break;
|
2022-10-03 18:14:32 +03:00
|
|
|
case T_CLASS:
|
|
|
|
case T_MODULE:
|
2022-12-09 01:16:52 +03:00
|
|
|
return RCLASS_SHAPE_ID(obj);
|
2022-10-03 18:14:32 +03:00
|
|
|
default:
|
2022-12-09 01:16:52 +03:00
|
|
|
return rb_generic_shape_id(obj);
|
2022-10-03 18:14:32 +03:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2022-12-06 14:56:51 +03:00
|
|
|
size_t
|
2022-12-06 03:48:47 +03:00
|
|
|
rb_shape_depth(rb_shape_t * shape)
|
|
|
|
{
|
2022-12-06 14:56:51 +03:00
|
|
|
size_t depth = 1;
|
2022-12-06 03:48:47 +03:00
|
|
|
|
|
|
|
while (shape->parent_id != INVALID_SHAPE_ID) {
|
|
|
|
depth++;
|
|
|
|
shape = rb_shape_get_parent(shape);
|
|
|
|
}
|
|
|
|
|
|
|
|
return depth;
|
|
|
|
}
|
|
|
|
|
2022-10-03 18:14:32 +03:00
|
|
|
rb_shape_t*
|
|
|
|
rb_shape_get_shape(VALUE obj)
|
|
|
|
{
|
|
|
|
return rb_shape_get_shape_by_id(rb_shape_get_shape_id(obj));
|
|
|
|
}
|
|
|
|
|
2023-03-10 02:50:58 +03:00
|
|
|
static rb_shape_t *
|
|
|
|
shape_alloc(void)
|
|
|
|
{
|
2023-02-17 16:32:51 +03:00
|
|
|
shape_id_t shape_id = GET_SHAPE_TREE()->next_shape_id;
|
|
|
|
GET_SHAPE_TREE()->next_shape_id++;
|
2023-03-10 02:50:58 +03:00
|
|
|
|
2023-10-19 21:00:54 +03:00
|
|
|
if (shape_id == (MAX_SHAPE_ID + 1)) {
|
2023-03-10 02:50:58 +03:00
|
|
|
// TODO: Make an OutOfShapesError ??
|
2023-05-20 08:00:14 +03:00
|
|
|
rb_bug("Out of shapes");
|
2023-03-10 02:50:58 +03:00
|
|
|
}
|
|
|
|
|
2023-02-17 16:32:51 +03:00
|
|
|
return &GET_SHAPE_TREE()->shape_list[shape_id];
|
2023-03-10 02:50:58 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static rb_shape_t *
|
|
|
|
rb_shape_alloc_with_parent_id(ID edge_name, shape_id_t parent_id)
|
|
|
|
{
|
|
|
|
rb_shape_t * shape = shape_alloc();
|
|
|
|
|
|
|
|
shape->edge_name = edge_name;
|
|
|
|
shape->next_iv_index = 0;
|
|
|
|
shape->parent_id = parent_id;
|
2023-03-22 18:47:29 +03:00
|
|
|
shape->edges = NULL;
|
2023-03-10 02:50:58 +03:00
|
|
|
|
|
|
|
return shape;
|
|
|
|
}
|
|
|
|
|
|
|
|
static rb_shape_t *
|
2023-03-22 18:47:29 +03:00
|
|
|
rb_shape_alloc(ID edge_name, rb_shape_t * parent, enum shape_type type)
|
2023-03-10 02:50:58 +03:00
|
|
|
{
|
|
|
|
rb_shape_t * shape = rb_shape_alloc_with_parent_id(edge_name, rb_shape_id(parent));
|
2023-03-22 18:47:29 +03:00
|
|
|
shape->type = (uint8_t)type;
|
|
|
|
shape->size_pool_index = parent->size_pool_index;
|
|
|
|
shape->capacity = parent->capacity;
|
2023-03-14 01:07:09 +03:00
|
|
|
shape->edges = 0;
|
2023-03-10 02:50:58 +03:00
|
|
|
return shape;
|
|
|
|
}
|
|
|
|
|
2023-10-23 23:53:42 +03:00
|
|
|
#ifdef HAVE_MMAP
|
2023-02-08 04:46:42 +03:00
|
|
|
static redblack_node_t *
|
|
|
|
redblack_cache_ancestors(rb_shape_t * shape)
|
|
|
|
{
|
2023-10-24 21:59:48 +03:00
|
|
|
if (!(shape->ancestor_index || shape->parent_id == INVALID_SHAPE_ID)) {
|
|
|
|
redblack_node_t * parent_index;
|
2023-02-08 04:46:42 +03:00
|
|
|
|
2023-10-24 21:59:48 +03:00
|
|
|
parent_index = redblack_cache_ancestors(rb_shape_get_parent(shape));
|
2023-02-08 04:46:42 +03:00
|
|
|
|
2023-10-24 21:59:48 +03:00
|
|
|
if (shape->type == SHAPE_IVAR) {
|
|
|
|
shape->ancestor_index = redblack_insert(parent_index, shape->edge_name, shape);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
shape->ancestor_index = parent_index;
|
2023-02-08 04:46:42 +03:00
|
|
|
}
|
|
|
|
}
|
2023-10-24 21:59:48 +03:00
|
|
|
|
|
|
|
return shape->ancestor_index;
|
2023-02-08 04:46:42 +03:00
|
|
|
}
|
2023-10-23 23:53:42 +03:00
|
|
|
#else
|
|
|
|
static redblack_node_t *
|
|
|
|
redblack_cache_ancestors(rb_shape_t * shape)
|
|
|
|
{
|
|
|
|
return LEAF;
|
|
|
|
}
|
|
|
|
#endif
|
2023-02-08 04:46:42 +03:00
|
|
|
|
2023-03-10 02:58:22 +03:00
|
|
|
static rb_shape_t *
|
|
|
|
rb_shape_alloc_new_child(ID id, rb_shape_t * shape, enum shape_type shape_type)
|
|
|
|
{
|
|
|
|
rb_shape_t * new_shape = rb_shape_alloc(id, shape, shape_type);
|
|
|
|
|
|
|
|
switch (shape_type) {
|
|
|
|
case SHAPE_IVAR:
|
|
|
|
new_shape->next_iv_index = shape->next_iv_index + 1;
|
2023-02-08 04:46:42 +03:00
|
|
|
if (new_shape->next_iv_index > ANCESTOR_CACHE_THRESHOLD) {
|
|
|
|
redblack_cache_ancestors(new_shape);
|
|
|
|
}
|
2023-03-10 02:58:22 +03:00
|
|
|
break;
|
|
|
|
case SHAPE_CAPACITY_CHANGE:
|
|
|
|
case SHAPE_FROZEN:
|
|
|
|
case SHAPE_T_OBJECT:
|
|
|
|
new_shape->next_iv_index = shape->next_iv_index;
|
|
|
|
break;
|
|
|
|
case SHAPE_OBJ_TOO_COMPLEX:
|
|
|
|
case SHAPE_INITIAL_CAPACITY:
|
|
|
|
case SHAPE_ROOT:
|
|
|
|
rb_bug("Unreachable");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return new_shape;
|
|
|
|
}
|
|
|
|
|
2022-10-03 18:14:32 +03:00
|
|
|
static rb_shape_t*
|
2023-10-24 22:20:46 +03:00
|
|
|
get_next_shape_internal(rb_shape_t * shape, ID id, enum shape_type shape_type, bool * variation_created, bool new_variations_allowed)
|
2022-10-03 18:14:32 +03:00
|
|
|
{
|
|
|
|
rb_shape_t *res = NULL;
|
2022-11-17 17:47:18 +03:00
|
|
|
|
2022-12-09 01:16:52 +03:00
|
|
|
// There should never be outgoing edges from "too complex"
|
|
|
|
RUBY_ASSERT(rb_shape_id(shape) != OBJ_TOO_COMPLEX_SHAPE_ID);
|
2022-12-09 00:48:48 +03:00
|
|
|
|
2022-12-09 01:16:52 +03:00
|
|
|
*variation_created = false;
|
2022-11-17 17:47:18 +03:00
|
|
|
|
2023-10-24 22:20:46 +03:00
|
|
|
if (GET_SHAPE_TREE()->next_shape_id <= MAX_SHAPE_ID) {
|
2022-12-09 01:16:52 +03:00
|
|
|
RB_VM_LOCK_ENTER();
|
|
|
|
{
|
2023-03-14 01:07:09 +03:00
|
|
|
// If the current shape has children
|
|
|
|
if (shape->edges) {
|
|
|
|
// Check if it only has one child
|
|
|
|
if (SINGLE_CHILD_P(shape->edges)) {
|
|
|
|
rb_shape_t * child = SINGLE_CHILD(shape->edges);
|
|
|
|
// If the one child has a matching edge name, then great,
|
|
|
|
// we found what we want.
|
|
|
|
if (child->edge_name == id) {
|
|
|
|
res = child;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// Otherwise we're going to have to create a new shape
|
|
|
|
// and insert it as a child node, so create an id
|
|
|
|
// table and insert the existing child
|
|
|
|
shape->edges = rb_id_table_create(2);
|
|
|
|
rb_id_table_insert(shape->edges, child->edge_name, (VALUE)child);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// If it has more than one child, do a hash lookup to find it.
|
|
|
|
VALUE lookup_result;
|
|
|
|
if (rb_id_table_lookup(shape->edges, id, &lookup_result)) {
|
|
|
|
res = (rb_shape_t *)lookup_result;
|
|
|
|
}
|
|
|
|
}
|
2022-11-17 17:47:18 +03:00
|
|
|
|
2023-03-14 01:07:09 +03:00
|
|
|
// If the shape we were looking for above was found,
|
|
|
|
// then `res` will be set to the child. If it isn't set, then
|
|
|
|
// we know we need a new child shape, and that we must insert
|
|
|
|
// it in to the table.
|
|
|
|
if (!res) {
|
2023-10-24 22:20:46 +03:00
|
|
|
if (new_variations_allowed) {
|
|
|
|
*variation_created = true;
|
|
|
|
rb_shape_t * new_shape = rb_shape_alloc_new_child(id, shape, shape_type);
|
|
|
|
rb_id_table_insert(shape->edges, id, (VALUE)new_shape);
|
|
|
|
res = new_shape;
|
|
|
|
}
|
2023-10-24 22:37:27 +03:00
|
|
|
else {
|
|
|
|
res = rb_shape_get_shape_by_id(OBJ_TOO_COMPLEX_SHAPE_ID);
|
|
|
|
}
|
2023-03-14 01:07:09 +03:00
|
|
|
}
|
2023-01-05 16:48:19 +03:00
|
|
|
}
|
|
|
|
else {
|
2023-03-14 01:07:09 +03:00
|
|
|
// If the shape didn't have any outgoing edges, then create
|
|
|
|
// the new outgoing edge and tag the pointer.
|
2023-03-10 02:58:22 +03:00
|
|
|
rb_shape_t * new_shape = rb_shape_alloc_new_child(id, shape, shape_type);
|
2023-03-14 01:07:09 +03:00
|
|
|
shape->edges = TAG_SINGLE_CHILD(new_shape);
|
2022-12-09 01:16:52 +03:00
|
|
|
res = new_shape;
|
|
|
|
}
|
2022-10-03 18:14:32 +03:00
|
|
|
}
|
2022-12-09 01:16:52 +03:00
|
|
|
RB_VM_LOCK_LEAVE();
|
2022-10-03 18:14:32 +03:00
|
|
|
}
|
2023-10-24 22:37:27 +03:00
|
|
|
else {
|
|
|
|
res = rb_shape_get_shape_by_id(OBJ_TOO_COMPLEX_SHAPE_ID);
|
|
|
|
}
|
2022-10-03 18:14:32 +03:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2023-03-07 08:34:31 +03:00
|
|
|
int
|
2022-10-03 18:14:32 +03:00
|
|
|
rb_shape_frozen_shape_p(rb_shape_t* shape)
|
|
|
|
{
|
|
|
|
return SHAPE_FROZEN == (enum shape_type)shape->type;
|
|
|
|
}
|
|
|
|
|
2022-12-06 03:48:47 +03:00
|
|
|
static void
|
|
|
|
move_iv(VALUE obj, ID id, attr_index_t from, attr_index_t to)
|
2022-10-03 18:14:32 +03:00
|
|
|
{
|
2022-12-06 03:48:47 +03:00
|
|
|
switch(BUILTIN_TYPE(obj)) {
|
|
|
|
case T_CLASS:
|
|
|
|
case T_MODULE:
|
|
|
|
RCLASS_IVPTR(obj)[to] = RCLASS_IVPTR(obj)[from];
|
|
|
|
break;
|
|
|
|
case T_OBJECT:
|
2022-12-09 01:16:52 +03:00
|
|
|
RUBY_ASSERT(!rb_shape_obj_too_complex(obj));
|
2022-12-06 03:48:47 +03:00
|
|
|
ROBJECT_IVPTR(obj)[to] = ROBJECT_IVPTR(obj)[from];
|
|
|
|
break;
|
|
|
|
default: {
|
|
|
|
struct gen_ivtbl *ivtbl;
|
|
|
|
rb_gen_ivtbl_get(obj, id, &ivtbl);
|
|
|
|
ivtbl->ivptr[to] = ivtbl->ivptr[from];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-10-03 18:14:32 +03:00
|
|
|
|
2022-12-06 03:48:47 +03:00
|
|
|
static rb_shape_t *
|
|
|
|
remove_shape_recursive(VALUE obj, ID id, rb_shape_t * shape, VALUE * removed)
|
|
|
|
{
|
|
|
|
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
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (shape->type == SHAPE_IVAR && shape->edge_name == id) {
|
|
|
|
// We've hit the edge we wanted to remove, return it's _parent_
|
|
|
|
// as the new parent while we go back down the stack.
|
|
|
|
attr_index_t index = shape->next_iv_index - 1;
|
|
|
|
|
|
|
|
switch(BUILTIN_TYPE(obj)) {
|
|
|
|
case T_CLASS:
|
|
|
|
case T_MODULE:
|
|
|
|
*removed = RCLASS_IVPTR(obj)[index];
|
|
|
|
break;
|
|
|
|
case T_OBJECT:
|
|
|
|
*removed = ROBJECT_IVPTR(obj)[index];
|
|
|
|
break;
|
|
|
|
default: {
|
|
|
|
struct gen_ivtbl *ivtbl;
|
|
|
|
rb_gen_ivtbl_get(obj, id, &ivtbl);
|
|
|
|
*removed = ivtbl->ivptr[index];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return rb_shape_get_parent(shape);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// This isn't the IV we want to remove, keep walking up.
|
|
|
|
rb_shape_t * new_parent = remove_shape_recursive(obj, id, rb_shape_get_parent(shape), removed);
|
|
|
|
|
|
|
|
// We found a new parent. Create a child of the new parent that
|
|
|
|
// has the same attributes as this shape.
|
|
|
|
if (new_parent) {
|
2022-12-09 00:48:48 +03:00
|
|
|
bool dont_care;
|
2023-10-24 22:05:05 +03:00
|
|
|
rb_shape_t * new_child = get_next_shape_internal(new_parent, shape->edge_name, shape->type, &dont_care, true);
|
2022-12-09 21:33:48 +03:00
|
|
|
new_child->capacity = shape->capacity;
|
2022-12-06 03:48:47 +03:00
|
|
|
if (new_child->type == SHAPE_IVAR) {
|
|
|
|
move_iv(obj, id, shape->next_iv_index - 1, new_child->next_iv_index - 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
return new_child;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// We went all the way to the top of the shape tree and couldn't
|
|
|
|
// find an IV to remove, so return NULL
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
rb_shape_transition_shape_remove_ivar(VALUE obj, ID id, rb_shape_t *shape, VALUE * removed)
|
|
|
|
{
|
|
|
|
rb_shape_t * new_shape = remove_shape_recursive(obj, id, shape, removed);
|
|
|
|
if (new_shape) {
|
|
|
|
rb_shape_set_shape(obj, new_shape);
|
|
|
|
}
|
2022-10-03 18:14:32 +03:00
|
|
|
}
|
|
|
|
|
2023-10-20 02:01:35 +03:00
|
|
|
rb_shape_t *
|
2022-10-03 18:14:32 +03:00
|
|
|
rb_shape_transition_shape_frozen(VALUE obj)
|
|
|
|
{
|
|
|
|
rb_shape_t* shape = rb_shape_get_shape(obj);
|
|
|
|
RUBY_ASSERT(shape);
|
|
|
|
RUBY_ASSERT(RB_OBJ_FROZEN(obj));
|
|
|
|
|
2022-12-09 01:16:52 +03:00
|
|
|
if (rb_shape_frozen_shape_p(shape) || rb_shape_obj_too_complex(obj)) {
|
2023-10-20 02:01:35 +03:00
|
|
|
return shape;
|
2022-10-03 18:14:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
rb_shape_t* next_shape;
|
|
|
|
|
|
|
|
if (shape == rb_shape_get_root_shape()) {
|
2023-10-20 02:01:35 +03:00
|
|
|
return rb_shape_get_shape_by_id(SPECIAL_CONST_SHAPE_ID);
|
2022-10-03 18:14:32 +03:00
|
|
|
}
|
|
|
|
|
2022-12-09 00:48:48 +03:00
|
|
|
bool dont_care;
|
2023-10-24 22:05:05 +03:00
|
|
|
next_shape = get_next_shape_internal(shape, (ID)id_frozen, SHAPE_FROZEN, &dont_care, true);
|
2022-10-03 18:14:32 +03:00
|
|
|
|
|
|
|
RUBY_ASSERT(next_shape);
|
2023-10-20 02:01:35 +03:00
|
|
|
return next_shape;
|
2022-10-03 18:14:32 +03:00
|
|
|
}
|
|
|
|
|
2022-11-08 23:35:31 +03:00
|
|
|
/*
|
|
|
|
* This function is used for assertions where we don't want to increment
|
|
|
|
* max_iv_count
|
|
|
|
*/
|
|
|
|
rb_shape_t *
|
|
|
|
rb_shape_get_next_iv_shape(rb_shape_t* shape, ID id)
|
|
|
|
{
|
2022-12-06 14:56:51 +03:00
|
|
|
RUBY_ASSERT(!is_instance_id(id) || RTEST(rb_sym2str(ID2SYM(id))));
|
2022-12-09 00:48:48 +03:00
|
|
|
bool dont_care;
|
2023-10-24 22:05:05 +03:00
|
|
|
return get_next_shape_internal(shape, id, SHAPE_IVAR, &dont_care, true);
|
2022-11-08 23:35:31 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
rb_shape_t *
|
2022-10-03 18:14:32 +03:00
|
|
|
rb_shape_get_next(rb_shape_t* shape, VALUE obj, ID id)
|
|
|
|
{
|
2022-12-09 00:48:48 +03:00
|
|
|
RUBY_ASSERT(!is_instance_id(id) || RTEST(rb_sym2str(ID2SYM(id))));
|
2023-10-19 21:00:54 +03:00
|
|
|
RUBY_ASSERT(shape->type != SHAPE_OBJ_TOO_COMPLEX);
|
2022-12-09 00:48:48 +03:00
|
|
|
|
2022-12-09 01:16:52 +03:00
|
|
|
bool allow_new_shape = true;
|
|
|
|
|
|
|
|
if (BUILTIN_TYPE(obj) == T_OBJECT) {
|
|
|
|
VALUE klass = rb_obj_class(obj);
|
|
|
|
allow_new_shape = RCLASS_EXT(klass)->variation_count < SHAPE_MAX_VARIATIONS;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool variation_created = false;
|
2023-10-24 22:05:05 +03:00
|
|
|
rb_shape_t * new_shape = get_next_shape_internal(shape, id, SHAPE_IVAR, &variation_created, allow_new_shape);
|
2022-12-09 01:16:52 +03:00
|
|
|
|
2022-11-08 23:35:31 +03:00
|
|
|
// Check if we should update max_iv_count on the object's class
|
|
|
|
if (BUILTIN_TYPE(obj) == T_OBJECT) {
|
|
|
|
VALUE klass = rb_obj_class(obj);
|
|
|
|
if (new_shape->next_iv_index > RCLASS_EXT(klass)->max_iv_count) {
|
|
|
|
RCLASS_EXT(klass)->max_iv_count = new_shape->next_iv_index;
|
|
|
|
}
|
2022-12-09 00:48:48 +03:00
|
|
|
|
|
|
|
if (variation_created) {
|
|
|
|
RCLASS_EXT(klass)->variation_count++;
|
2023-04-13 13:11:14 +03:00
|
|
|
if (rb_warning_category_enabled_p(RB_WARN_CATEGORY_PERFORMANCE)) {
|
|
|
|
if (RCLASS_EXT(klass)->variation_count >= SHAPE_MAX_VARIATIONS) {
|
2023-04-18 13:36:57 +03:00
|
|
|
rb_category_warn(
|
2023-04-13 13:11:14 +03:00
|
|
|
RB_WARN_CATEGORY_PERFORMANCE,
|
|
|
|
"Maximum shapes variations (%d) reached by %"PRIsVALUE", instance variables accesses will be slower.",
|
|
|
|
SHAPE_MAX_VARIATIONS,
|
|
|
|
rb_class_path(klass)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2022-12-09 00:48:48 +03:00
|
|
|
}
|
2022-11-08 23:35:31 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return new_shape;
|
|
|
|
}
|
|
|
|
|
2023-10-10 14:12:17 +03:00
|
|
|
static inline rb_shape_t *
|
rb_shape_transition_shape_capa: use optimal sizes transitions
Previously the growth was 3(embed), 6, 12, 24, ...
With this change it's now 3(embed), 8, 16, 32, 64, ... by default.
However, since power of two isn't the best size for all allocators,
if `malloc_usable_size` is vailable, we use it to discover the best
offset.
On Linux/glibc 2.35 for instance, the growth will be 3(embed), 7, 15, 31
to avoid wasting 8B per object.
Test program:
```c
size_t test(size_t slots) {
size_t allocated = slots * VALUE_SIZE;
void *test_ptr = malloc(allocated);
size_t wasted = malloc_usable_size(test_ptr) - allocated;
free(test_ptr);
fprintf(stderr, "slots = %lu, wasted_bytes = %lu\n", slots, wasted);
return wasted;
}
int main(int argc, char *argv[]) {
size_t best_padding = 0;
size_t padding = 0;
for (padding = 0; padding <= 2; padding++) {
size_t wasted = test(8 - padding);
if (wasted == 0) {
best_padding = padding;
break;
}
}
size_t index = 0;
fprintf(stderr, "=============== naive ================\n");
size_t list_size = 4;
for (index = 0; index < 10; index++) {
test(list_size);
list_size *= 2;
}
fprintf(stderr, "=============== auto-padded (-%lu) ================\n", best_padding);
list_size = 4;
for (index = 0; index < 10; index ++) {
test(list_size - best_padding);
list_size *= 2;
}
fprintf(stderr, "\n\n");
return 0;
}
```
```
===== glibc ======
slots = 8, wasted_bytes = 8
slots = 7, wasted_bytes = 0
=============== naive ================
slots = 4, wasted_bytes = 8
slots = 8, wasted_bytes = 8
slots = 16, wasted_bytes = 8
slots = 32, wasted_bytes = 8
slots = 64, wasted_bytes = 8
slots = 128, wasted_bytes = 8
slots = 256, wasted_bytes = 8
slots = 512, wasted_bytes = 8
slots = 1024, wasted_bytes = 8
slots = 2048, wasted_bytes = 8
=============== auto-padded (-1) ================
slots = 3, wasted_bytes = 0
slots = 7, wasted_bytes = 0
slots = 15, wasted_bytes = 0
slots = 31, wasted_bytes = 0
slots = 63, wasted_bytes = 0
slots = 127, wasted_bytes = 0
slots = 255, wasted_bytes = 0
slots = 511, wasted_bytes = 0
slots = 1023, wasted_bytes = 0
slots = 2047, wasted_bytes = 0
```
```
========== jemalloc =======
slots = 8, wasted_bytes = 0
=============== naive ================
slots = 4, wasted_bytes = 0
slots = 8, wasted_bytes = 0
slots = 16, wasted_bytes = 0
slots = 32, wasted_bytes = 0
slots = 64, wasted_bytes = 0
slots = 128, wasted_bytes = 0
slots = 256, wasted_bytes = 0
slots = 512, wasted_bytes = 0
slots = 1024, wasted_bytes = 0
slots = 2048, wasted_bytes = 0
=============== auto-padded (-0) ================
slots = 4, wasted_bytes = 0
slots = 8, wasted_bytes = 0
slots = 16, wasted_bytes = 0
slots = 32, wasted_bytes = 0
slots = 64, wasted_bytes = 0
slots = 128, wasted_bytes = 0
slots = 256, wasted_bytes = 0
slots = 512, wasted_bytes = 0
slots = 1024, wasted_bytes = 0
slots = 2048, wasted_bytes = 0
```
2023-10-10 16:32:12 +03:00
|
|
|
rb_shape_transition_shape_capa_create(rb_shape_t* shape, size_t new_capacity)
|
2022-11-08 23:35:31 +03:00
|
|
|
{
|
rb_shape_transition_shape_capa: use optimal sizes transitions
Previously the growth was 3(embed), 6, 12, 24, ...
With this change it's now 3(embed), 8, 16, 32, 64, ... by default.
However, since power of two isn't the best size for all allocators,
if `malloc_usable_size` is vailable, we use it to discover the best
offset.
On Linux/glibc 2.35 for instance, the growth will be 3(embed), 7, 15, 31
to avoid wasting 8B per object.
Test program:
```c
size_t test(size_t slots) {
size_t allocated = slots * VALUE_SIZE;
void *test_ptr = malloc(allocated);
size_t wasted = malloc_usable_size(test_ptr) - allocated;
free(test_ptr);
fprintf(stderr, "slots = %lu, wasted_bytes = %lu\n", slots, wasted);
return wasted;
}
int main(int argc, char *argv[]) {
size_t best_padding = 0;
size_t padding = 0;
for (padding = 0; padding <= 2; padding++) {
size_t wasted = test(8 - padding);
if (wasted == 0) {
best_padding = padding;
break;
}
}
size_t index = 0;
fprintf(stderr, "=============== naive ================\n");
size_t list_size = 4;
for (index = 0; index < 10; index++) {
test(list_size);
list_size *= 2;
}
fprintf(stderr, "=============== auto-padded (-%lu) ================\n", best_padding);
list_size = 4;
for (index = 0; index < 10; index ++) {
test(list_size - best_padding);
list_size *= 2;
}
fprintf(stderr, "\n\n");
return 0;
}
```
```
===== glibc ======
slots = 8, wasted_bytes = 8
slots = 7, wasted_bytes = 0
=============== naive ================
slots = 4, wasted_bytes = 8
slots = 8, wasted_bytes = 8
slots = 16, wasted_bytes = 8
slots = 32, wasted_bytes = 8
slots = 64, wasted_bytes = 8
slots = 128, wasted_bytes = 8
slots = 256, wasted_bytes = 8
slots = 512, wasted_bytes = 8
slots = 1024, wasted_bytes = 8
slots = 2048, wasted_bytes = 8
=============== auto-padded (-1) ================
slots = 3, wasted_bytes = 0
slots = 7, wasted_bytes = 0
slots = 15, wasted_bytes = 0
slots = 31, wasted_bytes = 0
slots = 63, wasted_bytes = 0
slots = 127, wasted_bytes = 0
slots = 255, wasted_bytes = 0
slots = 511, wasted_bytes = 0
slots = 1023, wasted_bytes = 0
slots = 2047, wasted_bytes = 0
```
```
========== jemalloc =======
slots = 8, wasted_bytes = 0
=============== naive ================
slots = 4, wasted_bytes = 0
slots = 8, wasted_bytes = 0
slots = 16, wasted_bytes = 0
slots = 32, wasted_bytes = 0
slots = 64, wasted_bytes = 0
slots = 128, wasted_bytes = 0
slots = 256, wasted_bytes = 0
slots = 512, wasted_bytes = 0
slots = 1024, wasted_bytes = 0
slots = 2048, wasted_bytes = 0
=============== auto-padded (-0) ================
slots = 4, wasted_bytes = 0
slots = 8, wasted_bytes = 0
slots = 16, wasted_bytes = 0
slots = 32, wasted_bytes = 0
slots = 64, wasted_bytes = 0
slots = 128, wasted_bytes = 0
slots = 256, wasted_bytes = 0
slots = 512, wasted_bytes = 0
slots = 1024, wasted_bytes = 0
slots = 2048, wasted_bytes = 0
```
2023-10-10 16:32:12 +03:00
|
|
|
RUBY_ASSERT(new_capacity < (size_t)MAX_IVARS);
|
|
|
|
|
2022-11-08 23:35:31 +03:00
|
|
|
ID edge_name = rb_make_temporary_id(new_capacity);
|
2022-12-09 00:48:48 +03:00
|
|
|
bool dont_care;
|
2023-10-24 22:05:05 +03:00
|
|
|
rb_shape_t * new_shape = get_next_shape_internal(shape, edge_name, SHAPE_CAPACITY_CHANGE, &dont_care, true);
|
2023-10-24 22:37:27 +03:00
|
|
|
RUBY_ASSERT(rb_shape_id(new_shape) != OBJ_TOO_COMPLEX_SHAPE_ID);
|
rb_shape_transition_shape_capa: use optimal sizes transitions
Previously the growth was 3(embed), 6, 12, 24, ...
With this change it's now 3(embed), 8, 16, 32, 64, ... by default.
However, since power of two isn't the best size for all allocators,
if `malloc_usable_size` is vailable, we use it to discover the best
offset.
On Linux/glibc 2.35 for instance, the growth will be 3(embed), 7, 15, 31
to avoid wasting 8B per object.
Test program:
```c
size_t test(size_t slots) {
size_t allocated = slots * VALUE_SIZE;
void *test_ptr = malloc(allocated);
size_t wasted = malloc_usable_size(test_ptr) - allocated;
free(test_ptr);
fprintf(stderr, "slots = %lu, wasted_bytes = %lu\n", slots, wasted);
return wasted;
}
int main(int argc, char *argv[]) {
size_t best_padding = 0;
size_t padding = 0;
for (padding = 0; padding <= 2; padding++) {
size_t wasted = test(8 - padding);
if (wasted == 0) {
best_padding = padding;
break;
}
}
size_t index = 0;
fprintf(stderr, "=============== naive ================\n");
size_t list_size = 4;
for (index = 0; index < 10; index++) {
test(list_size);
list_size *= 2;
}
fprintf(stderr, "=============== auto-padded (-%lu) ================\n", best_padding);
list_size = 4;
for (index = 0; index < 10; index ++) {
test(list_size - best_padding);
list_size *= 2;
}
fprintf(stderr, "\n\n");
return 0;
}
```
```
===== glibc ======
slots = 8, wasted_bytes = 8
slots = 7, wasted_bytes = 0
=============== naive ================
slots = 4, wasted_bytes = 8
slots = 8, wasted_bytes = 8
slots = 16, wasted_bytes = 8
slots = 32, wasted_bytes = 8
slots = 64, wasted_bytes = 8
slots = 128, wasted_bytes = 8
slots = 256, wasted_bytes = 8
slots = 512, wasted_bytes = 8
slots = 1024, wasted_bytes = 8
slots = 2048, wasted_bytes = 8
=============== auto-padded (-1) ================
slots = 3, wasted_bytes = 0
slots = 7, wasted_bytes = 0
slots = 15, wasted_bytes = 0
slots = 31, wasted_bytes = 0
slots = 63, wasted_bytes = 0
slots = 127, wasted_bytes = 0
slots = 255, wasted_bytes = 0
slots = 511, wasted_bytes = 0
slots = 1023, wasted_bytes = 0
slots = 2047, wasted_bytes = 0
```
```
========== jemalloc =======
slots = 8, wasted_bytes = 0
=============== naive ================
slots = 4, wasted_bytes = 0
slots = 8, wasted_bytes = 0
slots = 16, wasted_bytes = 0
slots = 32, wasted_bytes = 0
slots = 64, wasted_bytes = 0
slots = 128, wasted_bytes = 0
slots = 256, wasted_bytes = 0
slots = 512, wasted_bytes = 0
slots = 1024, wasted_bytes = 0
slots = 2048, wasted_bytes = 0
=============== auto-padded (-0) ================
slots = 4, wasted_bytes = 0
slots = 8, wasted_bytes = 0
slots = 16, wasted_bytes = 0
slots = 32, wasted_bytes = 0
slots = 64, wasted_bytes = 0
slots = 128, wasted_bytes = 0
slots = 256, wasted_bytes = 0
slots = 512, wasted_bytes = 0
slots = 1024, wasted_bytes = 0
slots = 2048, wasted_bytes = 0
```
2023-10-10 16:32:12 +03:00
|
|
|
new_shape->capacity = (uint32_t)new_capacity;
|
2022-11-08 23:35:31 +03:00
|
|
|
return new_shape;
|
2022-10-03 18:14:32 +03:00
|
|
|
}
|
|
|
|
|
2023-10-10 14:12:17 +03:00
|
|
|
rb_shape_t *
|
|
|
|
rb_shape_transition_shape_capa(rb_shape_t* shape)
|
|
|
|
{
|
2023-10-23 13:28:14 +03:00
|
|
|
return rb_shape_transition_shape_capa_create(shape, rb_malloc_grow_capa(shape->capacity, sizeof(VALUE)));
|
2023-10-10 14:12:17 +03:00
|
|
|
}
|
|
|
|
|
2022-10-03 18:14:32 +03:00
|
|
|
bool
|
2022-10-12 12:27:23 +03:00
|
|
|
rb_shape_get_iv_index(rb_shape_t * shape, ID id, attr_index_t *value)
|
|
|
|
{
|
2022-12-09 01:16:52 +03: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
|
|
|
|
RUBY_ASSERT(rb_shape_id(shape) != OBJ_TOO_COMPLEX_SHAPE_ID);
|
|
|
|
|
2022-10-03 20:52:40 +03:00
|
|
|
while (shape->parent_id != INVALID_SHAPE_ID) {
|
2023-02-08 04:46:42 +03:00
|
|
|
// Try the ancestor cache if it's available
|
|
|
|
if (shape->ancestor_index && shape->next_iv_index >= ANCESTOR_CACHE_THRESHOLD) {
|
|
|
|
redblack_node_t * node = redblack_find(shape->ancestor_index, id);
|
|
|
|
if (node) {
|
|
|
|
rb_shape_t * shape = redblack_value(node);
|
2022-10-21 23:24:29 +03:00
|
|
|
*value = shape->next_iv_index - 1;
|
2022-10-12 12:27:23 +03:00
|
|
|
return true;
|
2023-02-08 04:46:42 +03:00
|
|
|
}
|
|
|
|
else {
|
2022-10-12 12:27:23 +03:00
|
|
|
return false;
|
2023-02-08 04:46:42 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
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_iv_index > 0);
|
|
|
|
*value = shape->next_iv_index - 1;
|
|
|
|
return true;
|
|
|
|
case SHAPE_CAPACITY_CHANGE:
|
|
|
|
case SHAPE_ROOT:
|
|
|
|
case SHAPE_INITIAL_CAPACITY:
|
|
|
|
case SHAPE_T_OBJECT:
|
|
|
|
return false;
|
|
|
|
case SHAPE_OBJ_TOO_COMPLEX:
|
|
|
|
case SHAPE_FROZEN:
|
|
|
|
rb_bug("Ivar should not exist on transition");
|
|
|
|
}
|
2022-10-03 18:14:32 +03:00
|
|
|
}
|
|
|
|
}
|
2022-11-10 19:36:24 +03:00
|
|
|
shape = rb_shape_get_parent(shape);
|
2022-10-03 18:14:32 +03:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-03-07 08:34:31 +03:00
|
|
|
void
|
2022-10-03 18:14:32 +03:00
|
|
|
rb_shape_set_shape(VALUE obj, rb_shape_t* shape)
|
|
|
|
{
|
|
|
|
rb_shape_set_shape_id(obj, rb_shape_id(shape));
|
|
|
|
}
|
|
|
|
|
2022-12-02 20:33:20 +03:00
|
|
|
int32_t
|
|
|
|
rb_shape_id_offset(void)
|
|
|
|
{
|
2022-12-06 00:20:11 +03:00
|
|
|
return sizeof(uintptr_t) - SHAPE_ID_NUM_BITS / sizeof(uintptr_t);
|
2022-12-02 20:33:20 +03:00
|
|
|
}
|
|
|
|
|
2022-12-13 18:11:57 +03:00
|
|
|
rb_shape_t *
|
|
|
|
rb_shape_traverse_from_new_root(rb_shape_t *initial_shape, rb_shape_t *dest_shape)
|
|
|
|
{
|
|
|
|
RUBY_ASSERT(initial_shape->type == SHAPE_T_OBJECT);
|
|
|
|
rb_shape_t *next_shape = initial_shape;
|
|
|
|
|
|
|
|
if (dest_shape->type != initial_shape->type) {
|
|
|
|
next_shape = rb_shape_traverse_from_new_root(initial_shape, rb_shape_get_parent(dest_shape));
|
|
|
|
if (!next_shape) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch ((enum shape_type)dest_shape->type) {
|
|
|
|
case SHAPE_IVAR:
|
2023-03-17 17:12:37 +03:00
|
|
|
case SHAPE_FROZEN:
|
2022-12-13 18:11:57 +03:00
|
|
|
if (!next_shape->edges) {
|
|
|
|
return NULL;
|
|
|
|
}
|
2023-01-05 16:48:19 +03:00
|
|
|
|
|
|
|
VALUE lookup_result;
|
2023-03-14 01:07:09 +03:00
|
|
|
if (SINGLE_CHILD_P(next_shape->edges)) {
|
|
|
|
rb_shape_t * child = SINGLE_CHILD(next_shape->edges);
|
|
|
|
if (child->edge_name == dest_shape->edge_name) {
|
|
|
|
return child;
|
|
|
|
}
|
2023-04-19 00:45:18 +03:00
|
|
|
else {
|
|
|
|
return NULL;
|
|
|
|
}
|
2023-01-05 16:48:19 +03:00
|
|
|
}
|
|
|
|
else {
|
2023-03-14 01:07:09 +03:00
|
|
|
if (rb_id_table_lookup(next_shape->edges, dest_shape->edge_name, &lookup_result)) {
|
|
|
|
next_shape = (rb_shape_t *)lookup_result;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return NULL;
|
|
|
|
}
|
2022-12-13 18:11:57 +03:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SHAPE_ROOT:
|
|
|
|
case SHAPE_CAPACITY_CHANGE:
|
|
|
|
case SHAPE_INITIAL_CAPACITY:
|
|
|
|
case SHAPE_T_OBJECT:
|
|
|
|
break;
|
|
|
|
case SHAPE_OBJ_TOO_COMPLEX:
|
2023-05-20 08:00:14 +03:00
|
|
|
rb_bug("Unreachable");
|
2022-12-13 18:11:57 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return next_shape;
|
|
|
|
}
|
|
|
|
|
2022-11-08 23:35:31 +03:00
|
|
|
rb_shape_t *
|
|
|
|
rb_shape_rebuild_shape(rb_shape_t * initial_shape, rb_shape_t * dest_shape)
|
|
|
|
{
|
|
|
|
rb_shape_t * midway_shape;
|
|
|
|
|
2022-11-18 02:57:11 +03:00
|
|
|
RUBY_ASSERT(initial_shape->type == SHAPE_T_OBJECT);
|
|
|
|
|
|
|
|
if (dest_shape->type != initial_shape->type) {
|
2022-11-10 19:36:24 +03:00
|
|
|
midway_shape = rb_shape_rebuild_shape(initial_shape, rb_shape_get_parent(dest_shape));
|
2022-11-08 23:35:31 +03:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
midway_shape = initial_shape;
|
|
|
|
}
|
|
|
|
|
2022-11-18 02:57:11 +03:00
|
|
|
switch ((enum shape_type)dest_shape->type) {
|
2022-11-17 22:43:46 +03:00
|
|
|
case SHAPE_IVAR:
|
|
|
|
if (midway_shape->capacity <= midway_shape->next_iv_index) {
|
|
|
|
// There isn't enough room to write this IV, so we need to increase the capacity
|
2023-10-10 14:12:17 +03:00
|
|
|
midway_shape = rb_shape_transition_shape_capa(midway_shape);
|
2022-11-17 22:43:46 +03:00
|
|
|
}
|
2022-11-08 23:35:31 +03:00
|
|
|
|
2022-11-17 22:43:46 +03:00
|
|
|
midway_shape = rb_shape_get_next_iv_shape(midway_shape, dest_shape->edge_name);
|
|
|
|
break;
|
|
|
|
case SHAPE_ROOT:
|
|
|
|
case SHAPE_FROZEN:
|
|
|
|
case SHAPE_CAPACITY_CHANGE:
|
2022-11-18 02:57:11 +03:00
|
|
|
case SHAPE_INITIAL_CAPACITY:
|
|
|
|
case SHAPE_T_OBJECT:
|
2022-11-17 22:43:46 +03:00
|
|
|
break;
|
2022-12-09 01:16:52 +03:00
|
|
|
case SHAPE_OBJ_TOO_COMPLEX:
|
2023-05-20 08:00:14 +03:00
|
|
|
rb_bug("Unreachable");
|
2022-12-09 01:16:52 +03:00
|
|
|
break;
|
2022-11-08 23:35:31 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return midway_shape;
|
|
|
|
}
|
|
|
|
|
2023-03-07 08:56:40 +03:00
|
|
|
RUBY_FUNC_EXPORTED bool
|
2022-12-09 01:16:52 +03:00
|
|
|
rb_shape_obj_too_complex(VALUE obj)
|
|
|
|
{
|
|
|
|
return rb_shape_get_shape_id(obj) == OBJ_TOO_COMPLEX_SHAPE_ID;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
rb_shape_set_too_complex(VALUE obj)
|
|
|
|
{
|
|
|
|
RUBY_ASSERT(!rb_shape_obj_too_complex(obj));
|
|
|
|
rb_shape_set_shape_id(obj, OBJ_TOO_COMPLEX_SHAPE_ID);
|
|
|
|
}
|
|
|
|
|
2022-12-06 14:56:51 +03:00
|
|
|
size_t
|
|
|
|
rb_shape_edges_count(rb_shape_t *shape)
|
|
|
|
{
|
|
|
|
if (shape->edges) {
|
2023-03-14 01:07:09 +03:00
|
|
|
if (SINGLE_CHILD_P(shape->edges)) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return rb_id_table_size(shape->edges);
|
|
|
|
}
|
2022-12-06 14:56:51 +03:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t
|
|
|
|
rb_shape_memsize(rb_shape_t *shape)
|
|
|
|
{
|
|
|
|
size_t memsize = sizeof(rb_shape_t);
|
2023-03-14 01:07:09 +03:00
|
|
|
if (shape->edges && !SINGLE_CHILD_P(shape->edges)) {
|
2022-12-06 14:56:51 +03:00
|
|
|
memsize += rb_id_table_memsize(shape->edges);
|
|
|
|
}
|
|
|
|
return memsize;
|
|
|
|
}
|
|
|
|
|
2022-11-24 01:01:03 +03:00
|
|
|
#if SHAPE_DEBUG
|
2022-10-03 18:14:32 +03:00
|
|
|
/*
|
|
|
|
* Exposing Shape to Ruby via RubyVM.debug_shape
|
|
|
|
*/
|
2022-11-08 23:35:31 +03:00
|
|
|
|
2022-12-23 02:03:40 +03:00
|
|
|
/* :nodoc: */
|
2022-12-09 01:16:52 +03:00
|
|
|
static VALUE
|
|
|
|
rb_shape_too_complex(VALUE self)
|
|
|
|
{
|
|
|
|
rb_shape_t * shape;
|
2022-12-16 00:38:53 +03:00
|
|
|
shape = rb_shape_get_shape_by_id(NUM2INT(rb_struct_getmember(self, rb_intern("id"))));
|
2022-12-09 01:16:52 +03:00
|
|
|
if (rb_shape_id(shape) == OBJ_TOO_COMPLEX_SHAPE_ID) {
|
|
|
|
return Qtrue;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return Qfalse;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-12 12:27:23 +03:00
|
|
|
static VALUE
|
|
|
|
parse_key(ID key)
|
|
|
|
{
|
2022-12-06 14:56:51 +03:00
|
|
|
if (is_instance_id(key)) {
|
2022-10-03 18:14:32 +03:00
|
|
|
return ID2SYM(key);
|
|
|
|
}
|
2022-12-06 14:56:51 +03:00
|
|
|
return LONG2NUM(key);
|
2022-10-03 18:14:32 +03:00
|
|
|
}
|
|
|
|
|
2022-12-16 00:38:53 +03:00
|
|
|
static VALUE rb_shape_edge_name(rb_shape_t * shape);
|
|
|
|
|
2022-10-03 18:14:32 +03:00
|
|
|
static VALUE
|
2022-10-12 12:27:23 +03:00
|
|
|
rb_shape_t_to_rb_cShape(rb_shape_t *shape)
|
|
|
|
{
|
2022-12-16 00:38:53 +03:00
|
|
|
VALUE rb_cShape = rb_const_get(rb_cRubyVM, rb_intern("Shape"));
|
2022-10-03 18:14:32 +03:00
|
|
|
|
2022-12-16 00:38:53 +03:00
|
|
|
VALUE obj = rb_struct_new(rb_cShape,
|
|
|
|
INT2NUM(rb_shape_id(shape)),
|
|
|
|
INT2NUM(shape->parent_id),
|
|
|
|
rb_shape_edge_name(shape),
|
|
|
|
INT2NUM(shape->next_iv_index),
|
|
|
|
INT2NUM(shape->size_pool_index),
|
|
|
|
INT2NUM(shape->type),
|
|
|
|
INT2NUM(shape->capacity));
|
|
|
|
rb_obj_freeze(obj);
|
|
|
|
return obj;
|
2022-10-03 18:14:32 +03:00
|
|
|
}
|
|
|
|
|
2022-10-12 12:27:23 +03:00
|
|
|
static enum rb_id_table_iterator_result
|
|
|
|
rb_edges_to_hash(ID key, VALUE value, void *ref)
|
2022-10-03 18:14:32 +03:00
|
|
|
{
|
|
|
|
rb_hash_aset(*(VALUE *)ref, parse_key(key), rb_shape_t_to_rb_cShape((rb_shape_t*)value));
|
|
|
|
return ID_TABLE_CONTINUE;
|
|
|
|
}
|
|
|
|
|
2022-12-23 02:03:40 +03:00
|
|
|
/* :nodoc: */
|
2022-10-03 18:14:32 +03:00
|
|
|
static VALUE
|
|
|
|
rb_shape_edges(VALUE self)
|
|
|
|
{
|
|
|
|
rb_shape_t* shape;
|
2022-12-16 00:38:53 +03:00
|
|
|
|
|
|
|
shape = rb_shape_get_shape_by_id(NUM2INT(rb_struct_getmember(self, rb_intern("id"))));
|
2022-10-03 18:14:32 +03:00
|
|
|
|
|
|
|
VALUE hash = rb_hash_new();
|
|
|
|
|
|
|
|
if (shape->edges) {
|
2023-03-14 01:07:09 +03:00
|
|
|
if (SINGLE_CHILD_P(shape->edges)) {
|
|
|
|
rb_shape_t * child = SINGLE_CHILD(shape->edges);
|
|
|
|
rb_edges_to_hash(child->edge_name, (VALUE)child, &hash);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
rb_id_table_foreach(shape->edges, rb_edges_to_hash, &hash);
|
|
|
|
}
|
2022-10-03 18:14:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return hash;
|
|
|
|
}
|
|
|
|
|
|
|
|
static VALUE
|
2022-12-16 00:38:53 +03:00
|
|
|
rb_shape_edge_name(rb_shape_t * shape)
|
2022-10-03 18:14:32 +03:00
|
|
|
{
|
2022-12-06 14:56:51 +03:00
|
|
|
if (shape->edge_name) {
|
|
|
|
if (is_instance_id(shape->edge_name)) {
|
2022-11-08 23:35:31 +03:00
|
|
|
return ID2SYM(shape->edge_name);
|
|
|
|
}
|
2022-12-06 14:56:51 +03:00
|
|
|
return INT2NUM(shape->capacity);
|
2022-10-03 18:14:32 +03:00
|
|
|
}
|
2022-12-06 14:56:51 +03:00
|
|
|
return Qnil;
|
2022-10-03 18:14:32 +03:00
|
|
|
}
|
|
|
|
|
2022-12-23 02:03:40 +03:00
|
|
|
/* :nodoc: */
|
2022-10-03 18:14:32 +03:00
|
|
|
static VALUE
|
|
|
|
rb_shape_export_depth(VALUE self)
|
|
|
|
{
|
|
|
|
rb_shape_t* shape;
|
2022-12-16 00:38:53 +03:00
|
|
|
shape = rb_shape_get_shape_by_id(NUM2INT(rb_struct_getmember(self, rb_intern("id"))));
|
2022-12-06 14:56:51 +03:00
|
|
|
return SIZET2NUM(rb_shape_depth(shape));
|
2022-10-03 18:14:32 +03:00
|
|
|
}
|
|
|
|
|
2022-12-23 02:03:40 +03:00
|
|
|
/* :nodoc: */
|
2022-10-03 18:14:32 +03:00
|
|
|
static VALUE
|
|
|
|
rb_shape_parent(VALUE self)
|
|
|
|
{
|
|
|
|
rb_shape_t * shape;
|
2022-12-16 00:38:53 +03:00
|
|
|
shape = rb_shape_get_shape_by_id(NUM2INT(rb_struct_getmember(self, rb_intern("id"))));
|
2022-10-03 20:52:40 +03:00
|
|
|
if (shape->parent_id != INVALID_SHAPE_ID) {
|
2022-11-10 19:36:24 +03:00
|
|
|
return rb_shape_t_to_rb_cShape(rb_shape_get_parent(shape));
|
2022-10-03 18:14:32 +03:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
return Qnil;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-23 02:03:40 +03:00
|
|
|
/* :nodoc: */
|
2022-10-13 00:37:02 +03:00
|
|
|
static VALUE
|
2022-10-12 12:27:23 +03:00
|
|
|
rb_shape_debug_shape(VALUE self, VALUE obj)
|
|
|
|
{
|
2022-10-03 18:14:32 +03:00
|
|
|
return rb_shape_t_to_rb_cShape(rb_shape_get_shape(obj));
|
|
|
|
}
|
|
|
|
|
2022-12-23 02:03:40 +03:00
|
|
|
/* :nodoc: */
|
2022-10-13 00:37:02 +03:00
|
|
|
static VALUE
|
2022-10-12 12:27:23 +03:00
|
|
|
rb_shape_root_shape(VALUE self)
|
|
|
|
{
|
2022-10-03 18:14:32 +03:00
|
|
|
return rb_shape_t_to_rb_cShape(rb_shape_get_root_shape());
|
|
|
|
}
|
|
|
|
|
2023-10-19 21:00:54 +03:00
|
|
|
/* :nodoc: */
|
|
|
|
static VALUE
|
|
|
|
rb_shape_shapes_available(VALUE self)
|
|
|
|
{
|
|
|
|
return INT2NUM(MAX_SHAPE_ID - (GET_SHAPE_TREE()->next_shape_id - 1));
|
|
|
|
}
|
|
|
|
|
2022-10-03 18:14:32 +03:00
|
|
|
VALUE rb_obj_shape(rb_shape_t* shape);
|
|
|
|
|
|
|
|
static enum rb_id_table_iterator_result collect_keys_and_values(ID key, VALUE value, void *ref)
|
|
|
|
{
|
|
|
|
rb_hash_aset(*(VALUE *)ref, parse_key(key), rb_obj_shape((rb_shape_t*)value));
|
|
|
|
return ID_TABLE_CONTINUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static VALUE edges(struct rb_id_table* edges)
|
|
|
|
{
|
|
|
|
VALUE hash = rb_hash_new();
|
2023-03-14 01:07:09 +03:00
|
|
|
if (SINGLE_CHILD_P(edges)) {
|
|
|
|
rb_shape_t * child = SINGLE_CHILD(edges);
|
|
|
|
collect_keys_and_values(child->edge_name, (VALUE)child, &hash);
|
|
|
|
}
|
|
|
|
else {
|
2022-10-03 18:14:32 +03:00
|
|
|
rb_id_table_foreach(edges, collect_keys_and_values, &hash);
|
2023-03-14 01:07:09 +03:00
|
|
|
}
|
2022-10-03 18:14:32 +03:00
|
|
|
return hash;
|
|
|
|
}
|
|
|
|
|
2022-12-23 02:03:40 +03:00
|
|
|
/* :nodoc: */
|
2022-10-12 12:27:23 +03:00
|
|
|
VALUE
|
|
|
|
rb_obj_shape(rb_shape_t* shape)
|
|
|
|
{
|
2022-10-03 18:14:32 +03:00
|
|
|
VALUE rb_shape = rb_hash_new();
|
|
|
|
|
|
|
|
rb_hash_aset(rb_shape, ID2SYM(rb_intern("id")), INT2NUM(rb_shape_id(shape)));
|
|
|
|
rb_hash_aset(rb_shape, ID2SYM(rb_intern("edges")), edges(shape->edges));
|
|
|
|
|
|
|
|
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 20:52:40 +03:00
|
|
|
rb_hash_aset(rb_shape, ID2SYM(rb_intern("parent_id")), INT2NUM(shape->parent_id));
|
2022-10-03 18:14:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
rb_hash_aset(rb_shape, ID2SYM(rb_intern("edge_name")), rb_id2str(shape->edge_name));
|
|
|
|
return rb_shape;
|
|
|
|
}
|
|
|
|
|
2022-12-23 02:03:40 +03:00
|
|
|
/* :nodoc: */
|
2022-10-12 12:27:23 +03:00
|
|
|
static VALUE
|
|
|
|
shape_transition_tree(VALUE self)
|
|
|
|
{
|
2022-10-03 18:14:32 +03:00
|
|
|
return rb_obj_shape(rb_shape_get_root_shape());
|
|
|
|
}
|
|
|
|
|
2022-12-23 02:03:40 +03:00
|
|
|
/* :nodoc: */
|
2022-10-03 20:52:40 +03:00
|
|
|
static VALUE
|
|
|
|
rb_shape_find_by_id(VALUE mod, VALUE id)
|
|
|
|
{
|
2022-10-12 18:54:02 +03:00
|
|
|
shape_id_t shape_id = NUM2UINT(id);
|
2023-02-17 16:32:51 +03:00
|
|
|
if (shape_id >= GET_SHAPE_TREE()->next_shape_id) {
|
2022-10-03 20:52:40 +03:00
|
|
|
rb_raise(rb_eArgError, "Shape ID %d is out of bounds\n", shape_id);
|
|
|
|
}
|
|
|
|
return rb_shape_t_to_rb_cShape(rb_shape_get_shape_by_id(shape_id));
|
|
|
|
}
|
2022-10-13 00:37:02 +03:00
|
|
|
#endif
|
2022-10-03 20:52:40 +03:00
|
|
|
|
2023-02-17 18:51:16 +03:00
|
|
|
#ifdef HAVE_MMAP
|
2023-02-17 16:32:51 +03:00
|
|
|
#include <sys/mman.h>
|
2023-02-17 18:51:16 +03:00
|
|
|
#endif
|
2023-02-17 16:32:51 +03:00
|
|
|
|
2022-11-08 23:35:31 +03:00
|
|
|
void
|
|
|
|
Init_default_shapes(void)
|
|
|
|
{
|
2023-02-17 16:32:51 +03:00
|
|
|
rb_shape_tree_t *st = ruby_mimmalloc(sizeof(rb_shape_tree_t));
|
|
|
|
memset(st, 0, sizeof(rb_shape_tree_t));
|
|
|
|
rb_shape_tree_ptr = st;
|
|
|
|
|
|
|
|
#ifdef HAVE_MMAP
|
|
|
|
rb_shape_tree_ptr->shape_list = (rb_shape_t *)mmap(NULL, rb_size_mul_or_raise(SHAPE_BUFFER_SIZE, sizeof(rb_shape_t), rb_eRuntimeError),
|
|
|
|
PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
|
|
|
if (GET_SHAPE_TREE()->shape_list == MAP_FAILED) {
|
|
|
|
GET_SHAPE_TREE()->shape_list = 0;
|
|
|
|
}
|
|
|
|
#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 23:35:31 +03:00
|
|
|
id_frozen = rb_make_internal_id();
|
2022-11-18 02:57:11 +03:00
|
|
|
id_t_object = rb_make_internal_id();
|
2022-11-08 23:35:31 +03:00
|
|
|
|
2023-02-08 04:46:42 +03:00
|
|
|
#ifdef HAVE_MMAP
|
|
|
|
rb_shape_tree_ptr->shape_cache = (redblack_node_t *)mmap(NULL, rb_size_mul_or_raise(SHAPE_BUFFER_SIZE * 32, sizeof(redblack_node_t), rb_eRuntimeError),
|
|
|
|
PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
|
|
|
rb_shape_tree_ptr->cache_size = 0;
|
|
|
|
#endif
|
|
|
|
|
2022-11-08 23:35:31 +03:00
|
|
|
// Shapes by size pool
|
|
|
|
for (int i = 0; i < SIZE_POOL_COUNT; i++) {
|
|
|
|
size_pool_edge_names[i] = rb_make_internal_id();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Root shape
|
|
|
|
rb_shape_t * root = rb_shape_alloc_with_parent_id(0, INVALID_SHAPE_ID);
|
|
|
|
root->capacity = (uint32_t)((rb_size_pool_slot_size(0) - offsetof(struct RObject, as.ary)) / sizeof(VALUE));
|
|
|
|
root->type = SHAPE_ROOT;
|
|
|
|
root->size_pool_index = 0;
|
2023-02-17 16:32:51 +03:00
|
|
|
GET_SHAPE_TREE()->root_shape = root;
|
|
|
|
RUBY_ASSERT(rb_shape_id(GET_SHAPE_TREE()->root_shape) == ROOT_SHAPE_ID);
|
2022-11-08 23:35:31 +03:00
|
|
|
|
|
|
|
// Shapes by size pool
|
|
|
|
for (int i = 1; i < SIZE_POOL_COUNT; i++) {
|
rb_shape_transition_shape_capa: use optimal sizes transitions
Previously the growth was 3(embed), 6, 12, 24, ...
With this change it's now 3(embed), 8, 16, 32, 64, ... by default.
However, since power of two isn't the best size for all allocators,
if `malloc_usable_size` is vailable, we use it to discover the best
offset.
On Linux/glibc 2.35 for instance, the growth will be 3(embed), 7, 15, 31
to avoid wasting 8B per object.
Test program:
```c
size_t test(size_t slots) {
size_t allocated = slots * VALUE_SIZE;
void *test_ptr = malloc(allocated);
size_t wasted = malloc_usable_size(test_ptr) - allocated;
free(test_ptr);
fprintf(stderr, "slots = %lu, wasted_bytes = %lu\n", slots, wasted);
return wasted;
}
int main(int argc, char *argv[]) {
size_t best_padding = 0;
size_t padding = 0;
for (padding = 0; padding <= 2; padding++) {
size_t wasted = test(8 - padding);
if (wasted == 0) {
best_padding = padding;
break;
}
}
size_t index = 0;
fprintf(stderr, "=============== naive ================\n");
size_t list_size = 4;
for (index = 0; index < 10; index++) {
test(list_size);
list_size *= 2;
}
fprintf(stderr, "=============== auto-padded (-%lu) ================\n", best_padding);
list_size = 4;
for (index = 0; index < 10; index ++) {
test(list_size - best_padding);
list_size *= 2;
}
fprintf(stderr, "\n\n");
return 0;
}
```
```
===== glibc ======
slots = 8, wasted_bytes = 8
slots = 7, wasted_bytes = 0
=============== naive ================
slots = 4, wasted_bytes = 8
slots = 8, wasted_bytes = 8
slots = 16, wasted_bytes = 8
slots = 32, wasted_bytes = 8
slots = 64, wasted_bytes = 8
slots = 128, wasted_bytes = 8
slots = 256, wasted_bytes = 8
slots = 512, wasted_bytes = 8
slots = 1024, wasted_bytes = 8
slots = 2048, wasted_bytes = 8
=============== auto-padded (-1) ================
slots = 3, wasted_bytes = 0
slots = 7, wasted_bytes = 0
slots = 15, wasted_bytes = 0
slots = 31, wasted_bytes = 0
slots = 63, wasted_bytes = 0
slots = 127, wasted_bytes = 0
slots = 255, wasted_bytes = 0
slots = 511, wasted_bytes = 0
slots = 1023, wasted_bytes = 0
slots = 2047, wasted_bytes = 0
```
```
========== jemalloc =======
slots = 8, wasted_bytes = 0
=============== naive ================
slots = 4, wasted_bytes = 0
slots = 8, wasted_bytes = 0
slots = 16, wasted_bytes = 0
slots = 32, wasted_bytes = 0
slots = 64, wasted_bytes = 0
slots = 128, wasted_bytes = 0
slots = 256, wasted_bytes = 0
slots = 512, wasted_bytes = 0
slots = 1024, wasted_bytes = 0
slots = 2048, wasted_bytes = 0
=============== auto-padded (-0) ================
slots = 4, wasted_bytes = 0
slots = 8, wasted_bytes = 0
slots = 16, wasted_bytes = 0
slots = 32, wasted_bytes = 0
slots = 64, wasted_bytes = 0
slots = 128, wasted_bytes = 0
slots = 256, wasted_bytes = 0
slots = 512, wasted_bytes = 0
slots = 1024, wasted_bytes = 0
slots = 2048, wasted_bytes = 0
```
2023-10-10 16:32:12 +03:00
|
|
|
size_t capa = ((rb_size_pool_slot_size(i) - offsetof(struct RObject, as.ary)) / sizeof(VALUE));
|
2023-10-10 14:12:17 +03:00
|
|
|
rb_shape_t * new_shape = rb_shape_transition_shape_capa_create(root, capa);
|
2022-11-08 23:35:31 +03:00
|
|
|
new_shape->type = SHAPE_INITIAL_CAPACITY;
|
|
|
|
new_shape->size_pool_index = i;
|
2023-02-08 04:46:42 +03:00
|
|
|
new_shape->ancestor_index = LEAF;
|
2022-11-08 23:35:31 +03:00
|
|
|
RUBY_ASSERT(rb_shape_id(new_shape) == (shape_id_t)i);
|
|
|
|
}
|
|
|
|
|
2022-11-18 02:57:11 +03:00
|
|
|
// Make shapes for T_OBJECT
|
|
|
|
for (int i = 0; i < SIZE_POOL_COUNT; i++) {
|
|
|
|
rb_shape_t * shape = rb_shape_get_shape_by_id(i);
|
2022-12-09 00:48:48 +03:00
|
|
|
bool dont_care;
|
2022-11-18 02:57:11 +03:00
|
|
|
rb_shape_t * t_object_shape =
|
2023-10-24 22:05:05 +03:00
|
|
|
get_next_shape_internal(shape, id_t_object, SHAPE_T_OBJECT, &dont_care, true);
|
2022-12-09 00:48:48 +03:00
|
|
|
t_object_shape->edges = rb_id_table_create(0);
|
2023-02-08 04:46:42 +03:00
|
|
|
t_object_shape->ancestor_index = LEAF;
|
2022-11-18 02:57:11 +03:00
|
|
|
RUBY_ASSERT(rb_shape_id(t_object_shape) == (shape_id_t)(i + SIZE_POOL_COUNT));
|
|
|
|
}
|
|
|
|
|
2022-12-09 00:48:48 +03:00
|
|
|
bool dont_care;
|
2022-11-08 23:35:31 +03:00
|
|
|
// Special const shape
|
|
|
|
#if RUBY_DEBUG
|
|
|
|
rb_shape_t * special_const_shape =
|
|
|
|
#endif
|
2023-10-24 22:05:05 +03:00
|
|
|
get_next_shape_internal(root, (ID)id_frozen, SHAPE_FROZEN, &dont_care, true);
|
2022-11-08 23:35:31 +03:00
|
|
|
RUBY_ASSERT(rb_shape_id(special_const_shape) == SPECIAL_CONST_SHAPE_ID);
|
2023-02-17 16:32:51 +03:00
|
|
|
RUBY_ASSERT(SPECIAL_CONST_SHAPE_ID == (GET_SHAPE_TREE()->next_shape_id - 1));
|
2022-11-08 23:35:31 +03:00
|
|
|
RUBY_ASSERT(rb_shape_frozen_shape_p(special_const_shape));
|
2022-12-09 01:16:52 +03:00
|
|
|
|
|
|
|
rb_shape_t * hash_fallback_shape = rb_shape_alloc_with_parent_id(0, ROOT_SHAPE_ID);
|
|
|
|
hash_fallback_shape->type = SHAPE_OBJ_TOO_COMPLEX;
|
|
|
|
hash_fallback_shape->size_pool_index = 0;
|
2023-02-17 16:32:51 +03:00
|
|
|
RUBY_ASSERT(OBJ_TOO_COMPLEX_SHAPE_ID == (GET_SHAPE_TREE()->next_shape_id - 1));
|
2022-12-09 01:16:52 +03:00
|
|
|
RUBY_ASSERT(rb_shape_id(hash_fallback_shape) == OBJ_TOO_COMPLEX_SHAPE_ID);
|
2022-11-08 23:35:31 +03:00
|
|
|
}
|
|
|
|
|
2022-10-03 18:14:32 +03:00
|
|
|
void
|
|
|
|
Init_shape(void)
|
|
|
|
{
|
2022-11-24 01:01:03 +03:00
|
|
|
#if SHAPE_DEBUG
|
2022-12-16 00:38:53 +03:00
|
|
|
VALUE rb_cShape = rb_struct_define_under(rb_cRubyVM, "Shape",
|
|
|
|
"id",
|
|
|
|
"parent_id",
|
|
|
|
"edge_name",
|
|
|
|
"next_iv_index",
|
|
|
|
"size_pool_index",
|
|
|
|
"type",
|
|
|
|
"capacity",
|
|
|
|
NULL);
|
2022-10-03 18:14:32 +03: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);
|
2022-12-09 01:16:52 +03:00
|
|
|
rb_define_method(rb_cShape, "too_complex?", rb_shape_too_complex, 0);
|
2022-10-03 18:14:32 +03:00
|
|
|
rb_define_const(rb_cShape, "SHAPE_ROOT", INT2NUM(SHAPE_ROOT));
|
|
|
|
rb_define_const(rb_cShape, "SHAPE_IVAR", INT2NUM(SHAPE_IVAR));
|
2022-11-18 02:57:11 +03:00
|
|
|
rb_define_const(rb_cShape, "SHAPE_T_OBJECT", INT2NUM(SHAPE_T_OBJECT));
|
2022-10-03 18:14:32 +03:00
|
|
|
rb_define_const(rb_cShape, "SHAPE_FROZEN", INT2NUM(SHAPE_FROZEN));
|
2022-11-18 21:29:41 +03:00
|
|
|
rb_define_const(rb_cShape, "SHAPE_ID_NUM_BITS", INT2NUM(SHAPE_ID_NUM_BITS));
|
2022-10-03 20:52:40 +03:00
|
|
|
rb_define_const(rb_cShape, "SHAPE_FLAG_SHIFT", INT2NUM(SHAPE_FLAG_SHIFT));
|
2022-11-08 23:35:31 +03:00
|
|
|
rb_define_const(rb_cShape, "SPECIAL_CONST_SHAPE_ID", INT2NUM(SPECIAL_CONST_SHAPE_ID));
|
2022-12-09 01:16:52 +03:00
|
|
|
rb_define_const(rb_cShape, "OBJ_TOO_COMPLEX_SHAPE_ID", INT2NUM(OBJ_TOO_COMPLEX_SHAPE_ID));
|
|
|
|
rb_define_const(rb_cShape, "SHAPE_MAX_VARIATIONS", INT2NUM(SHAPE_MAX_VARIATIONS));
|
2022-10-03 20:52:40 +03: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 21:00:54 +03:00
|
|
|
rb_define_singleton_method(rb_cShape, "shapes_available", rb_shape_shapes_available, 0);
|
2022-10-13 00:37:02 +03:00
|
|
|
#endif
|
2022-10-03 18:14:32 +03:00
|
|
|
}
|