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.
This commit is contained in:
Calvin Rose
2026-09-10 19:26:52 -05:00
parent e1963cdfe0
commit 5b67f9435c
12 changed files with 110 additions and 19 deletions
+3
View File
@@ -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;
+1 -1
View File
@@ -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);
+3
View File
@@ -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. */
+2
View File
@@ -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;
+6 -2
View File
@@ -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);
}
+83 -11
View File
@@ -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);
}
+3 -3
View File
@@ -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);
}
}
+1 -1
View File
@@ -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,
+1 -1
View File
@@ -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
}
+2
View File
@@ -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(
+2
View File
@@ -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);
}
+3
View File
@@ -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,