load.c: consolidate irep allocation for .mrb loading

Pack pool/syms/reps arrays into a single calloc with the irep struct,
reducing 4 allocations per irep to 1. Arrays are ordered by descending
alignment (pool/reps/syms) to eliminate inter-array padding.

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2026-02-25 15:20:19 +09:00
parent 3c2922a361
commit 74fb046544
3 changed files with 129 additions and 29 deletions
+1
View File
@@ -82,6 +82,7 @@ struct mrb_irep {
#define MRB_ISEQ_NO_FREE 1
#define MRB_IREP_NO_FREE 2
#define MRB_IREP_STATIC (MRB_ISEQ_NO_FREE | MRB_IREP_NO_FREE)
#define MRB_IREP_CONSOLIDATED 4 /* pool/syms/reps packed with irep struct */
MRB_API mrb_irep *mrb_add_irep(mrb_state *mrb);
+123 -26
View File
@@ -59,6 +59,95 @@ str_to_double(mrb_state *mrb, const char *p)
}
#endif
#define ALIGN_UP(x, a) (((x) + (a) - 1) & ~((size_t)(a) - 1))
/*
* Lightweight prescan of pool/syms binary data to learn plen and slen
* without allocating. Must stay in sync with the pool/syms parsing
* in read_irep_record_1().
*/
static mrb_bool
prescan_pool_syms(const uint8_t *src, const uint8_t *end, uint16_t *plenp, uint16_t *slenp)
{
uint16_t plen;
int i;
if (src + sizeof(uint16_t) > end) return FALSE;
plen = bin_to_uint16(src);
src += sizeof(uint16_t);
for (i = 0; i < plen; i++) {
if (src >= end) return FALSE;
switch (*src++) {
case IREP_TT_INT32:
src += sizeof(uint32_t);
break;
case IREP_TT_INT64:
src += sizeof(uint32_t) * 2;
break;
case IREP_TT_BIGINT:
if (src >= end) return FALSE;
src += bin_to_uint8(src) + 2;
break;
case IREP_TT_FLOAT:
src += sizeof(double);
break;
case IREP_TT_STR:
if (src + sizeof(uint16_t) > end) return FALSE;
src += sizeof(uint16_t) + bin_to_uint16(src) + 1;
break;
default:
return FALSE;
}
if (src > end) return FALSE;
}
if (src + sizeof(uint16_t) > end) return FALSE;
*plenp = plen;
*slenp = bin_to_uint16(src);
return TRUE;
}
/*
* Single consolidated allocation for irep struct + pool + syms + reps arrays.
* Memory layout: [mrb_irep] [mrb_irep_pool[plen]] [mrb_irep*[rlen]] [mrb_sym[slen]]
* Ordered by descending alignment to minimize inter-array padding.
*/
static mrb_irep*
irep_alloc_consolidated(mrb_state *mrb, uint16_t plen, uint16_t slen, uint16_t rlen)
{
size_t off = sizeof(mrb_irep);
size_t pool_off = 0, syms_off = 0, reps_off = 0;
uint8_t *block;
mrb_irep *irep;
/* pool (8-byte aligned: contains int64_t/double in union) */
if (plen > 0) {
pool_off = ALIGN_UP(off, 8);
off = pool_off + sizeof(mrb_irep_pool) * plen;
}
/* reps (pointer-aligned: naturally follows 8-byte-aligned pool) */
if (rlen > 0) {
reps_off = ALIGN_UP(off, sizeof(void*));
off = reps_off + sizeof(mrb_irep*) * rlen;
}
/* syms (4-byte aligned: naturally follows pointer-aligned reps) */
if (slen > 0) {
syms_off = ALIGN_UP(off, sizeof(mrb_sym));
off = syms_off + sizeof(mrb_sym) * slen;
}
block = (uint8_t*)mrb_calloc(mrb, 1, off);
irep = (mrb_irep*)block;
irep->flags = MRB_IREP_CONSOLIDATED;
irep->refcnt = 1;
if (plen > 0) irep->pool = (const mrb_irep_pool*)(block + pool_off);
if (slen > 0) irep->syms = (const mrb_sym*)(block + syms_off);
if (rlen > 0) irep->reps = (const struct mrb_irep *const*)(block + reps_off);
return irep;
}
static mrb_bool
read_irep_record_1(mrb_state *mrb, const uint8_t *bin, const uint8_t *end, size_t *len, uint8_t flags, mrb_irep **irepp)
{
@@ -66,43 +155,58 @@ read_irep_record_1(mrb_state *mrb, const uint8_t *bin, const uint8_t *end, size_
const uint8_t *src = bin;
ptrdiff_t diff;
uint16_t tt, pool_data_len, snl;
uint16_t nlocals, nregs, rlen, clen;
uint32_t ilen;
int plen;
mrb_irep_pool *pool;
mrb_sym *syms;
mrb_irep *irep;
int ai = mrb_gc_arena_save(mrb);
mrb_irep *irep = mrb_add_irep(mrb);
*irepp = irep;
/* skip record size */
src += sizeof(uint32_t);
/* number of local variable */
irep->nlocals = bin_to_uint16(src);
/* parse header into local variables */
nlocals = bin_to_uint16(src);
src += sizeof(uint16_t);
/* number of register variable */
irep->nregs = bin_to_uint16(src);
nregs = bin_to_uint16(src);
src += sizeof(uint16_t);
/* number of child irep */
irep->rlen = bin_to_uint16(src);
rlen = bin_to_uint16(src);
src += sizeof(uint16_t);
/* Binary Data Section */
/* ISEQ BLOCK (and CATCH HANDLER TABLE BLOCK) */
irep->clen = bin_to_uint16(src); /* number of catch handler */
clen = bin_to_uint16(src); /* number of catch handler */
src += sizeof(uint16_t);
irep->ilen = bin_to_uint32(src);
ilen = bin_to_uint32(src);
src += sizeof(uint32_t);
/* prescan pool/syms to learn counts for consolidated allocation */
{
const uint8_t *pool_start = src;
uint16_t pre_plen, pre_slen;
if (ilen > 0) {
size_t iseq_len;
if (SIZE_ERROR_MUL(ilen, sizeof(mrb_code))) return FALSE;
iseq_len = sizeof(mrb_code) * ilen +
sizeof(struct mrb_irep_catch_handler) * clen;
if (src + iseq_len > end) return FALSE;
pool_start = src + iseq_len;
}
if (!prescan_pool_syms(pool_start, end, &pre_plen, &pre_slen)) return FALSE;
irep = irep_alloc_consolidated(mrb, pre_plen, pre_slen, rlen);
}
*irepp = irep;
irep->nlocals = nlocals;
irep->nregs = nregs;
irep->rlen = rlen;
irep->clen = clen;
irep->ilen = ilen;
if (irep->ilen > 0) {
size_t data_len = sizeof(mrb_code) * irep->ilen +
sizeof(struct mrb_irep_catch_handler) * irep->clen;
mrb_static_assert(sizeof(struct mrb_irep_catch_handler) == 13);
if (SIZE_ERROR_MUL(irep->ilen, sizeof(mrb_code))) {
return FALSE;
}
if (src + data_len > end) return FALSE;
if ((flags & FLAG_SRC_MALLOC) == 0) {
irep->iseq = (mrb_code*)src;
@@ -121,10 +225,7 @@ read_irep_record_1(mrb_state *mrb, const uint8_t *bin, const uint8_t *end, size_
src += sizeof(uint16_t);
if (src > end) return FALSE;
if (plen > 0) {
if (SIZE_ERROR_MUL(plen, sizeof(mrb_value))) {
return FALSE;
}
irep->pool = pool = (mrb_irep_pool*)mrb_calloc(mrb, sizeof(mrb_irep_pool), plen);
pool = (mrb_irep_pool*)irep->pool; /* in consolidated block */
for (i = 0; i < plen; i++) {
mrb_bool st = (flags & FLAG_SRC_MALLOC)==0;
@@ -217,10 +318,7 @@ read_irep_record_1(mrb_state *mrb, const uint8_t *bin, const uint8_t *end, size_
src += sizeof(uint16_t);
if (src > end) return FALSE;
if (irep->slen > 0) {
if (SIZE_ERROR_MUL(irep->slen, sizeof(mrb_sym))) {
return FALSE;
}
irep->syms = syms = (mrb_sym*)mrb_malloc(mrb, sizeof(mrb_sym) * irep->slen);
syms = (mrb_sym*)irep->syms; /* in consolidated block */
for (i = 0; i < irep->slen; i++) {
snl = bin_to_uint16(src); /* symbol name length */
@@ -263,8 +361,7 @@ read_irep_record(mrb_state *mrb, const uint8_t *bin, const uint8_t *end, size_t
return FALSE;
}
reps = (mrb_irep**)mrb_calloc(mrb, (*irepp)->rlen, sizeof(mrb_irep*));
(*irepp)->reps = (const mrb_irep**)reps;
reps = (mrb_irep**)(*irepp)->reps; /* in consolidated block */
bin += *len;
for (i=0; i<(*irepp)->rlen; i++) {
+5 -3
View File
@@ -134,8 +134,10 @@ void
mrb_irep_free(mrb_state *mrb, mrb_irep *irep)
{
int i;
mrb_bool consolidated;
if (irep->flags & MRB_IREP_NO_FREE) return;
consolidated = (irep->flags & MRB_IREP_CONSOLIDATED) != 0;
if (!(irep->flags & MRB_ISEQ_NO_FREE))
mrb_free(mrb, (void*)irep->iseq);
if (irep->pool) {
@@ -145,15 +147,15 @@ mrb_irep_free(mrb_state *mrb, mrb_irep *irep)
mrb_free(mrb, (void*)irep->pool[i].u.str);
}
}
mrb_free(mrb, (void*)irep->pool);
if (!consolidated) mrb_free(mrb, (void*)irep->pool);
}
mrb_free(mrb, (void*)irep->syms);
if (!consolidated) mrb_free(mrb, (void*)irep->syms);
if (irep->reps) {
for (i=0; i<irep->rlen; i++) {
if (irep->reps[i])
mrb_irep_decref(mrb, (mrb_irep*)irep->reps[i]);
}
mrb_free(mrb, (void*)irep->reps);
if (!consolidated) mrb_free(mrb, (void*)irep->reps);
}
mrb_free(mrb, (void*)irep->lv);
mrb_debug_info_free(mrb, irep->debug_info);