From c78293f4f8ca9af97103851488ec13f8c4e74809 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Tue, 8 Sep 2026 18:35:32 -0500 Subject: [PATCH] WIP for adding function optimization. This is very broken, please fix and ammend. Change git history if you need to. --- src/boot/boot.janet | 5 +- src/core/bytecode.c | 114 ++++++++++++++++++++++++++++++++++++++------ src/core/fiber.c | 35 +++++++------- src/core/ovm.c | 110 ++++++++++++++++++++++++++++++++---------- src/core/util.c | 3 +- src/core/vm.c | 2 +- 6 files changed, 210 insertions(+), 59 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 5b82942b..38e8d3cb 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -41,7 +41,10 @@ (array/push modifiers (string buf ")\n\n" docstr)) (if (dyn :debug) (array/push modifiers {:source-form (dyn :macro-form)})) # Build return value - ~(def ,name ,;modifiers (fn ,name ,;(tuple/slice more start))))) + ~(def ,name ,;modifiers (do (def __x__ (,ovm/optimize (fn ,name ,;(tuple/slice more start)))) + #(eprint "collecting...") + (gccollect) + __x__)))) (defn defmacro :macro :flycheck "Define a macro." diff --git a/src/core/bytecode.c b/src/core/bytecode.c index c3dce7ef..0883e670 100644 --- a/src/core/bytecode.c +++ b/src/core/bytecode.c @@ -555,42 +555,126 @@ JanetFuncDef *janet_funcdef_alloc(void) { return def; } +/* FuncDef duplication. Use with caution. */ + +static JanetFuncEnv *copy_env(JanetFuncEnv *env, JanetFuncDef *newdef) { + int32_t len = env->length; /* Maximum is 256 */ + size_t s = sizeof(Janet) * (size_t) len; + Janet *vmem = janet_malloc(s); + janet_vm.next_collection += (uint32_t) s; + if (NULL == vmem) { + JANET_OUT_OF_MEMORY; + } + Janet *values = env->offset <= 0 + ? env->as.values + : env->as.fiber->data + env->offset; + safe_memcpy(vmem, values, s); + /* Clear unneeded references in closure environment */ + if (env->offset <= 0) { + uint32_t *bitset = newdef->closure_bitset; + if (bitset) { + for (int32_t i = 0; i < len; i += 32) { + uint32_t mask = ~(bitset[i >> 5]); + int32_t maxj = i + 32 > len ? len : i + 32; + for (int32_t j = i; j < maxj; j++) { + if (mask & 1) vmem[j] = janet_wrap_nil(); + mask >>= 1; + } + } + } + } + JanetFuncEnv *newenv = janet_gcalloc(JANET_MEMORY_FUNCENV, sizeof(JanetFuncEnv)); + newenv->offset = 0; + newenv->length = len; + newenv->as.values = vmem; + return newenv; +} + /* Create a duplicate that can be freely modified as no internal state * has a reference to it. */ JanetFuncDef *janet_funcdef_duplicate(JanetFuncDef *original) { - JanetFuncDef *def = janet_gcalloc(JANET_MEMORY_FUNCDEF, sizeof(JanetFuncDef)); - *def = *original; - def->environments = array_duplicate(original->environments, sizeof(int32_t), def->environments_length); - def->constants = array_duplicate(original->constants, sizeof(Janet), def->constants_length); + JanetFuncDef *def = janet_funcdef_alloc(); + + /* Basic properties */ + def->slotcount = original->slotcount; + def->arity = original->arity; + def->min_arity = original->min_arity; + def->max_arity = original->max_arity; + def->named_args_count = original->named_args_count; + def->flags = original->flags & ~(JANET_FUNCDEF_FLAG_TAG); /* Remove tags as this is a new function */ + + /* Basic execution */ + def->bytecode_length = original->bytecode_length; def->bytecode = array_duplicate(original->bytecode, sizeof(uint32_t), def->bytecode_length); - size_t bitset_len = (def->slotcount + 31) / 32; - def->closure_bitset = array_duplicate(original->closure_bitset, sizeof(uint32_t), bitset_len); + def->constants_length = original->constants_length; + def->constants = array_duplicate(original->constants, sizeof(Janet), def->constants_length); + + /* Source mapping */ + def->source = original->source; + def->name = original->name; + def->symbolmap_length = original->symbolmap_length; def->symbolmap = array_duplicate(original->symbolmap, sizeof(JanetSymbolMap), def->symbolmap_length); - if (original->sourcemap) { - def->sourcemap = array_duplicate(original->sourcemap, sizeof(JanetSourceMapping), def->bytecode_length); + def->sourcemap = array_duplicate(original->sourcemap, sizeof(JanetSourceMapping), def->bytecode_length); + + /* Parent closures */ + def->environments_length = original->environments_length; + def->environments = array_duplicate(original->environments, sizeof(int32_t), def->environments_length); + + /* Child closures */ + if (original->closure_bitset) { + int32_t chunks = (def->slotcount + 31) >> 5; + def->closure_bitset = array_duplicate(original->closure_bitset, sizeof(uint32_t), chunks); } + def->defs_length = 0; + def->defs = NULL; + if (original->defs_length) { + int32_t len = original->defs_length; + def->defs = janet_malloc(sizeof(JanetFuncDef *) * len); + if (NULL == def->defs) { + JANET_OUT_OF_MEMORY; + } + for (int32_t i = 0; i < len; i++) { + def->defs[i] = NULL; + } + for (int32_t i = 0; i < len; i++) { + /* Can this recur into self? */ + def->defs[i] = janet_funcdef_duplicate(original->defs[i]); + def->defs_length++; + } + janet_assert(def->defs_length == len, "bad len"); + } + + /* Ensure flags are consistent */ + janet_def_addflags(def); return def; } /* Duplicate a function (and optionally it's def) for modifications */ JanetFunction *janet_func_duplicate(JanetFunction *func, int duplicate_def) { JanetFuncDef *def = func->def; + JanetFuncDef *newdef; if (duplicate_def) { - def = janet_funcdef_duplicate(def); + newdef = janet_funcdef_duplicate(def); + } else { + newdef = def; } - JanetFunction *newfunc = janet_gcalloc(JANET_MEMORY_FUNCTION, sizeof(JanetFunction) + def->environments_length * sizeof(JanetFuncEnv *)); - newfunc->def = def; - - for (int32_t i = 0; i < def->environments_length; i++) { - newfunc->envs[i] = func->envs[i]; + int32_t elen = def->environments_length; + JanetFunction *newfunc = janet_gcalloc(JANET_MEMORY_FUNCTION, sizeof(JanetFunction) + ((size_t) elen * sizeof(JanetFuncEnv *))); + newfunc->def = newdef; + for (int32_t i = 0; i < elen; i++) { + newfunc->envs[i] = NULL; + } + for (int32_t i = 0; i < elen; i++) { + janet_assert(func->envs[i], "NULL funcenv"); + newfunc->envs[i] = copy_env(func->envs[i], newdef); } return newfunc; } /* Create a simple closure from a funcdef */ JanetFunction *janet_thunk(JanetFuncDef *def) { + janet_assert(def->environments_length == 0, "tried to create thunk that needs upvalues"); JanetFunction *func = janet_gcalloc(JANET_MEMORY_FUNCTION, sizeof(JanetFunction)); func->def = def; - janet_assert(def->environments_length == 0, "tried to create thunk that needs upvalues"); return func; } diff --git a/src/core/fiber.c b/src/core/fiber.c index fd0578b6..67d333c8 100644 --- a/src/core/fiber.c +++ b/src/core/fiber.c @@ -185,17 +185,18 @@ static Janet make_struct_n(const Janet *args, int32_t n) { /* Push a stack frame to a fiber */ int janet_fiber_funcframe(JanetFiber *fiber, JanetFunction *func) { JanetStackFrame *newframe; + JanetFuncDef *def = func->def; int32_t i; int32_t oldtop = fiber->stacktop; int32_t oldframe = fiber->frame; int32_t nextframe = fiber->stackstart; - int32_t nextstacktop = nextframe + func->def->slotcount + JANET_FRAME_SIZE; + int32_t nextstacktop = nextframe + def->slotcount + JANET_FRAME_SIZE; int32_t next_arity = fiber->stacktop - fiber->stackstart; /* Check strict arity before messing with state */ - if (next_arity < func->def->min_arity) return 1; - if (next_arity > func->def->max_arity) return 1; + if (next_arity < def->min_arity) return 1; + if (next_arity > def->max_arity) return 1; if (fiber->capacity < nextstacktop) { janet_fiber_setcapacity(fiber, 2 * nextstacktop); @@ -215,16 +216,16 @@ int janet_fiber_funcframe(JanetFiber *fiber, JanetFunction *func) { fiber->stacktop = fiber->stackstart = nextstacktop; newframe = janet_fiber_frame(fiber); newframe->prevframe = oldframe; - newframe->pc = func->def->bytecode; + newframe->pc = def->bytecode; newframe->func = func; newframe->env = NULL; newframe->flags = 0; /* Check varargs */ - if (func->def->flags & JANET_FUNCDEF_FLAG_VARARG) { - int32_t tuplehead = fiber->frame + func->def->arity; + if (def->flags & JANET_FUNCDEF_FLAG_VARARG) { + int32_t tuplehead = fiber->frame + def->arity; janet_assert(tuplehead > 0, "fiber stack overflow"); - int st = func->def->flags & JANET_FUNCDEF_FLAG_STRUCTARG; + int st = def->flags & JANET_FUNCDEF_FLAG_STRUCTARG; if (tuplehead >= oldtop) { fiber->data[tuplehead] = st ? make_struct_n(NULL, 0) @@ -259,10 +260,11 @@ static void janet_env_detach(JanetFuncEnv *env) { } Janet *values = env->as.fiber->data + env->offset; safe_memcpy(vmem, values, s); - uint32_t *bitset = janet_stack_frame(values)->func->def->closure_bitset; + JanetFuncDef *def = janet_stack_frame(values)->func->def; + uint32_t *bitset = def->closure_bitset; if (bitset) { /* Clear unneeded references in closure environment */ - for (int32_t i = 0; i < len; i += 32) { + for (int32_t i = 0; i < len && i < def->slotcount; i += 32) { uint32_t mask = ~(bitset[i >> 5]); int32_t maxj = i + 32 > len ? len : i + 32; for (int32_t j = i; j < maxj; j++) { @@ -325,14 +327,15 @@ void janet_env_maybe_detach(JanetFuncEnv *env) { /* Create a tail frame for a function */ int janet_fiber_funcframe_tail(JanetFiber *fiber, JanetFunction *func) { int32_t i; - int32_t nextframetop = fiber->frame + func->def->slotcount; + JanetFuncDef *def = func->def; + int32_t nextframetop = fiber->frame + def->slotcount; int32_t nextstacktop = nextframetop + JANET_FRAME_SIZE; int32_t next_arity = fiber->stacktop - fiber->stackstart; int32_t stacksize; /* Check strict arity before messing with state */ - if (next_arity < func->def->min_arity) return 1; - if (next_arity > func->def->max_arity) return 1; + if (next_arity < def->min_arity) return 1; + if (next_arity > def->max_arity) return 1; if (fiber->capacity < nextstacktop) { janet_fiber_setcapacity(fiber, 2 * nextstacktop); @@ -348,9 +351,9 @@ int janet_fiber_funcframe_tail(JanetFiber *fiber, JanetFunction *func) { janet_fiber_frame(fiber)->env = NULL; /* Check varargs */ - if (func->def->flags & JANET_FUNCDEF_FLAG_VARARG) { - int32_t tuplehead = fiber->stackstart + func->def->arity; - int st = func->def->flags & JANET_FUNCDEF_FLAG_STRUCTARG; + if (def->flags & JANET_FUNCDEF_FLAG_VARARG) { + int32_t tuplehead = fiber->stackstart + def->arity; + int st = def->flags & JANET_FUNCDEF_FLAG_STRUCTARG; if (tuplehead >= fiber->stacktop) { if (tuplehead >= fiber->capacity) janet_fiber_setcapacity(fiber, 2 * (tuplehead + 1)); for (i = fiber->stacktop; i < tuplehead; ++i) fiber->data[i] = janet_wrap_nil(); @@ -382,7 +385,7 @@ int janet_fiber_funcframe_tail(JanetFiber *fiber, JanetFunction *func) { /* Set frame stuff */ janet_fiber_frame(fiber)->func = func; - janet_fiber_frame(fiber)->pc = func->def->bytecode; + janet_fiber_frame(fiber)->pc = def->bytecode; janet_fiber_frame(fiber)->flags |= JANET_STACKFRAME_TAILCALL; /* Good return */ diff --git a/src/core/ovm.c b/src/core/ovm.c index e9c1cf0e..055e7442 100644 --- a/src/core/ovm.c +++ b/src/core/ovm.c @@ -83,7 +83,7 @@ static void clear_bitset(uint32_t *bitset, size_t nbits) { /* TODO - use a representation that doesn't require touching GC and we can more easily cleanup. * Both the table and the strings will work fine not being on the heap and cleaned up immediately. */ -static JanetString save_typeflow_state(int32_t pc, uint16_t *types) { +static JanetString save_lattice_types_state(int32_t pc, uint16_t *types) { int32_t slotcount = janet_v_count(types); uint8_t *buf = janet_string_begin(4 + sizeof(uint16_t) * slotcount); ((int32_t *)buf)[0] = pc; @@ -91,7 +91,7 @@ static JanetString save_typeflow_state(int32_t pc, uint16_t *types) { return janet_string_end(buf); } -static void load_typeflow_state(JanetString saved_state, int32_t *pc, uint16_t *types) { +static void load_lattice_types_state(JanetString saved_state, int32_t *pc, uint16_t *types) { int32_t slot_count = (janet_string_length(saved_state) - 4) / 2; /* We know this work only in this case. If we have seen a state before such that we are loading it, types * has enough backing capacity already since we never decrease the backing store. */ @@ -113,7 +113,7 @@ typedef struct { uint16_t flags; } JanetTypeflowInstruction; -/* Flags for typeflow instruction */ +/* Flags for lattice_types instruction */ #define TYPEFLOW_UNUSED 0 #define TYPEFLOW_INPUT_A 1 #define TYPEFLOW_OUTPUT_A 2 @@ -169,7 +169,7 @@ typedef struct { * def->bytecode_length elements. The caller must free the returned pointer with janet_free. */ -JanetTypeflowInstruction *janet_bytecode_typeflow(JanetFuncDef *def, uint16_t *ret_types) { +JanetTypeflowInstruction *janet_bytecode_lattice_types(JanetFuncDef *def, uint16_t *ret_types) { JanetTable *states = janet_table(0); JanetString *state_stack = NULL; uint16_t *types = NULL; @@ -184,16 +184,16 @@ JanetTypeflowInstruction *janet_bytecode_typeflow(JanetFuncDef *def, uint16_t *r janet_v_push(types, JANET_TFLAG_NIL); } for (int32_t i = 0; i < def->max_arity && i < def->slotcount; i++) { - types[i] = (uint16_t) 0xFFFF;//JANET_TFLAG_NUMBER;//0xFFFF; + types[i] = (uint16_t) 0xFFFF; } - janet_v_push(state_stack, save_typeflow_state(0, types)); + janet_v_push(state_stack, save_lattice_types_state(0, types)); int32_t iterations = 0; /* debug counter */ /* While we have more states to visit, traverse them */ while (janet_v_count(state_stack)) { iterations += 1; JanetString state = janet_v_last(state_stack); janet_v_pop(state_stack); - load_typeflow_state(state, &pc, types); + load_lattice_types_state(state, &pc, types); /* Iterate over bytecode */ while (pc < def->bytecode_length) { /* Just in case prevent overrun */ uint32_t I = def->bytecode[pc]; @@ -358,7 +358,7 @@ JanetTypeflowInstruction *janet_bytecode_typeflow(JanetFuncDef *def, uint16_t *r case JOP_JUMP: if (Ids <= 0) { /* backwards jump */ - Janet state = janet_wrap_string(save_typeflow_state(pc + Ids, types)); + Janet state = janet_wrap_string(save_lattice_types_state(pc + Ids, types)); if (!janet_checktype(janet_table_get(states, state), JANET_STRING)) { janet_table_put(states, state, state); janet_v_push(state_stack, janet_unwrap_string(state)); @@ -388,7 +388,7 @@ JanetTypeflowInstruction *janet_bytecode_typeflow(JanetFuncDef *def, uint16_t *r uint16_t not_taken = invert ? true_t : false_t; if (taken != 0) { /* taken == 0 means we will never take the branch */ int32_t target = pc + Ies; - Janet state = janet_wrap_string(save_typeflow_state(target, types)); + Janet state = janet_wrap_string(save_lattice_types_state(target, types)); if (!janet_checktype(janet_table_get(states, state), JANET_STRING)) { janet_table_put(states, state, state); janet_v_push(state_stack, janet_unwrap_string(state)); @@ -818,7 +818,7 @@ static Janet debug_mask(uint16_t mask) { } /* Convert to Janet values for debugging */ -static Janet debug_typeflow_instruction(JanetTypeflowInstruction i) { +static Janet debug_lattice_types_instruction(JanetTypeflowInstruction i) { char buf[128] = { 0 }; Janet tbuf[20]; char *c = buf; @@ -906,7 +906,7 @@ static Janet debug_typeflow_instruction(JanetTypeflowInstruction i) { * A basic block has one entrance and one (normal) exit. The last instruction therefor will always * be a branch, jump, or return instruction, except in the degenerate case of an empty block. * - * Basic block with no successors where start != 0 is dead code. Instructions not in any + * Basic block with no predecessors where start != 0 is dead code. Instructions not in any * basic block are also dead. * * Conditional errors or resumable instructions (yield) are ignored and considered @@ -1183,6 +1183,7 @@ static uint32_t lvn_bytecode_loadconst(JanetFuncDef *def, Janet k, int32_t reg) } } +#if 0 static uint32_t lvn_bytecode_domove(uint32_t dest, uint32_t src) { janet_assert((dest < 256) || (src < 256), "target too large"); if (dest < 256) { @@ -1191,6 +1192,7 @@ static uint32_t lvn_bytecode_domove(uint32_t dest, uint32_t src) { return JOP_MOVE_FAR | ((src & 0xFF) << 8) | (dest << 16); } } +#endif /* Find needle with the lowest number */ static int32_t find_slot_for_constant(int32_t n, const NumberedValue *constants, Janet needle) { @@ -1247,22 +1249,21 @@ static void janet_ovm_value_numbering(JanetFuncDef *def, JanetBB bb) { case JOP_PUSH_2: case JOP_PUSH_3: { int32_t a = (Iop == JOP_PUSH) ? Id : Ia; - int32_t b = (Iop == JOP_PUSH) ? -1 : (Iop == JOP_PUSH_2) ? Ie : Ib; - int32_t c = (Iop == JOP_PUSH_3) ? Ic : -1; NumberedSlot arep = replacements[a]; if (arep.num >= 0) { def->bytecode[i] = def->bytecode[i] & 0xFFFF00FFU; def->bytecode[i] = def->bytecode[i] | (((uint32_t)arep.slot & 0xFF) << 8); } - if (b >= 0) { + if (Iop != JOP_PUSH) { + int32_t b = (Iop == JOP_PUSH_2) ? Ie : Ib; NumberedSlot brep = replacements[b]; if (brep.num >= 0) { def->bytecode[i] = def->bytecode[i] & 0xFF00FFFFU; def->bytecode[i] = def->bytecode[i] | (((uint32_t)brep.slot & 0xFF) << 16); } } - if (c >= 0) { - NumberedSlot crep = replacements[c]; + if (Iop == JOP_PUSH_3) { + NumberedSlot crep = replacements[Ic]; if (crep.num >= 0) { def->bytecode[i] = def->bytecode[i] & 0x00FFFFFFU; def->bytecode[i] = def->bytecode[i] | (((uint32_t)crep.slot) << 24); @@ -1328,13 +1329,16 @@ static void janet_ovm_value_numbering(JanetFuncDef *def, JanetBB bb) { * if slot is read from, mark bit to 0 * 3. If we encounter a function call, set all bits to 0 for now. We can probably use the closure_bitset * for more fine-grained tracking. + * + * As is, this can't do much since most primitives have side effects. We should combine with the lattice_types + * analysis to allow removal of instructions that don't have side effects. */ static void bb_remove_redundant_writes(JanetFuncDef *def, JanetBB bb) { if (bb.start >= bb.end) return; /* Degenerate block */ uint32_t *bitset = make_bitset(def->slotcount); /* Quick hack - if no successors, clean up writes even more - all final writes are redundant. */ if (bb.succ[0] == -1 && bb.succ[1] == -1) { - memset(bitset, 0xFF, (def->slotcount + 7) / 8); + //memset(bitset, 0xFF, (def->slotcount + 7) / 8); } for (int32_t i = bb.end - 1; i >= bb.start; i--) { int32_t ins[3]; @@ -1387,6 +1391,7 @@ static void bb_remove_redundant_writes(JanetFuncDef *def, JanetBB bb) { case JOP_JUMP_IF_NOT_NIL: case JOP_SET_UPVALUE: nins = 1; + pin = 1; /* Side effects */ ins[0] = Ia; break; /* Write A, Read B */ @@ -1505,6 +1510,7 @@ static void bb_remove_redundant_writes(JanetFuncDef *def, JanetBB bb) { case JOP_NOT_EQUALS: out = Ia; nins = 2; + pin = 1; /* side effects tracking can be made more accurate */ ins[0] = Ib; ins[1] = Ic; break; @@ -1550,7 +1556,7 @@ static void bb_remove_redundant_writes(JanetFuncDef *def, JanetBB bb) { * - remove noops (invalidate existing analysis) * - basic-block 2 * - stack-flow 2 - * - typeflow 1 + * - lattice_types 1 * - fine dead code elimination * - to bytecode * - strength reduce @@ -1559,7 +1565,7 @@ static void bb_remove_redundant_writes(JanetFuncDef *def, JanetBB bb) { * - ssa * - global value numbering * - dead code elimination 3 - * - typeflow 2 + * - lattice_types 2 * - strength reduce * - jump thread * - peephole @@ -1571,16 +1577,16 @@ static void bb_remove_redundant_writes(JanetFuncDef *def, JanetBB bb) { /* C Functions */ /* Test type flow analysis */ -JANET_CORE_FN(cfun_ovm_typeflow, - "(ovm/type-flow func)", +JANET_CORE_FN(cfun_ovm_lattice_types, + "(ovm/lattice-types func)", "Do some static analysis on a function.") { janet_fixarity(argc, 1); JanetFunction *func = janet_getfunction(argv, 0); uint16_t rettypes = 0; - JanetTypeflowInstruction *instrs = janet_bytecode_typeflow(func->def, &rettypes); + JanetTypeflowInstruction *instrs = janet_bytecode_lattice_types(func->def, &rettypes); JanetArray *ret = janet_array(func->def->bytecode_length + 1); for (int32_t i = 0; i < func->def->bytecode_length; i++) { - Janet x = debug_typeflow_instruction(instrs[i]); + Janet x = debug_lattice_types_instruction(instrs[i]); janet_array_push(ret, x); } janet_array_push(ret, debug_mask(rettypes)); @@ -1615,15 +1621,69 @@ JANET_CORE_FN(cfun_ovm_remove_redundant_writes, janet_v_free(bbs); janet_bytecode_remove_noops(func->def); janet_verify(func->def); - return argv[0]; + return janet_wrap_function(func); +} + +JANET_CORE_FN(cfun_ovm_optimize, + "(ovm/optimize func)", + "Improves bytecode for a function. Returns an improved function.") { + janet_fixarity(argc, 1); + JanetFunction *original = janet_getfunction(argv, 0); + + /* This creates some odd issues. For now, don't do this, although + * this should be able to work in the future. We want to ensure that + * we optimize function defs depth first. */ + if (original->def->environments_length > 0) { + return argv[0]; /* Don't optimize inner functions */ + } + + janet_eprintf("optimizing function %s: starting\n", original->def->name); + int32_t initial_length = original->def->bytecode_length; + JanetFunction *func = janet_func_duplicate(original, 1); + JanetBB *bbs = janet_basic_blocks(func->def); + for (int32_t i = 0; i < janet_v_count(bbs); i++) { + //janet_ovm_value_numbering(func->def, bbs[i]); // very wrong + } + for (int32_t i = 0; i < janet_v_count(bbs); i++) { + //bb_remove_redundant_writes(func->def, bbs[i]); // slightly wrong + } + janet_v_free(bbs); + janet_bytecode_remove_noops(func->def); + janet_verify(func->def); + int32_t delta = func->def->bytecode_length - initial_length; + janet_eprintf("optimizing function %s: finished. Remove %d instructions.\n", original->def->name, -delta); + if (delta == 0) { + /* Assume instrucitons didn't move, count changed instructions. + * If we have changes, dump the old and new IR */ + int32_t changed = 0; + for (int32_t i = 0; i < initial_length; i++) { + if (func->def->bytecode[i] != original->def->bytecode[i]) { + changed++; + } + } + if (changed) { + janet_eprintf("%d instructions changed\n", changed); + for (int32_t i = 0; i < initial_length; i++) { + if (func->def->bytecode[i] != original->def->bytecode[i]) { + janet_eprintf("\033[31m %j -> %j\033[0m\n", + janet_asm_decode_instruction(original->def->bytecode[i]), + janet_asm_decode_instruction(func->def->bytecode[i])); + } else { + janet_eprintf(" %Q\n", janet_asm_decode_instruction(func->def->bytecode[i])); + } + } + } + } + return janet_wrap_function(func); } /* Module entry point */ void janet_lib_ovm(JanetTable *env) { JanetRegExt cfuns[] = { - JANET_CORE_REG("ovm/type-flow", cfun_ovm_typeflow), + JANET_CORE_REG("ovm/lattice-types", cfun_ovm_lattice_types), JANET_CORE_REG("ovm/basic-blocks", cfun_ovm_basic_blocks), JANET_CORE_REG("ovm/remove-redundant-writes", cfun_ovm_remove_redundant_writes), + JANET_CORE_REG("ovm/optimize", cfun_ovm_optimize), JANET_REG_END }; janet_core_cfuns_ext(env, NULL, cfuns); diff --git a/src/core/util.c b/src/core/util.c index 671cfce3..5fa84b0a 100644 --- a/src/core/util.c +++ b/src/core/util.c @@ -1228,10 +1228,11 @@ void *array_allocate(size_t element_size, int32_t count) { void *array_duplicate(void *arr, size_t element_size, int32_t count) { janet_assert(element_size > 0, "bad element size"); + if (count <= 0) return NULL; if (arr == NULL) return NULL; void *ret = array_allocate(element_size, count); if (ret) { - memcpy(ret, arr, element_size * count); + safe_memcpy(ret, arr, element_size * count); } return ret; } diff --git a/src/core/vm.c b/src/core/vm.c index d0cf2769..0cfa7ee9 100644 --- a/src/core/vm.c +++ b/src/core/vm.c @@ -1,4 +1,4 @@ -/*u +/* * Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy