Integrate some more bytecode optimizations.

This commit is contained in:
Calvin Rose
2026-09-09 15:44:46 -05:00
parent c78293f4f8
commit 76028bb738
6 changed files with 400 additions and 266 deletions
+1 -4
View File
@@ -41,10 +41,7 @@
(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 (do (def __x__ (,ovm/optimize (fn ,name ,;(tuple/slice more start))))
#(eprint "collecting...")
(gccollect)
__x__))))
~(def ,name ,;modifiers (fn ,name ,;(tuple/slice more start)))))
(defn defmacro :macro :flycheck
"Define a macro."
+16 -3
View File
@@ -166,14 +166,21 @@ void janet_bytecode_remove_noops(JanetFuncDef *def) {
}
/* Rewrite symbolmap */
int32_t smout = 0;
for (int32_t i = 0; i < def->symbolmap_length; i++) {
JanetSymbolMap *sm = def->symbolmap + i;
int keep = 1;
/* Don't rewrite upvalue mappings */
if (sm->birth_pc < UINT32_MAX) {
sm->birth_pc = pc_map[sm->birth_pc];
sm->death_pc = pc_map[sm->death_pc];
/* entirely dead symbols can be removed from the symbol map. This can happen if a symbol is in dead code. */
if (sm->birth_pc >= sm->death_pc) keep = 0;
}
/* Now shift if needed */
if (keep) def->symbolmap[smout++] = *sm;
}
def->symbolmap_length = smout;
def->bytecode_length = new_bytecode_length;
def->bytecode = janet_realloc(def->bytecode, def->bytecode_length * sizeof(uint32_t));
@@ -488,7 +495,8 @@ int janet_verify(JanetFuncDef *def) {
* prevent running over the end of bytecode. However, valid functions with dead code will
* be rejected. */
{
uint32_t lastop = def->bytecode[def->bytecode_length - 1] & 0xFF;
uint32_t lasti = def->bytecode[def->bytecode_length - 1];
uint32_t lastop = lasti & 0xFF;
switch (lastop) {
default:
return 9;
@@ -498,6 +506,11 @@ int janet_verify(JanetFuncDef *def) {
case JOP_ERROR:
case JOP_TAILCALL:
break;
case JOP_SIGNAL: {
int32_t s = (int32_t) lasti >> 24;
if (s > 4) return 9; /* non-terminal signal */
}
break;
}
}
@@ -566,8 +579,8 @@ static JanetFuncEnv *copy_env(JanetFuncEnv *env, JanetFuncDef *newdef) {
JANET_OUT_OF_MEMORY;
}
Janet *values = env->offset <= 0
? env->as.values
: env->as.fiber->data + env->offset;
? env->as.values
: env->as.fiber->data + env->offset;
safe_memcpy(vmem, values, s);
/* Clear unneeded references in closure environment */
if (env->offset <= 0) {
+1 -4
View File
@@ -1130,10 +1130,6 @@ JanetFuncDef *janetc_pop_funcdef(JanetCompiler *c) {
/* Pop the scope */
janetc_popscope(c);
/* Do basic optimization */
janet_bytecode_movopt(def);
janet_bytecode_remove_noops(def);
return def;
}
@@ -1189,6 +1185,7 @@ JanetCompileResult janet_compile_lint(Janet source,
JanetFuncDef *def = janetc_pop_funcdef(&c);
def->name = janet_cstring("thunk");
janet_def_addflags(def);
janet_bytecode_ovm_optimize(def);
c.result.funcdef = def;
} else {
c.result.error_mapping = c.current_mapping;
+1
View File
@@ -290,5 +290,6 @@ Shadowing janetc_shadowcheck(JanetCompiler *c, const uint8_t *sym);
/* Bytecode optimization */
void janet_bytecode_movopt(JanetFuncDef *def);
void janet_bytecode_remove_noops(JanetFuncDef *def);
void janet_bytecode_ovm_optimize(JanetFuncDef *def);
#endif
+379 -255
View File
@@ -60,6 +60,18 @@ static uint32_t bs_mask(int32_t index) {
static uint32_t bs_indx(int32_t index) {
return index >> 5;
}
static void bs_set_bit(uint32_t *bitset, int32_t index) {
bitset[bs_indx(index)] |= bs_mask(index);
}
static void bs_clear_bit(uint32_t *bitset, int32_t index) {
bitset[bs_indx(index)] &= ~(bs_mask(index));
}
static void bs_toggle_bit(uint32_t *bitset, int32_t index) {
bitset[bs_indx(index)] ^= bs_mask(index);
}
static int bs_read_bit(uint32_t *bitset, int32_t index) {
return (bitset[bs_indx(index)] & bs_mask(index)) ? 1 : 0;
}
static uint32_t *make_bitset(size_t nbits) {
size_t bitset_len = (nbits + 31) / 32;
if (bitset_len == 0) return NULL;
@@ -77,6 +89,243 @@ static void clear_bitset(uint32_t *bitset, size_t nbits) {
}
}
/* Basic Block extraction
*
* From arbitrary bytecode, extract basic-blocks for CFG analysis.
*
* Each basic block includes a start (inclusive) index and end (exclusive)
* index, Up to two successor blocks, and up to two predecessor blocks. In the
* case where a block has more than these limits, generate empty blocks (start
* == end) with multiple predecessors and one successor to funnel the flow into
* the final successor block that contains instructions. Use -1 in the pred and
* succ arrays to represent no predecessor or successor.
*
* 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 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
* as normal operators with side-effects in most cases. Some unconditional errors
* are treated as block terminators, while others may be missed by the analysis.
*
* Used for subsequent optimization, coarse-grained DCE, local value numbering,
* lowering to SSA, etc.
*/
typedef struct {
int32_t start;
int32_t end;
int32_t pred[2];
int32_t succ[2];
} JanetBB;
/* Find a successor given a branch target */
static int32_t janet_find_bb_with_entry(JanetBB *bbs, int32_t n, int32_t entry_idx) {
for (int32_t i = 0; i < n; i++) {
if (bbs[i].start == entry_idx) {
return i;
}
}
janet_assert(0, "did not find block");
}
/* Add a successor, handling case when the successor already has two predecessors.
* If it does, add a new degenerate successor to allow another logical predecessor. */
static JanetBB *janet_bb_add_succ(JanetBB *bbs, int32_t index, int32_t succ) {
/* Check if succ already has two preds */
if (bbs[succ].pred[0] >= 0 && bbs[succ].pred[1] >= 0) {
/* Degenerate case: add a new empty basic block */
int32_t old_succpred = bbs[succ].pred[1];
int32_t new_index = janet_v_count(bbs);
JanetBB empty;
empty.start = empty.end = -1;
empty.pred[0] = old_succpred;
empty.pred[1] = -1;
empty.succ[0] = succ;
empty.succ[1] = -1;
/* Rewrite old */
if (bbs[old_succpred].succ[0] == succ) {
bbs[old_succpred].succ[0] = new_index;
} else {
bbs[old_succpred].succ[1] = new_index;
}
janet_v_push(bbs, empty);
succ = new_index;
}
/* We know succ has at most 1 existing pred at this point */
if (bbs[index].succ[0] == -1) {
bbs[index].succ[0] = succ;
} else if (bbs[index].succ[1] == -1) {
bbs[index].succ[1] = succ;
} else {
janet_assert(0, "index has too many successors");
}
if (bbs[succ].pred[0] == -1) {
bbs[succ].pred[0] = index;
} else if (bbs[succ].pred[1] == -1) {
bbs[succ].pred[1] = index;
} else {
janet_assert(0, "succ has too many predecessors");
}
return bbs; /* May have been reallocated */
}
JanetBB *janet_basic_blocks(JanetFuncDef *def) {
uint32_t *bytecode = def->bytecode;
int32_t blen = def->bytecode_length;
/* Mark branch target locs with bitset (leaders) Add a fake leader at the end. */
uint32_t *bitset = make_bitset(blen + 1);
bitset[0] = 1; /* Mark first bit as instruction 0 is a leader */
int did_jump = 0;
for (int32_t i = 0; i <= blen; i++) {
int32_t target = -1;
if (did_jump) bitset[bs_indx(i)] |= bs_mask(i);
if (i == blen) break;
switch (bytecode[i] & 0x7F) {
default:
did_jump = 0;
continue;
case JOP_RETURN:
case JOP_RETURN_NIL:
case JOP_ERROR:
case JOP_TAILCALL:
did_jump = 1;
continue;
case JOP_JUMP:
target = i + (((int32_t)(bytecode[i])) >> 8);
break;
case JOP_JUMP_IF:
case JOP_JUMP_IF_NOT:
case JOP_JUMP_IF_NIL:
case JOP_JUMP_IF_NOT_NIL:
target = i + (((int32_t)(bytecode[i])) >> 16);
break;
}
bitset[bs_indx(target)] |= bs_mask(target);
did_jump = 1;
}
/* Initially allocate basic blocks */
JanetBB *bbs = NULL;
int32_t start = 0;
for (int32_t i = 0; i <= blen; i++) {
int is_leader = (bitset[bs_indx(i)] & bs_mask(i));
if (!is_leader) continue;
if (i > 0) {
/* End current BB */
JanetBB bb;
bb.start = start;
bb.end = i;
bb.pred[0] = bb.pred[1] = bb.succ[0] = bb.succ[1] = -1;
janet_v_push(bbs, bb);
}
start = i;
}
/* Set successors and predecessors */
int32_t origcount = janet_v_count(bbs);
for (int32_t i = 0; i < origcount; i++) {
janet_assert(bbs[i].start < bbs[i].end, "no degenerate basic blocks");
int32_t endi = bbs[i].end;
uint32_t lasti = bytecode[endi - 1];
int32_t target = -1;
switch (lasti & 0x7F) {
default:
/* 1 successor is next instruction */
bbs = janet_bb_add_succ(bbs, i, janet_find_bb_with_entry(bbs, origcount, endi));
break;
case JOP_JUMP:
/* 1 successor */
target = endi - 1 + (((int32_t)(lasti)) >> 8);
bbs = janet_bb_add_succ(bbs, i, janet_find_bb_with_entry(bbs, origcount, target));
break;
case JOP_JUMP_IF:
case JOP_JUMP_IF_NOT:
case JOP_JUMP_IF_NIL:
case JOP_JUMP_IF_NOT_NIL:
/* 2 successors */
target = endi - 1 + (((int32_t)(lasti)) >> 16);
if (endi == target) {
/* Degenerate branch */
bbs = janet_bb_add_succ(bbs, i, janet_find_bb_with_entry(bbs, origcount, target));
} else {
bbs = janet_bb_add_succ(bbs, i, janet_find_bb_with_entry(bbs, origcount, endi));
bbs = janet_bb_add_succ(bbs, i, janet_find_bb_with_entry(bbs, origcount, target));
}
break;
case JOP_RETURN:
case JOP_RETURN_NIL:
case JOP_ERROR:
case JOP_TAILCALL:
/* No successors */
break;
}
}
janet_free(bitset);
return bbs;
}
/* Jump threading */
static void janet_jump_threading(JanetFuncDef *def) {
int limit = 100;
int32_t blen = def->bytecode_length;
uint32_t *code = def->bytecode;
int32_t *codes = (int32_t *)code;
while (limit--) {
int recur = 0;
for (int32_t i = 0; i < blen; i++) {
if ((code[i] & 0x7F) != JOP_JUMP) continue;
if (code[i] == JOP_JUMP) continue; /* 0-jump, infinite loop */
int32_t target = i + (codes[i] >> 8);
if (target == i + 1) {
code[i] = JOP_RETURN_NIL;
recur = 1;
continue;
}
while ((code[target] & 0x7F) == JOP_NOOP) { /* Skip noops */
target++;
}
if ((code[target] & 0x7F) == JOP_RETURN_NIL) {
code[i] = JOP_RETURN_NIL;
recur = 1;
} else if ((code[target] & 0x7F) == JOP_JUMP) {
code[i] = JOP_JUMP | (uint32_t)((target - i) << 8);
recur = 1;
}
}
if (!recur) break;
}
}
/* Create a debug structure for basic blocks */
static Janet debug_basic_blocks(JanetBB *bbs) {
Janet startkw = janet_ckeywordv("start");
Janet endkw = janet_ckeywordv("end");
Janet preds = janet_ckeywordv("preds");
Janet succs = janet_ckeywordv("succs");
JanetArray *array = janet_array(janet_v_count(bbs));
for (int32_t i = 0; i < janet_v_count(bbs); i++) {
JanetBB *bb = bbs + i;
JanetKV *st = janet_struct_begin(4);
janet_struct_put(st, startkw, janet_wrap_number(bb->start));
janet_struct_put(st, endkw, janet_wrap_number(bb->end));
JanetArray *succarray = janet_array(2);
JanetArray *predarray = janet_array(2);
if (bb->succ[0] != -1) janet_array_push(succarray, janet_wrap_number(bb->succ[0]));
if (bb->succ[1] != -1) janet_array_push(succarray, janet_wrap_number(bb->succ[1]));
if (bb->pred[0] != -1) janet_array_push(predarray, janet_wrap_number(bb->pred[0]));
if (bb->pred[1] != -1) janet_array_push(predarray, janet_wrap_number(bb->pred[1]));
janet_struct_put(st, preds, janet_wrap_array(predarray));
janet_struct_put(st, succs, janet_wrap_array(succarray));
janet_array_push(array, janet_wrap_struct(janet_struct_end(st)));
}
return janet_wrap_array(array);
}
/* Type-checking and static analysis */
/* Allow easily saving states in hashtable by packing them to a Janet string */
@@ -892,210 +1141,36 @@ static Janet debug_lattice_types_instruction(JanetTypeflowInstruction i) {
return janet_wrap_tuple(janet_tuple_end(tup));
}
/* Basic Block extraction
*
* From arbitrary bytecode, extract basic-blocks for CFG analysis.
*
* Each basic block includes a start (inclusive) index and end (exclusive)
* index, Up to two successor blocks, and up to two predecessor blocks. In the
* case where a block has more than these limits, generate empty blocks (start
* == end) with multiple predecessors and one successor to funnel the flow into
* the final successor block that contains instructions. Use -1 in the pred and
* succ arrays to represent no predecessor or successor.
*
* 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 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
* as normal operators with side-effects in most cases. Some unconditional errors
* are treated as block terminators, while others may be missed by the analysis.
*
* Used for subsequent optimization, coarse-grained DCE, local value numbering,
* lowering to SSA, etc.
*/
typedef struct {
int32_t start;
int32_t end;
int32_t pred[2];
int32_t succ[2];
} JanetBB;
/* Dead code routines */
/* Find a successor given a branch target */
static int32_t janet_find_bb_with_entry(JanetBB *bbs, int32_t n, int32_t entry_idx) {
for (int32_t i = 0; i < n; i++) {
if (bbs[i].start == entry_idx) {
return i;
/* Remove dead code not in any basic blocks */
static void bb_dead_to_noop(JanetFuncDef *def, JanetBB *bbs) {
uint32_t *bits = make_bitset(def->bytecode_length);
JanetBB *end = bbs + janet_v_count(bbs);
for (JanetBB *bb = bbs; bb < end; bb++) {
if (bb->start < 0) continue;
/* Orphan check */
if ((bb->start != 0) && bb->pred[0] == -1 && bb->pred[1] == -1) continue;
for (int32_t i = bb->start; i < bb->end; i++) {
bs_set_bit(bits, i);
}
}
janet_assert(0, "did not find block");
for (int32_t i = 0; i < def->bytecode_length; i++) {
if (!bs_read_bit(bits, i)) {
def->bytecode[i] = JOP_NOOP;
}
}
janet_free(bits);
}
/* Add a successor, handling case when the successor already has two predecessors.
* If it does, add a new degenerate successor to allow another logical predecessor. */
static JanetBB *janet_bb_add_succ(JanetBB *bbs, int32_t index, int32_t succ) {
/* Check if succ already has two preds */
if (bbs[succ].pred[0] >= 0 && bbs[succ].pred[1] >= 0) {
/* Degenerate case: add a new empty basic block */
int32_t old_succpred = bbs[succ].pred[1];
int32_t new_index = janet_v_count(bbs);
JanetBB empty;
empty.start = empty.end = -1;
empty.pred[0] = old_succpred;
empty.pred[1] = -1;
empty.succ[0] = succ;
empty.succ[1] = -1;
/* Rewrite old */
if (bbs[old_succpred].succ[0] == succ) {
bbs[old_succpred].succ[0] = new_index;
} else {
bbs[old_succpred].succ[1] = new_index;
}
janet_v_push(bbs, empty);
succ = new_index;
}
/* We know succ has at most 1 existing pred at this point */
if (bbs[index].succ[0] == -1) {
bbs[index].succ[0] = succ;
} else if (bbs[index].succ[1] == -1) {
bbs[index].succ[1] = succ;
} else {
janet_assert(0, "index has too many successors");
}
if (bbs[succ].pred[0] == -1) {
bbs[succ].pred[0] = index;
} else if (bbs[succ].pred[1] == -1) {
bbs[succ].pred[1] = index;
} else {
janet_assert(0, "succ has too many predecessors");
}
return bbs; /* May have been reallocated */
}
JanetBB *janet_basic_blocks(JanetFuncDef *def) {
uint32_t *bytecode = def->bytecode;
int32_t blen = def->bytecode_length;
/* Mark branch target locs with bitset (leaders) Add a fake leader at the end. */
uint32_t *bitset = make_bitset(blen + 1);
bitset[0] = 1; /* Mark first bit as instruction 0 is a leader */
int did_jump = 0;
for (int32_t i = 0; i <= blen; i++) {
int32_t target = -1;
if (did_jump) bitset[bs_indx(i)] |= bs_mask(i);
if (i == blen) break;
switch (bytecode[i] & 0x7F) {
default:
did_jump = 0;
continue;
case JOP_RETURN:
case JOP_RETURN_NIL:
case JOP_ERROR:
case JOP_TAILCALL:
did_jump = 1;
continue;
case JOP_JUMP:
target = i + (((int32_t)(bytecode[i])) >> 8);
break;
case JOP_JUMP_IF:
case JOP_JUMP_IF_NOT:
case JOP_JUMP_IF_NIL:
case JOP_JUMP_IF_NOT_NIL:
target = i + (((int32_t)(bytecode[i])) >> 16);
break;
}
bitset[bs_indx(target)] |= bs_mask(target);
did_jump = 1;
}
/* Initially allocate basic blocks */
JanetBB *bbs = NULL;
int32_t start = 0;
for (int32_t i = 0; i <= blen; i++) {
int is_leader = (bitset[bs_indx(i)] & bs_mask(i));
if (!is_leader) continue;
if (i > 0) {
/* End current BB */
JanetBB bb;
bb.start = start;
bb.end = i;
bb.pred[0] = bb.pred[1] = bb.succ[0] = bb.succ[1] = -1;
janet_v_push(bbs, bb);
}
start = i;
}
/* Set successors and predecessors */
int32_t origcount = janet_v_count(bbs);
for (int32_t i = 0; i < origcount; i++) {
janet_assert(bbs[i].start < bbs[i].end, "no degenerate basic blocks");
int32_t endi = bbs[i].end;
uint32_t lasti = bytecode[endi - 1];
int32_t target = -1;
switch (lasti & 0x7F) {
default:
/* 1 successor is next instruction */
bbs = janet_bb_add_succ(bbs, i, janet_find_bb_with_entry(bbs, origcount, endi));
break;
case JOP_JUMP:
/* 1 successor */
target = endi - 1 + (((int32_t)(lasti)) >> 8);
bbs = janet_bb_add_succ(bbs, i, janet_find_bb_with_entry(bbs, origcount, target));
break;
case JOP_JUMP_IF:
case JOP_JUMP_IF_NOT:
case JOP_JUMP_IF_NIL:
case JOP_JUMP_IF_NOT_NIL:
/* 2 successors */
target = endi - 1 + (((int32_t)(lasti)) >> 16);
if (endi == target) {
/* Degenerate branch */
bbs = janet_bb_add_succ(bbs, i, janet_find_bb_with_entry(bbs, origcount, target));
} else {
bbs = janet_bb_add_succ(bbs, i, janet_find_bb_with_entry(bbs, origcount, endi));
bbs = janet_bb_add_succ(bbs, i, janet_find_bb_with_entry(bbs, origcount, target));
}
break;
case JOP_RETURN:
case JOP_RETURN_NIL:
case JOP_ERROR:
case JOP_TAILCALL:
/* No successors */
break;
/* Remove dead code from lattice analysis */
static void lattice_dead_to_noop(JanetFuncDef *def, JanetTypeflowInstruction *infos) {
for (int32_t i = 0; i < def->bytecode_length; i++) {
if (!(infos[i].flags & TYPEFLOW_LIVE_CODE)) {
def->bytecode[i] = JOP_NOOP;
}
}
janet_free(bitset);
/* TODO - janet_v_flatten */
return bbs;
}
/* Create a debug structure for basic blocks */
static Janet debug_basic_blocks(JanetBB *bbs) {
Janet startkw = janet_ckeywordv("start");
Janet endkw = janet_ckeywordv("end");
Janet preds = janet_ckeywordv("preds");
Janet succs = janet_ckeywordv("succs");
JanetArray *array = janet_array(janet_v_count(bbs));
for (int32_t i = 0; i < janet_v_count(bbs); i++) {
JanetBB *bb = bbs + i;
JanetKV *st = janet_struct_begin(4);
janet_struct_put(st, startkw, janet_wrap_number(bb->start));
janet_struct_put(st, endkw, janet_wrap_number(bb->end));
JanetArray *succarray = janet_array(2);
JanetArray *predarray = janet_array(2);
if (bb->succ[0] != -1) janet_array_push(succarray, janet_wrap_number(bb->succ[0]));
if (bb->succ[1] != -1) janet_array_push(succarray, janet_wrap_number(bb->succ[1]));
if (bb->pred[0] != -1) janet_array_push(predarray, janet_wrap_number(bb->pred[0]));
if (bb->pred[1] != -1) janet_array_push(predarray, janet_wrap_number(bb->pred[1]));
janet_struct_put(st, preds, janet_wrap_array(predarray));
janet_struct_put(st, succs, janet_wrap_array(succarray));
janet_array_push(array, janet_wrap_struct(janet_struct_end(st)));
}
return janet_wrap_array(array);
}
/* Local value numbering
@@ -1336,9 +1411,9 @@ static void janet_ovm_value_numbering(JanetFuncDef *def, JanetBB bb) {
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 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];
@@ -1365,6 +1440,9 @@ static void bb_remove_redundant_writes(JanetFuncDef *def, JanetBB bb) {
case JOP_NOOP:
case JOP_RETURN_NIL:
continue;
/* Side effects, don't remove */
/* Write A, Read E */
case JOP_CALL:
/* Similar logic applies to tail calls, but they are always at the end of blocks anyway */
@@ -1372,7 +1450,6 @@ static void bb_remove_redundant_writes(JanetFuncDef *def, JanetBB bb) {
pin = 1;
nins = 1;
ins[0] = Ie;
clear_bitset(bitset, def->slotcount);
break;
/* Write A, Read B */
case JOP_SIGNAL:
@@ -1380,7 +1457,6 @@ static void bb_remove_redundant_writes(JanetFuncDef *def, JanetBB bb) {
nins = 1;
ins[0] = Ib;
pin = 1;
clear_bitset(bitset, def->slotcount);
break;
/* Read A */
case JOP_ERROR:
@@ -1391,48 +1467,26 @@ 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 */
pin = 1;
ins[0] = Ia;
break;
/* Write A, Read B */
case JOP_ADD_IMMEDIATE:
case JOP_SUBTRACT_IMMEDIATE:
case JOP_MULTIPLY_IMMEDIATE:
case JOP_DIVIDE_IMMEDIATE:
case JOP_SHIFT_LEFT_IMMEDIATE:
case JOP_SHIFT_RIGHT_IMMEDIATE:
case JOP_SHIFT_RIGHT_UNSIGNED_IMMEDIATE:
case JOP_GREATER_THAN_IMMEDIATE:
case JOP_LESS_THAN_IMMEDIATE:
case JOP_EQUALS_IMMEDIATE:
case JOP_NOT_EQUALS_IMMEDIATE:
case JOP_GET_INDEX:
out = Ia;
nins = 1;
ins[0] = Ib;
break;
/* Read D */
case JOP_RETURN:
case JOP_PUSH:
case JOP_PUSH_ARRAY:
case JOP_TAILCALL:
pin = 1;
nins = 1;
ins[0] = Id;
break;
case JOP_PUT:
case JOP_PUSH_3:
nins = 3;
pin = 1;
ins[0] = Ia;
ins[1] = Ib;
ins[2] = Ic;
break;
/* Write D */
case JOP_LOAD_NIL:
case JOP_LOAD_TRUE:
case JOP_LOAD_FALSE:
case JOP_LOAD_SELF:
out = Id;
break;
/* Write A */
case JOP_MAKE_ARRAY:
case JOP_MAKE_BUFFER:
@@ -1450,30 +1504,19 @@ static void bb_remove_redundant_writes(JanetFuncDef *def, JanetBB bb) {
case JOP_CLOSURE:
out = Ia;
break;
case JOP_MOVE_FAR:
out = Ie;
nins = 1;
ins[0] = Ia;
break;
/* Write A, Read E */
case JOP_MOVE_NEAR:
case JOP_LENGTH:
case JOP_BNOT:
out = Ia;
nins = 1;
ins[0] = Ie;
break;
/* Read A, B */
case JOP_PUT_INDEX:
nins = 2;
ins[0] = Ia;
ins[1] = Ib;
pin = 1;
break;
/* Read A, E */
case JOP_PUSH_2:
nins = 2;
ins[0] = Ia;
ins[1] = Ie;
pin = 1;
break;
/* A = B op C */
case JOP_PROPAGATE:
@@ -1482,9 +1525,43 @@ static void bb_remove_redundant_writes(JanetFuncDef *def, JanetBB bb) {
nins = 2;
ins[0] = Ib;
ins[1] = Ic;
clear_bitset(bitset, def->slotcount);
pin = 1;
break;
case JOP_CANCEL:
case JOP_NEXT:
pin = 1;
out = Ia;
nins = 2;
ins[0] = Ib;
ins[1] = Ic;
break;
/* No side effects below, can remove.
* Some of these instructions may have side effects if
* the inputs are abstracts. */
/* Loads that trite D */
case JOP_LOAD_NIL:
case JOP_LOAD_TRUE:
case JOP_LOAD_FALSE:
case JOP_LOAD_SELF:
out = Id;
break;
/* Moves and similar */
case JOP_MOVE_FAR:
out = Ie;
nins = 1;
ins[0] = Ia;
break;
case JOP_MOVE_NEAR:
case JOP_LENGTH:
case JOP_BNOT:
out = Ia;
nins = 1;
ins[0] = Ie;
break;
/* A = B op C */
case JOP_BAND:
case JOP_BOR:
@@ -1510,33 +1587,39 @@ 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;
case JOP_CANCEL:
case JOP_NEXT:
pin = 1;
/* A = op B imm */
case JOP_ADD_IMMEDIATE:
case JOP_SUBTRACT_IMMEDIATE:
case JOP_MULTIPLY_IMMEDIATE:
case JOP_DIVIDE_IMMEDIATE:
case JOP_SHIFT_LEFT_IMMEDIATE:
case JOP_SHIFT_RIGHT_IMMEDIATE:
case JOP_SHIFT_RIGHT_UNSIGNED_IMMEDIATE:
case JOP_GREATER_THAN_IMMEDIATE:
case JOP_LESS_THAN_IMMEDIATE:
case JOP_EQUALS_IMMEDIATE:
case JOP_NOT_EQUALS_IMMEDIATE:
case JOP_GET_INDEX:
out = Ia;
nins = 2;
nins = 1;
ins[0] = Ib;
ins[1] = Ic;
break;
}
/* Test and set output bit in bitmap. If already set, change to noop. */
/* Add check to avoid messing with upvalues */
if (out != -1) {
/*fprintf(stdout, "out = %d\n", out);*/
uint32_t mask = bs_mask(out);
uint32_t index = bs_indx(out);
if (!pin && (bitset[index] & mask)) {
/*fprintf(stdout, "clearing\n");*/
if (!pin && bs_read_bit(bitset, out) &&
(!def->closure_bitset || !bs_read_bit(def->closure_bitset, out))) {
def->bytecode[i] = JOP_NOOP;
}
bitset[index] |= mask;
bs_set_bit(bitset, out);
}
/* Clear input slots from bitmap */
for (int j = 0; j < nins; j++) {
/*fprintf(stdout, "input[%d] = %d\n", j, ins[j]);*/
uint32_t mask = bs_mask(ins[j]);
uint32_t index = bs_indx(ins[j]);
bitset[index] &= ~mask;
@@ -1624,6 +1707,36 @@ JANET_CORE_FN(cfun_ovm_remove_redundant_writes,
return janet_wrap_function(func);
}
static int32_t total_removed = 0;
void janet_bytecode_ovm_optimize(JanetFuncDef *def) {
//janet_eprintf("optimizing function %s: starting\n", def->name);
int32_t initial_length = def->bytecode_length;
//original_copy = array_duplicate(def->bytecode, sizeof(uint32_t), def->bytecode_length);
janet_bytecode_movopt(def);
janet_jump_threading(def);
{
JanetBB *bbs = janet_basic_blocks(def);
for (int32_t i = 0; i < janet_v_count(bbs); i++) {
//janet_ovm_value_numbering(def, bbs[i]);
}
for (int32_t i = 0; i < janet_v_count(bbs); i++) {
bb_remove_redundant_writes(def, bbs[i]); // slightly wrong
}
bb_dead_to_noop(def, bbs);
janet_v_free(bbs);
}
janet_jump_threading(def);
janet_bytecode_movopt(def);
janet_bytecode_remove_noops(def);
//janet_verify(def);
int32_t final_length = def->bytecode_length;
const char *name = (const char *) def->name;
total_removed += initial_length - final_length;
/*janet_eprintf("Optimizing %-30s %04d-%03d (total instructions removed %d)\n", name ? name : "<anon>",*/
/*initial_length, initial_length - final_length, total_removed);*/
}
JANET_CORE_FN(cfun_ovm_optimize,
"(ovm/optimize func)",
"Improves bytecode for a function. Returns an improved function.") {
@@ -1640,7 +1753,18 @@ JANET_CORE_FN(cfun_ovm_optimize,
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);
janet_jump_threading(func->def);
/* Lattice types */
uint16_t rettypes = 0;
//JanetTypeflowInstruction *instrs = janet_bytecode_lattice_types(func->def, &rettypes);
//lattice_dead_to_noop(func->def, instrs);
//janet_free(instrs);
/* Basic blocks analysis */
JanetBB *bbs = janet_basic_blocks(func->def);
bb_dead_to_noop(func->def, bbs);
for (int32_t i = 0; i < janet_v_count(bbs); i++) {
//janet_ovm_value_numbering(func->def, bbs[i]); // very wrong
}
@@ -1651,7 +1775,7 @@ JANET_CORE_FN(cfun_ovm_optimize,
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);
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 */
@@ -1666,8 +1790,8 @@ JANET_CORE_FN(cfun_ovm_optimize,
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]));
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]));
}
+2
View File
@@ -963,6 +963,7 @@ static JanetSlot janetc_while(JanetFopts opts, int32_t argn, const Janet *argv)
JanetFuncDef *def = janetc_pop_funcdef(c);
def->name = janet_cstring("while");
janet_def_addflags(def);
janet_bytecode_ovm_optimize(def);
int32_t defindex = janetc_addfuncdef(c, def);
/* And then load the closure and call it. */
int32_t cloreg = janetc_regalloc_temp(&c->scope->ra, JANETC_REGTEMP_0);
@@ -1208,6 +1209,7 @@ static JanetSlot janetc_fn(JanetFopts opts, int32_t argn, const Janet *argv) {
if (hasname) def->name = janet_unwrap_symbol(head); /* Also correctly unwraps keyword */
janet_def_addflags(def);
janet_bytecode_ovm_optimize(def);
defindex = janetc_addfuncdef(c, def);
/* Ensure enough slots for vararg function. */