Replies: 7 comments 4 replies
-
Also, cc @otrho as I believe you've been dealing with some of this recently :) |
Beta Was this translation helpful? Give feedback.
-
If we have resources, the third option sounds like the right thing to do.This will also break a lot of the code of our Pioneers. We need to take this into account before making a decision. |
Beta Was this translation helpful? Give feedback.
-
I'm a bit confused. Does it mean the first element (with value 1) is the least significant element in the word? Otherwise I'd call it a big-endian order. https://en.wikipedia.org/wiki/Endianness#/media/File:32bit-Endianess.svg |
Beta Was this translation helpful? Give feedback.
-
Overall, I find this hard to think about whenever I work on doing the same thing in Rust and in MASM; everything basically needs to be reversed. E.g. these are the equivalent operations in Rust and MASM: miden_objects::Hasher::hash_elements(&[0, 1, 2, 3, 4, 5, 6, 7].map(Felt::new)) and
So both 1) the words are reversed and 2) the elements within the words are reversed in the stack comment versus the way we write the slice in Rust. The main problem with this is just the way it is "displayed". What I don't understand is why our stack grows to the left. I think every other visualization of memory or stack I know of grows to the right, e.g.
Granted, this is not exactly the issue this discussion is about, but it felt related enough to bring it up here as well. Also, I'm sure there was some rationale for making stack comments the way they are now but I don't know that rationale, so if there's a good reason please let me know 🙂. If we were to change both this stack state ordering and go with the third option, then the word order might still match, but not the felt order within the words, if I understand correctly, but maybe that's still better than having both word and felt order be reversed. |
Beta Was this translation helpful? Give feedback.
-
If I ignore the reality of the pain that such a change would incur, IMO the best ordering would be the opposite of what we have, mainly for the reason @PhilippGackstatter touched on. Say I have the data
So basically I load the data from end to beginning (which is intuitive because of my mental model that I'm working with a stack), after which the data looks nice and "in proper order" on the stack (with the first item sitting at the top). If I run that code today, I get
Aren't a bunch of these assembler-specific, and could be true irrespective of our choice of stack ordering? e.g. As for |
Beta Was this translation helpful? Give feedback.
-
I've opened an issue to track possibly introducing a new |
Beta Was this translation helpful? Give feedback.
-
I will try to describe the problem we are considering through the formulation of a principle, call it the principle of Structural Preservation, which can be defined as the maintenance of data organization and semantic relationships when values move between different VM components (operand stack, advice stack, and linear memory). This principle ensures that compound data structures retain their internal organization and semantic meaning across all VM operations that transfer them across components. 1. Introduction and Motivation1.1 The ProblemMiden VM operates with field elements as its base data type. However, many computational patterns require working with compound structures:
Currently, Miden VM exhibits structural inconsistencies when these compound values move between memory and stack. For example:
This inconsistency breaks what programmers would expect—the same logical data structure should maintain its internal organization regardless of where it resides in the VM. 1.2 Why Structural Preservation MattersConceptual Clarity: Developers should think about data structures, not memory layouts. When a word represents a 256-bit hash, its internal structure should remain consistent whether it's in memory, on the operand stack, or in the advice provider. Cognitive Load: Related to the previous point, developers shouldn't need to maintain mental maps of different ordering conventions across VM components. Correctness: Many cryptographic and arithmetic operations depend on specific conventions for element ordering. Structural inconsistencies can lead to subtle bugs, especially in multi-precision arithmetic where limb ordering is critical and especially when crossing boundaries demanding a change in ordering conventions. 2. Structural Preservation Principle2.1 Core DefinitionStructural Preservation: When a compound data structure moves between any two VM components (operand stack, advice stack, linear memory), its internal organization must remain invariant under a well-defined, consistent mapping. 2.2 Intuition and ImplicationsIntuitively, we can say that, similar to how we wouldn't expect a This has several implications: 2.2.1 Individual vs Structured Push OperationsWe accept, positively, the mismatch between the behavior of:
Similarly for double-words:
Note: 2.2.2 Advice Stack OperationsWhen moving between the advice stack and operand stack, we accept, positively, that:
For double-word operations: 2.2.3 Multi-Precision ArithmeticPacking and unpacking of
3. Structural Preservation in Practice3.1 Operand StackCurrent Behavior:
Structure-Preserving Behavior:
Rationale: When we push a word as a structured unit, the first element should be most accessible (on top), maintaining the logical ordering 3.2 Linear MemoryLayout:
Structure-Preserving Operations:
3.3 Advice StackStructure-Preserving Operations: Starting with advice stack layout:
Operations:
3.4 Cross-Component ConsistencyMaintaining the above principle implies that logical positions should correspond to physical positions across all components:
4. Multi-Precision Arithmetic Considerations4.1 LSB-Accessible PrincipleFor multi-precision integers, we adopt the LSB-Accessible Principle: the least significant limb should be most accessible (on top of stack). Rationale:
4.2 Examplesu32_split Operation:
u64 Operations:
Ext2Felt Operations (quadratic extension field elements):
5. Cryptographic Hash State Management5.1 Hasher State OrganizationOur cryptographic operations based on
5.2 Stack Layout for Structure Preserving Hash OperationsBefore
Before
Note: The LSB-Accessible Principle ensures smooth transitions between these layouts. 5.3 Memory Streaming OperationsBoth Example with Initial State:
Structure-Preserving
This ensures that the double-word maintains its internal structure when transferred from advice/memory to the operand stack, making it compatible with subsequent 6. OutlookIf the above, makes sense, and provided we haven't missed something fundamental, then I think there is a strong case of accepting the (significant) cost associated with the migration effort. The proposal of @bitwalker will help make the transition gradual and less painful, though my preference would be to get it out of the way as soon as possible. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Currently, the ordering of elements in Miden VM works as follows:
[1, 2, 3, 4]
in memory at address0
, thenmem[0] == 1
,mem[1] == 2
etc.[1, 2, 3, 4]
on the stack, thenstack[0] == 4
,stack[1] == 3
etc.Why this approach was chosen
This approach was chosen to make the instruction set as internally consistent as possible (or at least that was the goal). Specifically, we wanted the following to be equivalent:
There are probably some more of these that I'm forgetting, but the core of the issue is that if we put 4 elements onto the stack one-by-one and a word, we won't end up in the same order unless we reverse the elements of the word. Same applies for 8 elements and 2 words etc.
Issues with this approach
While this approach is (mostly) internally consistent, it does have some issues:
mem_storew.0
is not the same asmem_store.0 mem_store.1 mem_store.2 mem_store.3
u128
value from memory onto the stack, it would place twou64
values in the following order:[val_lo, val_hi]
(@bitwalker can correct me if I'm wrong).The last point is especially annoying because it leaves us with the following options:
u128
values in Rust.u128
module in our stdlib that follows the WASM convention.a. This would also imply that we should re-work our
u64
module because it would be weird to haveu128
module work one way andu64
work in a different way.The first option is probably not an option - we do need to support
u128
integers in Rust.The second option is a bit annoying because how
u64
andu128
modules work will be inconsistent with the rest of the VM (though, we already have such inconsistencies - e.g., #571) - but it would be pretty simple to implement.The 3rd option is the "right way" to do it, but it is by far the hardest. Of the top of my head:
mem_loadw
,mem_storew
, but alsomem_stream
,hperm
, and mostu32
instructions.RPO2
).push.1.2.3.4
will be different frompush.1 push.2 push.3 push.4
.This is a LOT of work - though, potentially still doable until we go to mainnet (after mainnet, it won't be doable).
Would love to here other's though on these options or on anything else I missed here.
Beta Was this translation helpful? Give feedback.
All reactions