mirror of
https://github.com/janet-lang/janet
synced 2026-10-12 18:51:42 +00:00
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:
+4
-1
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -1,4 +1,4 @@
|
||||
/*u
|
||||
/*
|
||||
* Copyright (c) 2026 Calvin Rose
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
|
||||
Reference in New Issue
Block a user