WIP for adding function optimization.

This is very broken, please fix and ammend. Change git history if you
need to.
This commit is contained in:
Calvin Rose
2026-09-09 13:13:47 -05:00
parent b81fc6ffc1
commit c78293f4f8
6 changed files with 210 additions and 59 deletions
+4 -1
View File
@@ -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."
+99 -15
View File
@@ -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;
}
+19 -16
View File
@@ -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 */
+85 -25
View File
@@ -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);
+2 -1
View File
@@ -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;
}
+1 -1
View File
@@ -1,4 +1,4 @@
/*u
/*
* Copyright (c) 2026 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy