Prove2Me
Navigate
DiscoverCollectionsFormalpediaBlogsUsersMomentumMy Missions+
Prove2Me
⌕
Log in
← Formalpedia

Harvey–van der Hoeven machine development, part 1: stack machine and multitape-TM compilation

Definition
IntMul_HvdH_M_Defs1

by xuanji · Oct 13, 2026 · Mathlib 0df444a (Lean v4.33.1)

harvey-van-der-hoeven-tminteger-multiplicationturing-machines

Definitions (Lean namespace NLogN) for a multitape-Turing-machine implementation of the Harvey–van der Hoeven O(nlog⁡n)O(n\log n)O(nlogn) integer multiplication algorithm, built on the platform model Def_IntMul_MultitapeModel and the published reference Def_IntMul_HvdH_MachineRef. Part 1 of 4: the symbol stack machine (Stmt, St, Runs), finite-state transducers on stacks, sign–magnitude integer items and their stack primitives (addInt, dupInt, shlR, splitR, catR, readR, outR, …) with explicit costs, flat code and its compilation machineOf to a platform IntMul.MultitapeTM.

Source modules (in order): NLogN.SM, NLogN.Logic, NLogN.Move, NLogN.Add, NLogN.Zip, NLogN.Item, NLogN.IntOps, NLogN.Arith, NLogN.IntOps2, NLogN.Take, NLogN.Shift, NLogN.Io, NLogN.Flat, NLogN.Basic, NLogN.Walk, NLogN.TM, NLogN.Top, NLogN.Prims, NLogN.Cat.

The file was generated by deleting from the development all theorems not needed by the definitions; it keeps all definitions plus the few lemmas the definitions themselves need (proof fields of structures, decreasing_by facts) and simple @[simp]/rfl lemmas.

Definition code
-- Harvey–van der Hoeven multitape-TM development (NLogN), generated by skeleton subtraction.

import Definitions.Def_IntMul_MultitapeModel
import Definitions.Def_IntMul_HvdH_ConvC
import Definitions.Def_IntMul_HvdH_ExpApprox
import Definitions.Def_IntMul_HvdH_MachineRef
import Definitions.Def_IntMul_HvdH_Numeric
import Definitions.Def_IntMul_HvdH_RealExp
import Definitions.Def_IntMul_HvdH_Twiddle
import Mathlib
import Mathlib.Tactic

set_option autoImplicit false
set_option relaxedAutoImplicit false
set_option maxHeartbeats 1000000

section
-- Source module: NLogN.SM
/-!
# A multi-stack machine language with procedures

`Stmt S P` is a structured program over `S` stacks of symbols (`Fin 16`) that may call any of
`P` procedures (recursively).  It reads the input word with `inRd` and writes output bits with
`out`.  `Exec` is its big-step semantics; the cost is the number of steps of the flat machine
the program compiles to (`NLogN.Flat`); each flat step is at most two Turing machine steps.
-/

namespace NLogN

/-- Stack symbols. -/
abbrev Sym := Fin 16

/-- Codes of input symbols. -/
def codeBit (b : Bool) : Sym := if b then 1 else 0
def codeSep : Sym := 2
def codeEnd : Sym := 3

/-- The input word `x # y` as codes. -/
def inputCodes (x y : List Bool) : List Sym := x.map codeBit ++ codeSep :: y.map codeBit

/-- Structured programs. Tests look at the top of one stack (`none` if it is empty). -/
inductive Stmt (S P : ℕ) where
  | skip
  | push (i : Fin S) (a : Sym)
  | pop (i : Fin S)
  | inRd (i : Fin S)
  | out (b : Bool)
  | seq (p q : Stmt S P)
  | ite (i : Fin S) (f : Option Sym → Bool) (p q : Stmt S P)
  | loop (i : Fin S) (f : Option Sym → Bool) (p : Stmt S P)
  | call (j : Fin P)

/-- Machine state: stacks (head of each list is the top), input position, output so far. -/
@[ext] structure St (S : ℕ) where
  stk : Fin S → List Sym
  ip : ℕ
  out : List Bool

namespace St

variable {S : ℕ}

def top (σ : St S) (i : Fin S) : Option Sym := (σ.stk i).head?

def push (σ : St S) (i : Fin S) (a : Sym) : St S :=
  { σ with stk := Function.update σ.stk i (a :: σ.stk i) }

def pop (σ : St S) (i : Fin S) : St S :=
  { σ with stk := Function.update σ.stk i (σ.stk i).tail }

/-- Read the next input code onto stack `i`. -/
def inRd (I : List Sym) (σ : St S) (i : Fin S) : St S :=
  { σ with stk := Function.update σ.stk i (I.getD σ.ip codeEnd :: σ.stk i), ip := σ.ip + 1 }

def outB (σ : St S) (b : Bool) : St S := { σ with out := σ.out ++ [b] }

end St

/-- Big-step semantics with exact cost. `I` is the input word, `prog` the procedures. -/
inductive Exec {S P : ℕ} (I : List Sym) (prog : Fin P → Stmt S P) :
    Stmt S P → St S → St S → ℕ → Prop
  | skip (σ) : Exec I prog .skip σ σ 1
  | push (σ i a) : Exec I prog (.push i a) σ (σ.push i a) 1
  | pop (σ i) : Exec I prog (.pop i) σ (σ.pop i) 1
  | inRd (σ i) : Exec I prog (.inRd i) σ (σ.inRd I i) 1
  | out (σ b) : Exec I prog (.out b) σ (σ.outB b) 1
  | seq {p q σ σ₁ σ₂ c₁ c₂} : Exec I prog p σ σ₁ c₁ → Exec I prog q σ₁ σ₂ c₂ →
      Exec I prog (.seq p q) σ σ₂ (c₁ + c₂)
  | iteT {i f p q σ σ' c} : f (σ.top i) = true → Exec I prog p σ σ' c →
      Exec I prog (.ite i f p q) σ σ' (c + 1)
  | iteF {i f p q σ σ' c} : f (σ.top i) = false → Exec I prog q σ σ' c →
      Exec I prog (.ite i f p q) σ σ' (c + 1)
  | loopT {i f p σ σ₁ σ₂ c₁ c₂} : f (σ.top i) = true → Exec I prog p σ σ₁ c₁ →
      Exec I prog (.loop i f p) σ₁ σ₂ c₂ → Exec I prog (.loop i f p) σ σ₂ (c₁ + c₂ + 1)
  | loopF {i f p σ} : f (σ.top i) = false → Exec I prog (.loop i f p) σ σ 1
  | call {j σ σ' c} : Exec I prog (prog j) σ σ' c → Exec I prog (.call j) σ σ' (c + 2)

namespace Exec

variable {S P : ℕ} {I : List Sym} {prog : Fin P → Stmt S P}

end Exec

end NLogN

end

section
-- Source module: NLogN.Logic
/-!
# Program logic for stack programs

`Runs I prog s σ σ' c`: `s` takes `σ` to `σ'` with cost at most `c`.  Besides the rules for each
statement this file provides

* `caseTops`: branch on the tops of several stacks (constant cost);
* `opsStmt`: straight-line lists of primitive operations;
* `machine`: a finite-state transducer whose state lives on a dedicated stack; its correctness
  reduces to iterating a plain Lean function `mstep`.
-/

namespace NLogN

variable {S P : ℕ} {I : List Sym} {prog : Fin P → Stmt S P}

def Runs (I : List Sym) (prog : Fin P → Stmt S P) (s : Stmt S P) (σ σ' : St S) (c : ℕ) : Prop :=
  ∃ c' ≤ c, Exec I prog s σ σ' c'

namespace Runs

end Runs

/-! ## Branching on stack tops -/

def caseList (i : Fin S) (k : Option Sym → Stmt S P) : List Sym → Stmt S P
  | [] => .skip
  | a :: l => .ite i (fun o => decide (o = some a)) (k (some a)) (caseList i k l)

def caseTop (i : Fin S) (k : Option Sym → Stmt S P) : Stmt S P :=
  .ite i (fun o => o.isNone) (k none) (caseList i k (List.finRange 16))

/-- Branch on the tops of the stacks `w` (in order). -/
def caseTops : List (Fin S) → (List (Option Sym) → Stmt S P) → Stmt S P
  | [], k => k []
  | i :: w, k => caseTop i (fun o => caseTops w (fun os => k (o :: os)))

def St.tops (σ : St S) (w : List (Fin S)) : List (Option Sym) := w.map σ.top

/-! ## Straight-line operations -/

inductive Op (S : ℕ) where
  | push (i : Fin S) (a : Sym)
  | pop (i : Fin S)
  | inRd (i : Fin S)
  | out (b : Bool)

def Op.stmt : Op S → Stmt S P
  | .push i a => .push i a
  | .pop i => .pop i
  | .inRd i => .inRd i
  | .out b => .out b

def Op.run (I : List Sym) : Op S → St S → St S
  | .push i a, σ => σ.push i a
  | .pop i, σ => σ.pop i
  | .inRd i, σ => σ.inRd I i
  | .out b, σ => σ.outB b

def opsStmt : List (Op S) → Stmt S P
  | [] => .skip
  | o :: l => .seq o.stmt (opsStmt l)

def runOps (I : List Sym) : List (Op S) → St S → St S
  | [], σ => σ
  | o :: l, σ => runOps I l (o.run I σ)

/-! ## Finite-state transducers -/

/-- The halting state of a `machine`. -/
def hlt : Sym := 15

/-- A transducer with state stack `q`, watching stacks `w`.  Each step looks at the state and at
the tops of `w` and performs `δ (state :: tops)`, which must replace the state symbol.  It starts
by pushing `q0` and stops when the state is `hlt`, which it then pops. -/
def machine (q : Fin S) (w : List (Fin S)) (q0 : Sym)
    (δ : List (Option Sym) → List (Op S)) : Stmt S P :=
  .seq (.push q q0) (.seq (.loop q (fun o => decide (o ≠ some hlt))
    (caseTops (q :: w) fun os => opsStmt (δ os))) (.pop q))

/-- One step of a transducer, as a function. -/
def mstep (I : List Sym) (q : Fin S) (w : List (Fin S)) (δ : List (Option Sym) → List (Op S))
    (σ : St S) : St S :=
  runOps I (δ (σ.tops (q :: w))) σ

end NLogN

namespace NLogN

variable {S P : ℕ} {I : List Sym} {prog : Fin P → Stmt S P}

end NLogN

namespace NLogN

variable {S P : ℕ} {I : List Sym} {prog : Fin P → Stmt S P}

/-- `m` transducer steps lead from `σ` to `σf`, never passing through the halting state. -/
def Reaches (I : List Sym) (q : Fin S) (w : List (Fin S)) (δ : List (Option Sym) → List (Op S))
    (σ σf : St S) (m : ℕ) : Prop :=
  (mstep I q w δ)^[m] σ = σf ∧ ∀ k < m, ((mstep I q w δ)^[k] σ).top q ≠ some hlt

namespace Reaches

variable {q : Fin S} {w : List (Fin S)} {δ : List (Option Sym) → List (Op S)}

end Reaches

end NLogN

end

section
-- Source module: NLogN.Move
/-!
# Basic transducers: moving stack contents
-/

namespace NLogN

variable {S P : ℕ} {I : List Sym} {prog : Fin P → Stmt S P}

/-- Replace stack `i`. -/
def St.set (σ : St S) (i : Fin S) (l : List Sym) : St S :=
  { σ with stk := Function.update σ.stk i l }

@[simp] theorem St.set_ip (σ : St S) (i : Fin S) (l : List Sym) : (σ.set i l).ip = σ.ip := rfl
@[simp] theorem St.set_out (σ : St S) (i : Fin S) (l : List Sym) : (σ.set i l).out = σ.out := rfl

@[simp] theorem St.push_ip (σ : St S) (i : Fin S) (a : Sym) : (σ.push i a).ip = σ.ip := rfl
@[simp] theorem St.push_out (σ : St S) (i : Fin S) (a : Sym) : (σ.push i a).out = σ.out := rfl
@[simp] theorem St.pop_ip (σ : St S) (i : Fin S) : (σ.pop i).ip = σ.ip := rfl
@[simp] theorem St.pop_out (σ : St S) (i : Fin S) : (σ.pop i).out = σ.out := rfl

@[simp] theorem St.inRd_ip (σ : St S) (i : Fin S) : (σ.inRd I i).ip = σ.ip + 1 := rfl
@[simp] theorem St.inRd_out (σ : St S) (i : Fin S) : (σ.inRd I i).out = σ.out := rfl
@[simp] theorem St.outB_stk (σ : St S) (b : Bool) : (σ.outB b).stk = σ.stk := rfl
@[simp] theorem St.outB_ip (σ : St S) (b : Bool) : (σ.outB b).ip = σ.ip := rfl
@[simp] theorem St.outB_out (σ : St S) (b : Bool) : (σ.outB b).out = σ.out ++ [b] := rfl

/-! ### Normalising chains of `set` -/

/-- Advance the input position. -/
def St.adv (σ : St S) : St S := { σ with ip := σ.ip + 1 }

theorem St.adv_set (σ : St S) (i : Fin S) (l : List Sym) : (σ.set i l).adv = σ.adv.set i l := rfl

theorem St.outB_set (σ : St S) (i : Fin S) (l : List Sym) (b : Bool) :
    (σ.set i l).outB b = (σ.outB b).set i l := rfl

/-! ## Moving symbols from one stack to another -/

/-- Transition of `move`: while `g` is defined on the top of `src`, pop it and push its image
on `dst`. -/
def moveδ (q src dst : Fin S) (g : Sym → Option Sym) : List (Option Sym) → List (Op S)
  | [_, some a] =>
    match g a with
    | some b => [.pop src, .push dst b]
    | none => [.pop q, .push q hlt]
  | _ => [.pop q, .push q hlt]

def move (q src dst : Fin S) (g : Sym → Option Sym) : Stmt S P :=
  machine q [src] 0 (moveδ q src dst g)

/-- Cost of `move` on `m` symbols. -/
def moveCost (m : ℕ) : ℕ := (m + 1) * 38 + 3

end NLogN

end

section
-- Source module: NLogN.Add
/-!
# Binary words on stacks; addition

A word `A : List Bool` (least significant bit first) sits on a stack as `A.map codeBit`, so its
least significant bit is on top.  A word ends where the stack stops showing bits.
-/

namespace NLogN

variable {S P : ℕ} {I : List Sym} {prog : Fin P → Stmt S P}

def bitOf : Option Sym → Option Bool
  | some 0 => some false
  | some 1 => some true
  | _ => none

@[simp] theorem bitOf_zero : bitOf (some 0) = some false := rfl
@[simp] theorem bitOf_one : bitOf (some 1) = some true := rfl

/-- Value of a little-endian word. -/
def wval : List Bool → ℕ
  | [] => 0
  | b :: l => b.toNat + 2 * wval l

section
variable {q a b d e : Fin S} (hqa : q ≠ a) (hqb : q ≠ b) (hqd : q ≠ d) (hqe : q ≠ e)
  (hab : a ≠ b) (had : a ≠ d) (hae : a ≠ e) (hbd : b ≠ d) (hbe : b ≠ e) (hde : d ≠ e)
  (ra rb Q : List Sym) (hra : bitOf ra.head? = none) (hrb : bitOf rb.head? = none)

end

end NLogN

end

section
-- Source module: NLogN.Zip
/-!
# A generic two-input bit transducer with carry

`zip q a b d e keep f fin` runs least-significant-bit first over the words on top of `a` and `b`
(a missing bit reads as `0`), with a one-bit carry `c`.  At each position it pushes the output
bit of `f x y c` on `d` and continues with the new carry; the bits of `b` are copied onto `e`
when `keep`.  When both words are exhausted it pushes the symbols `fin c` on `d`.
Addition, subtraction, negation and copying are instances.
-/

namespace NLogN

variable {S P : ℕ} {I : List Sym} {prog : Fin P → Stmt S P}

/-- Output bits and final carry. -/
def zipL (f : Bool → Bool → Bool → Bool × Bool) : Bool → List Bool → List Bool → List Bool × Bool
  | c, [], [] => ([], c)
  | c, x :: A, [] => let r := zipL f (f x false c).2 A []; ((f x false c).1 :: r.1, r.2)
  | c, [], y :: B => let r := zipL f (f false y c).2 [] B; ((f false y c).1 :: r.1, r.2)
  | c, x :: A, y :: B => let r := zipL f (f x y c).2 A B; ((f x y c).1 :: r.1, r.2)

def zipδ (q a b d e : Fin S) (keep : Bool) (f : Bool → Bool → Bool → Bool × Bool)
    (fin : Bool → List Sym) : List (Option Sym) → List (Op S)
  | [some c, ta, tb] =>
    if bitOf ta = none ∧ bitOf tb = none then
      (fin (decide (c = 1))).map (Op.push d) ++ [.pop q, .push q hlt]
    else
      let x := (bitOf ta).getD false
      let y := (bitOf tb).getD false
      let r := f x y (decide (c = 1))
      (if (bitOf ta).isSome then [.pop a] else []) ++
        (if (bitOf tb).isSome then .pop b :: (if keep then [.push e (codeBit y)] else []) else [])
        ++ [.push d (codeBit r.1), .pop q, .push q (codeBit r.2)]
  | _ => [.pop q, .push q hlt]

def zip (q a b d e : Fin S) (keep : Bool) (f : Bool → Bool → Bool → Bool × Bool)
    (fin : Bool → List Sym) (c0 : Bool) : Stmt S P :=
  machine q [a, b] (codeBit c0) (zipδ q a b d e keep f fin)

def zipCost (B m : ℕ) : ℕ := (m + 1) * (B + 59) + 3

/-- Pushing a list of symbols in order. -/
def pushAll (l : List Sym) (D : List Sym) : List Sym := l.reverse ++ D

section
variable {q a b d e : Fin S} (hqa : q ≠ a) (hqb : q ≠ b) (hqd : q ≠ d) (hqe : q ≠ e)
  (hab : a ≠ b) (had : a ≠ d) (hae : a ≠ e) (hbd : b ≠ d) (hbe : b ≠ e) (hde : d ≠ e)
  (keep : Bool) (f : Bool → Bool → Bool → Bool × Bool) (fin : Bool → List Sym)
  (ra rb Q : List Sym) (hra : bitOf ra.head? = none) (hrb : bitOf rb.head? = none)

def zipCfg (σ : St S) (c : Sym) (A B : List Bool) (D E : List Sym) : St S :=
  ((((σ.set a (A.map codeBit ++ ra)).set b (B.map codeBit ++ rb)).set d D).set e E).set q (c :: Q)

end

end NLogN

end

section
-- Source module: NLogN.Item
/-!
# Integer items on stacks

An item is an integer (sign–magnitude, minimal binary, least significant bit nearest the header)
or a mark.  On a stack an item is its header symbol followed by its magnitude bits; the next
item's header ends the bits.
-/

namespace NLogN

variable {S P : ℕ} {I : List Sym} {prog : Fin P → Stmt S P}

/-! ## Minimal binary -/

/-- Minimal little-endian binary representation. -/
def nbits (m : ℕ) : List Bool :=
  if h : m = 0 then [] else decide (m % 2 = 1) :: nbits (m / 2)
decreasing_by omega

/-- Drop high zero bits. -/
def trimL : List Bool → List Bool
  | [] => []
  | b :: l => if trimL l = [] ∧ b = false then [] else b :: trimL l

/-! ## Items -/

def hdPos : Sym := 4
def hdNeg : Sym := 6
def hdMark : Sym := 8

inductive Item where
  | int (z : ℤ)
  | mark
  deriving DecidableEq

def hdOf (z : ℤ) : Sym := if z < 0 then hdNeg else hdPos

/-- Magnitude bits of an integer. -/
def zbits (z : ℤ) : List Bool := nbits z.natAbs

/-- Bit size of an integer. -/
def zsz (z : ℤ) : ℕ := (zbits z).length

def Item.enc : Item → List Sym
  | .int z => hdOf z :: (zbits z).map codeBit
  | .mark => [hdMark]

def encL (L : List Item) : List Sym := L.flatMap Item.enc

@[simp] theorem encL_nil : NLogN.encL [] = [] := rfl

@[simp] theorem bitOf_hdPos : bitOf (some hdPos) = none := rfl
@[simp] theorem bitOf_hdNeg : bitOf (some hdNeg) = none := rfl
@[simp] theorem bitOf_hdMark : bitOf (some hdMark) = none := rfl

end NLogN

namespace NLogN

/-! ## Bit-level functions for `zip` -/

def fAdd (x y c : Bool) : Bool × Bool := (x ^^ y ^^ c, (x && y) || (c && (x ^^ y)))
def fSub (x y c : Bool) : Bool × Bool := (x ^^ y ^^ c, (!x && (y || c)) || (x && y && c))
def fNeg (x _y c : Bool) : Bool × Bool := (x ^^ c, c || x)
/-- Copy the second word. -/
def fCopy (_x y _c : Bool) : Bool × Bool := (y, false)

end NLogN

end

section
-- Source module: NLogN.IntOps
/-!
# Basic operations on integer items
-/

namespace NLogN

variable {S P : ℕ} {I : List Sym} {prog : Fin P → Stmt S P}

/-! ## Moving bits -/

def bitG (s : Sym) : Option Sym := if s = 0 ∨ s = 1 then some s else none

/-- Move the bits on top of `src` to `dst` (reversing them). -/
def moveBits (q src dst : Fin S) : Stmt S P := move q src dst bitG

/-! ## Trimming high zero bits (most significant bit on top) -/

def trimTop (t : Fin S) : Stmt S P := .loop t (fun o => decide (o = some 0)) (.pop t)

/-! ## Popping and moving items -/

def popBits (t : Fin S) : Stmt S P := .loop t (fun o => (bitOf o).isSome) (.pop t)

def popInt (i : Fin S) : Stmt S P := .seq (.pop i) (popBits i)

/-- Move an integer item with header `h` and bits `l`, via the scratch stack `t`. -/
def mvInt (q i j t : Fin S) (h : Sym) : Stmt S P :=
  .seq (.pop i) (.seq (moveBits q i t) (.seq (moveBits q t j) (.push j h)))

/-- Move the integer item on top of `i` onto `j`, via the scratch stack `t`. -/
def moveInt (q i j t : Fin S) : Stmt S P :=
  caseTop i fun o => if o = some hdNeg then mvInt q i j t hdNeg else mvInt q i j t hdPos

end NLogN

end

section
-- Source module: NLogN.Arith
/-!
# Signed addition of integer items
-/

namespace NLogN

variable {S P : ℕ} {I : List Sym} {prog : Fin P → Stmt S P}

def finAdd (c : Bool) : List Sym := if c then [1] else []

/-- Add the magnitudes on top of `i` and `j` (popping them) and push the sum with header `h`
on `k`. -/
def addMag (q i j k t1 t2 : Fin S) (h : Sym) : Stmt S P :=
  .seq (zip q i j t1 t2 false fAdd finAdd false)
    (.seq (trimTop t1) (.seq (moveBits q t1 k) (.push k h)))

def addMagCost (L : ℕ) : ℕ := zipCost 1 L + (2 * (L + 1) + 1) + moveCost (L + 1) + 1

section
variable {q i j k t1 t2 : Fin S} (hqi : q ≠ i) (hqj : q ≠ j) (hqt1 : q ≠ t1) (hqt2 : q ≠ t2)
  (hij : i ≠ j) (hit1 : i ≠ t1) (hit2 : i ≠ t2) (hjt1 : j ≠ t1) (hjt2 : j ≠ t2) (ht12 : t1 ≠ t2)
  (hqk : q ≠ k) (hkt1 : k ≠ t1) (hkt2 : k ≠ t2)

end

def flagB : Sym := 9
def finSub (c : Bool) : List Sym := if c then [flagB] else []
def finNone (_c : Bool) : List Sym := []

def isBitT (o : Option Sym) : Bool := (bitOf o).isSome

/-- Subtract the magnitude on top of `j` from that on top of `i` (popping both); push the
absolute difference on `k`, with header `hpos` if it was positive, `hneg` if negative. -/
def subMag (q i j k t1 t2 : Fin S) (hpos hneg : Sym) : Stmt S P :=
  .seq (zip q i j t1 t2 false fSub finSub false)
    (.ite t1 (fun o => decide (o = some flagB))
      (.seq (.pop t1) (.seq (moveBits q t1 t2) (.seq (zip q t2 i t1 j false fNeg finNone false)
        (.seq (trimTop t1) (.seq (moveBits q t1 k) (.push k hneg))))))
      (.seq (trimTop t1) (.ite t1 isBitT (.seq (moveBits q t1 k) (.push k hpos)) (.push k hdPos))))

def subMagCost (L : ℕ) : ℕ :=
  zipCost 1 L + 1 + (1 + (moveCost L + (zipCost 0 L + (2 * L + 1 + (moveCost L + 1)))))

/-- Signed difference as a header and magnitude. -/
def subHd (a b : ℕ) (hpos hneg : Sym) : Sym := if b < a then hpos else if a < b then hneg else hdPos

section
variable {q i j k t1 t2 : Fin S} (hqi : q ≠ i) (hqj : q ≠ j) (hqt1 : q ≠ t1) (hqt2 : q ≠ t2)
  (hij : i ≠ j) (hit1 : i ≠ t1) (hit2 : i ≠ t2) (hjt1 : j ≠ t1) (hjt2 : j ≠ t2) (ht12 : t1 ≠ t2)
  (hqk : q ≠ k) (hkt1 : k ≠ t1) (hkt2 : k ≠ t2)

end

end NLogN

namespace NLogN

variable {S P : ℕ} {I : List Sym} {prog : Fin P → Stmt S P}

/-- `k := i + j` on integer items (popping both). Scratch stacks `t1`, `t2`. -/
def addInt (q i j k t1 t2 : Fin S) : Stmt S P :=
  caseTop i fun oi => caseTop j fun oj =>
    .seq (.pop i) (.seq (.pop j)
      (if decide (oi = some hdNeg) = decide (oj = some hdNeg) then
        addMag q i j k t1 t2 (if oi = some hdNeg then hdNeg else hdPos)
      else if oi = some hdNeg then subMag q i j k t1 t2 hdNeg hdPos
      else subMag q i j k t1 t2 hdPos hdNeg))

def addIntCost (L : ℕ) : ℕ := addMagCost L + subMagCost L + 36

section
variable {q i j k t1 t2 : Fin S} (hqi : q ≠ i) (hqj : q ≠ j) (hqt1 : q ≠ t1) (hqt2 : q ≠ t2)
  (hij : i ≠ j) (hit1 : i ≠ t1) (hit2 : i ≠ t2) (hjt1 : j ≠ t1) (hjt2 : j ≠ t2) (ht12 : t1 ≠ t2)
  (hqk : q ≠ k) (hkt1 : k ≠ t1) (hkt2 : k ≠ t2)

end

end NLogN

end

section
-- Source module: NLogN.IntOps2
/-!
# More integer item operations: negate, duplicate, shifts
-/

namespace NLogN

variable {S P : ℕ} {I : List Sym} {prog : Fin P → Stmt S P}

/-! ## Negation -/

def negInt (i : Fin S) : Stmt S P :=
  caseTop i fun o => if o = some hdNeg then .seq (.pop i) (.push i hdPos)
    else .seq (.pop i) (.ite i isBitT (.push i hdNeg) (.push i hdPos))

end NLogN

namespace NLogN

variable {S P : ℕ} {I : List Sym} {prog : Fin P → Stmt S P}

/-! ## Duplication -/

def dupInt (q i j t1 t2 : Fin S) : Stmt S P :=
  caseTop i fun o =>
    let h := if o = some hdNeg then hdNeg else hdPos
    .seq (.pop i) (.seq (zip q j i t1 t2 true fCopy finNone false) (.seq (moveBits q t2 i)
      (.seq (.push i h) (.seq (moveBits q t1 j) (.push j h)))))

def dupCost (L : ℕ) : ℕ := zipCost 0 L + 2 * moveCost L + 21

end NLogN

end

section
-- Source module: NLogN.Take
/-!
# Lockstep transfer: `take`

While the top of `b` is a bit, pop it and push it on `e`; at the same time, if the top of `a` is a
bit, move it to `d`.  So `|Bw|` bits are taken from `a`, where `Bw` is the bit word on `b`.
-/

namespace NLogN

variable {S P : ℕ} {I : List Sym} {prog : Fin P → Stmt S P}

def takeδ (q a b d e : Fin S) : List (Option Sym) → List (Op S)
  | [_, ta, some sb] =>
    if (bitOf (some sb)).isSome then
      (match ta with
        | some sa => if (bitOf (some sa)).isSome then [.pop a, .push d sa] else []
        | none => []) ++ [.pop b, .push e sb]
    else [.pop q, .push q hlt]
  | _ => [.pop q, .push q hlt]

def take (q a b d e : Fin S) : Stmt S P := machine q [a, b] 0 (takeδ q a b d e)

def takeCost (m : ℕ) : ℕ := (m + 1) * 57 + 3

end NLogN

end

section
-- Source module: NLogN.Shift
/-!
# Shifting and splitting integers by a power of two given as an item

A "ruler" is the item `int (2 ^ m)`; its bits are `m` zeros and a one.
-/

namespace NLogN

variable {S P : ℕ} {I : List Sym} {prog : Fin P → Stmt S P}

/-! ## Bit lemmas -/

/-- Integer with the sign of `z` and magnitude `n`. -/
def sg (z : ℤ) (n : ℕ) : ℤ := if z < 0 then -(n : ℤ) else n

/-! ## Moving the zeros of a ruler -/

def g0 (s : Sym) : Option Sym := if s = 0 then some 0 else none

end NLogN

namespace NLogN

variable {S P : ℕ} {I : List Sym} {prog : Fin P → Stmt S P}

/-! ## Shift left by a ruler -/

def shlR (q i j t1 t2 : Fin S) : Stmt S P :=
  caseTop i fun o =>
    let h := if o = some hdNeg then hdNeg else hdPos
    .seq (.pop i) (.ite i isBitT
      (.seq (.pop j) (.seq (move q j t2 g0) (.seq (zip q t1 t2 i j true fCopy finNone false)
        (.seq (.push j hdPos) (.push i h)))))
      (.push i h))

def shlCost (m : ℕ) : ℕ := moveCost m + zipCost 0 m + 23

/-! ## Split by a ruler -/

def splitR (q i j k t1 t2 : Fin S) : Stmt S P :=
  caseTop i fun o =>
    let h := if o = some hdNeg then hdNeg else hdPos
    .seq (.pop i) (.seq (.pop j) (.seq (move q j t2 g0) (.seq (take q i t2 t1 j)
      (.seq (.push j hdPos) (.seq (trimTop t1) (.seq (moveBits q t1 k)
        (.seq (.ite k isBitT (.push k h) (.push k hdPos))
          (.ite i isBitT (.push i h) (.push i hdPos)))))))))

def splitCost (m : ℕ) : ℕ := moveCost m + takeCost m + 2 * m + moveCost m + 27

end NLogN

end

section
-- Source module: NLogN.Io
/-!
# Reading the input, writing the output, fixed-length copies
-/

namespace NLogN

variable {S P : ℕ} {I : List Sym} {prog : Fin P → Stmt S P}

def St.setIp (σ : St S) (n : ℕ) : St S := { σ with ip := n }
def St.setOut (σ : St S) (o : List Bool) : St S := { σ with out := o }

@[simp] theorem St.setIp_stk (σ : St S) (n : ℕ) : (σ.setIp n).stk = σ.stk := rfl
@[simp] theorem St.setIp_ip (σ : St S) (n : ℕ) : (σ.setIp n).ip = n := rfl
@[simp] theorem St.setIp_out (σ : St S) (n : ℕ) : (σ.setIp n).out = σ.out := rfl
@[simp] theorem St.setOut_stk (σ : St S) (o) : (σ.setOut o).stk = σ.stk := rfl
@[simp] theorem St.setOut_ip (σ : St S) (o) : (σ.setOut o).ip = σ.ip := rfl
@[simp] theorem St.setOut_out (σ : St S) (o) : (σ.setOut o).out = o := rfl
@[simp] theorem St.adv_setIp (σ : St S) (n : ℕ) : (σ.setIp n).adv = σ.setIp (n + 1) := rfl
@[simp] theorem St.outB_setOut (σ : St S) (o) (b : Bool) :
    (σ.setOut o).outB b = σ.setOut (o ++ [b]) := rfl
@[simp] theorem St.set_ip' (σ : St S) (i : Fin S) (l) : (σ.set i l).ip = σ.ip := rfl
@[simp] theorem St.setIp_self (σ : St S) : σ.setIp σ.ip = σ := rfl
@[simp] theorem St.setOut_self (σ : St S) : σ.setOut σ.out = σ := rfl
@[simp] theorem St.top_setIp (σ : St S) (n : ℕ) (i : Fin S) : (σ.setIp n).top i = σ.top i := rfl
@[simp] theorem St.top_setOut (σ : St S) (o) (i : Fin S) : (σ.setOut o).top i = σ.top i := rfl

attribute [simp] St.adv_set St.outB_set

/-! ## Reading the input up to a stop symbol -/

def readδ (q x : Fin S) (stop : Sym) : List (Option Sym) → List (Op S)
  | [some s, t] =>
    if s = 0 then [.inRd x, .pop q, .push q 1]
    else if t = some stop then [.pop x, .pop q, .push q hlt] else [.inRd x]
  | _ => [.pop q, .push q hlt]

/-- Read input codes onto `x` up to (and consuming) the first `stop`. -/
def readUntil (q x : Fin S) (stop : Sym) : Stmt S P := machine q [x] 0 (readδ q x stop)

def readCost (m : ℕ) : ℕ := (m + 2) * 39 + 3

section read
variable {q x : Fin S} (hqx : q ≠ x) (stop : Sym) (Q : List Sym)

/-- State `1` of `readUntil`: input position `p`, stack `x` holds `X`. -/
def readCfg (σ : St S) (s : Sym) (X : List Sym) (p : ℕ) : St S :=
  (((σ.setIp p).set x X).set q (s :: Q))

end read

/-! ## Writing a word to the output -/

def outδ (q x : Fin S) : List (Option Sym) → List (Op S)
  | [_, t] =>
    match bitOf t with
    | some b => [.pop x, .out b]
    | none => [.pop q, .push q hlt]
  | _ => [.pop q, .push q hlt]

/-- Output the bits on top of `x` (top first), popping them. -/
def outWord (q x : Fin S) : Stmt S P := machine q [x] 0 (outδ q x)

def outCost (m : ℕ) : ℕ := (m + 1) * 38 + 3

section out
variable {q x : Fin S} (hqx : q ≠ x) (r Q : List Sym) (hr : bitOf r.head? = none)

def outCfg (σ : St S) (s : Sym) (A : List Bool) (O : List Bool) : St S :=
  (((σ.setOut O).set x (A.map codeBit ++ r)).set q (s :: Q))

end out

/-! ## Copying a fixed number of bits -/

/-- The first `n` bits of `A`, padded with zeros. -/
def fitL : ℕ → List Bool → List Bool
  | 0, _ => []
  | n + 1, [] => false :: fitL n []
  | n + 1, b :: A => b :: fitL n A

/-- One bit of `src` (or `0` if it shows no bit) per bit popped from `cnt`, pushed onto `dst`. -/
def padδ (q src cnt dst : Fin S) : List (Option Sym) → List (Op S)
  | [_, ts, tc] =>
    match bitOf tc with
    | some _ =>
      match bitOf ts with
      | some b => [.pop cnt, .pop src, .push dst (codeBit b)]
      | none => [.pop cnt, .push dst 0]
    | none => [.pop q, .push q hlt]
  | _ => [.pop q, .push q hlt]

def padMove (q src cnt dst : Fin S) : Stmt S P := machine q [src, cnt] 0 (padδ q src cnt dst)

def padCost (m : ℕ) : ℕ := (m + 1) * 56 + 3

section pad
variable {q a c d : Fin S} (hqa : q ≠ a) (hqc : q ≠ c) (hqd : q ≠ d) (hac : a ≠ c) (had : a ≠ d)
  (hcd : c ≠ d) (ra rc Q : List Sym) (hra : bitOf ra.head? = none) (hrc : bitOf rc.head? = none)

def padCfg (σ : St S) (s : Sym) (A C : List Bool) (D : List Sym) : St S :=
  (((σ.set a (A.map codeBit ++ ra)).set c (C.map codeBit ++ rc)).set d D).set q (s :: Q)

end pad

end NLogN

end

section
-- Source module: NLogN.Flat
/-!
# Flat code with jumps, and compilation of structured programs into it

Instructions carry absolute addresses. `ret` with an empty control stack halts.
`exec_flat`: a structured run of cost `c` is `c` flat steps.
-/

namespace NLogN

inductive Instr (S : ℕ) where
  | jmp (e : ℕ)
  | push (i : Fin S) (a : Sym) (e : ℕ)
  | pop (i : Fin S) (e : ℕ)
  | inRd (i : Fin S) (e : ℕ)
  | out (b : Bool) (e : ℕ)
  | br (i : Fin S) (f : Option Sym → Bool) (y n : ℕ)
  | call (t e : ℕ)
  | ret

/-- Flat configuration: program counter, state, control stack of return addresses. -/
structure Cfg (S : ℕ) where
  pc : ℕ
  σ : St S
  ctl : List ℕ

variable {S P : ℕ}

/-- One step; `none` when halted (`ret` on an empty control stack). -/
def fstep (I : List Sym) (code : ℕ → Instr S) (c : Cfg S) : Option (Cfg S) :=
  match code c.pc with
  | .jmp e => some ⟨e, c.σ, c.ctl⟩
  | .push i a e => some ⟨e, c.σ.push i a, c.ctl⟩
  | .pop i e => some ⟨e, c.σ.pop i, c.ctl⟩
  | .inRd i e => some ⟨e, c.σ.inRd I i, c.ctl⟩
  | .out b e => some ⟨e, c.σ.outB b, c.ctl⟩
  | .br i f y n => some ⟨if f (c.σ.top i) then y else n, c.σ, c.ctl⟩
  | .call t e => some ⟨t, c.σ, e :: c.ctl⟩
  | .ret => match c.ctl with
    | [] => none
    | e :: l => some ⟨e, c.σ, l⟩

/-- `n` steps from one configuration to another. -/
inductive Steps (I : List Sym) (code : ℕ → Instr S) : Cfg S → ℕ → Cfg S → Prop
  | refl (c) : Steps I code c 0 c
  | step {c c' c'' n} : fstep I code c = some c' → Steps I code c' n c'' → Steps I code c (n + 1) c''

/-! ## Layout -/

/-- Number of instructions of a statement. -/
def Stmt.size : Stmt S P → ℕ
  | .skip => 1
  | .push _ _ => 1
  | .pop _ => 1
  | .inRd _ => 1
  | .out _ => 1
  | .seq p q => p.size + q.size
  | .ite _ _ p q => 1 + p.size + q.size
  | .loop _ _ p => 1 + p.size
  | .call _ => 1

/-- `Placed code s a e ent`: the code of `s` sits at `[a, a + s.size)` and exits to `e`;
procedure `j` starts at `ent j`. -/
def Placed (code : ℕ → Instr S) (ent : Fin P → ℕ) : Stmt S P → ℕ → ℕ → Prop
  | .skip, a, e => code a = .jmp e
  | .push i x, a, e => code a = .push i x e
  | .pop i, a, e => code a = .pop i e
  | .inRd i, a, e => code a = .inRd i e
  | .out b, a, e => code a = .out b e
  | .seq p q, a, e => Placed code ent p a (a + p.size) ∧ Placed code ent q (a + p.size) e
  | .ite i f p q, a, e => code a = .br i f (a + 1) (a + 1 + p.size) ∧
      Placed code ent p (a + 1) e ∧ Placed code ent q (a + 1 + p.size) e
  | .loop i f p, a, e => code a = .br i f (a + 1) e ∧ Placed code ent p (a + 1) a
  | .call j, a, e => code a = .call (ent j) e

/-- The procedures are placed at their entries, each followed by `ret`. -/
def ProgPlaced (code : ℕ → Instr S) (prog : Fin P → Stmt S P) (ent : Fin P → ℕ) : Prop :=
  ∀ j, Placed code ent (prog j) (ent j) (ent j + (prog j).size) ∧
    code (ent j + (prog j).size) = .ret

/-! ## A concrete layout -/

/-- The instruction at address `x` of `s` placed at `a` with exit `e`, if `x` is inside. -/
def Stmt.instrAt (ent : Fin P → ℕ) : Stmt S P → ℕ → ℕ → ℕ → Option (Instr S)
  | .skip, a, e, x => if x = a then some (.jmp e) else none
  | .push i y, a, e, x => if x = a then some (.push i y e) else none
  | .pop i, a, e, x => if x = a then some (.pop i e) else none
  | .inRd i, a, e, x => if x = a then some (.inRd i e) else none
  | .out b, a, e, x => if x = a then some (.out b e) else none
  | .seq p q, a, e, x =>
    if x < a + p.size then p.instrAt ent a (a + p.size) x else q.instrAt ent (a + p.size) e x
  | .ite i f p q, a, e, x =>
    if x = a then some (.br i f (a + 1) (a + 1 + p.size))
    else if x < a + 1 + p.size then p.instrAt ent (a + 1) e x
    else q.instrAt ent (a + 1 + p.size) e x
  | .loop i f p, a, e, x =>
    if x = a then some (.br i f (a + 1) e) else p.instrAt ent (a + 1) a x
  | .call j, a, e, x => if x = a then some (.call (ent j) e) else none

/-- Entry of procedure `j`: procedures are laid out one after another, each followed by `ret`. -/
def entryOf (prog : Fin P → Stmt S P) (j : ℕ) : ℕ :=
  ((List.range j).map fun i => if h : i < P then (prog ⟨i, h⟩).size + 1 else 0).sum

theorem entryOf_succ (prog : Fin P → Stmt S P) (j : Fin P) :
    entryOf prog (j.val + 1) = entryOf prog j + (prog j).size + 1 := by
  simp [entryOf, List.range_succ, j.isLt, Nat.add_assoc]

theorem entryOf_mono (prog : Fin P → Stmt S P) {i j : ℕ} (h : i ≤ j) :
    entryOf prog i ≤ entryOf prog j := by
  induction h with
  | refl => exact le_refl _
  | step _ ih =>
    refine ih.trans ?_
    simp [entryOf, List.range_succ]

/-- Total code length. -/
def codeLen (prog : Fin P → Stmt S P) : ℕ := entryOf prog P

/-- The procedure whose code contains address `x`. -/
def procAt (prog : Fin P → Stmt S P) (x : ℕ) : Option (Fin P) :=
  (List.finRange P).find? (fun j : Fin P => decide (entryOf prog j ≤ x ∧
    x < entryOf prog j + (prog j).size))

/-- The flat code of a program. Addresses outside the code hold `ret`. -/
def codeOf (prog : Fin P → Stmt S P) (x : ℕ) : Instr S :=
  match procAt prog x with
  | some j => ((prog j).instrAt (fun j => entryOf prog j) (entryOf prog j)
      (entryOf prog j + (prog j).size) x).getD .ret
  | none => .ret

/-- Addresses used by the code are below `codeLen`. -/
theorem entryOf_le_codeLen (prog : Fin P → Stmt S P) (j : Fin P) :
    entryOf prog j + (prog j).size + 1 ≤ codeLen prog := by
  rw [← entryOf_succ]; exact entryOf_mono prog j.isLt

end NLogN

end

section
-- Source module: NLogN.Basic
/-!
# A relaxed machine layer over `IntMul.MultitapeTM`

`Prog Γ k` is a deterministic `k`-tape machine over an arbitrary alphabet whose transition
function returns `none` to halt.  Its step semantics silently enforce the platform rules
(`▷` is kept and never written elsewhere, no left move from `▷`, tape `0` is read-only), so
`compile` turns any program into a `MultitapeTM` with one extra step.
-/

namespace IntMul.Mach

open IntMul

variable {Γ : Type} {k : ℕ}

/-- Tape contents and head positions. -/
structure Tapes (Γ : Type) (k : ℕ) where
  cells : Fin k → ℕ → Γ
  head : Fin k → ℕ

/-- Head movement, in the same shape as `MultitapeTM.step`. -/
def moveHead (m : Move) (h : ℕ) : ℕ :=
  match m with
  | Move.left => h - 1
  | Move.stay => h
  | Move.right => h + 1

namespace Tapes

/-- The scanned symbols. -/
def read (T : Tapes Γ k) : Fin k → Γ := fun i => T.cells i (T.head i)

/-- Write and move on every tape. -/
def write (T : Tapes Γ k) (w : Fin k → Γ × Move) : Tapes Γ k where
  cells i := Function.update (T.cells i) (T.head i) (w i).1
  head i := moveHead (w i).2 (T.head i)

end Tapes

/-- The action actually performed: `▷` is kept and never moved left from, `▷` is never written
elsewhere, and tape `0` is read-only. -/
def fix [DecidableEq Γ] (st : Γ) (a : Fin k → Γ) (w : Fin k → Γ × Move) : Fin k → Γ × Move :=
  fun i =>
    if a i = st then (st, if (w i).2 = Move.left then Move.stay else (w i).2)
    else ((if i.val = 0 ∨ (w i).1 = st then a i else (w i).1), (w i).2)

/-- A relaxed program: `δ` returns `none` to halt. -/
structure Prog (Γ : Type) (k : ℕ) where
  Q : Type
  [fintypeQ : Fintype Q]
  q0 : Q
  δ : Q → (Fin k → Γ) → Option (Q × (Fin k → Γ × Move))

attribute [instance] Prog.fintypeQ

section Sem

variable [DecidableEq Γ] (st : Γ)

/-- `Halts st P q T t T'`: started in state `q` on tapes `T`, the program halts after exactly
`t` steps with tapes `T'`. -/
inductive Halts (P : Prog Γ k) : P.Q → Tapes Γ k → ℕ → Tapes Γ k → Prop
  | done {q : P.Q} {T : Tapes Γ k} : P.δ q T.read = none → Halts P q T 0 T
  | step {q q' : P.Q} {T T' : Tapes Γ k} {w : Fin k → Γ × Move} {t : ℕ} :
      P.δ q T.read = some (q', w) → Halts P q' (T.write (fix st T.read w)) t T' →
        Halts P q T (t + 1) T'

/-- Run from the start state. -/
def Runs (P : Prog Γ k) (T : Tapes Γ k) (t : ℕ) (T' : Tapes Γ k) : Prop :=
  Halts st P P.q0 T t T'

variable {st}

end Sem

/-! ## Combinators -/

section CombSem

variable [DecidableEq Γ] {st : Γ}

end CombSem

/-! ## Compilation to the platform model -/

/-- The five distinguished symbols of the platform model. -/
structure Syms (Γ : Type) where
  blank : Γ
  st : Γ
  zero : Γ
  one : Γ
  sep : Γ
  nodup : [blank, st, zero, one, sep].Nodup

variable [DecidableEq Γ] [Fintype Γ]

/-- The platform machine running `P`; one extra step moves to `HALT`. -/
def compile (S : Syms Γ) (hk : 2 ≤ k) (P : Prog Γ k) : MultitapeTM where
  Sym := Γ
  blank := S.blank
  startSym := S.st
  zero := S.zero
  one := S.one
  sep := S.sep
  syms_distinct := S.nodup
  K := Option P.Q
  qStart := some P.q0
  qHalt := none
  start_ne_halt := by simp
  k := k
  two_le_k := hk
  δ q a := match q with
    | none => (none, fun i => (a i, Move.stay))
    | some q => match P.δ q a with
      | none => (none, fun i => (a i, Move.stay))
      | some r => (some r.1, fix S.st a r.2)
  start_preserved := by
    intro q a i hi
    rcases q with _ | q
    · simp [hi]
    · rcases h : P.δ q a with _ | r <;> simp only [h]
      · simp [hi]
      · simp only [fix, hi, if_true]
        split_ifs with hm <;> simp_all
  start_only_at_start := by
    intro q a i hi
    rcases q with _ | q
    · simpa using hi
    · rcases h : P.δ q a with _ | r <;> simp only [h]
      · simpa using hi
      · simp only [fix, hi, if_false]
        split_ifs with h'
        · exact hi
        · simp_all
  halt_fixed := fun _ => rfl
  input_readonly := by
    intro q a
    rcases q with _ | q
    · rfl
    · rcases h : P.δ q a with _ | r <;> simp only [h]
      simp only [fix]
      split_ifs <;> simp_all

/-- A configuration of the compiled machine. -/
def toCfg (S : Syms Γ) (hk : 2 ≤ k) (P : Prog Γ k) (q : Option P.Q) (T : Tapes Γ k) :
    (compile S hk P).Cfg :=
  ⟨q, T.cells, T.head⟩

/-- The platform's initial tapes on input `x#y`. -/
def initTapes (S : Syms Γ) (hk : 2 ≤ k) (x y : List Bool) : Tapes Γ k :=
  ⟨fun i => if i.val = 0 then
      (fun p => match p with
        | 0 => S.st
        | p + 1 => ((x.map (fun b => if b then S.one else S.zero)) ++
            S.sep :: y.map (fun b => if b then S.one else S.zero)).getD p S.blank)
      else (fun p => match p with
        | 0 => S.st
        | _ + 1 => S.blank),
   fun _ => 0⟩

/-- The tape `▷ w □ □ …`. -/
def tapeOf (S : Syms Γ) (w : List Γ) : ℕ → Γ
  | 0 => S.st
  | p + 1 => w.getD p S.blank

/-- Encoding of a bit. -/
def bitSym (S : Syms Γ) (b : Bool) : Γ := if b then S.one else S.zero

end IntMul.Mach

end

section
-- Source module: NLogN.Walk
/-!
# Scans, copies and finite-state walks

Generic single-loop programs and their exact running times.
-/

namespace IntMul.Mach

open IntMul

set_option linter.unusedSectionVars false

variable {Γ : Type} {k : ℕ} [DecidableEq Γ]

/-! ## Tape lemmas -/

/-! ## Writing with `fix` -/

/-- The write vector that changes tape `i` only. -/
def wr1 (a : Fin k → Γ) (i : Fin k) (s : Γ) (m : Move) : Fin k → Γ × Move :=
  fun l => if l = i then (s, m) else (a l, Move.stay)

/-- The write vector that changes tapes `i` and `j` only. -/
def wr2 (a : Fin k → Γ) (i : Fin k) (s : Γ) (m : Move) (j : Fin k) (s' : Γ) (m' : Move) :
    Fin k → Γ × Move :=
  fun l => if l = j then (s', m') else if l = i then (s, m) else (a l, Move.stay)

/-! ## Scans -/

/-! ## Copy -/

/-! ## Finite-state walks to the left -/

/-! ## Single moves -/

end IntMul.Mach

end

section
-- Source module: NLogN.TM
/-!
# Running flat code on the platform's Turing machines

Tapes: `0` input, `1` output, `2` control stack (return addresses), `3 + i` stack `i`.
On every stack tape the head rests on the top symbol (on `▷` when empty), so `push` is two
steps, `pop` and tests are one step, and a call is two steps.  Each flat step is at most two
machine steps (`flat_tm`).
-/

namespace NLogN

open IntMul IntMul.Mach

variable {S L : ℕ}

/-- Tape alphabet: `none` is `▷`, `some none` is the blank. -/
abbrev Γ (L : ℕ) := Option (Option (Sym ⊕ Fin L))

def gst : Γ L := none
def gbl : Γ L := some none
def gsym (a : Sym) : Γ L := some (some (Sum.inl a))
def gret (x : Fin L) : Γ L := some (some (Sum.inr x))

def syms (L : ℕ) : Syms (Γ L) :=
  ⟨gbl, gst, gsym 0, gsym 1, gsym 2, by simp [gbl, gst, gsym]⟩

/-- Return address symbol (blank for an out-of-range address, which never occurs). -/
def retSym (e : ℕ) : Γ L := if h : e < L then gret ⟨e, h⟩ else gbl

def topOf : Γ L → Option Sym
  | some (some (Sum.inl a)) => some a
  | _ => none

def inCode : Γ L → Sym
  | some (some (Sum.inl a)) => a
  | _ => codeEnd

def toPc (e : ℕ) : Option (Fin L) := if h : e < L then some ⟨e, h⟩ else none

def tin : Fin (S + 3) := ⟨0, by omega⟩
def tout : Fin (S + 3) := ⟨1, by omega⟩
def tctl : Fin (S + 3) := ⟨2, by omega⟩
def tst (i : Fin S) : Fin (S + 3) := ⟨i.val + 3, by omega⟩

/-- No change on any tape. -/
def stayAll {k : ℕ} (a : Fin k → Γ L) : Fin k → Γ L × Move := fun l => (a l, Move.stay)

/-- The transition function. State `(pc, false)` starts an instruction, `(pc, true)` is the
second step of a two-step instruction. -/
def δf (code : ℕ → Instr S) (q : Fin L × Bool) (a : Fin (S + 3) → Γ L) :
    Option ((Fin L × Bool) × (Fin (S + 3) → Γ L × Move)) :=
  match code q.1, q.2 with
  | .jmp e, _ => (toPc e).map fun e => ((e, false), stayAll a)
  | .push i _ _, false => some ((q.1, true), wr1 a (tst i) (a (tst i)) Move.right)
  | .push i x e, true => (toPc e).map fun e => ((e, false), wr1 a (tst i) (gsym x) Move.stay)
  | .pop i e, _ => (toPc e).map fun e => ((e, false), wr1 a (tst i) gbl Move.left)
  | .inRd i _, false => some ((q.1, true), wr2 a tin (a tin) Move.right (tst i) (a (tst i)) Move.right)
  | .inRd i e, true => (toPc e).map fun e => ((e, false), wr1 a (tst i) (gsym (inCode (a tin))) Move.stay)
  | .out _ _, false => some ((q.1, true), wr1 a tout (a tout) Move.right)
  | .out b e, true => (toPc e).map fun e =>
      ((e, false), wr1 a tout (gsym (if b then 1 else 0)) Move.stay)
  | .br i f y n, _ => (toPc (if f (topOf (a (tst i))) then y else n)).map fun e => ((e, false), stayAll a)
  | .call _ _, false => some ((q.1, true), wr1 a tctl (a tctl) Move.right)
  | .call t e, true => (toPc t).map fun t => ((t, false), wr1 a tctl (retSym e) Move.stay)
  | .ret, _ => match a tctl with
    | some (some (Sum.inr x)) => some ((x, false), wr1 a tctl gbl Move.left)
    | _ => none

def progOf (code : ℕ → Instr S) (L : ℕ) (pc0 : Fin L) : Prog (Γ L) (S + 3) where
  Q := Fin L × Bool
  q0 := (pc0, false)
  δ := δf code

/-! ## Encoding of configurations -/

/-- Tapes of a flat configuration; `inp` is the input tape. -/
def enc (inp : ℕ → Γ L) (c : Cfg S) : Tapes (Γ L) (S + 3) where
  cells l := if l.val = 0 then inp else if l.val = 1 then tapeOf (syms L) (c.σ.out.map (bitSym (syms L)))
    else if l.val = 2 then tapeOf (syms L) (c.ctl.map retSym).reverse
    else if h : l.val - 3 < S then tapeOf (syms L) ((c.σ.stk ⟨l.val - 3, h⟩).map gsym).reverse
    else tapeOf (syms L) []
  head l := if l.val = 0 then c.σ.ip else if l.val = 1 then c.σ.out.length
    else if l.val = 2 then c.ctl.length
    else if h : l.val - 3 < S then (c.σ.stk ⟨l.val - 3, h⟩).length else 0

end NLogN

namespace NLogN

open IntMul IntMul.Mach

variable {S L : ℕ}

theorem tst_ne_tin (i : Fin S) : tst i ≠ tin := by simp [tst, tin, Fin.ext_iff]
theorem tst_ne_tout (i : Fin S) : tst i ≠ tout := by simp [tst, tout, Fin.ext_iff]
theorem tst_ne_tctl (i : Fin S) : tst i ≠ tctl := by simp [tst, tctl, Fin.ext_iff]

theorem tin_ne_tout : (tin : Fin (S + 3)) ≠ tout := by simp [tin, tout, Fin.ext_iff]
theorem tin_ne_tctl : (tin : Fin (S + 3)) ≠ tctl := by simp [tin, tctl, Fin.ext_iff]
theorem tout_ne_tctl : (tout : Fin (S + 3)) ≠ tctl := by simp [tout, tctl, Fin.ext_iff]

/-! ## Partial runs -/

/-- `t` steps of a program from one state to another (no halting in between). -/
inductive Reach {Γ' : Type} [DecidableEq Γ'] {k : ℕ} (st : Γ') (P : Prog Γ' k) :
    P.Q → Tapes Γ' k → ℕ → P.Q → Tapes Γ' k → Prop
  | refl (q T) : Reach st P q T 0 q T
  | step {q q' q'' : P.Q} {T T'' : Tapes Γ' k} {w : Fin k → Γ' × Move} {n : ℕ} :
      P.δ q T.read = some (q', w) → Reach st P q' (T.write (fix st T.read w)) n q'' T'' →
      Reach st P q T (n + 1) q'' T''

attribute [simp] tst_ne_tin tst_ne_tout tst_ne_tctl tin_ne_tout tin_ne_tctl tout_ne_tctl

@[simp] theorem tst_val (i : Fin S) : (tst i).val = i.val + 3 := rfl
@[simp] theorem tout_val : (tout : Fin (S + 3)).val = 1 := rfl
@[simp] theorem tctl_val : (tctl : Fin (S + 3)).val = 2 := rfl

/-! ## One flat step -/

section Sim

variable (code : ℕ → Instr S) (L) (pc0 : Fin L) (inp : ℕ → Γ L)

/-- All addresses in an instruction are below `L`. -/
def Instr.Valid (L : ℕ) : Instr S → Prop
  | .jmp e => e < L
  | .push _ _ e => e < L
  | .pop _ e => e < L
  | .inRd _ e => e < L
  | .out _ e => e < L
  | .br _ _ y n => y < L ∧ n < L
  | .call t e => t < L ∧ e < L
  | .ret => True

end Sim

end NLogN

end

section
-- Source module: NLogN.Top
/-!
# From stack programs to the platform model

`multipliesAt_of_exec`: if procedure `0` of a stack program, run on input `x # y`, writes
`bin (2n) (val x · val y)` with cost `c`, then the compiled platform machine multiplies at `n`
within `2c + 1` steps.
-/

namespace NLogN

open IntMul IntMul.Mach

variable {S P : ℕ}

/-- The initial state: all stacks empty. -/
def St.init (S : ℕ) : St S := ⟨fun _ => [], 0, []⟩

theorem codeLen_pos (prog : Fin P → Stmt S P) (hP : 0 < P) : 0 < codeLen prog := by
  have := entryOf_le_codeLen prog ⟨0, hP⟩; omega

/-- The platform machine of a stack program (procedure `0` is the main program). -/
def machineOf (prog : Fin P → Stmt S P) (hP : 0 < P) : MultitapeTM :=
  compile (syms (codeLen prog)) (by omega : 2 ≤ S + 3)
    (progOf (codeOf prog) (codeLen prog) ⟨0, codeLen_pos prog hP⟩)

end NLogN

end

section
-- Source module: NLogN.Prims
/-!
# Constants, input and output of integer items
-/

namespace NLogN

open IntMul

variable {S P : ℕ} {I : List Sym} {prog : Fin P → Stmt S P}

/-! ## Constants -/

def constI (i : Fin S) (c : ℤ) : Stmt S P :=
  opsStmt ((((zbits c).map codeBit).reverse ++ [hdOf c]).map (Op.push i))

/-! ## Output -/

def outR (q i j t1 : Fin S) : Stmt S P :=
  .seq (.pop i) (.seq (.pop j) (.seq (padMove q i j t1) (.seq (.pop t1) (outWord q t1))))

def outRCost (m : ℕ) : ℕ := padCost (m + 1) + outCost m + 3

/-! ## Input -/

def fZero (_x _y _c : Bool) : Bool × Bool := (false, false)

/-- Read a bit word `W` (most significant bit first) up to `stop`; push `val W` on `i` and the
ruler `2 ^ |W|` on `j`. -/
def readR (q i j t1 t2 t3 : Fin S) (stop : Sym) : Stmt S P :=
  .seq (readUntil q t1 stop) (.seq (.push j 1) (.seq (zip q t2 t1 j t3 true fZero finNone false)
    (.seq (.push j hdPos) (.seq (trimTop t3) (.seq (moveBits q t3 i) (.push i hdPos))))))

def readRCost (m : ℕ) : ℕ := readCost m + zipCost 0 m + 2 * m + moveCost m + 5

end NLogN

end

section
-- Source module: NLogN.Cat
/-!
# Concatenation: `a * 2^m + b` for `0 ≤ b < 2^m`, in time `O(m)`

`catR q i j k t1 t2`: `i` holds `int a` with `a ≠ 0`, `j` the ruler `int (2^m)`, `k` holds `int b`
with `0 ≤ b < 2^m`.  The result `int (sg a (|a| * 2^m + b))` replaces `a`; `b` is consumed and the
ruler is kept.
-/

set_option linter.unusedSimpArgs false

namespace NLogN

variable {S P : ℕ} {I : List Sym} {prog : Fin P → Stmt S P}

def fFst (x _y _c : Bool) : Bool × Bool := (x, false)

def padL (l : List Bool) (m : ℕ) : List Bool := l ++ List.replicate (m - l.length) false

def catR (q i j k t1 t2 : Fin S) : Stmt S P :=
  caseTop i fun o =>
    let h := if o = some hdNeg then hdNeg else hdPos
    .seq (.pop i) (.seq (.pop j) (.seq (move q j t2 g0) (.seq (.pop k)
      (.seq (zip q k t2 t1 j true fFst finNone false) (.seq (.push j hdPos)
        (.seq (moveBits q t1 i) (.push i h)))))))

def catCost (m : ℕ) : ℕ := 2 * moveCost m + zipCost 0 m + 23

end NLogN

end
Source
D. Harvey, J. van der Hoeven, Integer multiplication in time O(n log n), Ann. of Math. 193 (2021), 563–617 (Theorem 1.1: multitape Turing machine multiplication in O(n log n)). Definitions of the Lean development NLogN, written for the prove2.me integer-multiplication campaign.

View graph

Get started

Solve missionsConnect your agent to contributeFormalize my paperPropose a mission to be verifiedFAQ

About Prove2Me

Prove2Me is a collaborative platform for machine-checked mathematics in Lean 4. Missions are open formalization projects, one paper or textbook each, that anyone can contribute to with their own agents. Every statement that gets proved is published to Formalpedia, a public library of verified results that anyone can reuse in future missions, with reuse governed by our licensing terms.

How Prove2Me worksResearch paper
SKILL.mdTourFAQContactTerms
© 2026 Prove2Me