@@ -80,62 +80,45 @@ actual behavior.
8080
8181## Intentional Modeling Simplifications
8282
83- ### S-1: Unbounded stack depth
84-
85- ** Lean:** ` List Felt ` (unbounded, no minimum depth)
86- ** Rust:** Minimum 16 elements (zero-padded), maximum 2^16
87-
88- The Lean model allows operations on stacks smaller than 16 elements.
89- This is standard in formal machine models (see LNSym, eth-isabelle,
90- Cairo formal proofs). It avoids modeling the zero-padding logic, which
91- is an implementation detail not relevant to procedure correctness.
92-
93- ** Impact:** Proofs do not verify stack overflow behavior. Programs that
94- depend on the zero-initialized padding below the 16th element would need
95- explicit hypotheses.
96-
97- ** Soundness for proven theorems:** All proven procedure theorems take
98- the initial stack as an explicit hypothesis (e.g.,
99- ` hs : s.stack = a :: b :: c :: rest ` ). This fixes the stack depth to
100- at least the number of named elements. No proven theorem relies on
101- accessing zero-padded positions below its explicitly stated inputs,
102- so the unbounded model produces identical results to the Rust model
103- for all proven theorems.
104-
105- ### S-2: Element-addressed memory vs word-addressed memory
106-
107- ** Lean:** ` Nat -> Felt ` (one felt per address, total function)
83+ ### S-1: Stack depth enforcement
84+
85+ ** Lean:** ` List Felt ` with overflow guards; ` MIN_STACK_DEPTH = 16 ` ,
86+ ` MAX_STACK_DEPTH = 2^16 ` , ` wellFormed ` predicate in State.lean.
87+ 11 instruction handlers that increase stack depth check
88+ ` s.stack.length + N > MAX_STACK_DEPTH ` and return ` none ` on overflow.
89+ ** Rust:** Minimum 16 elements (zero-padded), maximum 2^16.
90+
91+ Remaining differences:
92+ - The Lean model does not auto-pad stacks below 16 elements with
93+ zeros. Operations on short stacks fail via pattern matching rather
94+ than reading zero from padding. The ` padStack ` function exists but
95+ is not called automatically.
96+ - The Lean model does not prevent the stack from shrinking below 16
97+ via pop operations. In the Rust VM, the visible stack is always
98+ exactly 16 elements with an overflow table backing it.
99+
100+ All proven procedure theorems carry
101+ ` hlen : rest.length + 30 <= MAX_STACK_DEPTH ` , which ensures the
102+ stack stays within bounds throughout execution. The constant 30
103+ accounts for the maximum intermediate stack growth of any proven
104+ procedure.
105+
106+ ### S-2: Word-addressed memory (matched)
107+
108+ ** Lean:** ` Nat -> Word ` (word-addressed, total function, 0-initialized)
108109** Rust:** ` BTreeMap<(ContextId, u32), Word> ` (word-addressed, sparse)
109110
110- The Lean model stores one felt per natural number address. Word
111- operations (memLoadw, memStorew) read/write 4 consecutive addresses. The
112- Lean model provides separate Be (big-endian) and Le (little-endian)
113- variants for word operations.
111+ The Lean model now matches the Rust VM's word-addressed memory layout.
112+ Each address maps to a ` Word ` (4 field elements). ` memLoad ` reads
113+ element 0 of the word; ` memStore ` writes element 0 (preserving elements
114+ 1-3). ` memLoadw ` /` memStorew ` read/write full words. Be and Le variants
115+ control element ordering on the stack.
114116
115- The Rust model stores 4-element Words at word-aligned addresses.
116- Individual element access uses ` split_addr(addr) ` to decompose into
117- (word_addr, index_within_word). The Rust ` op_mloadw ` reads a Word and
118- places ` word[0] ` at stack top; ` op_mstorew ` writes the top 4 stack
119- elements as a Word starting from position 1 (after popping the address).
120-
121- The Rust VM's element ordering within a word is:
122- - ` word[0] ` <-> lowest address within the 4-element group
123- - ` word[0] ` <-> stack top after mloadw
124-
125- This matches the Lean Le (little-endian) variant where the lowest
126- address goes to the stack top.
127-
128- ** Impact:** Proofs using word memory operations must specify which
129- variant (Be or Le) is being used. For fidelity with the Rust VM, the Le
130- variants should be preferred.
131-
132- ** Soundness for proven theorems:** The only memory-writing procedure
133- proved is ` word.store_word_u32s_le ` , which uses ` memStorewLe ` (the Le
134- variant matching Rust's element ordering). Its theorem states the
135- exact memory addresses written and their values. The per-element
136- memory model gives identical results to the per-word model when
137- accessed through word-aligned Le operations, which is the only
138- pattern used in proven theorems.
117+ Remaining differences:
118+ - Lean uses a total function (` Nat -> Word ` ) returning ` Word.zero ` for
119+ unwritten addresses. Rust uses a sparse ` BTreeMap ` with implicit zero
120+ default. Functionally equivalent.
121+ - Lean does not model ` ContextId ` . See S-3.
139122
140123### S-3: No execution contexts
141124
@@ -146,36 +129,27 @@ The Lean model does not support multiple execution contexts. This is
146129appropriate for the current scope (single-procedure correctness proofs)
147130since all proven procedures execute in a single context.
148131
149- ### S-4: Emit as pure no-op
132+ ### S-4: Emit records event IDs (matched)
150133
151- ** Lean:** ` execEmit ` checks stack >= 1, returns state unchanged
134+ ** Lean:** ` execEmit ` reads top stack element as event ID, appends it
135+ to ` s.events ` . ` emitImm v ` appends ` v ` directly. Stack unchanged.
152136** Rust:** ` op_emit ` reads top element as event ID, dispatches to host.
153- Stack is unchanged in both .
137+ Stack unchanged.
154138
155- The Lean model does not extract the event ID or model host interaction.
156- This is correct for functional semantics since emit does not modify the
157- VM state (stack, memory, advice) .
139+ The Lean model now records emitted event IDs in the state's ` events `
140+ field (most recent first). This matches the Rust VM's event dispatch
141+ behavior at the state level .
158142
159143** ` emitImm ` in ` u64::divmod ` :** The divmod procedure begins with
160144` .inst (.emitImm 14153021663962350784) ` . In the real Miden VM, this
161- emit triggers the host to push the quotient and remainder (as u32
162- limbs) onto the advice stack. The subsequent ` advPush 2 ` then
163- consumes those values. The Lean model abstracts this host interaction
164- by treating emitImm as a no-op and requiring the divmod correctness
165- theorem (` u64_divmod_correct ` ) to take explicit advice-tape
166- hypotheses: the advice stack must contain ` [q_hi, q_lo, r_hi, r_lo] `
167- satisfying the division relation ` divisor * q + r = n ` with
168- ` r < divisor ` . This makes the host's role explicit in the theorem
169- statement rather than modeling it in the semantics.
170-
171- ** Soundness for proven theorems:** ` emitImm ` is a pure no-op in both
172- the Lean model and the Rust VM (neither modifies stack, memory, or
173- locals). The only functional effect is the host pushing advice values,
174- which the Lean model captures via explicit theorem hypotheses
175- (the advice stack must contain specific values). This is strictly
176- stronger than the Rust model: the theorem specifies exactly what
177- the host must provide, rather than relying on an implicit host
178- protocol.
145+ emit triggers the host to push the quotient and remainder onto the
146+ advice stack. The Lean model records the event ID but does not model
147+ host-side effects. The divmod correctness theorem takes explicit
148+ advice-tape hypotheses instead.
149+
150+ Remaining difference: the Lean model does not model host-side effects
151+ triggered by emit events (e.g., advice stack population). This is
152+ captured by explicit hypotheses in affected theorems.
179153
180154### S-5: Error codes as strings
181155
@@ -251,4 +225,5 @@ cases across all modeled instruction categories:
251225| Advice stack | 4 | ordering, insufficient |
252226| Memory | 4 | store/load, unwritten, OOB |
253227| Control flow | 6 | ifElse, repeat, while |
228+ | Stack depth | 8 | overflow on push/dup/padw/etc |
254229
0 commit comments