From 5b67f9435c1cfd6a4c33045fa79cfc5b9912e605 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Thu, 10 Sep 2026 19:26:52 -0500 Subject: [PATCH] Allow loading bytecode lazily on first use. Ugly code, but avoids copying the bytecode until needed. Relatively small but measurable reduction in memory usage for "hello world" style programs. --- src/core/bytecode.c | 3 ++ src/core/corelib.c | 2 +- src/core/debug.c | 3 ++ src/core/fiber.c | 2 + src/core/gc.c | 8 +++- src/core/marsh.c | 94 +++++++++++++++++++++++++++++++++++++++------ src/core/specials.c | 6 +-- src/core/strtod.c | 2 +- src/core/util.c | 2 +- src/core/util.h | 2 + src/core/vm.c | 2 + src/include/janet.h | 3 ++ 12 files changed, 110 insertions(+), 19 deletions(-) diff --git a/src/core/bytecode.c b/src/core/bytecode.c index f1fbbe1d..d8ba0f15 100644 --- a/src/core/bytecode.c +++ b/src/core/bytecode.c @@ -408,6 +408,9 @@ int janet_verify(JanetFuncDef *def) { int32_t maxslot = def->arity + vargs; int32_t sc = def->slotcount; + /* Yes, this skips checks. Only for loading from core image */ + if (def->flags & JANET_FUNCDEF_FLAG_LAZY_BYTECODE) return 0; + if (def->environments_length > 256) return 15; if (def->bytecode_length == 0) return 1; diff --git a/src/core/corelib.c b/src/core/corelib.c index 541587bc..a8ac3d82 100644 --- a/src/core/corelib.c +++ b/src/core/corelib.c @@ -1384,7 +1384,7 @@ JanetTable *janet_core_env(JanetTable *replacements) { Janet marsh_out = janet_unmarshal( janet_core_image, janet_core_image_size, - 0, + JANET_MARSHAL_LAZY_BYTECODE, dict, NULL); diff --git a/src/core/debug.c b/src/core/debug.c index 6c39dfe8..878a98d2 100644 --- a/src/core/debug.c +++ b/src/core/debug.c @@ -35,6 +35,7 @@ /* Add a break point to a function */ void janet_debug_break(JanetFuncDef *def, int32_t pc) { + janet_def_lazy_init(def); if (pc >= def->bytecode_length || pc < 0) janet_panic("invalid bytecode offset"); def->bytecode[pc] |= 0x80; @@ -42,6 +43,7 @@ void janet_debug_break(JanetFuncDef *def, int32_t pc) { /* Remove a break point from a function */ void janet_debug_unbreak(JanetFuncDef *def, int32_t pc) { + janet_def_lazy_init(def); if (pc >= def->bytecode_length || pc < 0) janet_panic("invalid bytecode offset"); def->bytecode[pc] &= ~((uint32_t)0x80); @@ -67,6 +69,7 @@ void janet_debug_find( if (def->sourcemap && def->source && !janet_string_compare(source, def->source)) { + janet_def_lazy_init(def); /* Correct source file, check mappings. The chosen * pc index is the instruction closest to the given line column, but * not after. */ diff --git a/src/core/fiber.c b/src/core/fiber.c index 9751b449..90ca3a8a 100644 --- a/src/core/fiber.c +++ b/src/core/fiber.c @@ -217,6 +217,7 @@ int janet_fiber_funcframe(JanetFiber *fiber, JanetFunction *func) { } /* Set up the next frame */ + janet_def_lazy_init(func->def); fiber->frame = nextframe; fiber->stacktop = fiber->stackstart = nextstacktop; newframe = janet_fiber_frame(fiber); @@ -387,6 +388,7 @@ int janet_fiber_funcframe_tail(JanetFiber *fiber, JanetFunction *func) { fiber->stacktop = fiber->stackstart = nextstacktop; /* Set frame stuff */ + janet_def_lazy_init(func->def); janet_fiber_frame(fiber)->func = func; janet_fiber_frame(fiber)->pc = func->def->bytecode; janet_fiber_frame(fiber)->flags |= JANET_STACKFRAME_TAILCALL; diff --git a/src/core/gc.c b/src/core/gc.c index f609bcc2..825e1d0e 100644 --- a/src/core/gc.c +++ b/src/core/gc.c @@ -368,8 +368,12 @@ static void janet_deinit_block(JanetGCObject *mem) { janet_free(def->defs); janet_free(def->environments); janet_free(def->constants); - janet_free(def->bytecode); - janet_free(def->sourcemap); + if (!(def->flags & JANET_FUNCDEF_FLAG_LAZY_BYTECODE)) { + janet_free(def->bytecode); + } + if (!(def->flags & JANET_FUNCDEF_FLAG_LAZY_SOURCEMAP)) { + janet_free(def->sourcemap); + } janet_free(def->closure_bitset); janet_free(def->symbolmap); } diff --git a/src/core/marsh.c b/src/core/marsh.c index 043fa8c1..9a03d2ef 100644 --- a/src/core/marsh.c +++ b/src/core/marsh.c @@ -256,6 +256,64 @@ static void janet_marshal_u32s(MarshalState *st, const uint32_t *u32s, int32_t n } } +static int32_t unsafe_readint(const uint8_t **atdata) { + const uint8_t *data = *atdata; + int32_t ret; + if (*data < 128) { + ret = *data++; + } else if (*data < 192) { + uint32_t uret = ((data[0] & 0x3F) << 8) + data[1]; + /* Sign extend 18 MSBs */ + uret |= (uret >> 13) ? 0xFFFFC000 : 0; + ret = (int32_t)uret; + data += 2; + } else if (*data == LB_INTEGER) { + uint32_t ui = ((uint32_t)(data[1]) << 24) | + ((uint32_t)(data[2]) << 16) | + ((uint32_t)(data[3]) << 8) | + (uint32_t)(data[4]); + ret = (int32_t)ui; + data += 5; + } else { + janet_assert(0, "bad int"); + ret = 0; + } + *atdata = data; + return ret; +} + +/* Lazy initialization of function defs from ROM */ +void janet_def_lazy_init(JanetFuncDef *def) { + if (def->flags & JANET_FUNCDEF_FLAG_LAZY_BYTECODE) { + def->flags &= ~((uint32_t)JANET_FUNCDEF_FLAG_LAZY_BYTECODE); + uint8_t *data = (uint8_t *) def->bytecode; + int32_t len = def->bytecode_length; + def->bytecode = array_allocate(sizeof(uint32_t), len); + for (int32_t i = 0; i < len; i++) { + int s = sizeof(uint32_t); + /* Handle endianess */ + def->bytecode[i] = + (uint32_t)(data[s * i]) | + ((uint32_t)(data[s * i + 1]) << 8) | + ((uint32_t)(data[s * i + 2]) << 16) | + ((uint32_t)(data[s * i + 3]) << 24); + } + } + if (def->flags & JANET_FUNCDEF_FLAG_LAZY_SOURCEMAP) { + def->flags &= ~((uint32_t)JANET_FUNCDEF_FLAG_LAZY_SOURCEMAP); + def->flags |= JANET_FUNCDEF_FLAG_HASSOURCEMAP; + const uint8_t *data = (const uint8_t *) def->sourcemap; + int32_t len = def->bytecode_length; + def->sourcemap = array_allocate(sizeof(JanetSourceMapping), len); + int32_t current = 0; + for (int32_t i = 0; i < len; i++) { + current += unsafe_readint(&data); + def->sourcemap[i].line = current; + def->sourcemap[i].column = unsafe_readint(&data); + } + } +} + /* Marshal a function def */ static void marshal_one_def(MarshalState *st, JanetFuncDef *def, int flags) { MARSH_STACKCHECK; @@ -269,6 +327,7 @@ static void marshal_one_def(MarshalState *st, JanetFuncDef *def, int flags) { /* Add to lookup */ janet_v_push(st->seen_defs, def); + janet_def_lazy_init(def); pushint(st, def->flags); pushint(st, def->slotcount); pushint(st, def->arity); @@ -1001,7 +1060,13 @@ static const uint8_t *unmarshal_one_def( if (!def->bytecode) { JANET_OUT_OF_MEMORY; } - data = janet_unmarshal_u32s(st, data, def->bytecode, bytecode_length); + if (flags & JANET_MARSHAL_LAZY_BYTECODE) { + def->flags |= JANET_FUNCDEF_FLAG_LAZY_BYTECODE; + def->bytecode = (uint32_t *) data; + data += bytecode_length * sizeof(uint32_t); + } else { + data = janet_unmarshal_u32s(st, data, def->bytecode, bytecode_length); + } def->bytecode_length = bytecode_length; /* Unmarshal environments */ @@ -1034,15 +1099,22 @@ static const uint8_t *unmarshal_one_def( /* Unmarshal source maps if needed */ if (def->flags & JANET_FUNCDEF_FLAG_HASSOURCEMAP) { - int32_t current = 0; - def->sourcemap = array_allocate(sizeof(JanetSourceMapping), bytecode_length); - if (!def->sourcemap) { - JANET_OUT_OF_MEMORY; - } - for (int32_t i = 0; i < bytecode_length; i++) { - current += readint(st, &data); - def->sourcemap[i].line = current; - def->sourcemap[i].column = readint(st, &data); + if (flags & JANET_MARSHAL_LAZY_BYTECODE) { + def->flags &= ~((uint32_t)JANET_FUNCDEF_FLAG_HASSOURCEMAP); + def->flags |= JANET_FUNCDEF_FLAG_LAZY_SOURCEMAP; + def->sourcemap = (JanetSourceMapping *) data; + for (int32_t i = 0; i < bytecode_length; i++) { + readint(st, &data); + readint(st, &data); + } + } else { + int32_t current = 0; + def->sourcemap = array_allocate(sizeof(JanetSourceMapping), bytecode_length); + for (int32_t i = 0; i < bytecode_length; i++) { + current += readint(st, &data); + def->sourcemap[i].line = current; + def->sourcemap[i].column = readint(st, &data); + } } } else { def->sourcemap = NULL; @@ -1484,7 +1556,7 @@ static const uint8_t *unmarshal_one( /* Tuple */ Janet *tup = janet_tuple_begin(len); int32_t flag = readint(st, &data); - janet_tuple_flag(tup) |= (int32_t) (((uint32_t) flag) << 16); /* Avoid left shift of negative value */ + janet_tuple_flag(tup) |= (int32_t)(((uint32_t) flag) << 16); /* Avoid left shift of negative value */ for (int32_t i = 0; i < len; i++) { data = unmarshal_one(st, data, tup + i, flags + 1); } diff --git a/src/core/specials.c b/src/core/specials.c index 1f154d8b..102ba5dc 100644 --- a/src/core/specials.c +++ b/src/core/specials.c @@ -734,10 +734,10 @@ static JanetSlot janetc_if(JanetFopts opts, int32_t argn, const Janet *argv) { labeld = janet_v_count(c->buffer); if (labeljr < labeld) { check_16bit_jump(c, labeljr, labelr); - c->buffer[labeljr] |= (uint32_t) (labelr - labeljr) << 16; + c->buffer[labeljr] |= (uint32_t)(labelr - labeljr) << 16; if (!tail && labeljd < labeld) { check_24bit_jump(c, labeljd, labeld); - c->buffer[labeljd] |= (uint32_t) (labeld - labeljd) << 8; + c->buffer[labeljd] |= (uint32_t)(labeld - labeljd) << 8; } } @@ -991,7 +991,7 @@ static JanetSlot janetc_while(JanetFopts opts, int32_t argn, const Janet *argv) for (int32_t i = labelwt; i < labeld; i++) { if (c->buffer[i] == (0x80 | JOP_JUMP)) { check_24bit_jump(c, i, labeld); - c->buffer[i] = JOP_JUMP | ((uint32_t) (labeld - i) << 8); + c->buffer[i] = JOP_JUMP | ((uint32_t)(labeld - i) << 8); } } diff --git a/src/core/strtod.c b/src/core/strtod.c index 20ae54ac..42a844ed 100644 --- a/src/core/strtod.c +++ b/src/core/strtod.c @@ -53,7 +53,7 @@ /* Lookup table for getting values of characters when parsing numbers. Handles * digits 0-9 and a-z (and A-Z). A-Z have values of 10 to 35. */ -static uint8_t digit_lookup[128] = { +static const uint8_t digit_lookup[128] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, diff --git a/src/core/util.c b/src/core/util.c index 1156081e..c678703d 100644 --- a/src/core/util.c +++ b/src/core/util.c @@ -584,7 +584,7 @@ static void janet_check_pointer_align(void *p) { uintptr_t u; } un; un.p = p; - janet_assert(!(un.u & (uintptr_t) ((1 << JANET_NANBOX_64_POINTER_SHIFT) - 1)), + janet_assert(!(un.u & (uintptr_t)((1 << JANET_NANBOX_64_POINTER_SHIFT) - 1)), "unaligned pointer wrap - cfunction pointers and abstract types must be aligned with this nanboxing configuration."); #endif } diff --git a/src/core/util.h b/src/core/util.h index 101fc27d..3af395d2 100644 --- a/src/core/util.h +++ b/src/core/util.h @@ -99,6 +99,8 @@ void janet_def_addflags(JanetFuncDef *def); void janet_buffer_dtostr(JanetBuffer *buffer, double x); +void janet_def_lazy_init(JanetFuncDef *def); + const char *janet_strerror(int e); const void *janet_strbinsearch( diff --git a/src/core/vm.c b/src/core/vm.c index 0cfa7ee9..b9e542e0 100644 --- a/src/core/vm.c +++ b/src/core/vm.c @@ -1049,6 +1049,7 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) { } if (janet_checktype(callee, JANET_FUNCTION)) { func = janet_unwrap_function(callee); + janet_def_lazy_init(func->def); if (func->gc.flags & JANET_FUNCFLAG_TRACE) { vm_do_trace(func, fiber->stacktop - fiber->stackstart, fiber->data + fiber->stackstart); } @@ -1089,6 +1090,7 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) { } if (janet_checktype(callee, JANET_FUNCTION)) { func = janet_unwrap_function(callee); + janet_def_lazy_init(func->def); if (func->gc.flags & JANET_FUNCFLAG_TRACE) { vm_do_trace(func, fiber->stacktop - fiber->stackstart, fiber->data + fiber->stackstart); } diff --git a/src/include/janet.h b/src/include/janet.h index 5ba50e9a..dc6fb20c 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -1105,6 +1105,8 @@ struct JanetAbstractHead { #define JANET_FUNCDEF_FLAG_STRUCTARG 0x1000000 #define JANET_FUNCDEF_FLAG_HASCLOBITSET 0x2000000 #define JANET_FUNCDEF_FLAG_NAMEDARGS 0x4000000 +#define JANET_FUNCDEF_FLAG_LAZY_BYTECODE 0x8000000 +#define JANET_FUNCDEF_FLAG_LAZY_SOURCEMAP 0x10000000 #define JANET_FUNCDEF_FLAG_TAG 0xFFFF /* Source mapping structure for a bytecode instruction */ @@ -1909,6 +1911,7 @@ JANET_API JanetModule janet_native(const char *name, JanetString *error); /* Marshaling */ #define JANET_MARSHAL_UNSAFE 0x20000 #define JANET_MARSHAL_NO_CYCLES 0x40000 +#define JANET_MARSHAL_LAZY_BYTECODE 0x80000 JANET_API void janet_marshal( JanetBuffer *buf,