From cfd9214b3d0a50da458931f708c8e1d39dbcfa5e Mon Sep 17 00:00:00 2001 From: "Yukihiro \"Matz\" Matsumoto" Date: Thu, 19 Feb 2026 23:25:16 +0900 Subject: [PATCH] class.c: add MRB_MT_FROZEN_BIT for method table layers When mrb_mt_init_rom() is called on a class that already has a mutable method table (from prior mrb_define_method_id() calls), the mutable top layer is now frozen in place instead of being left as a writable layer that wastes RAM on embedded systems. The frozen bit (bit 29 of alloc field) marks heap-allocated method table layers as temporarily immutable. Unlike the readonly bit (bit 30, for true ROM), frozen layers are automatically unfrozen when methods are later added via mrb_define_method_raw() or removed via mrb_remove_method(). This preserves the c->mt pointer, which is critical because iclasses (from module inclusion) hold a copy of it. Co-authored-by: Claude --- include/mruby/internal.h | 3 ++- src/class.c | 20 +++++++++++++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/include/mruby/internal.h b/include/mruby/internal.h index dda169f13..ece832238 100644 --- a/include/mruby/internal.h +++ b/include/mruby/internal.h @@ -36,12 +36,13 @@ union mrb_mt_ptr { typedef struct mrb_mt_tbl { int size; - int alloc; /* bit 30: MRB_MT_READONLY_BIT */ + int alloc; /* bit 30: MRB_MT_READONLY_BIT, bit 29: MRB_MT_FROZEN_BIT */ union mrb_mt_ptr *ptr; struct mrb_mt_tbl *next; } mrb_mt_tbl; #define MRB_MT_READONLY_BIT (1 << 30) +#define MRB_MT_FROZEN_BIT (1 << 29) #define MRB_MT_KEY_SHIFT 4 #define MRB_MT_KEY(sym, flags) ((sym)<>MRB_MT_KEY_SHIFT) #define MT_KEY_FLG(k) ((k)&MT_KEY_MASK) -#define MT_ALLOC(t) ((t)->alloc & ~MRB_MT_READONLY_BIT) +#define MRB_MT_FLAG_BITS (MRB_MT_READONLY_BIT | MRB_MT_FROZEN_BIT) +#define MT_ALLOC(t) ((t)->alloc & ~MRB_MT_FLAG_BITS) #define mt_readonly_p(t) ((t)->alloc & MRB_MT_READONLY_BIT) +#define mt_frozen_p(t) ((t)->alloc & MRB_MT_FROZEN_BIT) /* Helper to get keys array from method table */ static inline mrb_sym* @@ -65,7 +67,7 @@ mt_grow(mrb_state *mrb, mrb_mt_tbl *t, int new_alloc) /* move the old key array up to its new position */ memmove(new_keys, old_keys, old_alloc * sizeof(mrb_sym)); } - t->alloc = new_alloc; + t->alloc = (t->alloc & MRB_MT_FLAG_BITS) | new_alloc; } /* Creates a new empty method table */ @@ -285,6 +287,10 @@ mrb_mt_init_rom(struct RClass *c, mrb_mt_tbl *rom) c->mt = rom; } else { + /* freeze mutable top, insert ROM behind it; + * c->mt must not change because iclasses (module inclusion) + * hold a copy of the mt pointer */ + t->alloc |= MRB_MT_FROZEN_BIT; rom->next = t->next; t->next = rom; } @@ -1090,6 +1096,11 @@ mrb_define_method_raw(mrb_state *mrb, struct RClass *c, mrb_sym mid, mrb_method_ if (!h) { h = c->mt = mt_new(mrb); } + else if (mt_frozen_p(h)) { + /* unfreeze heap-allocated frozen layer to preserve c->mt pointer + * (iclasses hold a copy of the mt pointer for included modules) */ + h->alloc &= ~MRB_MT_FROZEN_BIT; + } else if (mt_readonly_p(h)) { /* COW: create mutable top layer, chain to ROM */ mrb_mt_tbl *top = mt_new(mrb); @@ -3654,7 +3665,10 @@ mrb_remove_method(mrb_state *mrb, struct RClass *c0, mrb_sym mid) union mrb_mt_ptr tombstone; tombstone.func = NULL; found = TRUE; - if (mt_readonly_p(h)) { + if (mt_frozen_p(h)) { + h->alloc &= ~MRB_MT_FROZEN_BIT; + } + else if (mt_readonly_p(h)) { mrb_mt_tbl *top = mt_new(mrb); top->next = h; h = c->mt = top;