diff --git a/include/mruby.h b/include/mruby.h index 6ca2760b5..1c12df08e 100644 --- a/include/mruby.h +++ b/include/mruby.h @@ -325,6 +325,8 @@ typedef struct mrb_state { struct mrb_mt_rom_list *rom_mt; /* heap-allocated ROM wrappers (freed at close) */ + struct mrb_iv_shape *root_shape; /* root of IV shape tree */ + void *ud; /* auxiliary data */ #ifdef MRB_FIXED_STATE_ATEXIT_STACK diff --git a/include/mruby/internal.h b/include/mruby/internal.h index 7e643ce31..4b06dd522 100644 --- a/include/mruby/internal.h +++ b/include/mruby/internal.h @@ -199,6 +199,10 @@ void mrb_gc_free_gv(mrb_state*); size_t mrb_gc_mark_iv(mrb_state*, struct RObject*); void mrb_gc_free_iv(mrb_state*, struct RObject*); +/* IV shape tree */ +void mrb_init_shape(mrb_state*); +void mrb_free_shape(mrb_state*); + /* VM */ #define MRB_CI_VISIBILITY(ci) MRB_FLAGS_GET((ci)->vis, 0, 2) #define MRB_CI_SET_VISIBILITY(ci, visi) MRB_FLAGS_SET((ci)->vis, 0, 2, visi) diff --git a/include/mruby/object.h b/include/mruby/object.h index 916874b30..096651b19 100644 --- a/include/mruby/object.h +++ b/include/mruby/object.h @@ -24,6 +24,11 @@ struct RBasic { #define MRB_OBJ_IS_FROZEN 1 #define mrb_frozen_p(o) ((o)->frozen) +/* Object shape flag -- when set, obj->iv is shaped, not iv_tbl* */ +/* Bit 5: avoids conflict with MRB_INSTANCE_TT_MASK (bits 0-4) */ +#define MRB_FL_OBJ_SHAPED (1 << 5) +#define MRB_OBJ_SHAPED_P(o) ((o)->flags & MRB_FL_OBJ_SHAPED) + struct RObject { MRB_OBJECT_HEADER; struct iv_tbl *iv; diff --git a/src/gc.c b/src/gc.c index 0c7dd4ad4..69aa89341 100644 --- a/src/gc.c +++ b/src/gc.c @@ -601,6 +601,9 @@ mrb_obj_alloc(mrb_state *mrb, enum mrb_vtype ttype, struct RClass *cls) *p = RVALUE_zero; p->as.basic.tt = ttype; p->as.basic.c = cls; + if (ttype == MRB_TT_OBJECT) { + p->as.basic.flags |= MRB_FL_OBJ_SHAPED; + } paint_partial_white(gc, &p->as.basic); return &p->as.basic; } diff --git a/src/state.c b/src/state.c index f4ecbaa96..642a1b963 100644 --- a/src/state.c +++ b/src/state.c @@ -22,12 +22,16 @@ void mrb_gc_destroy(mrb_state*, mrb_gc *gc); int mrb_core_init_protect(mrb_state *mrb, void (*body)(mrb_state*, void*), void *opaque); +void mrb_init_shape(mrb_state*); +void mrb_free_shape(mrb_state*); + static void init_gc_and_core(mrb_state *mrb, void *opaque) { static const struct mrb_context mrb_context_zero = { 0 }; mrb_gc_init(mrb, &mrb->gc); + mrb_init_shape(mrb); mrb->c = (struct mrb_context*)mrb_malloc(mrb, sizeof(struct mrb_context)); *mrb->c = mrb_context_zero; mrb->root_c = mrb->c; @@ -187,6 +191,7 @@ mrb_close(mrb_state *mrb) /* free */ mrb_gc_free_gv(mrb); mrb_gc_destroy(mrb, &mrb->gc); + mrb_free_shape(mrb); mrb_free_context(mrb, mrb->root_c); mrb_free_symtbl(mrb); diff --git a/src/variable.c b/src/variable.c index 9b056cf69..a40353ce4 100644 --- a/src/variable.c +++ b/src/variable.c @@ -239,6 +239,287 @@ iv_free(mrb_state *mrb, iv_tbl *t) mrb_free(mrb, t); } +/* + * Object Shape (Hidden Class) structures. + * + * A shape describes the IV layout of an object: which syms are stored + * at which indices. Shapes form a tree rooted at the empty root shape. + * Each child adds one IV (its "edge" sym). Objects sharing the same + * set of IVs (assigned in the same order) share the same shape, + * eliminating per-object key storage. + * + * Only MRB_TT_OBJECT instances are shaped. RClass, RHash, etc. keep + * traditional iv_tbl. + */ + +/* Maximum IV count before de-shaping to iv_tbl */ +#define MRB_SHAPE_MAX_IVS 16 + +/* Shape descriptor -- shared across objects with same IV layout */ +typedef struct mrb_iv_shape { + struct mrb_iv_shape *parent; /* parent shape (one fewer IV) */ + struct mrb_iv_shape *children; /* linked list of child shapes */ + struct mrb_iv_shape *sibling; /* next child of same parent */ + mrb_sym edge; /* IV sym added from parent */ + uint16_t count; /* number of IV slots */ +} mrb_iv_shape; + +/* Per-object shaped IV storage (allocated via struct hack) */ +typedef struct mrb_shaped_iv { + mrb_iv_shape *shape; + mrb_value values[1]; /* shape->count elements */ +} mrb_shaped_iv; + +/* Create the empty root shape */ +static mrb_iv_shape* +shape_root(mrb_state *mrb) +{ + mrb_iv_shape *s = (mrb_iv_shape*)mrb_calloc(mrb, 1, sizeof(mrb_iv_shape)); + return s; +} + +/* Find a child shape with the given edge sym */ +static mrb_iv_shape* +shape_find_child(mrb_iv_shape *shape, mrb_sym sym) +{ + mrb_iv_shape *c = shape->children; + while (c) { + if (c->edge == sym) return c; + c = c->sibling; + } + return NULL; +} + +/* Find or create a child shape for adding sym */ +static mrb_iv_shape* +shape_transition(mrb_state *mrb, mrb_iv_shape *shape, mrb_sym sym) +{ + mrb_iv_shape *child = shape_find_child(shape, sym); + if (child) return child; + + /* create new child shape */ + child = (mrb_iv_shape*)mrb_malloc(mrb, sizeof(mrb_iv_shape)); + child->parent = shape; + child->children = NULL; + child->sibling = shape->children; + child->edge = sym; + child->count = shape->count + 1; + shape->children = child; + return child; +} + +/* + * Look up sym in shape by walking the parent chain. + * Returns the value index (0-based), or -1 if not found. + */ +static int +shape_lookup(mrb_iv_shape *shape, mrb_sym sym) +{ + mrb_iv_shape *s = shape; + while (s->count > 0) { + if (s->edge == sym) return s->count - 1; + s = s->parent; + } + return -1; +} + +/* Recursively free all shapes in the tree */ +static void +shape_free_tree(mrb_state *mrb, mrb_iv_shape *shape) +{ + mrb_iv_shape *c = shape->children; + while (c) { + mrb_iv_shape *next = c->sibling; + shape_free_tree(mrb, c); + c = next; + } + mrb_free(mrb, shape); +} + +/* Allocate a mrb_shaped_iv with room for count values */ +static mrb_shaped_iv* +shaped_iv_alloc(mrb_state *mrb, mrb_iv_shape *shape) +{ + size_t sz = offsetof(mrb_shaped_iv, values) + + sizeof(mrb_value) * shape->count; + mrb_shaped_iv *siv = (mrb_shaped_iv*)mrb_malloc(mrb, sz); + siv->shape = shape; + return siv; +} + +/* Convert a shaped object back to traditional iv_tbl (de-shape) */ +static void +shaped_to_iv_tbl(mrb_state *mrb, struct RObject *obj) +{ + mrb_shaped_iv *siv = (mrb_shaped_iv*)obj->iv; + iv_tbl *t = NULL; + + if (siv) { + mrb_iv_shape *shape = siv->shape; + + if (shape->count > 0) { + /* reconstruct keys from parent chain */ + mrb_sym keys[MRB_SHAPE_MAX_IVS]; + mrb_iv_shape *s = shape; + while (s->count > 0) { + keys[s->count - 1] = s->edge; + s = s->parent; + } + + t = iv_new(mrb); + for (int i = 0; i < shape->count; i++) { + if (!mrb_undef_p(siv->values[i])) { + iv_put(mrb, t, keys[i], siv->values[i]); + } + } + } + mrb_free(mrb, siv); + } + obj->iv = t; + obj->flags &= ~MRB_FL_OBJ_SHAPED; +} + +/* --- Shaped IV operations --- */ + +static void +shaped_iv_set(mrb_state *mrb, struct RObject *obj, mrb_sym sym, mrb_value v) +{ + mrb_shaped_iv *siv = (mrb_shaped_iv*)obj->iv; + mrb_iv_shape *shape = siv ? siv->shape : mrb->root_shape; + + /* check if sym already exists in current shape */ + int idx = shape_lookup(shape, sym); + if (idx >= 0) { + siv->values[idx] = v; + return; + } + + /* transition to new shape */ + mrb_iv_shape *new_shape = shape_transition(mrb, shape, sym); + + /* de-shape if too many IVs */ + if (new_shape->count > MRB_SHAPE_MAX_IVS) { + shaped_to_iv_tbl(mrb, obj); + if (!obj->iv) { + obj->iv = iv_new(mrb); + } + iv_put(mrb, obj->iv, sym, v); + return; + } + + /* allocate new shaped_iv with room for new shape */ + mrb_shaped_iv *new_siv = shaped_iv_alloc(mrb, new_shape); + + /* copy old values (they are a prefix) */ + if (siv) { + memcpy(new_siv->values, siv->values, + sizeof(mrb_value) * shape->count); + mrb_free(mrb, siv); + } + /* new IV goes in the last slot */ + new_siv->values[new_shape->count - 1] = v; + + obj->iv = (iv_tbl*)new_siv; +} + +static mrb_value +shaped_iv_get(struct RObject *obj, mrb_sym sym) +{ + mrb_shaped_iv *siv = (mrb_shaped_iv*)obj->iv; + if (!siv) return mrb_nil_value(); + int idx = shape_lookup(siv->shape, sym); + if (idx >= 0 && !mrb_undef_p(siv->values[idx])) + return siv->values[idx]; + return mrb_nil_value(); +} + +static mrb_bool +shaped_iv_defined(struct RObject *obj, mrb_sym sym) +{ + mrb_shaped_iv *siv = (mrb_shaped_iv*)obj->iv; + if (!siv) return FALSE; + int idx = shape_lookup(siv->shape, sym); + if (idx >= 0 && !mrb_undef_p(siv->values[idx])) + return TRUE; + return FALSE; +} + +static void +shaped_iv_foreach(mrb_state *mrb, struct RObject *obj, + mrb_iv_foreach_func *func, void *p) +{ + mrb_shaped_iv *siv = (mrb_shaped_iv*)obj->iv; + if (!siv) return; + mrb_iv_shape *shape = siv->shape; + if (shape->count == 0) return; + + /* reconstruct keys from parent chain */ + mrb_sym keys[MRB_SHAPE_MAX_IVS]; + mrb_iv_shape *s = shape; + while (s->count > 0) { + keys[s->count - 1] = s->edge; + s = s->parent; + } + + for (int i = 0; i < shape->count; i++) { + if (!mrb_undef_p(siv->values[i])) { + if ((*func)(mrb, keys[i], siv->values[i], p) != 0) return; + } + } +} + +static size_t +shaped_iv_mark(mrb_state *mrb, struct RObject *obj) +{ + mrb_shaped_iv *siv = (mrb_shaped_iv*)obj->iv; + if (!siv) return 0; + mrb_iv_shape *shape = siv->shape; + for (int i = 0; i < shape->count; i++) { + if (!mrb_undef_p(siv->values[i])) { + mrb_gc_mark_value(mrb, siv->values[i]); + } + } + return shape->count; +} + +static void +shaped_iv_free(mrb_state *mrb, struct RObject *obj) +{ + if (obj->iv) { + mrb_free(mrb, obj->iv); + } +} + +static void +shaped_iv_copy(mrb_state *mrb, struct RObject *dst, struct RObject *src) +{ + mrb_shaped_iv *ssiv = (mrb_shaped_iv*)src->iv; + if (!ssiv) { + dst->iv = NULL; + return; + } + mrb_iv_shape *shape = ssiv->shape; + mrb_shaped_iv *dsiv = shaped_iv_alloc(mrb, shape); + memcpy(dsiv->values, ssiv->values, sizeof(mrb_value) * shape->count); + dst->iv = (iv_tbl*)dsiv; +} + +/* Public init/free for shape tree (called from state.c) */ +void +mrb_init_shape(mrb_state *mrb) +{ + mrb->root_shape = shape_root(mrb); +} + +void +mrb_free_shape(mrb_state *mrb) +{ + if (mrb->root_shape) { + shape_free_tree(mrb, mrb->root_shape); + mrb->root_shape = NULL; + } +} + static int iv_mark_i(mrb_state *mrb, mrb_sym sym, mrb_value v, void *p) { @@ -270,6 +551,9 @@ mrb_gc_free_gv(mrb_state *mrb) size_t mrb_gc_mark_iv(mrb_state *mrb, struct RObject *obj) { + if (MRB_OBJ_SHAPED_P(obj)) { + return shaped_iv_mark(mrb, obj); + } mark_tbl(mrb, obj->iv); return iv_size(mrb, obj->iv); } @@ -277,6 +561,10 @@ mrb_gc_mark_iv(mrb_state *mrb, struct RObject *obj) void mrb_gc_free_iv(mrb_state *mrb, struct RObject *obj) { + if (MRB_OBJ_SHAPED_P(obj)) { + shaped_iv_free(mrb, obj); + return; + } if (obj->iv) { iv_free(mrb, obj->iv); } @@ -331,8 +619,11 @@ class_iv_ptr(struct RClass *c) MRB_API mrb_value mrb_obj_iv_get(mrb_state *mrb, struct RObject *obj, mrb_sym sym) { - mrb_value v; + if (MRB_OBJ_SHAPED_P(obj)) { + return shaped_iv_get(obj, sym); + } + mrb_value v; if (obj->iv && iv_get(mrb, obj->iv, sym, &v)) return v; return mrb_nil_value(); @@ -402,6 +693,11 @@ mrb_obj_iv_set_force(mrb_state *mrb, struct RObject *obj, mrb_sym sym, mrb_value if (namespace_p(obj->tt)) { assign_class_name(mrb, obj, sym, v); } + if (MRB_OBJ_SHAPED_P(obj)) { + shaped_iv_set(mrb, obj, sym, v); + mrb_field_write_barrier_value(mrb, (struct RBasic*)obj, v); + return; + } if (!obj->iv) { obj->iv = iv_new(mrb); } @@ -447,6 +743,10 @@ MRB_API void mrb_iv_foreach(mrb_state *mrb, mrb_value obj, mrb_iv_foreach_func *func, void *p) { if (!obj_iv_p(obj)) return; + if (MRB_OBJ_SHAPED_P(mrb_obj_ptr(obj))) { + shaped_iv_foreach(mrb, mrb_obj_ptr(obj), func, p); + return; + } iv_foreach(mrb, mrb_obj_ptr(obj)->iv, func, p); } @@ -492,9 +792,11 @@ mrb_iv_set(mrb_state *mrb, mrb_value obj, mrb_sym sym, mrb_value v) MRB_API mrb_bool mrb_obj_iv_defined(mrb_state *mrb, struct RObject *obj, mrb_sym sym) { - iv_tbl *t; + if (MRB_OBJ_SHAPED_P(obj)) { + return shaped_iv_defined(obj, sym); + } - t = obj->iv; + iv_tbl *t = obj->iv; if (t && iv_get(mrb, t, sym, NULL)) return TRUE; return FALSE; } @@ -586,13 +888,55 @@ mrb_iv_copy(mrb_state *mrb, mrb_value dest, mrb_value src) struct RObject *d = mrb_obj_ptr(dest); struct RObject *s = mrb_obj_ptr(src); - if (d->iv) { - iv_free(mrb, d->iv); - d->iv = 0; + /* free dest's existing IVs */ + if (MRB_OBJ_SHAPED_P(d)) { + shaped_iv_free(mrb, d); + d->iv = NULL; } - if (s->iv) { - mrb_write_barrier(mrb, (struct RBasic*)d); - d->iv = iv_copy(mrb, s->iv); + else if (d->iv) { + iv_free(mrb, d->iv); + d->iv = NULL; + } + + if (MRB_OBJ_SHAPED_P(s) && MRB_OBJ_SHAPED_P(d)) { + /* both shaped: share shape, memcpy values */ + if (s->iv) { + mrb_write_barrier(mrb, (struct RBasic*)d); + shaped_iv_copy(mrb, d, s); + } + } + else if (MRB_OBJ_SHAPED_P(s)) { + /* src shaped, dest unshaped: convert src to iv_tbl copy */ + mrb_shaped_iv *ssiv = (mrb_shaped_iv*)s->iv; + if (ssiv) { + mrb_iv_shape *shape = ssiv->shape; + if (shape->count > 0) { + mrb_sym keys[MRB_SHAPE_MAX_IVS]; + mrb_iv_shape *sh = shape; + while (sh->count > 0) { + keys[sh->count - 1] = sh->edge; + sh = sh->parent; + } + iv_tbl *t = iv_new(mrb); + for (int i = 0; i < shape->count; i++) { + if (!mrb_undef_p(ssiv->values[i])) { + iv_put(mrb, t, keys[i], ssiv->values[i]); + } + } + mrb_write_barrier(mrb, (struct RBasic*)d); + d->iv = t; + } + } + } + else { + /* both unshaped or dest shaped but src unshaped */ + if (MRB_OBJ_SHAPED_P(d)) { + d->flags &= ~MRB_FL_OBJ_SHAPED; + } + if (s->iv) { + mrb_write_barrier(mrb, (struct RBasic*)d); + d->iv = iv_copy(mrb, s->iv); + } } } @@ -614,10 +958,25 @@ mrb_iv_remove(mrb_state *mrb, mrb_value obj, mrb_sym sym) { if (obj_iv_p(obj)) { struct RObject *o = mrb_obj_ptr(obj); + mrb_check_frozen(mrb, o); + + if (MRB_OBJ_SHAPED_P(o)) { + mrb_shaped_iv *siv = (mrb_shaped_iv*)o->iv; + if (siv) { + int idx = shape_lookup(siv->shape, sym); + if (idx >= 0 && !mrb_undef_p(siv->values[idx])) { + mrb_value val = siv->values[idx]; + /* de-shape, then remove the key */ + shaped_to_iv_tbl(mrb, o); + iv_del(mrb, o->iv, sym, NULL); + return val; + } + } + return mrb_undef_value(); + } + iv_tbl *t = o->iv; mrb_value val; - - mrb_check_frozen(mrb, o); if (iv_del(mrb, t, sym, &val)) { return val; } @@ -661,7 +1020,13 @@ mrb_obj_instance_variables(mrb_state *mrb, mrb_value self) mrb_value ary = mrb_ary_new(mrb); if (obj_iv_p(self)) { - iv_foreach(mrb, mrb_obj_ptr(self)->iv, iv_i, &ary); + struct RObject *obj = mrb_obj_ptr(self); + if (MRB_OBJ_SHAPED_P(obj)) { + shaped_iv_foreach(mrb, obj, iv_i, &ary); + } + else { + iv_foreach(mrb, obj->iv, iv_i, &ary); + } } return ary; } @@ -1458,7 +1823,14 @@ mrb_class_find_path(mrb_state *mrb, struct RClass *c) size_t mrb_obj_iv_tbl_memsize(mrb_value obj) { - iv_tbl *t = mrb_obj_ptr(obj)->iv; + struct RObject *o = mrb_obj_ptr(obj); + if (MRB_OBJ_SHAPED_P(o)) { + mrb_shaped_iv *siv = (mrb_shaped_iv*)o->iv; + if (!siv) return 0; + return offsetof(mrb_shaped_iv, values) + + sizeof(mrb_value) * siv->shape->count; + } + iv_tbl *t = o->iv; if (t == NULL) return 0; return sizeof(iv_tbl) + t->alloc*(sizeof(mrb_value)+sizeof(mrb_sym)); }