From d2560d538b634468fd35fc329065e97f0e7af5af Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Wed, 23 Sep 2026 19:54:25 -0500 Subject: [PATCH] Add bytecode slot compaction pass. Codegen may allocate and then never use slots. Free up those slots and rewrite bytecode to be smaller. The resulting bytecode will be slightly faster, result in better inlining, and be somewhat normalized for further optimizations. --- src/core/bytecode.c | 136 +++++++++++++++++++++++++++++++++++++++ src/core/marsh.c | 6 +- test/suite-asm.janet | 2 +- test/suite-unknown.janet | 10 +-- 4 files changed, 146 insertions(+), 8 deletions(-) diff --git a/src/core/bytecode.c b/src/core/bytecode.c index 52bd6c16..900338ae 100644 --- a/src/core/bytecode.c +++ b/src/core/bytecode.c @@ -1446,6 +1446,141 @@ void janet_bytecode_movopt_full(JanetFuncDef *def, BytecodeBB *blocks, int32_t n janet_free(bitsets); } +/* Rename slots to normalize code and reduce def->slotcount */ +void janet_bytecode_compact_slots(JanetFuncDef *def) { + /* TODO - we would need to be somewhat careful in that case. */ + if (def->slotcount > 256) return; + /* Remap: old slot -> new slot */ + /* Invert: new slot -> old slot */ + int32_t *remap = array_allocate(sizeof(int32_t), def->slotcount * 2); + int32_t *invert = remap + def->slotcount; + uint32_t *code = def->bytecode; + int32_t len = def->bytecode_length; + int32_t next_slot = def->arity; + int32_t greatest = 0; + for (int32_t i = 0; i < def->slotcount * 2; i++) remap[i] = -1; + /* Pin closure slots for now */ + if (def->closure_bitset) { + for (int32_t i = 0; i < def->slotcount; i++) { + if (bs_read_bit(def->closure_bitset, i)) { + remap[i] = i; + invert[i] = i; + greatest = i; + } + } + } + /* Pin arguments - TODO slots for unused arguments could be reused */ + for (int32_t i = 0; i < def->arity && i < def->slotcount; i++) { + remap[i] = i; + invert[i] = i; + if (i > greatest) greatest = i; + } + if (def->flags & (JANET_FUNCDEF_FLAG_VARARG | JANET_FUNCDEF_FLAG_NAMEDARGS | JANET_FUNCDEF_FLAG_STRUCTARG)) { + remap[def->arity] = def->arity; + invert[def->arity] = def->arity; + next_slot++; + } + /* Rename 8 bit slots first, then larger slots */ + for (int32_t pc = 0; pc < len; pc++) { + enum JanetInstructionType itype = janet_instructions[code[pc] & 0x7F]; + int nslots = 0; + uint32_t slots[3]; + /* rewrite slots. For now, assume adding slot delta will not overflow any slot indices */ + switch (itype) { + case JINT_0: + case JINT_L: + continue; + case JINT_S: + nslots = 1; + slots[0] = (code[pc] >> 8) & 0xFFFFFFU; + break; + case JINT_SES: + case JINT_SI: + case JINT_SU: + case JINT_ST: + case JINT_SL: + case JINT_SD: + case JINT_SC: + nslots = 1; + slots[0] = (code[pc] >> 8) & 0xFFU; + break; + case JINT_SS: + nslots = 2; + slots[0] = (code[pc] >> 8) & 0xFFU; + slots[1] = (code[pc] >> 16) & 0xFFFFU; + break; + case JINT_SSI: + case JINT_SSU: + nslots = 2; + slots[0] = (code[pc] >> 8) & 0xFFU; + slots[1] = (code[pc] >> 16) & 0xFFU; + break; + case JINT_SSS: + nslots = 3; + slots[0] = (code[pc] >> 8) & 0xFFU; + slots[1] = (code[pc] >> 16) & 0xFFU; + slots[2] = code[pc] >> 24; + break; + } + /* Find new slots */ + for (int i = 0; i < nslots; i++) { + if (remap[slots[i]] == -1) { + while (invert[next_slot] != -1) next_slot++; + remap[slots[i]] = next_slot; + invert[next_slot] = slots[i]; + next_slot++; + } + slots[i] = remap[slots[i]]; + } + /* Now rewrite */ + switch (itype) { + case JINT_0: + case JINT_L: + continue; + case JINT_S: + code[pc] = (code[pc] & 0xFFU) | (slots[0] << 8); + break; + case JINT_SES: + case JINT_SI: + case JINT_SU: + case JINT_ST: + case JINT_SL: + case JINT_SD: + case JINT_SC: + code[pc] = (code[pc] & 0xFFFF00FFU) | ((slots[0] & 0xFF) << 8); + break; + case JINT_SS: + code[pc] = (code[pc] & 0xFFU) | ((slots[0] & 0xFF) << 8) | (slots[1] << 16); + break; + case JINT_SSI: + case JINT_SSU: + code[pc] = (code[pc] & 0xFF0000FFU) | ((slots[0] & 0xFF) << 8) | ((slots[1] & 0xFF) << 16); + break; + case JINT_SSS: + code[pc] = (code[pc] & 0xFFU) | ((slots[0] & 0xFF) << 8) | ((slots[1] & 0xFF) << 16) | (slots[2] << 24); + break; + } + } + /* Fix symbol mappings */ + int32_t smout = 0; + for (int32_t i = 0; i < def->symbolmap_length; i++) { + if (def->symbolmap[i].birth_pc == UINT32_MAX) { + smout++; + continue; /* upvalue */ + } + JanetSymbolMap sm = def->symbolmap[i]; + uint32_t old_slot = sm.slot_index; + if (remap[old_slot] != -1) { /* If not remapped, drop! */ + sm.slot_index = (uint32_t) remap[old_slot]; + def->symbolmap[smout++] = sm; + } + } + def->symbolmap_length = smout; + if (greatest + 1 > next_slot) next_slot = greatest + 1; + def->slotcount = next_slot; + janet_free(remap); +} + /* * Inlining */ @@ -1625,6 +1760,7 @@ void janet_bytecode_optimize(JanetFuncDef *def, int32_t level) { } while (delta < 0); janet_bytecode_remove_unused_constants(def); janet_bytecode_remove_unused_closures(def); + janet_bytecode_compact_slots(def); } #ifdef JANET_BOOTSTRAP total_instruction_count += def->bytecode_length; diff --git a/src/core/marsh.c b/src/core/marsh.c index 028c644f..e20e2ebd 100644 --- a/src/core/marsh.c +++ b/src/core/marsh.c @@ -1059,8 +1059,10 @@ static const uint8_t *unmarshal_one_def( } /* Validate */ - if (janet_verify(def)) - janet_panic("funcdef has invalid bytecode"); + int status; + if ((status = janet_verify(def))) { + janet_panicf("funcdef has invalid bytecode: %d", status); + } /* Set def */ *out = def; diff --git a/test/suite-asm.janet b/test/suite-asm.janet index a5733207..1bc183a4 100644 --- a/test/suite-asm.janet +++ b/test/suite-asm.janet @@ -64,7 +64,7 @@ # Optimization in assembler # a bit brittle, but easy to fix (def optimized (disasm (asm ~{:bytecode [(ldi 0 10) (movn 1 0) (add 2 0 1) (add 3 1 1) (add 4 0 0) (add 5 2 3) (ret 5)] :slotcount 40} :o) :bytecode)) -(assert (deep= optimized '@[(ldi 5 40) (ret 5)]) "bytecode optimization 1") +(assert (deep= optimized '@[(ldi 0 40) (ret 0)]) "bytecode optimization 1") (end-suite) diff --git a/test/suite-unknown.janet b/test/suite-unknown.janet index 4dfe7c76..20c1004e 100644 --- a/test/suite-unknown.janet +++ b/test/suite-unknown.janet @@ -259,7 +259,7 @@ # 88813c4 (assert (deep= (in (disasm (defn a [] (def x 10) x)) :symbolmap) - @[[0 2 0 'a] [0 2 1 'x]]) + @[[0 2 0 'x]]) "symbolmap when *debug* is true") (defn a [arg] @@ -289,10 +289,10 @@ (def z 30) (+ x y z)))) :symbolmap) @[[0 6 0 'arg] - [0 6 1 'a] - [0 6 2 'x] - [1 6 3 'y] - [2 6 4 'z]]) + #[0 6 1 'a] + [0 6 1 'x] + [1 6 2 'y] + [2 6 3 'z]]) "arg & inner symbolmap") # @[(0 2 0 arg) (0 2 1 a) (0 2 2 x) (0 2 3 y) (0 2 4 z)]