Download forth/Forth/Compile.lean from Snapkitty/ai-free: direct link, hf CLI and curl.
- Browser
- Download file 6.61 kB
-
https://huggingface.co/Snapkitty/ai-free/resolve/main/forth/Forth/Compile.lean
- Command line
-
hf download hf://Snapkitty/ai-free/forth/Forth/Compile.lean
-
curl -L -o Compile.lean https://huggingface.co/Snapkitty/ai-free/resolve/main/forth/Forth/Compile.lean
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 | |