Operand stack
Miden VM is a stack machine. The stack is a push-down stack of practically unlimited depth (in practical terms, the depth will never exceed ), but only the top items are directly accessible to the VM. Items on the stack are elements in a prime field with modulus .
To keep the constraint system for the stack manageable, we impose the following rules:
- All operations executed on the VM can shift the stack by at most one item. That is, the end result of an operation must be that the stack shrinks by one item, grows by one item, or the number of items on the stack stays the same.
- Stack depth must always be greater than or equal to . At the start of program execution, the stack is initialized with exactly input values, all of which could be 's.
- By the end of program execution, exactly items must remain on the stack (again, all of them could be 's). These items comprise the output of the program.
To ensure that managing stack depth does not impose significant burden, we adopt the following rule:
- When the stack depth is , removing additional items from the stack does not change its depth. To keep the depth at , 's are inserted into the deep end of the stack for each removed item.
Stack representation
The VM allocates trace columns for the stack. The layout of the columns is illustrated below.

The meaning of the above columns is as follows:
- are the columns representing the top slots of the stack.
- Column contains the number of items on the stack (i.e., the stack depth). In the above picture, there are 16 items on the stacks, so .
- Column contains an address of a row in the "overflow table" in which we'll store the data that doesn't fit into the top slots. When , it means that all stack data fits into the top slots of the stack.
- Helper column is used to ensure that stack depth does not drop below . Values in this column are set by the prover non-deterministically to when , and to any other value otherwise.
Overflow table
To track data outside the top 16 stack slots, the VM uses the stack-overflow virtual relation.
The table itself can be thought of as having 3 columns as illustrated below.

The meaning of the columns is as follows:
- Column contains row address. Every address in the table must be unique.
- Column contains the value that overflowed the stack.
- Column contains the address of the row containing the value that overflowed the stack right before the value in the current row. For example, in the picture above, first value overflowed the stack, then overflowed the stack, and then value overflowed the stack. Thus, row with value points back to the row with value , and row with value points back to the row with value .
For lookup purposes, a row is encoded using the stack-overflow bus prefix and lookup challenge :
The lookup argument balances insertions and removals of these encoded rows. By the end of execution, every row inserted into the overflow relation must have a matching removal.
There are a couple of other rules we'll need to enforce:
- We can delete a row only after the row has been inserted into the table.
- We can't insert a row with the same address twice into the table (even if the row was inserted and then deleted).
How these are enforced will be described a bit later.
Right shift
If an operation adds data to the stack, we say that the operation caused a right shift. For example, PUSH and DUP operations cause a right shift. Graphically, this looks like so:

Here, we pushed value onto the stack. All other values on the stack are shifted by one slot to the right and the stack depth increases by . There is not enough space at the top of the stack for all values, thus, needs to be moved to the overflow table.
To do this, we need to rely on another column: . This is a system column which keeps track of the current VM cycle. The value in this column is simply incremented by with every step.
The row we want to add to the overflow table is defined by tuple , and after it is added, the table would look like so:
The reason we use VM clock cycle as row address is that the clock cycle is guaranteed to be unique, and thus, the same row can not be added to the table twice.
Let's push another item onto the stack:

Again, as we push onto the stack, all items on the stack are shifted to the right, and now needs to be moved to the overflow table. The tuple we want to insert into the table now is . After the operation, the overflow table will look like so:

Notice that for row which contains value points back to the row with address .
Overall, during a right shift we do the following:
- Increment stack depth by .
- Shift stack columns right by slot.
- Add a row to the overflow table described by tuple .
- Set the next value of to the current value of .
Also, as mentioned previously, the prover sets values in non-deterministically to .
Left shift
If an operation removes an item from the stack, we say that the operation caused a left shift. For example, a DROP operation causes a left shift. Assuming the stack is in the state we left it at the end of the previous section, graphically, this looks like so:

Overall, during the left shift we do the following:
- When stack depth is greater than :
- Decrement stack depth by .
- Shift stack columns left by slot.
- Remove a row from the overflow table with equal to the current value of .
- Set the next value of to the value in of the removed overflow table row.
- Set the next value of to the value in of the removed overflow table row.
- When the stack depth is equal to :
- Keep the stack depth the same.
- Shift stack columns left by slot.
- Set the value of to .
- Set the value to to (or any other value).
If the stack depth becomes (or remains) , the prover can set to any value (e.g., ). But if the depth is greater than the prover sets to .
AIR Constraints
To simplify constraint descriptions, we'll assume that the VM exposes two binary flag values described below.
| Flag | Degree | Description |
|---|---|---|
| 6 | Low-degree aggregate for operations which shift the stack right by one element. | |
| 5 | Low-degree aggregate for ordinary one-element left shifts; DYNCALL is handled separately. |
These flags are mutually exclusive. That is, if , then and vice versa. However, both flags can be set to simultaneously. How these flags are computed is described here.
We also use a combined call-entry flag to denote entry into a new execution context.
Here, . We use
for an END that consumes a caller-frame entry. The block-stack
relation authenticates the restored depth, so caller-frame restoration is handled separately from
the ordinary stack-depth rule below.
Stack overflow flag
Additionally, we'll define a flag to indicate whether the overflow table contains values. This flag will be set to when the overflow table is empty, and to otherwise (i.e., when stack depth ). This flag can be computed as follows:
To ensure that this flag is set correctly, we need to impose the following constraint:
The above constraint can be satisfied only when either of the following holds:
- , in which case evaluates to , regardless of the value of .
- , in which case cannot be equal to (and must be set to ).
Stack depth constraints
To make sure stack depth column is updated correctly, we need to impose the following constraints:
| Condition | Constraint__ | Description |
|---|---|---|
| A one-element right shift adds one row to the overflow table. | ||
| | A one-element left shift removes one row when the overflow table is not empty. | |
| On CALL/SYSCALL/DYNCALL entry, the stack depth resets to the accessible top 16 positions. | ||
| from caller frame | A caller-frame END restores the authenticated saved depth through the block-stack relation. | |
| otherwise | In all other cases, stack depth should not change. |
For rows with , we can combine the shift constraints into a single expression as follows:
On CALL/SYSCALL/DYNCALL entry, we instead enforce via a dedicated term. Caller-frame END depth updates are handled by the block-stack relation.
Overflow table constraints
On a right shift, tuple is added to the overflow relation. Its denominator is:
On a left shift with non-empty overflow, tuple is removed. Its denominator is:
When DYNCALL executes with non-empty overflow, it removes instead; holds the saved caller overflow address because is reset on call entry. Its denominator is:
The row contribution to the LogUp sum is:
The compiled lookup constraint cross-multiplies these fractions and has degree .
On a left shift with non-empty overflow, the constraint forces the prover to set the next values of and to values and of the row removed from the overflow table.
On a right shift, we also need to make sure that the next value of is set to the current value of . This can be done with the following constraint:
Entering a CALL, DYNCALL, or SYSCALL context starts with an empty overflow table, so we also enforce:
All other operations must preserve , except for right shifts, non-empty left shifts, or
caller-frame END rows. Those transitions update or restore through the overflow or
block-stack relation. Define:
The direct preservation constraint is:
When the overflow table is empty, every operation that left-shifts into position 15 must set to zero. DYNCALL is excluded from because call entry resets the ordinary depth and overflow-pointer columns, but it still performs this stack shift. Thus the local selector for this constraint is , and we enforce:
Boundary constraints
In addition to the constraints described above, we also need to enforce the following boundary constraints:
- at the first and at the last row of execution trace.
- at the first and at the last row of execution trace.