ai-free / forth /Forth /Compile.lean
SNAPKITTYWEST's picture
October 2026 main drop: mirror from GitHub
f5deffb verified
Raw History Blame Contribute Delete
6.61 kB
import Forth.Semantics
import WordIR
/-!
# Forth.Compile
Lowering of Forth to Universal Word IR. Forth's data stack *is* the IR data stack, so the
stack words map one-to-one; only comparisons need more than one instruction, because Forth
true is `-1` while `CMP` yields `1`.
Colon definitions become IR subroutines. Program layout:
```
0: main block
size main: HALT
wordAddr … 0: body of word 0, RET
wordAddr … 1: body of word 1, RET
…
```
A call of word `i` lowers to `CALL (wordAddr … i)`; `EXIT` lowers to `RET`, the same
instruction that ends a word body, so a word returns identically whether it falls off its end
or exits early. `MOD` lowers to `OVER OVER SDIV MUL SUB` and `0=` to `0 =`.
Variables need no code: they are literals (their cell addresses). Code sizes do not depend on the call
targets (`compile_size`), so the addresses are prefix sums of `Block.size`.
The return-data stack is the IR's auxiliary stack: `>R`, `R>`, `R@` and `I` lower to `TOR`,
`FROMR`, `RFETCH`, `RFETCH`, and `J` to `FROMR FROMR RFETCH SWAP TOR SWAP TOR`.
`DO body LOOP` lowers to
```
L: SWAP TOR TOR ( limit index -- ) R: ( -- limit index )
body
FROMR 1 ADD FROMR SWAP OVER OVER SUB ( -- index+1 limit limit-(index+1) )
BRANCH L nonzero: go around again (re-entering at L)
DROP DROP
```
(`Frag.repeatLoop`). The test leaves `limit - (index + 1)`, which is nonzero exactly when the
loop must go around again, so no comparison flag is needed (a `CMP` flag would be `1`, which is
`0` when `n = 0`). Going around again re-enters at `L` with the parameters `index+1 limit` back
on the data stack, which is the `Run.doAgain` rule.
-/
namespace WordDialect
namespace Forth
open IR
def compileOp {n : Nat} : Op β†’ List (Instr n)
| .lit z => [.word (BitVec.ofInt n z)]
| .dup => [.dup]
| .drop => [.drop]
| .swap => [.swap]
| .over => [.over]
| .rot => [.rot]
| .add => [.add]
| .sub => [.sub]
| .mul => [.mul]
| .div => [.sdiv]
| .mod => [.over, .over, .sdiv, .mul, .sub]
| .and => [.and]
| .or => [.or]
| .xor => [.xor]
| .invert => [.not]
| .lshift => [.shl]
| .rshift => [.shr]
| .eq => cmpFlagCode .eq
| .ne => cmpFlagCode .ne
| .lt => cmpFlagCode .slt
| .gt => cmpFlagCode .sgt
| .ult => cmpFlagCode .ult
| .ugt => cmpFlagCode .ugt
| .zeq => .word 0#n :: cmpFlagCode .eq
| .fetch => [.load]
| .store => [.store]
| .tor => [.tor]
| .fromr => [.fromr]
| .rfetch => [.rfetch]
| .loopI => [.rfetch]
| .loopJ => [.fromr, .fromr, .rfetch, .swap, .tor, .swap, .tor]
/-- Number of IR instructions an operation lowers to. -/
def Op.len : Op β†’ Nat
| .eq | .ne | .lt | .gt | .ult | .ugt => 4
| .mod | .zeq => 5
| .loopJ => 7
| _ => 1
theorem compileOp_length {n : Nat} (o : Op) : (compileOp o : List (Instr n)).length = o.len := by
cases o <;> rfl
/-- `DO`: move `limit start` from the data stack to the auxiliary stack (index on top). -/
def loopEnter {n : Nat} : List (Instr n) := [.swap, .tor, .tor]
/-- After the body: increment the index and leave `index+1 limit (limit - (index+1))`. -/
def loopTest {n : Nat} : List (Instr n) :=
[.fromr, .word 1#n, .add, .fromr, .swap, .over, .over, .sub]
/-- After the loop: drop `index+1 limit`. -/
def loopLeave {n : Nat} : List (Instr n) := [.drop, .drop]
/-- Number of IR instructions a block lowers to (independent of call targets). -/
def Block.size : Block β†’ Nat
| .nil => 0
| .op o rest => o.len + rest.size
| .ite t e rest => (e.size + t.size + 2) + rest.size
| .untilL body rest => (body.size + 2) + rest.size
| .call _ rest => 1 + rest.size
| .exit rest => 1 + rest.size
| .doLoop body rest => ((3 + (body.size + 8)) + 1) + (2 + rest.size)
/-- Lower a block; `addr i` is the code address of word `i`. -/
def compile {n : Nat} (addr : Nat β†’ Nat) : Block β†’ Frag n
| .nil => Frag.empty
| .op o rest => (Frag.ofCode (compileOp o)).seq (compile addr rest)
| .ite t e rest => (Frag.ite (compile addr t) (compile addr e)).seq (compile addr rest)
| .untilL body rest => (Frag.untilLoop (compile addr body)).seq (compile addr rest)
| .call i rest => (Frag.ofCode [.call (addr i)]).seq (compile addr rest)
| .exit rest => (Frag.ofCode [.ret]).seq (compile addr rest)
| .doLoop body rest =>
(Frag.repeatLoop ((Frag.ofCode loopEnter).seq ((compile addr body).seq (Frag.ofCode loopTest)))).seq
((Frag.ofCode loopLeave).seq (compile addr rest))
theorem compile_size {n : Nat} (addr : Nat β†’ Nat) (b : Block) :
(compile addr b : Frag n).size = b.size := by
induction b with
| nil => rfl
| op o rest ih =>
simp only [compile, Frag.seq_size, Frag.ofCode_size, compileOp_length, ih, Block.size]
| ite t e rest iht ihe ihr =>
simp only [compile, Frag.seq_size, Frag.ite_size, iht, ihe, ihr, Block.size]
| untilL body rest ihb ihr =>
simp only [compile, Frag.seq_size, Frag.until_size, ihb, ihr, Block.size]
| call i rest ih =>
simp only [compile, Frag.seq_size, Frag.ofCode_size, ih, Block.size, List.length_singleton]
| exit rest ih =>
simp only [compile, Frag.seq_size, Frag.ofCode_size, ih, Block.size, List.length_singleton]
| doLoop body rest ihb ihr =>
simp only [compile, Frag.seq_size, Frag.repeat_size, Frag.ofCode_size, ihb, ihr, Block.size]
rfl
/-- Address of word `i` when the dictionary `defs` is laid out from `base`, each body followed
by `RET`. For `i β‰₯ defs.length` this is the address just past the last body. -/
def wordAddr : Nat β†’ List Block β†’ Nat β†’ Nat
| base, [], _ => base
| base, _ :: _, 0 => base
| base, b :: bs, i + 1 => wordAddr (base + b.size + 1) bs i
/-- The dictionary's code, laid out from `base`: each body followed by `RET`. -/
def emitDefs {n : Nat} (addr : Nat β†’ Nat) : Nat β†’ List Block β†’ List (Instr n)
| _, [] => []
| base, b :: bs => (compile addr b : Frag n).emit base ++ [.ret] ++ emitDefs addr (base + b.size + 1) bs
/-- Code addresses of the words of program `P`. -/
def Program.addr (P : Program) : Nat β†’ Nat := wordAddr (P.main.size + 1) P.defs
/-- A complete program: the main block at address 0, then `HALT`, then the definitions. -/
def Program.compile {n : Nat} (P : Program) : Prog n :=
(Forth.compile P.addr P.main : Frag n).emit 0 ++ [.halt] ++ emitDefs P.addr (P.main.size + 1) P.defs
/-- A program with no definitions: the block at address 0, followed by `HALT`. -/
def compileProgram {n : Nat} (b : Block) : Prog n := Program.compile { defs := [], main := b }
end Forth
end WordDialect