From 1e0f9b76c348fa8f6e0be7154b1f76b0b2698e4d Mon Sep 17 00:00:00 2001 From: "Yukihiro \"Matz\" Matsumoto" Date: Wed, 18 Feb 2026 15:53:18 +0900 Subject: [PATCH] class.c: add chained method table infrastructure for ROM support Add next pointer and readonly flag to mt_tbl struct to support chained ROM method table layers. mt_get() walks the chain, mt_copy() shares ROM layers, mt_free() and mrb_gc_mark_mt() skip readonly layers. COW in mrb_define_method_raw() creates a mutable top layer when the existing table is readonly. mt_flatten() merges all layers for the rare remove_method case. No ROM tables exist yet -- all tables have next==NULL and no readonly flag, so behavior is identical to the previous code. Co-authored-by: Claude --- src/class.c | 215 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 158 insertions(+), 57 deletions(-) diff --git a/src/class.c b/src/class.c index 828bb5dba..5cbf7a9ae 100644 --- a/src/class.c +++ b/src/class.c @@ -46,14 +46,19 @@ union mt_ptr { /* method table structure */ typedef struct mt_tbl { int size; /* # of used entries */ - int alloc; /* capacity */ + int alloc; /* capacity (bit 30: MT_READONLY_BIT) */ union mt_ptr *ptr; /* block: [ ptr[0...alloc] | keys[0...alloc] ] */ + struct mt_tbl *next; /* next (lower-priority) layer, or NULL */ } mt_tbl; +#define MT_READONLY_BIT (1 << 30) +#define MT_ALLOC(t) ((t)->alloc & ~MT_READONLY_BIT) +#define mt_readonly_p(t) ((t)->alloc & MT_READONLY_BIT) + /* Helper to get keys array from method table */ static inline mrb_sym* mt_keys(mt_tbl *t) { - return (mrb_sym*)&t->ptr[t->alloc]; + return (mrb_sym*)&t->ptr[MT_ALLOC(t)]; } /* Helper to get values array from method table */ @@ -66,7 +71,7 @@ mt_vals(mt_tbl *t) { static void mt_grow(mrb_state *mrb, mt_tbl *t, int new_alloc) { - int old_alloc = t->alloc; + int old_alloc = MT_ALLOC(t); size_t new_block = new_alloc * (sizeof(union mt_ptr) + sizeof(mrb_sym)); t->ptr = (union mt_ptr*)mrb_realloc(mrb, t->ptr, new_block); @@ -92,6 +97,7 @@ mt_new(mrb_state *mrb) t->size = 0; t->alloc = 0; t->ptr = NULL; + t->next = NULL; return t; } @@ -129,11 +135,11 @@ mt_put(mrb_state *mrb, mt_tbl *t, mrb_sym sym, mrb_sym flags, union mt_ptr ptrva mrb_sym key = MT_KEY(sym, flags); /* Ensure there is capacity */ - if (t->alloc == 0) { + if (MT_ALLOC(t) == 0) { mt_grow(mrb, t, 8); } - else if (t->size == t->alloc) { - mt_grow(mrb, t, t->alloc * 2); + else if (t->size == MT_ALLOC(t)) { + mt_grow(mrb, t, MT_ALLOC(t) * 2); } mrb_sym *keys = mt_keys(t); @@ -165,26 +171,22 @@ mt_put(mrb_state *mrb, mt_tbl *t, mrb_sym sym, mrb_sym flags, union mt_ptr ptrva t->size++; } -/* Retrieves a value from the method table */ +/* Retrieves a value from the method table (walks chain) */ static mrb_sym mt_get(mrb_state *mrb, mt_tbl *t, mrb_sym sym, union mt_ptr *pp) { - /* Return 0 if table is empty or null */ - if (!t || t->size == 0) return 0; - - mrb_sym *keys = mt_keys(t); - union mt_ptr *vals = mt_vals(t); - - /* Find the position in a branch-free manner */ - int lo = mt_bsearch_idx(keys, t->size, sym); - - /* If found, set *pp to the value and return the full key */ - if (lo < t->size && MT_KEY_SYM(keys[lo]) == sym) { - *pp = vals[lo]; - return keys[lo]; + while (t) { + if (t->size > 0) { + mrb_sym *keys = mt_keys(t); + union mt_ptr *vals = mt_vals(t); + int lo = mt_bsearch_idx(keys, t->size, sym); + if (lo < t->size && MT_KEY_SYM(keys[lo]) == sym) { + *pp = vals[lo]; + return keys[lo]; + } + } + t = t->next; } - - /* Not found */ return 0; } @@ -215,26 +217,85 @@ mt_del(mrb_state *mrb, mt_tbl *t, mrb_sym sym) return FALSE; } +/* Checks if any layer in the chain contains the given symbol */ +static mrb_bool +mt_chain_has(mt_tbl *t, mrb_sym sym) +{ + while (t) { + if (t->size > 0) { + mrb_sym *keys = mt_keys(t); + int lo = mt_bsearch_idx(keys, t->size, sym); + if (lo < t->size && MT_KEY_SYM(keys[lo]) == sym) return TRUE; + } + t = t->next; + } + return FALSE; +} + +/* Flattens all chain layers into a single mutable table */ +static void +mt_flatten(mrb_state *mrb, struct RClass *c) +{ + mt_tbl *t = c->mt; + if (!t || (!t->next && !mt_readonly_p(t))) return; + + mt_tbl *flat = mt_new(mrb); + /* collect all layers into an array, then iterate backwards */ + mt_tbl *layers[16]; + int n = 0; + for (mt_tbl *l = t; l && n < 16; l = l->next) + layers[n++] = l; + for (int i = n - 1; i >= 0; i--) { + mt_tbl *l = layers[i]; + mrb_sym *keys = mt_keys(l); + union mt_ptr *vals = mt_vals(l); + for (int j = 0; j < l->size; j++) + mt_put(mrb, flat, MT_KEY_SYM(keys[j]), MT_KEY_FLG(keys[j]), vals[j]); + } + /* free only mutable layers */ + for (mt_tbl *l = t; l && !mt_readonly_p(l); ) { + mt_tbl *next = l->next; + mrb_free(mrb, l->ptr); + mrb_free(mrb, l); + l = next; + } + c->mt = flat; +} + /* Creates a copy of the method table */ static mt_tbl* mt_copy(mrb_state *mrb, mt_tbl *t) { - if (!t || t->size == 0) return NULL; + if (!t) return NULL; + if (mt_readonly_p(t)) { + /* source is ROM — new class gets empty mutable top + shared ROM chain */ + if (t->size == 0 && !t->next) return NULL; + mt_tbl *t2 = mt_new(mrb); + t2->next = t; + return t2; + } + if (t->size == 0 && !t->next) return NULL; mt_tbl *t2 = mt_new(mrb); - mt_grow(mrb, t2, t->size); - /* copy used entries */ - memcpy(mt_vals(t2), mt_vals(t), t->size * sizeof(union mt_ptr)); - memcpy(mt_keys(t2), mt_keys(t), t->size * sizeof(mrb_sym)); - t2->size = t->size; + if (t->size > 0) { + mt_grow(mrb, t2, t->size); + memcpy(mt_vals(t2), mt_vals(t), t->size * sizeof(union mt_ptr)); + memcpy(mt_keys(t2), mt_keys(t), t->size * sizeof(mrb_sym)); + t2->size = t->size; + } + t2->next = t->next; /* share ROM chain */ return t2; } -/* Frees memory of the method table */ +/* Frees memory of the method table (skips readonly/ROM layers) */ static void mt_free(mrb_state *mrb, mt_tbl *t) { - mrb_free(mrb, t->ptr); - mrb_free(mrb, t); + while (t && !mt_readonly_p(t)) { + mt_tbl *next = t->next; + mrb_free(mrb, t->ptr); + mrb_free(mrb, t); + t = next; + } } /* Creates a method value structure from key and pointer */ @@ -250,14 +311,43 @@ MRB_API void mrb_mt_foreach(mrb_state *mrb, struct RClass *c, mrb_mt_foreach_func *fn, void *p) { mt_tbl *t = c->mt; - if (!t || t->size == 0) return; + if (!t) return; - for (int i=0; isize; i++) { - union mt_ptr *vals = mt_vals(t); - mrb_sym *keys = mt_keys(t); - mrb_sym key = keys[i]; - if (fn(mrb, MT_KEY_SYM(key), create_method_value(mrb, key, vals[i]), p) != 0) { - return; + /* fast path: single layer */ + if (!t->next) { + for (int i = 0; i < t->size; i++) { + union mt_ptr *vals = mt_vals(t); + mrb_sym *keys = mt_keys(t); + if (fn(mrb, MT_KEY_SYM(keys[i]), + create_method_value(mrb, keys[i], vals[i]), p) != 0) + return; + } + return; + } + + /* multi-layer: iterate each layer, skip if shadowed by a higher one */ + for (mt_tbl *layer = t; layer; layer = layer->next) { + mrb_sym *keys = mt_keys(layer); + union mt_ptr *vals = mt_vals(layer); + for (int i = 0; i < layer->size; i++) { + mrb_sym sym = MT_KEY_SYM(keys[i]); + /* check if shadowed by a higher layer */ + if (layer != t) { + mrb_bool shadowed = FALSE; + for (mt_tbl *upper = t; upper != layer; upper = upper->next) { + if (upper->size > 0) { + mrb_sym *ukeys = mt_keys(upper); + int lo = mt_bsearch_idx(ukeys, upper->size, sym); + if (lo < upper->size && MT_KEY_SYM(ukeys[lo]) == sym) { + shadowed = TRUE; + break; + } + } + } + if (shadowed) continue; + } + if (fn(mrb, sym, create_method_value(mrb, keys[i], vals[i]), p) != 0) + return; } } } @@ -266,30 +356,31 @@ mrb_mt_foreach(mrb_state *mrb, struct RClass *c, mrb_mt_foreach_func *fn, void * size_t mrb_gc_mark_mt(mrb_state *mrb, struct RClass *c) { - mt_tbl *t = c->mt; - - if (!t || t->size == 0) return 0; - - mrb_sym *keys = mt_keys(t); - union mt_ptr *vals = mt_vals(t); - for (int i=0; isize; i++) { - if (MT_KEY_P(keys[i]) && (keys[i] & MT_FUNC) == 0) { /* Proc pointer */ - const struct RProc *p = vals[i].proc; - mrb_gc_mark(mrb, (struct RBasic*)p); + size_t children = 0; + for (mt_tbl *t = c->mt; t; t = t->next) { + if (mt_readonly_p(t)) continue; /* ROM layers need no GC marking */ + if (t->size == 0) continue; + mrb_sym *keys = mt_keys(t); + union mt_ptr *vals = mt_vals(t); + for (int i = 0; i < t->size; i++) { + if (MT_KEY_P(keys[i]) && (keys[i] & MT_FUNC) == 0) { + mrb_gc_mark(mrb, (struct RBasic*)vals[i].proc); + } } + children += (size_t)t->size; } - if (!t) return 0; - return (size_t)t->size; + return children; } -/* Returns memory size of class method table */ +/* Returns memory size of class method table (mutable layers only) */ size_t mrb_class_mt_memsize(mrb_state *mrb, struct RClass *c) { - struct mt_tbl *h = c->mt; - - if (!h) return 0; - return sizeof(struct mt_tbl) + (size_t)h->size * sizeof(mrb_method_t); + size_t total = 0; + for (mt_tbl *h = c->mt; h && !mt_readonly_p(h); h = h->next) + total += sizeof(mt_tbl) + (size_t)MT_ALLOC(h) * + (sizeof(union mt_ptr) + sizeof(mrb_sym)); + return total; } /* Frees class method table for garbage collection */ @@ -1002,7 +1093,15 @@ mrb_define_method_raw(mrb_state *mrb, struct RClass *c, mrb_sym mid, mrb_method_ else { mrb_check_frozen(mrb, c); } - if (!h) h = c->mt = mt_new(mrb); + if (!h) { + h = c->mt = mt_new(mrb); + } + else if (mt_readonly_p(h)) { + /* COW: create mutable top layer, chain to ROM */ + mt_tbl *top = mt_new(mrb); + top->next = h; + h = c->mt = top; + } if (MRB_METHOD_PROC_P(m)) { struct RProc *p = (struct RProc*)MRB_METHOD_PROC(m); @@ -3553,7 +3652,9 @@ mrb_remove_method(mrb_state *mrb, struct RClass *c0, mrb_sym mid) MRB_CLASS_ORIGIN(c); mt_tbl *h = c->mt; - if (h && mt_del(mrb, h, mid)) { + if ((h && mt_del(mrb, h, mid)) || + (h && h->next && mt_chain_has(h->next, mid) && + (mt_flatten(mrb, c), mt_del(mrb, c->mt, mid)))) { mrb_sym removed; mrb_value recv;