diff --git a/hir-type/src/lib.rs b/hir-type/src/lib.rs index 06989967..a3febf9b 100644 --- a/hir-type/src/lib.rs +++ b/hir-type/src/lib.rs @@ -3,10 +3,82 @@ extern crate alloc; use alloc::{alloc::Layout, boxed::Box, vec::Vec}; -use core::fmt; +use core::{fmt, num::NonZeroU8}; +const FELT_SIZE: usize = core::mem::size_of::(); const WORD_SIZE: usize = core::mem::size_of::<[u64; 4]>(); +/// This enum represents a [Type] decorated with the way in which it should be represented +/// on the operand stack of the Miden VM. +/// +/// We don't use this representation when laying out types in memory however, since we must +/// preserve the semantics of a byte-addressable address space for consumers of the IR. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum TypeRepr { + /// This value is a zero-sized type, and is not actually reified on the operand stack + Zst(Type), + /// This value is a sized type that fits in a single element on the operand stack + Default(Type), + /// This value is a sized type that is split across multiple elements on the operand stack, + /// for example, the representation of u64 in Miden uses two field elements, rather than + /// packing it in a single element. + /// + /// We call these "sparse" because the value is split along arbitrary lines, rather than + /// due to binary representation requirements. + Sparse(Type, NonZeroU8), + /// This value is a sized type which is encoded into one or more field elements as follows: + /// + /// * Each element is logically split into multiple u32 values + /// * The type is encoded into its binary representation, and spread across as many u32 + /// values as necessary to hold it + /// * The number of u32 values is then rounded up to the nearest multiple of two + /// * The u32 values are packed into field elements using the inverse of `u32.split` + /// * The packed field elements are pushed on the stack such that the lowest bits of + /// the binary representation are nearest to the top of the stack. + Packed(Type), +} +impl TypeRepr { + /// Returns the size in field elements of this type, in the given representation + pub fn size(&self) -> usize { + match self { + Self::Zst(_) => 0, + Self::Default(_) => 1, + Self::Sparse(_, n) => n.get() as usize, + Self::Packed(ref ty) => ty.size_in_felts(), + } + } + + /// Returns true if this type is a zero-sized type + pub fn is_zst(&self) -> bool { + matches!(self, Self::Zst(_)) + } + + /// Returns true if this type is sparsely encoded + pub fn is_sparse(&self) -> bool { + matches!(self, Self::Sparse(_, _)) + } + + /// Returns true if this type is densely encoded (packed) + pub fn is_packed(&self) -> bool { + matches!(self, Self::Packed(_)) + } + + /// Returns a reference to the underlying [Type] + pub fn ty(&self) -> &Type { + match self { + Self::Zst(ref ty) + | Self::Default(ref ty) + | Self::Sparse(ref ty, _) + | Self::Packed(ref ty) => ty, + } + } +} +impl fmt::Display for TypeRepr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(self.ty(), f) + } +} + /// Represents the type of a value #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Type { @@ -94,54 +166,184 @@ impl Type { } pub fn is_signed_integer(&self) -> bool { - match self { - Self::I1 | Self::I8 | Self::I16 | Self::I32 | Self::I64 | Self::I128 | Self::Isize => { - true - } - _ => false, - } + matches!( + self, + Self::I8 | Self::I16 | Self::I32 | Self::I64 | Self::I128 | Self::Isize + ) + } + + pub fn is_unsigned_integer(&self) -> bool { + matches!( + self, + Self::U8 | Self::U16 | Self::U32 | Self::U64 | Self::U128 | Self::Usize + ) } #[inline] pub fn is_float(&self) -> bool { - match self { - Self::F64 => true, - _ => false, - } + matches!(self, Self::F64) } #[inline] pub fn is_felt(&self) -> bool { - match self { - Self::Felt => true, - _ => false, - } + matches!(self, Self::Felt) } #[inline] pub fn is_pointer(&self) -> bool { - match self { - Self::Ptr(_) | Self::NativePtr(_) => true, - _ => false, - } + matches!(self, Self::Ptr(_) | Self::NativePtr(_)) } #[inline] pub fn is_struct(&self) -> bool { - match self { - Self::Struct(_) => true, - _ => false, - } + matches!(self, Self::Struct(_)) } #[inline] pub fn is_array(&self) -> bool { - match self { - Self::Array(_, _) => true, + matches!(self, Self::Array(_, _)) + } + + /// Returns true if `self` and `other` are compatible operand types for a binary operator, e.g. `add` + /// + /// In short, the rules are as follows: + /// + /// * The operand order is assumed to be `self other`, i.e. `op` is being applied + /// to `self` using `other`. The left-hand operand is used as the "controlling" type + /// for the operator, i.e. it determines what instruction will be used to perform the + /// operation. + /// * The operand types must be numeric, or support being manipulated numerically + /// * If the controlling type is unsigned, it is never compatible with signed types, because Miden + /// instructions for unsigned types use a simple unsigned binary encoding, thus they will not handle + /// signed operands using two's complement correctly. + /// * If the controlling type is signed, it is compatible with both signed and unsigned types, as long + /// as the values fit in the range of the controlling type, e.g. adding a `u16` to an `i32` is fine, + /// but adding a `u32` to an `i32` is not. + /// * Pointer types are permitted to be the controlling type, and since they are represented using u32, + /// they have the same compatibility set as u32 does. In all other cases, pointer types are treated + /// the same as any other non-numeric type. + /// * Non-numeric types are always incompatible, since no operators support these types + pub fn is_compatible_operand(&self, other: &Type) -> bool { + match (self, other) { + (Type::I1, Type::I1) => true, + (Type::I8, Type::I8) => true, + (Type::U8, Type::U8) => true, + (Type::I16, Type::I8 | Type::U8 | Type::I16) => true, + (Type::U16, Type::U8 | Type::U16) => true, + ( + Type::I32 | Type::Isize, + Type::I8 | Type::U8 | Type::I16 | Type::U16 | Type::I32 | Type::Isize, + ) => true, + (Type::U32 | Type::Usize, Type::U8 | Type::U16 | Type::U32 | Type::Usize) => true, + ( + Type::Felt, + Type::I8 + | Type::U8 + | Type::I16 + | Type::U16 + | Type::I32 + | Type::U32 + | Type::Isize + | Type::Usize + | Type::Felt, + ) => true, + ( + Type::I64, + Type::I8 + | Type::U8 + | Type::I16 + | Type::U16 + | Type::I32 + | Type::U32 + | Type::Isize + | Type::Usize + | Type::Felt + | Type::I64, + ) => true, + (Type::U64, Type::U8 | Type::U16 | Type::U32 | Type::Usize | Type::U64) => true, + ( + Type::I128, + Type::I8 + | Type::U8 + | Type::I16 + | Type::U16 + | Type::I32 + | Type::U32 + | Type::Isize + | Type::Usize + | Type::Felt + | Type::I64 + | Type::U64 + | Type::I128, + ) => true, + ( + Type::U128, + Type::U8 | Type::U16 | Type::U32 | Type::Usize | Type::U64 | Type::U128, + ) => true, + (Type::U256, rty) => rty.is_integer(), + (Type::F64, Type::F64) => true, + (Type::Ptr(_) | Type::NativePtr(_), Type::U8 | Type::U16 | Type::U32 | Type::Usize) => { + true + } _ => false, } } + /// Returns the [TypeRepr] corresponding to this type. + /// + /// If the type is unknown, returns `None`. + pub fn repr(&self) -> Option { + match self { + Type::Unknown | Type::Never => None, + // The unit type is a zero-sized type + Type::Unit => Some(TypeRepr::Zst(Type::Unit)), + // All numeric types < 64 bits in size use the default representation + ty @ (Type::I1 + | Type::I8 + | Type::U8 + | Type::I16 + | Type::U16 + | Type::I32 + | Type::U32 + | Type::Isize + | Type::Usize + | Type::F64 + | Type::Felt) => Some(TypeRepr::Default(ty.clone())), + // 64-bit integers are represented sparsely, as two field elements + ty @ (Type::I64 | Type::U64) => Some(TypeRepr::Sparse(ty.clone(), unsafe { + NonZeroU8::new_unchecked(2) + })), + // 128-bit integers are represented sparsely, as three field elements + ty @ (Type::I128 | Type::U128) => Some(TypeRepr::Sparse(ty.clone(), unsafe { + NonZeroU8::new_unchecked(3) + })), + // 256-bit integers are represented sparsely, as five field elements + ty @ Type::U256 => Some(TypeRepr::Sparse(ty.clone(), unsafe { + NonZeroU8::new_unchecked(5) + })), + // All pointer types use a single field element + ty @ (Type::Ptr(_) | Type::NativePtr(_)) => Some(TypeRepr::Default(ty.clone())), + // Empty structs are zero-sized by definition + Type::Struct(ref fields) if fields.is_empty() => { + Some(TypeRepr::Zst(Type::Struct(Vec::new()))) + } + // Structs are "packed" across one or more field elements + Type::Struct(ref fields) => { + match fields.as_slice() { + // Single-field structs have transparent representation + [field_ty] => field_ty.repr(), + fields => Some(TypeRepr::Packed(Type::Struct(fields.to_vec()))), + } + } + // Zero-sized arrays are treated as zero-sized types + ty @ Type::Array(_, 0) => Some(TypeRepr::Zst(ty.clone())), + // Single-element arrays have transparent representation + Type::Array(ref element_ty, 1) => element_ty.repr(), + // N-ary arrays are "packed" across one or more field elements + ty @ Type::Array(_, _) => Some(TypeRepr::Packed(ty.clone())), + } + } + #[inline] pub fn pointee(&self) -> Option<&Type> { use core::ops::Deref; @@ -179,6 +381,13 @@ impl Type { self.layout().pad_to_align().size() } + /// Returns the size in field elements of this type, including necessary alignment padding + pub fn size_in_felts(&self) -> usize { + let bytes = self.size_in_bytes(); + let trailing = bytes % FELT_SIZE; + (bytes / FELT_SIZE) + ((trailing > 0) as usize) + } + /// Returns the size in words of this type, including necessary alignment padding pub fn size_in_words(&self) -> usize { let bytes = self.size_in_bytes(); diff --git a/hir/src/asm/builder.rs b/hir/src/asm/builder.rs new file mode 100644 index 00000000..85f7ebeb --- /dev/null +++ b/hir/src/asm/builder.rs @@ -0,0 +1,2073 @@ +use crate::{ + CallConv, DataFlowGraph, Felt, FunctionIdent, Inst, InstBuilder, Instruction, Overflow, + SourceSpan, Type, TypeRepr, Value, +}; + +use super::*; + +/// Used to construct an [InlineAsm] instruction, while checking the input/output types, +/// and enforcing various safety invariants. +pub struct MasmBuilder { + /// The [InstBuilderBase] which we are building from + builder: B, + /// The span of the resulting inline assembly block + span: SourceSpan, + /// The inline assembly block we're building + asm: InlineAsm, + /// The current code block in the inline assembly that the builder is inserting into + current_block: MasmBlockId, + /// The emulated operand stack, primarily used to track the number of stack elements + /// upon entry and exit from the inline assembly block. + /// + /// The only `Type` which is represented on this stack is `Type::Felt`, since we're only + /// interested in the number of stack elements at any given point. In the future, we may + /// choose to do additional type checking. + /// + /// Upon exit from the inline assembly block, the state of the stack must have enough elements + /// to store a value of the expected result type, represented by `ty`. Whether those elements + /// actually store a valid value of that type is beyond the scope of this builder, for now. + stack: OperandStack, +} +impl<'f, B: InstBuilder<'f>> MasmBuilder { + /// Construct a new inline assembly builder in the function represented by `dfg`, to be inserted at `ip`. + /// + /// The `args` list represents the arguments which will be visible on the operand stack in this inline assembly block. + /// + /// The `results` set represents the types that are expected to be found on the operand stack when the inline + /// assembly block finishes executing. Use an empty set to represent no results. + /// + /// Any attempt to modify the operand stack beyond what is made visible via arguments, or introduced within the + /// inline assembly block, will cause an assertion to fail. + pub fn new(mut builder: B, args: &[Value], results: Vec, span: SourceSpan) -> Self { + // Construct the initial operand stack with the given arguments + let mut stack = OperandStack::::default(); + { + let dfg = builder.data_flow_graph(); + for arg in args.iter().rev().copied() { + stack.push(dfg.value_type(arg).clone()); + } + } + + // Construct an empty inline assembly block with the given arguments + let mut asm = InlineAsm::new(results); + { + let dfg = builder.data_flow_graph_mut(); + let mut vlist = ValueList::default(); + vlist.extend(args.iter().copied(), &mut dfg.value_lists); + asm.args = vlist; + } + + let current_block = asm.body; + Self { + builder, + span, + asm, + current_block, + stack, + } + } + + /// Create a new, empty MASM code block, for use with control flow instructions + #[inline] + pub fn create_block(&mut self) -> MasmBlockId { + self.asm.create_block() + } + + /// Change the insertion point of the builder to the end of `block` + #[inline(always)] + pub fn switch_to_block(&mut self, block: MasmBlockId) { + self.current_block = block; + } + + /// Get a builder for a single MASM instruction + pub fn ins<'a, 'b: 'a>(&'b mut self) -> MasmOpBuilder<'a> { + MasmOpBuilder { + dfg: self.builder.data_flow_graph_mut(), + asm: &mut self.asm, + stack: &mut self.stack, + ip: self.current_block, + } + } + + /// Finalize this inline assembly block, inserting it into the `Function` from which this builder was derived. + /// + /// Returns the [Inst] which corresponds to the inline assembly instruction, and the inner [DataFlowGraph] reference + /// held by the underlying [InstBuilderBase]. + pub fn build(self) -> Inst { + if self.asm.results.is_empty() { + assert!(self.stack.is_empty(), "invalid inline assembly: expected operand stack to be empty upon exit, found: {:?}", self.stack.display()); + } else { + let mut len = 0; + for ty in self.asm.results.iter() { + let repr = ty.repr().expect("invalid result type"); + len += repr.size(); + } + assert_eq!( + len, + self.stack.len(), + "invalid inline assembly: needed {} elements on the operand stack, found: {:?}", + len, + self.stack.display() + ); + } + + let span = self.span; + let data = Instruction::InlineAsm(self.asm); + self.builder.build(data, Type::Unit, span).0 + } +} + +/// Used to construct a single MASM opcode +pub struct MasmOpBuilder<'a> { + dfg: &'a mut DataFlowGraph, + /// The inline assembly block being created + asm: &'a mut InlineAsm, + /// The state of the operand stack at this point in the program + stack: &'a mut OperandStack, + /// The block to which this builder should append the instruction it builds + ip: MasmBlockId, +} +impl<'a> MasmOpBuilder<'a> { + /// Pads the stack with four zero elements + pub fn padw(mut self) { + self.build(self.ip, MasmOp::Padw); + } + + /// Pushes an element on the stack + pub fn push(mut self, imm: Felt) { + self.build(self.ip, MasmOp::Push(imm)); + } + + /// Pushes a word on the stack + pub fn pushw(mut self, word: [Felt; 4]) { + self.build(self.ip, MasmOp::Pushw(word)); + } + + /// Pushes an element representing an unsigned 8-bit integer on the stack + pub fn push_u8(mut self, imm: u8) { + self.build(self.ip, MasmOp::PushU8(imm)); + } + + /// Pushes an element representing an unsigned 16-bit integer on the stack + pub fn push_u16(mut self, imm: u16) { + self.build(self.ip, MasmOp::PushU16(imm)); + } + + /// Pushes an element representing an unsigned 32-bit integer on the stack + pub fn push_u32(mut self, imm: u32) { + self.build(self.ip, MasmOp::PushU32(imm)); + } + + /// Drops the element on the top of the stack + pub fn drop(mut self) { + self.build(self.ip, MasmOp::Drop); + } + + /// Drops the word (first four elements) on the top of the stack + pub fn dropw(mut self) { + self.build(self.ip, MasmOp::Dropw); + } + + /// Duplicates the `n`th element from the top of the stack, to the top of the stack + /// + /// A `n` of zero, duplicates the element on top of the stack + /// + /// The valid range for `n` is 0..=15 + pub fn dup(mut self, n: usize) { + self.build(self.ip, MasmOp::Dup(n as u8)); + } + + /// Duplicates the `n`th word from the top of the stack, to the top of the stack + /// + /// A `n` of zero, duplicates the word on top of the stack + /// + /// The valid range for `n` is 0..=3 + pub fn dupw(mut self, n: usize) { + self.build(self.ip, MasmOp::Dupw(n as u8)); + } + + /// Swaps the `n`th element and the element on top of the stack + /// + /// The valid range for `n` is 1..=15 + pub fn swap(mut self, n: usize) { + self.build(self.ip, MasmOp::Swap(n as u8)); + } + + /// Swaps the `n`th word and the word on top of the stack + /// + /// The valid range for `n` is 1..=3 + pub fn swapw(mut self, n: usize) { + self.build(self.ip, MasmOp::Swapw(n as u8)); + } + + /// Moves the `n`th element to the top of the stack + /// + /// The valid range for `n` is 2..=15 + pub fn movup(mut self, idx: usize) { + self.build(self.ip, MasmOp::Movup(idx as u8)); + } + + /// Moves the `n`th word to the top of the stack + /// + /// The valid range for `n` is 2..=3 + pub fn movupw(mut self, idx: usize) { + self.build(self.ip, MasmOp::Movupw(idx as u8)); + } + + /// Moves the element on top of the stack, making it the `n`th element + /// + /// The valid range for `n` is 2..=15 + pub fn movdn(mut self, idx: usize) { + self.build(self.ip, MasmOp::Movdn(idx as u8)); + } + + /// Moves the word on top of the stack, making it the `n`th word + /// + /// The valid range for `n` is 2..=3 + pub fn movdnw(mut self, idx: usize) { + self.build(self.ip, MasmOp::Movdnw(idx as u8)); + } + + /// Pops a boolean element off the stack, and swaps the top two elements + /// on the stack if that boolean is true. + /// + /// Traps if the conditional is not 0 or 1. + pub fn cswap(mut self) { + self.build(self.ip, MasmOp::Cswap); + } + + /// Pops a boolean element off the stack, and swaps the top two words + /// on the stack if that boolean is true. + /// + /// Traps if the conditional is not 0 or 1. + pub fn cswapw(mut self) { + self.build(self.ip, MasmOp::Cswapw); + } + + /// Pops a boolean element off the stack, and drops the top element on the + /// stack if the boolean is true, otherwise it drops the next element down. + /// + /// Traps if the conditional is not 0 or 1. + pub fn cdrop(mut self) { + self.build(self.ip, MasmOp::Cdrop); + } + + /// Pops a boolean element off the stack, and drops the top word on the + /// stack if the boolean is true, otherwise it drops the next word down. + /// + /// Traps if the conditional is not 0 or 1. + pub fn cdropw(mut self) { + self.build(self.ip, MasmOp::Cdropw); + } + + /// Pops the top element on the stack, and traps if that element is != 1. + pub fn assert(mut self) { + self.build(self.ip, MasmOp::Assert); + } + + /// Pops the top element on the stack, and traps if that element is != 0. + pub fn assertz(mut self) { + self.build(self.ip, MasmOp::Assertz); + } + + /// Pops the top two elements on the stack, and traps if they are not equal. + pub fn assert_eq(mut self) { + self.build(self.ip, MasmOp::AssertEq); + } + + /// Pops the top two words on the stack, and traps if they are not equal. + pub fn assert_eqw(mut self) { + self.build(self.ip, MasmOp::AssertEq); + } + + /// Pops an element containing a memory address from the top of the stack, + /// and loads the first element of the word at that address to the top of the stack. + pub fn load(mut self) { + self.build(self.ip, MasmOp::MemLoad); + } + + /// Loads the first element of the word at the given address to the top of the stack. + pub fn load_imm(mut self, addr: u32) { + self.build(self.ip, MasmOp::MemLoadImm(addr)); + } + + /// Pops an element containing a memory address + element offset from the top of the stack, + /// and loads the element of the word at that address + offset to the top of the stack. + /// + /// NOTE: This is an experimental instruction which is not implemented in Miden VM yet. + pub fn load_offset(mut self) { + self.build(self.ip, MasmOp::MemLoadOffset); + } + + /// Loads the element of the word at the given address and element offset to the top of the stack. + /// + /// NOTE: This is an experimental instruction which is not implemented in Miden VM yet. + pub fn load_offset_imm(mut self, addr: u32, offset: u8) { + assert!( + offset < 4, + "invalid element offset, must be in the range 0..=3, got {}", + offset + ); + self.build(self.ip, MasmOp::MemLoadOffsetImm(addr, offset)); + } + + /// Pops an element containing a memory address from the top of the stack, + /// and loads the word at that address to the top of the stack. + pub fn loadw(mut self) { + self.build(self.ip, MasmOp::MemLoadw); + } + + /// Loads the word at the given address to the top of the stack. + pub fn loadw_imm(mut self, addr: u32) { + self.build(self.ip, MasmOp::MemLoadwImm(addr)); + } + + /// Pops two elements, the first containing a memory address from the top of the stack, + /// the second the value to be stored as the first element of the word at that address. + pub fn store(mut self) { + self.build(self.ip, MasmOp::MemStore); + } + + /// Pops an element from the top of the stack, and stores it as the first element of + /// the word at the given address. + pub fn store_imm(mut self, addr: u32) { + self.build(self.ip, MasmOp::MemStoreImm(addr)); + } + + /// Pops two elements, the first containing a memory address + element offset from the + /// top of the stack, the second the value to be stored to the word at that address, + /// using the offset to determine which element will be written to. + pub fn store_offset(mut self) { + self.build(self.ip, MasmOp::MemStoreOffset); + } + + /// Pops an element from the top of the stack, and stores it at the given offset of + /// the word at the given address. + pub fn store_offset_imm(mut self, addr: u32, offset: u8) { + assert!( + offset < 4, + "invalid element offset, must be in the range 0..=3, got {}", + offset + ); + self.build(self.ip, MasmOp::MemStoreOffsetImm(addr, offset)); + } + + /// Pops an element containing a memory address from the top of the stack, + /// and then pops a word from the stack and stores it as the word at that address. + pub fn storew(mut self) { + self.build(self.ip, MasmOp::MemStorew); + } + + /// Pops a word from the stack and stores it as the word at the given address. + pub fn storew_imm(mut self, addr: u32) { + self.build(self.ip, MasmOp::MemStorewImm(addr)); + } + + /// Begins construction of a `if.true` statement. + /// + /// An `if.true` pops a boolean value off the stack, and uses it to choose between + /// one of two branches. The "then" branch is taken if the conditional is true, + /// the "else" branch otherwise. + /// + /// NOTE: This function will panic if the top of the operand stack is not of boolean type + /// when called. + /// + /// You must ensure that both branches of the `if.true` statement leave the operand stack + /// in the same abstract state, so that when control resumes after the `if.true`, the remaining + /// program is well-formed. This will be validated automatically for you, but if validation + /// fails, the builder will panic. + pub fn if_true(self) -> IfTrueBuilder<'a> { + let cond = self.stack.pop().expect("operand stack is empty"); + assert_eq!( + cond, + Type::I1, + "expected while.true condition to be a boolean value" + ); + let out_stack = self.stack.clone(); + IfTrueBuilder { + dfg: self.dfg, + asm: self.asm, + in_stack: self.stack, + out_stack, + ip: self.ip, + then_blk: None, + else_blk: None, + } + } + + /// Begins construction of a `while.true` loop. + /// + /// A `while.true` pops a boolean value off the stack to use as the condition for + /// entering the loop, and will then execute the loop body for as long as the value + /// on top of the stack is a boolean and true. If the condition is not a boolean, + /// execution traps. + /// + /// NOTE: This function will panic if the top of the operand stack is not of boolean type + /// when called. + /// + /// Before finalizing construction of the loop body, you must ensure two things: + /// + /// 1. There is a value of boolean type on top of the operand stack + /// 2. The abstract state of the operand stack, assuming the boolean just mentioned + /// has been popped, must be consistent with the state of the operand stack when the + /// loop was entered, as well as if the loop was skipped due to the conditional being + /// false. The abstract state referred to here is the number, and type, of the elements + /// on the operand stack. + /// + /// Both of these are validated by [LoopBuilder], and a panic is raised if validation fails. + pub fn while_true(self) -> LoopBuilder<'a> { + let cond = self.stack.pop().expect("operand stack is empty"); + assert_eq!( + cond, + Type::I1, + "expected while.true condition to be a boolean value" + ); + let out_stack = self.stack.clone(); + let body = self.asm.create_block(); + LoopBuilder { + dfg: self.dfg, + asm: self.asm, + in_stack: self.stack, + out_stack, + ip: self.ip, + body, + style: LoopType::While, + } + } + + /// Begins construction of a `repeat` loop, with an iteration count of `n`. + /// + /// A `repeat` instruction requires no operands on the stack, and will execute the loop body `n` times. + /// + /// NOTE: The iteration count must be non-zero, or this function will panic. + pub fn repeat(self, n: u8) -> LoopBuilder<'a> { + assert!( + n > 0, + "invalid iteration count for `repeat.n`, must be non-zero" + ); + let out_stack = self.stack.clone(); + let body = self.asm.create_block(); + LoopBuilder { + dfg: self.dfg, + asm: self.asm, + in_stack: self.stack, + out_stack, + ip: self.ip, + body, + style: LoopType::Repeat(n), + } + } + + /// Executes the named procedure as a regular function. + pub fn exec(mut self, id: FunctionIdent) { + self.build(self.ip, MasmOp::Exec(id)); + } + + /// Executes the named procedure as a syscall. + pub fn syscall(mut self, id: FunctionIdent) { + self.build(self.ip, MasmOp::Syscall(id)); + } + + /// Pops two field elements from the stack, adds them, and places the result on the stack. + pub fn add(mut self) { + self.build(self.ip, MasmOp::Add); + } + + /// Pops a field element from the stack, adds the given value to it, and places the result on the stack. + pub fn add_imm(mut self, imm: Felt) { + self.build(self.ip, MasmOp::AddImm(imm)); + } + + /// Pops two field elements from the stack, subtracts the second from the first, and places the result on the stack. + pub fn sub(mut self) { + self.build(self.ip, MasmOp::Sub); + } + + /// Pops a field element from the stack, subtracts the given value from it, and places the result on the stack. + pub fn sub_imm(mut self, imm: Felt) { + self.build(self.ip, MasmOp::SubImm(imm)); + } + + /// Pops two field elements from the stack, multiplies them, and places the result on the stack. + pub fn mul(mut self) { + self.build(self.ip, MasmOp::Mul); + } + + /// Pops a field element from the stack, multiplies it by the given value, and places the result on the stack. + pub fn mul_imm(mut self, imm: Felt) { + self.build(self.ip, MasmOp::MulImm(imm)); + } + + /// Pops two field elements from the stack, divides the first by the second, and places the result on the stack. + pub fn div(mut self) { + self.build(self.ip, MasmOp::Div); + } + + /// Pops a field element from the stack, divides it by the given value, and places the result on the stack. + pub fn div_imm(mut self, imm: Felt) { + self.build(self.ip, MasmOp::DivImm(imm)); + } + + /// Negates the field element on top of the stack + pub fn neg(mut self) { + self.build(self.ip, MasmOp::Neg); + } + + /// Replaces the field element on top of the stack with it's multiplicative inverse, i.e. `a^-1 mod p` + pub fn inv(mut self) { + self.build(self.ip, MasmOp::Inv); + } + + /// Increments the field element on top of the stack + pub fn incr(mut self) { + self.build(self.ip, MasmOp::Incr); + } + + /// Pops an element, `a`, from the top of the stack, and places the result of `2^a` on the stack. + /// + /// Traps if `a` is not in the range 0..=63 + pub fn pow2(mut self) { + self.build(self.ip, MasmOp::Pow2); + } + + /// Pops two elements from the stack, `b` and `a` respectively, and places the result of `a^b` on the stack. + /// + /// Traps if `b` is not in the range 0..=63 + pub fn exp(mut self) { + self.build(self.ip, MasmOp::Exp); + } + + /// Pops an element from the stack, `a`, and places the result of `a^b` on the stack, where `b` is + /// the given immediate value. + /// + /// Traps if `b` is not in the range 0..=63 + pub fn exp_imm(mut self, exponent: u8) { + self.build(self.ip, MasmOp::ExpImm(exponent)); + } + + /// Pops a value off the stack, and applies logical NOT, and places the result back on the stack. + /// + /// Traps if the value is not 0 or 1. + pub fn not(mut self) { + self.build(self.ip, MasmOp::Not); + } + + /// Pops two values off the stack, applies logical AND, and places the result back on the stack. + /// + /// Traps if either value is not 0 or 1. + pub fn and(mut self) { + self.build(self.ip, MasmOp::And); + } + + /// Pops a value off the stack, applies logical AND with the given immediate, and places the result back on the stack. + /// + /// Traps if the value is not 0 or 1. + pub fn and_imm(mut self, imm: bool) { + self.build(self.ip, MasmOp::AndImm(imm)); + } + + /// Pops two values off the stack, applies logical OR, and places the result back on the stack. + /// + /// Traps if either value is not 0 or 1. + pub fn or(mut self) { + self.build(self.ip, MasmOp::Or); + } + + /// Pops a value off the stack, applies logical OR with the given immediate, and places the result back on the stack. + /// + /// Traps if the value is not 0 or 1. + pub fn or_imm(mut self, imm: bool) { + self.build(self.ip, MasmOp::OrImm(imm)); + } + + /// Pops two values off the stack, applies logical XOR, and places the result back on the stack. + /// + /// Traps if either value is not 0 or 1. + pub fn xor(mut self) { + self.build(self.ip, MasmOp::Xor); + } + + /// Pops a value off the stack, applies logical XOR with the given immediate, and places the result back on the stack. + /// + /// Traps if the value is not 0 or 1. + pub fn xor_imm(mut self, imm: bool) { + self.build(self.ip, MasmOp::XorImm(imm)); + } + + /// Pops two elements off the stack, and pushes 1 on the stack if they are equal, else 0. + pub fn eq(mut self) { + self.build(self.ip, MasmOp::Eq); + } + + /// Pops an element off the stack, and pushes 1 on the stack if that value and the given immediate are equal, else 0. + pub fn eq_imm(mut self, imm: Felt) { + self.build(self.ip, MasmOp::EqImm(imm)); + } + + /// Pops two words off the stack, and pushes 1 on the stack if they are equal, else 0. + pub fn eqw(mut self) { + self.build(self.ip, MasmOp::Eqw); + } + + /// Pops two elements off the stack, and pushes 1 on the stack if they are not equal, else 0. + pub fn neq(mut self) { + self.build(self.ip, MasmOp::Neq); + } + + /// Pops an element off the stack, and pushes 1 on the stack if that value and the given immediate are not equal, else 0. + pub fn neq_imm(mut self, imm: Felt) { + self.build(self.ip, MasmOp::NeqImm(imm)); + } + + /// Pops two elements off the stack, and pushes 1 on the stack if the first is greater than the second, else 0. + pub fn gt(mut self) { + self.build(self.ip, MasmOp::Gt); + } + + /// Pops an element off the stack, and pushes 1 on the stack if that value is greater than the given immediate, else 0. + pub fn gt_imm(mut self, imm: Felt) { + self.build(self.ip, MasmOp::GtImm(imm)); + } + + /// Pops two elements off the stack, and pushes 1 on the stack if the first is greater than or equal to the second, else 0. + pub fn gte(mut self) { + self.build(self.ip, MasmOp::Gte); + } + + /// Pops an element off the stack, and pushes 1 on the stack if that value is greater than or equal to the given immediate, else 0. + pub fn gte_imm(mut self, imm: Felt) { + self.build(self.ip, MasmOp::GteImm(imm)); + } + + /// Pops two elements off the stack, and pushes 1 on the stack if the first is less than the second, else 0. + pub fn lt(mut self) { + self.build(self.ip, MasmOp::Lt); + } + + /// Pops an element off the stack, and pushes 1 on the stack if that value is less than the given immediate, else 0. + pub fn lt_imm(mut self, imm: Felt) { + self.build(self.ip, MasmOp::LtImm(imm)); + } + + /// Pops two elements off the stack, and pushes 1 on the stack if the first is less than or equal to the second, else 0. + pub fn lte(mut self) { + self.build(self.ip, MasmOp::Lte); + } + + /// Pops an element off the stack, and pushes 1 on the stack if that value is less than or equal to the given immediate, else 0. + pub fn lte_imm(mut self, imm: Felt) { + self.build(self.ip, MasmOp::LteImm(imm)); + } + + /// Pops an element off the stack, and pushes 1 on the stack if that value is an odd number, else 0. + pub fn is_odd(mut self) { + self.build(self.ip, MasmOp::IsOdd); + } + + /// Pushes the current value of the cycle counter (clock) on the stack + pub fn clk(mut self) { + self.build(self.ip, MasmOp::Clk); + } + + /// Pushes 1 on the stack if the element on top of the stack is less than 2^32, else 0. + pub fn test_u32(mut self) { + self.build(self.ip, MasmOp::U32Test); + } + + /// Pushes 1 on the stack if every element of the word on top of the stack is less than 2^32, else 0. + pub fn testw_u32(mut self) { + self.build(self.ip, MasmOp::U32Testw); + } + + /// Traps if the element on top of the stack is greater than or equal to 2^32 + pub fn assert_u32(mut self) { + self.build(self.ip, MasmOp::U32Assert); + } + + /// Traps if either of the first two elements on top of the stack are greater than or equal to 2^32 + pub fn assert2_u32(mut self) { + self.build(self.ip, MasmOp::U32Assert2); + } + + /// Traps if any element of the first word on the stack are greater than or equal to 2^32 + pub fn assertw_u32(mut self) { + self.build(self.ip, MasmOp::U32Assertw); + } + + /// Casts the element on top of the stack, `a`, to a valid u32 value, by computing `a mod 2^32` + pub fn cast_u32(mut self) { + self.build(self.ip, MasmOp::U32Cast); + } + + /// Pops an element, `a`, from the stack, and splits it into two elements, `b` and `c`, each of which are a valid u32 value. + /// + /// The value for `b` is given by `a mod 2^32`, and the value for `c` by `a / 2^32`. They are pushed on the stack in + /// that order, i.e. `c` will be on top of the stack afterwards. + pub fn split_u32(mut self) { + self.build(self.ip, MasmOp::U32Split); + } + + /// Performs unsigned addition of the top two elements on the stack, `b` and `a` respectively, which + /// are expected to be valid u32 values. + /// + /// See the [Overflow] enum for how `overflow` modifies the semantics of this instruction. + pub fn add_u32(mut self, overflow: Overflow) { + let op = match overflow { + Overflow::Unchecked => MasmOp::Add, + Overflow::Checked => MasmOp::U32CheckedAdd, + Overflow::Overflowing => MasmOp::U32OverflowingAdd, + Overflow::Wrapping => MasmOp::U32WrappingAdd, + }; + self.build(self.ip, op); + } + + /// Same as above, but `a` is provided by the given immediate. + pub fn add_imm_u32(mut self, imm: u32, overflow: Overflow) { + let op = match overflow { + Overflow::Unchecked => MasmOp::AddImm(Felt::new(imm as u64)), + Overflow::Checked => MasmOp::U32CheckedAddImm(imm), + Overflow::Overflowing => MasmOp::U32OverflowingAddImm(imm), + Overflow::Wrapping => MasmOp::U32WrappingAddImm(imm), + }; + self.build(self.ip, op); + } + + /// Pops three elements from the stack, `c`, `b`, and `a`, and computes `a + b + c` using the + /// overflowing semantics of `add_u32`. The first two elements on the stack after this instruction + /// will be a boolean indicating whether addition overflowed, and the result itself, mod 2^32. + pub fn add3_overflowing_u32(mut self) { + self.build(self.ip, MasmOp::U32OverflowingAdd3); + } + + /// Pops three elements from the stack, `c`, `b`, and `a`, and computes `a + b + c` using the + /// wrapping semantics of `add_u32`. The result will be on top of the stack afterwards, mod 2^32. + pub fn add3_wrapping_u32(mut self) { + self.build(self.ip, MasmOp::U32WrappingAdd3); + } + + /// Performs unsigned subtraction of the top two elements on the stack, `b` and `a` respectively, which + /// are expected to be valid u32 values. + /// + /// See the [Overflow] enum for how `overflow` modifies the semantics of this instruction. + pub fn sub_u32(mut self, overflow: Overflow) { + let op = match overflow { + Overflow::Unchecked => MasmOp::Sub, + Overflow::Checked => MasmOp::U32CheckedSub, + Overflow::Overflowing => MasmOp::U32OverflowingSub, + Overflow::Wrapping => MasmOp::U32WrappingSub, + }; + self.build(self.ip, op); + } + + /// Same as above, but `a` is provided by the given immediate. + pub fn sub_imm_u32(mut self, imm: u32, overflow: Overflow) { + let op = match overflow { + Overflow::Unchecked => MasmOp::SubImm(Felt::new(imm as u64)), + Overflow::Checked => MasmOp::U32CheckedSubImm(imm), + Overflow::Overflowing => MasmOp::U32OverflowingSubImm(imm), + Overflow::Wrapping => MasmOp::U32WrappingSubImm(imm), + }; + self.build(self.ip, op); + } + + /// Performs unsigned multiplication of the top two elements on the stack, `b` and `a` respectively, which + /// are expected to be valid u32 values. + /// + /// See the [Overflow] enum for how `overflow` modifies the semantics of this instruction. + pub fn mul_u32(mut self, overflow: Overflow) { + let op = match overflow { + Overflow::Unchecked => MasmOp::Mul, + Overflow::Checked => MasmOp::U32CheckedMul, + Overflow::Overflowing => MasmOp::U32OverflowingMul, + Overflow::Wrapping => MasmOp::U32WrappingMul, + }; + self.build(self.ip, op); + } + + /// Same as above, but `a` is provided by the given immediate. + pub fn mul_imm_u32(mut self, imm: u32, overflow: Overflow) { + let op = match overflow { + Overflow::Unchecked => MasmOp::MulImm(Felt::new(imm as u64)), + Overflow::Checked => MasmOp::U32CheckedMulImm(imm), + Overflow::Overflowing => MasmOp::U32OverflowingMulImm(imm), + Overflow::Wrapping => MasmOp::U32WrappingMulImm(imm), + }; + self.build(self.ip, op); + } + + /// Pops three elements from the stack, `b`, `a`, and `c`, and computes `a * b + c`, using overflowing + /// semantics, i.e. the result is wrapped mod 2^32, and a flag is pushed on the stack if the result + /// overflowed the u32 range. + pub fn madd_overflowing_u32(mut self) { + self.build(self.ip, MasmOp::U32OverflowingMadd); + } + + /// Pops three elements from the stack, `b`, `a`, and `c`, and computes `a * b + c`, using wrapping + /// semantics, i.e. the result is wrapped mod 2^32. + pub fn madd_wrapping_u32(mut self) { + self.build(self.ip, MasmOp::U32WrappingMadd); + } + + /// Performs unsigned division of the top two elements on the stack, `b` and `a` respectively, which + /// are expected to be valid u32 values. + /// + /// This operation is checked, meaning that if either operand is >= 2^32, then it will trap. + /// + /// Traps if `b` is 0. + pub fn div_u32(mut self) { + self.build(self.ip, MasmOp::U32CheckedDiv); + } + + /// Same as above, but `b` is provided by the given immediate + pub fn div_imm_u32(mut self, imm: u32) { + self.build(self.ip, MasmOp::U32CheckedDivImm(imm)); + } + + /// Performs unsigned division of the top two elements on the stack, `b` and `a` respectively, which + /// are expected to be valid u32 values. + /// + /// This operation is unchecked, so if either operand is >= 2^32, the result is undefined. + /// + /// Traps if `b` is 0. + pub fn div_unchecked_u32(mut self) { + self.build(self.ip, MasmOp::U32UncheckedDiv); + } + + /// Same as above, but `b` is provided by the given immediate + pub fn div_imm_unchecked_u32(mut self, imm: u32) { + self.build(self.ip, MasmOp::U32UncheckedDivImm(imm)); + } + + /// Pops two elements off the stack, `b` and `a` respectively, and computes `a mod b`. + /// + /// This operation is checked, meaning that if either operand is >= 2^32, then it will trap. + /// + /// Traps if `b` is 0. + pub fn mod_u32(mut self) { + self.build(self.ip, MasmOp::U32CheckedMod); + } + + /// Same as above, but `b` is provided by the given immediate + pub fn mod_imm_u32(mut self, imm: u32) { + self.build(self.ip, MasmOp::U32CheckedModImm(imm)); + } + + /// Pops two elements off the stack, `b` and `a` respectively, and computes `a mod b`. + /// + /// This operation is unchecked, so if either operand is >= 2^32, the result is undefined. + /// + /// Traps if `b` is 0. + pub fn mod_unchecked_u32(mut self) { + self.build(self.ip, MasmOp::U32UncheckedMod); + } + + /// Same as above, but `b` is provided by the given immediate + pub fn mod_imm_unchecked_u32(mut self, imm: u32) { + self.build(self.ip, MasmOp::U32UncheckedModImm(imm)); + } + + /// Pops two elements off the stack, `b` and `a` respectively, and computes `a / b`, and `a mod b`, + /// pushing the results of each on the stack in that order. + /// + /// This operation is checked, meaning that if either operand is >= 2^32, then it will trap. + /// + /// Traps if `b` is 0. + pub fn divmod_u32(mut self) { + self.build(self.ip, MasmOp::U32CheckedDivMod); + } + + /// Same as above, but `b` is provided by the given immediate + pub fn divmod_imm_u32(mut self, imm: u32) { + self.build(self.ip, MasmOp::U32CheckedDivModImm(imm)); + } + + /// Pops two elements off the stack, `b` and `a` respectively, and computes `a / b`, and `a mod b`, + /// pushing the results of each on the stack in that order. + /// + /// This operation is unchecked, so if either operand is >= 2^32, the results are undefined. + /// + /// Traps if `b` is 0. + pub fn divmod_unchecked_u32(mut self) { + self.build(self.ip, MasmOp::U32UncheckedDivMod); + } + + /// Same as above, but `b` is provided by the given immediate + pub fn divmod_imm_unchecked_u32(mut self, imm: u32) { + self.build(self.ip, MasmOp::U32UncheckedDivModImm(imm)); + } + + /// Pops two elements off the stack, and computes the bitwise AND of those values, placing the result on the stack. + /// + /// Traps if either element is not a valid u32 value. + pub fn band_u32(mut self) { + self.build(self.ip, MasmOp::U32And); + } + + /// Pops two elements off the stack, and computes the bitwise OR of those values, placing the result on the stack. + /// + /// Traps if either element is not a valid u32 value. + pub fn bor_u32(mut self) { + self.build(self.ip, MasmOp::U32Or); + } + + /// Pops two elements off the stack, and computes the bitwise XOR of those values, placing the result on the stack. + /// + /// Traps if either element is not a valid u32 value. + pub fn bxor_u32(mut self) { + self.build(self.ip, MasmOp::U32Xor); + } + + /// Pops an element off the stack, and computes the bitwise NOT of that value, placing the result on the stack. + /// + /// Traps if the element is not a valid u32 value. + pub fn bnot_u32(mut self) { + self.build(self.ip, MasmOp::U32Not); + } + + /// Pops two elements off the stack, `b` and `a` respectively, and shifts `a` left by `b` bits. More precisely, + /// the result is computed as `(a * 2^b) mod 2^32`. + /// + /// Traps if `a` is not a valid u32, or `b` > 31. + pub fn shl_u32(mut self) { + self.build(self.ip, MasmOp::U32CheckedShl); + } + + /// Same as `shl_u32`, but `b` is provided by immediate. + pub fn shl_imm_u32(mut self, imm: u32) { + self.build(self.ip, MasmOp::U32CheckedShlImm(imm)); + } + + /// Pops two elements off the stack, `b` and `a` respectively, and shifts `a` left by `b` bits. More precisely, + /// the result is computed as `(a * 2^b) mod 2^32`. + /// + /// The result is undefined if `a` is not a valid u32, or `b` is > 31. + pub fn shl_unchecked_u32(mut self) { + self.build(self.ip, MasmOp::U32UncheckedShl); + } + + /// Same as `shl_unchecked_u32`, but `b` is provided by immediate. + pub fn shl_unchecked_imm_u32(mut self, imm: u32) { + self.build(self.ip, MasmOp::U32UncheckedShlImm(imm)); + } + + /// Pops two elements off the stack, `b` and `a` respectively, and shifts `a` right by `b` bits. More precisely, + /// the result is computed as `a / 2^b`. + /// + /// Traps if `a` is not a valid u32, or `b` > 31. + pub fn shr_u32(mut self) { + self.build(self.ip, MasmOp::U32CheckedShr); + } + + /// Same as `shr_u32`, but `b` is provided by immediate. + pub fn shr_imm_u32(mut self, imm: u32) { + self.build(self.ip, MasmOp::U32CheckedShrImm(imm)); + } + + /// Pops two elements off the stack, `b` and `a` respectively, and shifts `a` right by `b` bits. More precisely, + /// the result is computed as `a / 2^b`. + /// + /// The result is undefined if `a` is not a valid u32, or `b` is > 31. + pub fn shr_unchecked_u32(mut self) { + self.build(self.ip, MasmOp::U32UncheckedShr); + } + + /// Same as `shr_unchecked_u32`, but `b` is provided by immediate. + pub fn shr_unchecked_imm_u32(mut self, imm: u32) { + self.build(self.ip, MasmOp::U32UncheckedShrImm(imm)); + } + + /// Pops two elements off the stack, `b` and `a` respectively, and rotates the binary representation of `a` + /// left by `b` bits. + /// + /// Traps if `a` is not a valid u32, or `b` > 31 + pub fn rotl_u32(mut self) { + self.build(self.ip, MasmOp::U32CheckedRotl); + } + + /// Same as `rotl_u32`, but `b` is provided by immediate. + pub fn rotl_imm_u32(mut self, imm: u32) { + self.build(self.ip, MasmOp::U32CheckedRotlImm(imm)); + } + + /// Pops two elements off the stack, `b` and `a` respectively, and rotates the binary representation of `a` + /// left by `b` bits. + /// + /// The result is undefined if `a` is not a valid u32, or `b` is > 31. + pub fn rotl_unchecked_u32(mut self) { + self.build(self.ip, MasmOp::U32UncheckedRotl); + } + + /// Same as `rotl_unchecked_u32`, but `b` is provided by immediate. + pub fn rotl_unchecked_imm_u32(mut self, imm: u32) { + self.build(self.ip, MasmOp::U32UncheckedRotlImm(imm)); + } + + /// Pops two elements off the stack, `b` and `a` respectively, and rotates the binary representation of `a` + /// right by `b` bits. + /// + /// Traps if `a` is not a valid u32, or `b` > 31 + pub fn rotr_u32(mut self) { + self.build(self.ip, MasmOp::U32CheckedRotr); + } + + /// Same as `rotr_u32`, but `b` is provided by immediate. + pub fn rotr_imm_u32(mut self, imm: u32) { + self.build(self.ip, MasmOp::U32CheckedRotrImm(imm)); + } + + /// Pops two elements off the stack, `b` and `a` respectively, and rotates the binary representation of `a` + /// right by `b` bits. + /// + /// The result is undefined if `a` is not a valid u32, or `b` is > 31. + pub fn rotr_unchecked_u32(mut self) { + self.build(self.ip, MasmOp::U32UncheckedRotr); + } + + /// Same as `rotr_unchecked_u32`, but `b` is provided by immediate. + pub fn rotr_unchecked_imm_u32(mut self, imm: u32) { + self.build(self.ip, MasmOp::U32UncheckedRotrImm(imm)); + } + + /// Pops an element off the stack, and computes the number of set bits in its binary representation, i.e. + /// its hamming weight, and places the result on the stack. + /// + /// Traps if the input value is not a valid u32. + pub fn popcnt_u32(mut self) { + self.build(self.ip, MasmOp::U32CheckedPopcnt); + } + + /// Pops an element off the stack, and computes the number of set bits in its binary representation, i.e. + /// its hamming weight, and places the result on the stack. + /// + /// The result is undefined if the input value is not a valid u32. + pub fn popcnt_unchecked_u32(mut self) { + self.build(self.ip, MasmOp::U32UncheckedPopcnt); + } + + /// This is the same as `eq`, but also asserts that both operands are valid u32 values. + pub fn eq_u32(mut self) { + self.build(self.ip, MasmOp::U32Eq); + } + + /// This is the same as `eq_imm`, but also asserts that both operands are valid u32 values. + pub fn eq_imm_u32(mut self, imm: u32) { + self.build(self.ip, MasmOp::U32EqImm(imm)); + } + + /// This is the same as `neq`, but also asserts that both operands are valid u32 values. + pub fn neq_u32(mut self) { + self.build(self.ip, MasmOp::U32Neq); + } + + /// This is the same as `neq_imm`, but also asserts that both operands are valid u32 values. + pub fn neq_imm_u32(mut self, imm: u32) { + self.build(self.ip, MasmOp::U32NeqImm(imm)); + } + + /// This is the same as `lt`, but also asserts that both operands are valid u32 values. + pub fn lt_u32(mut self) { + self.build(self.ip, MasmOp::U32CheckedLt); + } + + /// This is the same as `lt`, but the result is undefined if either operand is not a valid u32 value. + pub fn lt_unchecked_u32(mut self) { + self.build(self.ip, MasmOp::U32UncheckedLt); + } + + /// This is the same as `lte`, but also asserts that both operands are valid u32 values. + pub fn lte_u32(mut self) { + self.build(self.ip, MasmOp::U32CheckedLte); + } + + /// This is the same as `lte`, but the result is undefined if either operand is not a valid u32 value. + pub fn lte_unchecked_u32(mut self) { + self.build(self.ip, MasmOp::U32UncheckedLte); + } + + /// This is the same as `gt`, but also asserts that both operands are valid u32 values. + pub fn gt_u32(mut self) { + self.build(self.ip, MasmOp::U32CheckedGt); + } + + /// This is the same as `gt`, but the result is undefined if either operand is not a valid u32 value. + pub fn gt_unchecked_u32(mut self) { + self.build(self.ip, MasmOp::U32UncheckedGt); + } + + /// This is the same as `gte`, but also asserts that both operands are valid u32 values. + pub fn gte_u32(mut self) { + self.build(self.ip, MasmOp::U32CheckedGte); + } + + /// This is the same as `gte`, but the result is undefined if either operand is not a valid u32 value. + pub fn gte_unchecked_u32(mut self) { + self.build(self.ip, MasmOp::U32UncheckedGte); + } + + /// This is the same as `min`, but also asserts that both operands are valid u32 values. + pub fn min_u32(mut self) { + self.build(self.ip, MasmOp::U32CheckedMin); + } + + /// This is the same as `min`, but the result is undefined if either operand is not a valid u32 value. + pub fn min_unchecked_u32(mut self) { + self.build(self.ip, MasmOp::U32UncheckedMin); + } + + /// This is the same as `max`, but also asserts that both operands are valid u32 values. + pub fn max_u32(mut self) { + self.build(self.ip, MasmOp::U32CheckedMax); + } + + /// This is the same as `max`, but the result is undefined if either operand is not a valid u32 value. + pub fn max_unchecked_u32(mut self) { + self.build(self.ip, MasmOp::U32UncheckedMax); + } + + #[inline(never)] + fn build(&mut self, ip: MasmBlockId, op: MasmOp) { + apply_op_stack_effects(&op, &mut self.stack, self.dfg, self.asm); + self.asm.push(ip, op); + } +} + +#[doc(hidden)] +enum IfBranch { + Then, + Else, +} + +/// This builder is used to construct an `if.true` instruction, while maintaining +/// the invariant that the operand stack has a uniform state upon exit from either +/// branch of the `if.true`, i.e. the number of elements, and their types, must +/// match. +/// +/// We do this by snapshotting the state of the operand stack on entry, using it +/// when visiting each branch as the initial stack state, and then validating that +/// when both branches have been constructed, that the stack state on exit is the +/// same. The first branch to be completed defines the expected state of the stack +/// for the remaining branch. +/// +/// # Example +/// +/// The general usage here looks like this, where `masm_builder` is an instance of +/// [MasmBuilder]: +/// +/// ```rust,ignore +/// // If the current top of the stack is > 0, decrement the next stack element, which +/// is a counter, and then call a function, otherwise, pop the counter, push 0, and proceed. +/// masm_builder.ins().gt_imm(Felt::ZERO); +/// let if_builder = masm_builder.ins().if_true(); +/// +/// // Build the then branch +/// let then_b = if_builder.build_then(); +/// then_b.ins().sub_imm(Felt::new(1 as u64)); +/// then_b.ins().exec("do_some_stuff_and_return_a_boolean".parse().unwrap()); +/// then_b.end(); +/// +/// // Build the else branch +/// let else_b = if_builder.build_else(); +/// else_b.ins().pop(); +/// else_b.ins().push(Felt::ZERO); +/// else_b.end(); +/// +/// // Finalize +/// if_builder.build(); +/// ``` +pub struct IfTrueBuilder<'a> { + dfg: &'a mut DataFlowGraph, + asm: &'a mut InlineAsm, + /// This reference is to the operand stack in the parent [MasmOpBuilder], + /// which represents the operand stack on entry to the `if.true`. Upon + /// finalizatio of the `if.true`, we use update this operand stack to + /// reflect the state upon exit from the `if.true`. + /// + /// In effect, when the call to `if_true` returns, the operand stack in the + /// parent builder will look as if the `if.true` instruction has finished executing. + in_stack: &'a mut OperandStack, + /// This is set when the first branch is finished being constructed, and + /// will be used as the expected state of the operand stack when we finish + /// constructing the second branch and validate the `if.true`. + out_stack: OperandStack, + /// This is the block to which the `if.true` will be appended + ip: MasmBlockId, + /// The block id for the then branch, unset until it has been finalized + then_blk: Option, + /// The block id for the else branch, unset until it has been finalized + else_blk: Option, +} +impl<'f> IfTrueBuilder<'f> { + /// Start constructing the then block for this `if.true` instruction + /// + /// NOTE: This function will panic if the then block has already been built + pub fn build_then<'a: 'f, 'b: 'f + 'a>(&'b mut self) -> IfTrueBlockBuilder<'a> { + assert!( + self.then_blk.is_none(), + "cannot build the 'then' branch twice" + ); + let then_blk = self.asm.create_block(); + let stack = self.in_stack.clone(); + IfTrueBlockBuilder { + builder: self, + stack, + block: then_blk, + branch: IfBranch::Then, + } + } + + /// Start constructing the else block for this `if.true` instruction + /// + /// NOTE: This function will panic if the else block has already been built + pub fn build_else<'a: 'f, 'b: 'f + 'a>(&'b mut self) -> IfTrueBlockBuilder<'a> { + assert!( + self.else_blk.is_none(), + "cannot build the 'else' branch twice" + ); + let else_blk = self.asm.create_block(); + let stack = self.in_stack.clone(); + IfTrueBlockBuilder { + builder: self, + stack, + block: else_blk, + branch: IfBranch::Else, + } + } + + /// Finalize this `if.true` instruction, inserting it into the block this + /// builder was constructed from. + pub fn build(mut self) { + let then_blk = self.then_blk.expect("missing 'then' block"); + let else_blk = self.else_blk.expect("missing 'else' block"); + self.asm.push(self.ip, MasmOp::If(then_blk, else_blk)); + // Update the operand stack to represent the state after execution of the `if.true` + let in_stack = self.in_stack.stack_mut(); + in_stack.clear(); + in_stack.append(self.out_stack.stack_mut()); + } +} + +/// Used to construct a single branch of an `if.true` instruction +/// +/// See [IfTrueBuilder] for usage. +pub struct IfTrueBlockBuilder<'a> { + builder: &'a mut IfTrueBuilder<'a>, + // The state of the operand stack in this block + stack: OperandStack, + // The block we're building + block: MasmBlockId, + branch: IfBranch, +} +impl<'f> IfTrueBlockBuilder<'f> { + /// Construct a MASM instruction in this block + pub fn ins<'a, 'b: 'a>(&'b mut self) -> MasmOpBuilder<'a> { + MasmOpBuilder { + dfg: self.builder.dfg, + asm: self.builder.asm, + stack: &mut self.stack, + ip: self.block, + } + } + + /// Finalize this block, and release the builder + pub fn end(self) {} +} +impl<'a> Drop for IfTrueBlockBuilder<'a> { + fn drop(&mut self) { + match self.branch { + IfBranch::Then => { + self.builder.then_blk = Some(self.block); + } + IfBranch::Else => { + self.builder.else_blk = Some(self.block); + } + } + + // If the if.true instruction is complete, validate that the operand stack in + // both branches is identical + // + // Otherwise, save the state of the stack here to be compared to the other + // branch when it is constructed + let is_complete = self.builder.then_blk.is_some() && self.builder.else_blk.is_some(); + if is_complete { + assert_eq!(self.stack.stack(), self.builder.out_stack.stack(), "expected the operand stack to be in the same abstract state upon exit from either branch of this if.true instruction"); + } else { + core::mem::swap(&mut self.builder.out_stack, &mut self.stack); + } + } +} + +#[doc(hidden)] +enum LoopType { + While, + Repeat(u8), +} + +/// This builder is used to construct both `while.true` and `repeat.n` loops, enforcing +/// their individual invariants with regard to the operand stack. +/// +/// In particular, this builder ensures that the body of a `while.true` loop is valid, +/// i.e. that when returning to the top of the loop to evaluate the conditional, that +/// there is a boolean value on top of the stack for that purpose. Similarly, it validates +/// that after the conditional has been evaluated, that the abstract state of the operand +/// stack is the same across iterations, and regardless of whether the loop is taken. The +/// abstract state in question is the number, and type, of the operands on the stack. +/// +/// # Example +/// +/// The general usage here looks like this, where `masm_builder` is an instance of +/// [MasmBuilder]: +/// +/// ```rust,ignore +/// // For our example here, we're generating inline assembly that performs +/// // the equivalent of `for (i = 0; i < len; i++) sum += array[i / 4][i % 4]`, +/// // where `array` is a pointer to words, and we're attempting to sum `len` +/// // field elements, across how ever many words that spans. +/// // +/// // We assume the operand stack is as follows (top to bottom): +/// // +/// // [len, sum, array] +/// // +/// // First, build out the loop header +/// masm_builder.ins().push(Felt::ZERO); // [i, len, sum, array] +/// masm_builder.ins().dup(0); // [i, i, len, sum, array] +/// masm_builder.ins().dup(2); // [len, i, i, len, sum, array] +/// masm_builder.ins().lt(); // [i < len, i, len, sum, array] +/// +/// // Now, build the loop body +/// // +/// // The state of the stack on entry is: [i, len, sum, array] +/// let mut lb = masm_builder.ins().while_true(); +/// +/// // Calculate `i / 4` +/// lb.ins().dup(0); // [i, i, len, sum, array] +/// lb.ins().div_imm(4); // [word_offset, i, len, sum, array] +/// +/// // Calculate the address for `array[i / 4]` +/// lb.ins().dup(4); // [array, word_offset, ..] +/// lb.ins().add_u32(Overflow::Checked); // [array + word_offset, i, ..] +/// +/// // Calculate the `i % 4` +/// lb.ins().dup(1); // [i, array + word_offset, ..] +/// lb.ins().mod_imm_u32(4); // [element_offset, array + word_offset, ..] +/// +/// // Precalculate what elements of the word to drop, so that +/// // we are only left with the specific element we wanted +/// lb.ins().dup(0); // [element_offset, element_offset, ..] +/// lb.ins().lt_imm(Felt::new(3)); // [element_offset < 3, element_offset, ..] +/// lb.ins().dup(1); // [element_offset, element_offset < 3, ..] +/// lb.ins().lt_imm(Felt::new(2)); // [element_offset < 2, element_offset < 3, ..] +/// lb.ins().dup(2); // [element_offset, element_offset < 2, ..] +/// lb.ins().lt_imm(Felt::new(1)); // [element_offset < 1, element_offset < 2, ..] +/// +/// // Load the word +/// lb.ins().dup(4); // [array + word_offset, element_offset < 1] +/// lb.ins().loadw(); // [word[0], word[1], word[2], word[3], element_offset < 1] +/// +/// // Select the element, `E`, that we want by conditionally dropping +/// // elements on the operand stack with a carefully chosen sequence +/// // of conditionals: E < N forall N in 0..=3 +/// lb.ins().movup(4); // [element_offset < 1, word[0], ..] +/// lb.ins().cdrop(); // [word[0 or 1], word[2], word[3], element_offset < 2] +/// lb.ins().movup(3); // [element_offset < 2, word[0 or 1], ..] +/// lb.ins().cdrop(); // [word[0 or 1 or 2], word[3], element_offset < 3] +/// lb.ins().movup(2); // [element_offset < 3, ..] +/// lb.ins().cdrop(); // [array[i], i, len, sum, array] +/// lb.ins().movup(3); // [sum, array[i], i, len, array] +/// lb.ins().add(); // [sum + array[i], i, len, array] +/// lb.ins().movdn(2); // [i, len, sum + array[i], array] +/// +/// // We've reached the end of the loop, but we need a copy of the +/// // loop header here in order to use the expression `i < len` as +/// // the condition for the loop +/// lb.ins().dup(0); // [i, i, len, ..] +/// lb.ins().dup(2); // [len, i, i, len, ..] +/// lb.ins().lt(); // [i < len, i, len, sum, array] +/// +/// // Finalize, it is at this point that validation will occur +/// lb.build(); +/// ``` +pub struct LoopBuilder<'a> { + dfg: &'a mut DataFlowGraph, + asm: &'a mut InlineAsm, + /// This reference is to the operand stack in the parent [MasmOpBuilder], + /// which represents the operand stack on entry to the loop. Upon finalization + /// of the loop, we use update this operand stack to reflect the state upon + /// exit from the loop. + /// + /// In effect, when the call to `while_true` or `repeat` returns, the operand + /// stack in the parent builder will look as if the loop instruction has finished + /// executing. + in_stack: &'a mut OperandStack, + /// This is the operand stack state within the loop. + /// + /// Upon finalization of the loop instruction, this state is used to validate + /// the effect of the loop body on the operand stack. For `repeat`, which is + /// unconditionally entered, no special validation is performed. However, for + /// `while.true`, we must validate two things: + /// + /// 1. That the top of the stack holds a boolean value + /// 2. That after popping the boolean, the output state of the operand stack + /// matches the input state in number and type of elements. This is required, + /// as otherwise program behavior is undefined based on whether the loop is + /// entered or not. + out_stack: OperandStack, + /// The block to which the loop instruction will be appended + ip: MasmBlockId, + /// The top-level block for the loop + body: MasmBlockId, + /// The type of loop we're building + style: LoopType, +} +impl<'f> LoopBuilder<'f> { + /// Get a builder for a single MASM instruction + pub fn ins<'a, 'b: 'a>(&'b mut self) -> MasmOpBuilder<'a> { + MasmOpBuilder { + dfg: self.dfg, + asm: self.asm, + stack: &mut self.out_stack, + ip: self.body, + } + } + + /// Finalize construction of this loop, performing any final validation. + pub fn build(mut self) { + match self.style { + LoopType::While => { + // First, validate that the top of the stack holds a boolean + let cond = self.out_stack.pop().expect("operand stack is empty"); + assert_eq!(cond, Type::I1, "expected there to be a boolean on top of the stack at the end of the while.true body"); + // Next, validate that the contents of the operand stack match + // the input stack, in order to ensure that the operand stack + // is consistent whether the loop is taken or not + assert_eq!(self.in_stack.stack(), self.out_stack.stack(), "expected the operand stack to be in the same abstract state whether the while.true loop is taken or skipped"); + self.asm.push(self.ip, MasmOp::While(self.body)); + } + LoopType::Repeat(1) => { + // This is an edge case, but a single iteration `repeat` is no different than + // inlining the loop body into the outer code block and eliding the `repeat`. + // + // Since that is the case, we literally do that transformation here, to simplify + // the IR as much as possible during construction. + let id = self.body; + let mut block = + core::mem::replace(&mut self.asm.blocks[id], MasmBlock { id, ops: vec![] }); + self.asm.blocks[self.ip].append(&mut block.ops); + } + LoopType::Repeat(n) => { + // Apply the stack effects of the loop body `n` times, asserting if some operation + // in the loop fails due to type mismatches. This is sufficient to validate `repeat`, + // as it's iteration count is known statically, entry into the loop is unconditional, + // and the only way to exit the loop is to complete all `n` iterations. + // + // By validating in this way, we also implicitly validate the following: + // + // 1. If we were to translate this to SSA form, the resulting control flow graph would + // have the same number and type of arguments passed to the loop header both on entry + // and along the loopback edges. + // + // 2. If the body of the loop removes elements from the stack, we ensure that all `n` + // iterations can be performed without exhausting the stack, or perform any other invalid + // stack operation. + let code = &self.asm.blocks[self.body]; + for _ in 1..n { + for op in code.ops.iter() { + apply_op_stack_effects(op, &mut self.out_stack, self.dfg, self.asm); + } + } + self.asm.push(self.ip, MasmOp::Repeat(n, self.body)); + } + } + + // Update the operand stack to represent the state after execution of this loop + let in_stack = self.in_stack.stack_mut(); + in_stack.clear(); + in_stack.append(self.out_stack.stack_mut()); + } +} + +/// Asserts that the given value is an integer type which is compatible with u32 operations +macro_rules! assert_compatible_u32_operand { + ($ty:ident) => { + assert!( + $ty.is_pointer() || Type::U32.is_compatible_operand(&$ty), + "expected operand to be u32-compatible, got {}", + $ty + ); + }; + + ($ty:ident, $op:expr) => { + assert!( + $ty.is_pointer() || Type::U32.is_compatible_operand(&$ty), + "expected operand for {} to be u32-compatible, got {}", + $op, + $ty + ); + }; +} + +/// Asserts that the given value is an integer type which is compatible with u32 operations +macro_rules! assert_compatible_u32_operands { + ($lty:ident, $rty:ident) => { + assert_matches!( + $lty, + Type::U8 | Type::U16 | Type::U32 | Type::Usize | Type::Ptr(_) | Type::NativePtr(_), + "expected controlling type to be u32-compatible, got {}", + $lty + ); + assert_compatible_operand_types!($lty, $rty); + }; + + ($lty:ident, $rty:ident, $op:expr) => { + assert_matches!( + $lty, + Type::U8 | Type::U16 | Type::U32 | Type::Usize | Type::Ptr(_) | Type::NativePtr(_), + "expected controlling type for {} to be u32-compatible, got {}", + $op, + $lty + ); + assert_compatible_operand_types!($lty, $rty, $op); + }; +} + +/// Asserts that the given value is an integer type which is compatible with felt operations +macro_rules! assert_compatible_felt_operand { + ($ty:ident) => { + assert!( + Type::Felt.is_compatible_operand(&$ty), + "expected operand to be felt-compatible, got {}", + $ty + ); + }; + + ($ty:ident, $op:expr) => { + assert!( + Type::Felt.is_compatible_operand(&$ty), + "expected operand for {} to be felt-compatible, got {}", + $op, + $ty + ); + }; +} + +/// Asserts that the given value is an integer type which is compatible with felt operations +macro_rules! assert_compatible_felt_operands { + ($lty:ident, $rty:ident) => { + assert_matches!( + $lty, + Type::U8 + | Type::I8 + | Type::U16 + | Type::I16 + | Type::U32 + | Type::I32 + | Type::Usize + | Type::Isize + | Type::Felt, + "expected controlling type to be felt-compatible, got {}", + $lty + ); + assert_compatible_operand_types!($lty, $rty); + }; + + ($lty:ident, $rty:ident, $op:expr) => { + assert_matches!( + $lty, + Type::U8 + | Type::I8 + | Type::U16 + | Type::I16 + | Type::U32 + | Type::I32 + | Type::Usize + | Type::Isize + | Type::Felt, + "expected controlling type for {} to be felt-compatible, got {}", + $op, + $lty + ); + assert_compatible_operand_types!($lty, $rty, $op); + }; +} + +/// Asserts that the two operands are of compatible types, where the first operand is assumed to determine the controlling type +macro_rules! assert_compatible_operand_types { + ($lty:ident, $rty:ident) => { + assert!($lty.is_compatible_operand(&$rty), "expected operands to be compatible types, the controlling type {} is not compatible with {}", $lty, $rty); + }; + + ($lty:ident, $rty:ident, $op:expr) => { + assert!($lty.is_compatible_operand(&$rty), "expected operands for {} to be compatible types, the controlling type {} is not compatible with {}", $op, $lty, $rty); + }; +} + +fn apply_op_stack_effects( + op: &MasmOp, + stack: &mut OperandStack, + dfg: &DataFlowGraph, + asm: &InlineAsm, +) { + match op { + MasmOp::Padw => { + stack.padw(); + } + MasmOp::Push(_) => { + stack.push(Type::Felt); + } + MasmOp::Pushw(_) => { + stack.padw(); + } + MasmOp::PushU8(_) => { + stack.push(Type::U8); + } + MasmOp::PushU16(_) => { + stack.push(Type::U16); + } + MasmOp::PushU32(_) => { + stack.push(Type::U32); + } + MasmOp::Drop => { + stack.drop(); + } + MasmOp::Dropw => { + stack.dropw(); + } + MasmOp::Dup(idx) => { + stack.dup(*idx as usize); + } + MasmOp::Dupw(idx) => { + stack.dupw(*idx as usize); + } + MasmOp::Swap(idx) => { + stack.swap(*idx as usize); + } + MasmOp::Swapw(idx) => { + stack.swapw(*idx as usize); + } + MasmOp::Movup(idx) => { + stack.movup(*idx as usize); + } + MasmOp::Movupw(idx) => { + stack.movupw(*idx as usize); + } + MasmOp::Movdn(idx) => { + stack.movdn(*idx as usize); + } + MasmOp::Movdnw(idx) => { + stack.movdnw(*idx as usize); + } + MasmOp::Cswap | MasmOp::Cswapw => { + let ty = stack.pop().expect("operand stack is empty"); + assert_eq!(ty, Type::I1, "expected boolean operand on top of the stack"); + } + MasmOp::Cdrop => { + let ty = stack.pop().expect("operand stack is empty"); + assert_eq!(ty, Type::I1, "expected boolean operand on top of the stack"); + stack.drop(); + } + MasmOp::Cdropw => { + let ty = stack.pop().expect("operand stack is empty"); + assert_eq!(ty, Type::I1, "expected boolean operand on top of the stack"); + stack.dropw(); + } + MasmOp::Assert | MasmOp::Assertz => { + stack.drop(); + } + MasmOp::AssertEq => { + stack.dropn(2); + } + MasmOp::AssertEqw => { + stack.dropn(8); + } + MasmOp::LocAddr(_id) => unreachable!(), + MasmOp::MemLoad | MasmOp::MemLoadOffset => { + let ty = stack.pop().expect("operand stack is empty"); + assert_matches!( + ty, + Type::Ptr(_) | Type::NativePtr(_), + "invalid load: expected pointer operand" + ); + stack.push(ty.pointee().unwrap().clone()); + } + MasmOp::MemLoadImm(_) | MasmOp::MemLoadOffsetImm(_, _) => { + // We don't know what we're loading, so fall back to the default type of field element + stack.push(Type::Felt); + } + MasmOp::MemLoadw => { + let ty = stack.pop().expect("operand stack is empty"); + assert_matches!( + ty, + Type::Ptr(_) | Type::NativePtr(_), + "invalid load: expected pointer operand" + ); + // We're always loading a raw word with this op + stack.padw(); + } + MasmOp::MemLoadwImm(_) => { + // We're always loading a raw word with this op + stack.padw(); + } + MasmOp::MemStore | MasmOp::MemStoreOffset => { + let ty = stack.pop().expect("operand stack is empty"); + assert_matches!( + ty, + Type::Ptr(_) | Type::NativePtr(_), + "invalid store: expected pointer operand" + ); + stack.drop(); + } + MasmOp::MemStoreImm(_) | MasmOp::MemStoreOffsetImm(_, _) => { + stack.drop(); + } + MasmOp::MemStorew => { + let ty = stack.pop().expect("operand stack is empty"); + assert_matches!( + ty, + Type::Ptr(_) | Type::NativePtr(_), + "invalid store: expected pointer operand" + ); + stack.dropw(); + } + MasmOp::MemStorewImm(_) => { + stack.dropw(); + } + // This function is not called from [MasmOpBuilder] when building an `if.true` instruction, + // instead, the only time we are evaluating this is when traversing the body of a `repeat.n` + // instruction and applying the stack effects of instructions which have already been inserted + // once. + // + // NOTE: We only apply the effects from a single branch, because we have already validated + // that regardless of which branch is taken, the stack effects are the same. + MasmOp::If(then_body, _else_body) => { + let lty = stack.pop().expect("operand stack is empty"); + assert_eq!(lty, Type::I1, "expected boolean conditional"); + let body = asm.blocks[*then_body].ops.as_slice(); + for op in body.iter() { + apply_op_stack_effects(op, stack, dfg, asm); + } + } + // This function is not called from [MasmOpBuilder] when building an `while.true` instruction, + // instead, the only time we are evaluating this is when traversing the body of a `repeat.n` + // instruction and applying the stack effects of instructions which have already been inserted + // once. + // + // NOTE: We don't need to traverse the body of the `while.true`, because we have already validated + // that whether the loop is taken or not, the stack effects are the same + MasmOp::While(_body) => { + let lty = stack.pop().expect("operand stack is empty"); + assert_eq!(lty, Type::I1, "expected boolean conditional"); + } + // This function is not called from [MasmOpBuilder] when building an `repeat.n` instruction, + // instead, the only time we are evaluating this is when traversing the body of a `repeat.n` + // instruction and applying the stack effects of instructions which have already been inserted + // once. + MasmOp::Repeat(n, body) => { + let body = asm.blocks[*body].ops.as_slice(); + for _ in 0..*n { + for op in body.iter() { + apply_op_stack_effects(op, stack, dfg, asm); + } + } + } + MasmOp::Exec(ref id) => { + execute_call(id, false, stack, dfg); + } + MasmOp::Syscall(ref id) => { + execute_call(id, false, stack, dfg); + } + MasmOp::Add | MasmOp::Sub | MasmOp::Mul | MasmOp::Div => { + let rty = stack.pop().expect("operand stack is empty"); + let lty = stack.pop().expect("operand stack is empty"); + assert_compatible_felt_operands!(lty, rty, op); + stack.push(lty); + } + MasmOp::AddImm(_) + | MasmOp::SubImm(_) + | MasmOp::MulImm(_) + | MasmOp::DivImm(_) + | MasmOp::Neg + | MasmOp::Inv + | MasmOp::Incr + | MasmOp::Pow2 + | MasmOp::ExpImm(_) => { + let ty = stack.peek().expect("operand stack is empty"); + assert_compatible_felt_operand!(ty, op); + } + MasmOp::Exp => { + let rty = stack.pop().expect("operand stack is empty"); + let lty = stack.pop().expect("operand stack is empty"); + assert_compatible_felt_operands!(lty, rty); + stack.push(lty); + } + MasmOp::Not | MasmOp::AndImm(_) | MasmOp::OrImm(_) | MasmOp::XorImm(_) => { + let ty = stack.pop().expect("operand stack is empty"); + assert_eq!(ty, Type::I1, "expected boolean type"); + stack.push(ty); + } + MasmOp::And | MasmOp::Or | MasmOp::Xor => { + let rty = stack.pop().expect("operand stack is empty"); + let lty = stack.pop().expect("operand stack is empty"); + assert_eq!(lty, rty, "expected operands for {} to be the same type", op); + assert_eq!(lty, Type::I1, "expected boolean operands for {}", op); + stack.push(lty); + } + MasmOp::Eq | MasmOp::Neq | MasmOp::Gt | MasmOp::Gte | MasmOp::Lt | MasmOp::Lte => { + let rty = stack.pop().expect("operand stack is empty"); + let lty = stack.pop().expect("operand stack is empty"); + assert_compatible_felt_operands!(lty, rty, op); + stack.push(Type::I1); + } + MasmOp::EqImm(_) + | MasmOp::NeqImm(_) + | MasmOp::GtImm(_) + | MasmOp::GteImm(_) + | MasmOp::LtImm(_) + | MasmOp::LteImm(_) + | MasmOp::IsOdd => { + let ty = stack.pop().expect("operand stack is empty"); + assert_compatible_felt_operand!(ty, op); + stack.push(Type::I1); + } + MasmOp::Eqw => { + stack.dropn(8); + stack.push(Type::I1); + } + MasmOp::Clk => { + stack.push(Type::Felt); + } + MasmOp::U32Test => { + assert!(!stack.is_empty()); + stack.push(Type::I1); + } + MasmOp::U32Testw => { + assert!( + stack.len() > 3, + "expected at least 4 elements on the operand stack" + ); + stack.push(Type::I1); + } + MasmOp::U32Assert => { + assert!(!stack.is_empty()); + } + MasmOp::U32Assert2 => { + assert!( + stack.len() > 1, + "expected at least 2 elements on the operand stack" + ); + } + MasmOp::U32Assertw => { + assert!( + stack.len() > 3, + "expected at least 4 elements on the operand stack" + ); + } + MasmOp::U32Cast => { + let lty = stack.pop().expect("operand stack is empty"); + assert_eq!(lty, Type::Felt, "expected felt operand"); + stack.push(Type::U32); + } + MasmOp::U32Split => { + let lty = stack.pop().expect("operand stack is empty"); + assert_eq!(lty, Type::Felt, "expected felt operand"); + stack.push(Type::U32); + stack.push(Type::U32); + } + MasmOp::U32CheckedAdd + | MasmOp::U32CheckedSub + | MasmOp::U32CheckedMul + | MasmOp::U32CheckedDiv + | MasmOp::U32CheckedMod + | MasmOp::U32CheckedDivMod + | MasmOp::U32CheckedShl + | MasmOp::U32CheckedShr + | MasmOp::U32CheckedRotl + | MasmOp::U32CheckedRotr + | MasmOp::U32CheckedMin + | MasmOp::U32CheckedMax + | MasmOp::U32WrappingAdd + | MasmOp::U32WrappingSub + | MasmOp::U32WrappingMul + | MasmOp::U32UncheckedDiv + | MasmOp::U32UncheckedMod + | MasmOp::U32UncheckedDivMod + | MasmOp::U32UncheckedShl + | MasmOp::U32UncheckedShr + | MasmOp::U32UncheckedRotl + | MasmOp::U32UncheckedRotr + | MasmOp::U32UncheckedMin + | MasmOp::U32UncheckedMax + | MasmOp::U32And + | MasmOp::U32Or + | MasmOp::U32Xor => { + let rty = stack.pop().expect("operand stack is empty"); + let lty = stack.pop().expect("operand stack is empty"); + assert_compatible_u32_operands!(lty, rty, op); + stack.push(lty); + } + MasmOp::U32CheckedAddImm(_) + | MasmOp::U32CheckedSubImm(_) + | MasmOp::U32CheckedMulImm(_) + | MasmOp::U32CheckedDivImm(_) + | MasmOp::U32CheckedModImm(_) + | MasmOp::U32CheckedDivModImm(_) + | MasmOp::U32CheckedShlImm(_) + | MasmOp::U32CheckedShrImm(_) + | MasmOp::U32CheckedRotlImm(_) + | MasmOp::U32CheckedRotrImm(_) + | MasmOp::U32CheckedPopcnt + | MasmOp::U32WrappingAddImm(_) + | MasmOp::U32WrappingSubImm(_) + | MasmOp::U32WrappingMulImm(_) + | MasmOp::U32UncheckedDivImm(_) + | MasmOp::U32UncheckedModImm(_) + | MasmOp::U32UncheckedDivModImm(_) + | MasmOp::U32UncheckedShlImm(_) + | MasmOp::U32UncheckedShrImm(_) + | MasmOp::U32UncheckedRotlImm(_) + | MasmOp::U32UncheckedRotrImm(_) + | MasmOp::U32UncheckedPopcnt + | MasmOp::U32Not => { + let ty = stack.pop().expect("operand stack is empty"); + assert_compatible_u32_operand!(ty, op); + stack.push(ty); + } + MasmOp::U32OverflowingAdd | MasmOp::U32OverflowingSub | MasmOp::U32OverflowingMul => { + let rty = stack.pop().expect("operand stack is empty"); + let lty = stack.pop().expect("operand stack is empty"); + assert_compatible_u32_operands!(lty, rty, op); + stack.push(lty); + stack.push(Type::I1); + } + MasmOp::U32OverflowingAddImm(_) + | MasmOp::U32OverflowingSubImm(_) + | MasmOp::U32OverflowingMulImm(_) => { + let ty = stack.pop().expect("operand stack is empty"); + assert_compatible_u32_operand!(ty, op); + stack.push(ty); + stack.push(Type::I1); + } + MasmOp::U32OverflowingAdd3 => { + let cty = stack.pop().expect("operand stack is empty"); + let bty = stack.pop().expect("operand stack is empty"); + let aty = stack.pop().expect("operand stack is empty"); + assert_compatible_u32_operands!(aty, bty); + assert_compatible_u32_operands!(aty, cty); + stack.push(aty); + stack.push(Type::U32); + } + MasmOp::U32OverflowingMadd => { + let bty = stack.pop().expect("operand stack is empty"); + let aty = stack.pop().expect("operand stack is empty"); + let cty = stack.pop().expect("operand stack is empty"); + assert_compatible_u32_operands!(aty, bty); + assert_compatible_u32_operands!(aty, cty); + stack.push(aty); + stack.push(Type::U32); + } + MasmOp::U32WrappingAdd3 => { + let cty = stack.pop().expect("operand stack is empty"); + let bty = stack.pop().expect("operand stack is empty"); + let aty = stack.pop().expect("operand stack is empty"); + assert_compatible_u32_operands!(aty, bty); + assert_compatible_u32_operands!(aty, cty); + stack.push(aty); + } + MasmOp::U32WrappingMadd => { + let bty = stack.pop().expect("operand stack is empty"); + let aty = stack.pop().expect("operand stack is empty"); + let cty = stack.pop().expect("operand stack is empty"); + assert_compatible_u32_operands!(aty, bty); + assert_compatible_u32_operands!(aty, cty); + stack.push(aty); + } + MasmOp::U32Eq + | MasmOp::U32Neq + | MasmOp::U32CheckedLt + | MasmOp::U32CheckedLte + | MasmOp::U32CheckedGt + | MasmOp::U32CheckedGte + | MasmOp::U32UncheckedLt + | MasmOp::U32UncheckedLte + | MasmOp::U32UncheckedGt + | MasmOp::U32UncheckedGte => { + let rty = stack.pop().expect("operand stack is empty"); + let lty = stack.pop().expect("operand stack is empty"); + // We support pointer operands for these operators, but unlike + // other u32 ops, both operands may be pointer values, so we + // handle that here by checking compatiblity separately + if lty.is_pointer() { + assert_compatible_u32_operand!(rty, op); + stack.push(Type::I1); + } else { + assert_compatible_u32_operands!(lty, rty, op); + stack.push(Type::I1); + } + } + MasmOp::U32EqImm(_) | MasmOp::U32NeqImm(_) => { + let ty = stack.pop().expect("operand stack is empty"); + assert_compatible_u32_operand!(ty, op); + stack.push(Type::I1); + } + } +} + +/// Validate that a call to `id` is possible given the current state of the operand stack, +/// and if so, update the state of the operand stack to reflect the call. +fn execute_call( + id: &FunctionIdent, + is_syscall: bool, + stack: &mut OperandStack, + dfg: &DataFlowGraph, +) { + let import = dfg + .get_import(&id) + .expect("unknown function, are you missing an import?"); + if is_syscall { + assert_eq!( + import.signature.cc, + CallConv::Kernel, + "cannot call a non-kernel function with the `syscall` instruction" + ); + } else { + assert_ne!( + import.signature.cc, + CallConv::Kernel, + "`syscall` cannot be used to call non-kernel functions" + ); + } + match import.signature.cc { + // For now, we're treating all calling conventions the same as SystemV + CallConv::Fast | CallConv::SystemV | CallConv::Kernel => { + // Visit the argument list in reverse (so that the top of the stack on entry + // is the first argument), and allocate elements based on the argument types. + let mut elements_needed = 0; + for param in import.signature.params().iter().rev() { + let repr = param.repr().expect("invalid parameter type"); + elements_needed += repr.size(); + } + + // Verify that we have `elements_needed` values on the operand stack + let elements_available = stack.len(); + assert!(elements_needed <= elements_available, "the operand stack does not contain enough values to call {} ({} exepected vs {} available)", id, elements_needed, elements_available); + stack.dropn(elements_needed); + + // Update the operand stack to reflect the results + for result in import.signature.results().iter().rev() { + let repr = result.repr().expect("invalid result type"); + match repr { + TypeRepr::Zst(_) => continue, + TypeRepr::Default(ty) => stack.push(ty), + TypeRepr::Sparse(_, n) => { + for _ in 0..n.get() { + stack.push(Type::Felt); + } + } + TypeRepr::Packed(ty) => { + for _ in 0..ty.size_in_felts() { + stack.push(Type::Felt); + } + } + } + } + } + } +} diff --git a/hir/src/asm/display.rs b/hir/src/asm/display.rs new file mode 100644 index 00000000..8bdd02d0 --- /dev/null +++ b/hir/src/asm/display.rs @@ -0,0 +1,307 @@ +use std::fmt; + +use super::*; +use crate::{write::DisplayIndent, DataFlowGraph}; + +pub struct DisplayInlineAsm<'a> { + asm: &'a InlineAsm, + dfg: &'a DataFlowGraph, + indent: usize, +} +impl<'a> DisplayInlineAsm<'a> { + pub fn new(asm: &'a InlineAsm, dfg: &'a DataFlowGraph, indent: usize) -> Self { + Self { asm, dfg, indent } + } +} +impl<'a> fmt::Display for DisplayInlineAsm<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use crate::write::DisplayValues; + + { + let args = self.asm.args.as_slice(&self.dfg.value_lists); + writeln!(f, "({}) {{", DisplayValues(args))?; + } + + let indent = self.indent; + let block = self.asm.body; + writeln!( + f, + "{}", + DisplayBlock { + asm: self.asm, + block, + indent: indent + 1, + } + )?; + + writeln!(f, "{}}}", DisplayIndent(indent)) + } +} + +struct DisplayBlock<'a> { + asm: &'a InlineAsm, + block: MasmBlockId, + indent: usize, +} +impl<'a> fmt::Display for DisplayBlock<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let block = &self.asm.blocks[self.block]; + let indent = self.indent; + for op in block.ops.iter() { + writeln!( + f, + "{}", + DisplayOp { + asm: self.asm, + op, + indent + } + )?; + } + Ok(()) + } +} + +struct DisplayOp<'a> { + asm: &'a InlineAsm, + op: &'a MasmOp, + indent: usize, +} +impl<'a> fmt::Display for DisplayOp<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", DisplayIndent(self.indent))?; + match self.op { + MasmOp::Padw => f.write_str("padw"), + MasmOp::Push(imm) => write!(f, "push.{}", imm), + MasmOp::Pushw(word) => write!( + f, + "push.{}.{}.{}.{}", + &word[0], &word[1], &word[2], &word[3] + ), + MasmOp::PushU8(imm) => write!(f, "push.{:#0x}", imm), + MasmOp::PushU16(imm) => write!(f, "push.{:#0x}", imm), + MasmOp::PushU32(imm) => write!(f, "push.{:#0x}", imm), + MasmOp::Drop => f.write_str("drop"), + MasmOp::Dropw => f.write_str("dropw"), + MasmOp::Dup(idx) => write!(f, "dup.{idx}"), + MasmOp::Dupw(idx) => write!(f, "dupw.{idx}"), + MasmOp::Swap(idx) => write!(f, "swap.{idx}"), + MasmOp::Swapw(idx) => write!(f, "swapw.{idx}"), + MasmOp::Movup(idx) => write!(f, "movup.{idx}"), + MasmOp::Movupw(idx) => write!(f, "movupw.{idx}"), + MasmOp::Movdn(idx) => write!(f, "movdn.{idx}"), + MasmOp::Movdnw(idx) => write!(f, "movdnw.{idx}"), + MasmOp::Cswap => f.write_str("cswap"), + MasmOp::Cswapw => f.write_str("cswapw"), + MasmOp::Cdrop => f.write_str("cdrop"), + MasmOp::Cdropw => f.write_str("cdropw"), + MasmOp::Assert => f.write_str("assert"), + MasmOp::Assertz => f.write_str("assertz"), + MasmOp::AssertEq => f.write_str("assert_eq"), + MasmOp::AssertEqw => f.write_str("assert_eqw"), + MasmOp::LocAddr(id) => write!(f, "locaddr.{}", id.as_usize()), + MasmOp::MemLoad | MasmOp::MemLoadOffset => write!(f, "mem_load"), + MasmOp::MemLoadImm(addr) => write!(f, "mem_load.{:#0x}", addr), + MasmOp::MemLoadOffsetImm(addr, offset) => write!(f, "mem_load.{:#0x}.{}", addr, offset), + MasmOp::MemLoadw => write!(f, "mem_loadw"), + MasmOp::MemLoadwImm(addr) => write!(f, "mem_loadw.{:#0x}", addr), + MasmOp::MemStore | MasmOp::MemStoreOffset => write!(f, "mem_store"), + MasmOp::MemStoreImm(addr) => write!(f, "mem_store.{:#0x}", addr), + MasmOp::MemStoreOffsetImm(addr, offset) => { + write!(f, "mem_store.{:#0x}.{}", addr, offset) + } + MasmOp::MemStorew => write!(f, "mem_storew"), + MasmOp::MemStorewImm(addr) => write!(f, "mem_storew.{:#0x}", addr), + MasmOp::If(then_blk, else_blk) => { + f.write_str("if.true\n")?; + { + let then_block = &self.asm.blocks[*then_blk]; + let indent = self.indent + 1; + for op in then_block.ops.iter() { + writeln!( + f, + "{}", + DisplayOp { + asm: self.asm, + op, + indent + } + )?; + } + } + writeln!(f, "{}else", DisplayIndent(self.indent))?; + { + let else_block = &self.asm.blocks[*else_blk]; + let indent = self.indent + 1; + for op in else_block.ops.iter() { + writeln!( + f, + "{}", + DisplayOp { + asm: self.asm, + op, + indent + } + )?; + } + } + write!(f, "{}end", DisplayIndent(self.indent)) + } + MasmOp::While(blk) => { + f.write_str("while.true\n")?; + { + let body = &self.asm.blocks[*blk]; + let indent = self.indent + 1; + for op in body.ops.iter() { + writeln!( + f, + "{}", + DisplayOp { + asm: self.asm, + op, + indent + } + )?; + } + } + write!(f, "{}end", DisplayIndent(self.indent)) + } + MasmOp::Repeat(n, blk) => { + writeln!(f, "repeat.{}", n)?; + { + let body = &self.asm.blocks[*blk]; + let indent = self.indent + 1; + for op in body.ops.iter() { + writeln!( + f, + "{}", + DisplayOp { + asm: self.asm, + op, + indent + } + )?; + } + } + write!(f, "{}end", DisplayIndent(self.indent)) + } + MasmOp::Exec(id) => write!(f, "exec.{}", id), + MasmOp::Syscall(id) => write!(f, "syscall.{}", id), + MasmOp::Add => f.write_str("add"), + MasmOp::AddImm(imm) => write!(f, "add.{}", imm), + MasmOp::Sub => f.write_str("sub"), + MasmOp::SubImm(imm) => write!(f, "sub.{}", imm), + MasmOp::Mul => f.write_str("mul"), + MasmOp::MulImm(imm) => write!(f, "mul.{}", imm), + MasmOp::Div => f.write_str("div"), + MasmOp::DivImm(imm) => write!(f, "div.{}", imm), + MasmOp::Neg => f.write_str("neg"), + MasmOp::Inv => f.write_str("inv"), + MasmOp::Incr => f.write_str("incr"), + MasmOp::Pow2 => f.write_str("pow2"), + MasmOp::Exp => f.write_str("exp.u64"), + MasmOp::ExpImm(imm) => write!(f, "exp.{}", imm), + MasmOp::Not => f.write_str("not"), + MasmOp::And => f.write_str("and"), + MasmOp::AndImm(imm) => write!(f, "and.{}", imm), + MasmOp::Or => f.write_str("or"), + MasmOp::OrImm(imm) => write!(f, "or.{}", imm), + MasmOp::Xor => f.write_str("xor"), + MasmOp::XorImm(imm) => write!(f, "xor.{}", imm), + MasmOp::Eq => f.write_str("eq"), + MasmOp::EqImm(imm) => write!(f, "eq.{}", imm), + MasmOp::Neq => f.write_str("neq"), + MasmOp::NeqImm(imm) => write!(f, "neq.{}", imm), + MasmOp::Gt => f.write_str("gt"), + MasmOp::GtImm(imm) => write!(f, "gt.{}", imm), + MasmOp::Gte => f.write_str("gte"), + MasmOp::GteImm(imm) => write!(f, "gte.{}", imm), + MasmOp::Lt => f.write_str("lt"), + MasmOp::LtImm(imm) => write!(f, "lt.{}", imm), + MasmOp::Lte => f.write_str("lte"), + MasmOp::LteImm(imm) => write!(f, "lte.{}", imm), + MasmOp::IsOdd => f.write_str("is_odd"), + MasmOp::Eqw => f.write_str("eqw"), + MasmOp::Clk => f.write_str("clk"), + MasmOp::U32Test => f.write_str("u32.test"), + MasmOp::U32Testw => f.write_str("u32.testw"), + MasmOp::U32Assert => f.write_str("u32.assert"), + MasmOp::U32Assert2 => f.write_str("u32.assert2"), + MasmOp::U32Assertw => f.write_str("u32.assertw"), + MasmOp::U32Cast => f.write_str("u23.cast"), + MasmOp::U32Split => f.write_str("u32.split"), + MasmOp::U32CheckedAdd => f.write_str("u32.add.checked"), + MasmOp::U32CheckedAddImm(imm) => write!(f, "u32.add.checked.{:#0x}", imm), + MasmOp::U32OverflowingAdd => f.write_str("u32.add.overflowing"), + MasmOp::U32OverflowingAddImm(imm) => write!(f, "u32.add.overflowing.{:#0x}", imm), + MasmOp::U32WrappingAdd => f.write_str("u32.add.wrapping"), + MasmOp::U32WrappingAddImm(imm) => write!(f, "u32.add.wrapping.{:#0x}", imm), + MasmOp::U32OverflowingAdd3 => f.write_str("u32.add3.overflowing"), + MasmOp::U32WrappingAdd3 => f.write_str("u32.add3.wrapping"), + MasmOp::U32CheckedSub => f.write_str("u32.sub.checked"), + MasmOp::U32CheckedSubImm(imm) => write!(f, "u32.sub.checked.{:#0x}", imm), + MasmOp::U32OverflowingSub => f.write_str("u32.sub.overflowing"), + MasmOp::U32OverflowingSubImm(imm) => write!(f, "u32.sub.overflowing.{:#0x}", imm), + MasmOp::U32WrappingSub => f.write_str("u32.sub.wrapping"), + MasmOp::U32WrappingSubImm(imm) => write!(f, "u32.sub.wrapping.{:#0x}", imm), + MasmOp::U32CheckedMul => f.write_str("u32.mul.checked"), + MasmOp::U32CheckedMulImm(imm) => write!(f, "u32.mul.checked.{:#0x}", imm), + MasmOp::U32OverflowingMul => f.write_str("u32.mul.overflowing"), + MasmOp::U32OverflowingMulImm(imm) => write!(f, "u32.mul.overflowing.{:#0x}", imm), + MasmOp::U32WrappingMul => f.write_str("u32.mul.wrapping"), + MasmOp::U32WrappingMulImm(imm) => write!(f, "u32.mul.wrapping.{:#0x}", imm), + MasmOp::U32OverflowingMadd => f.write_str("u32.madd.overflowing"), + MasmOp::U32WrappingMadd => f.write_str("u32.madd.wrapping"), + MasmOp::U32CheckedDiv => f.write_str("u32.div.checked"), + MasmOp::U32CheckedDivImm(imm) => write!(f, "u32.div.checked.{:#0x}", imm), + MasmOp::U32UncheckedDiv => f.write_str("u32.div.unchecked"), + MasmOp::U32UncheckedDivImm(imm) => write!(f, "u32.div.unchecked.{:#0x}", imm), + MasmOp::U32CheckedMod => f.write_str("u32.mod.checked"), + MasmOp::U32CheckedModImm(imm) => write!(f, "u32.mod.unchecked.{:#0x}", imm), + MasmOp::U32UncheckedMod => f.write_str("u32.mod.unchecked"), + MasmOp::U32UncheckedModImm(imm) => write!(f, "u32.mod.unchecked.{:#0x}", imm), + MasmOp::U32CheckedDivMod => f.write_str("u32.divmod.checked"), + MasmOp::U32CheckedDivModImm(imm) => write!(f, "u32.divmod.checked.{:#0x}", imm), + MasmOp::U32UncheckedDivMod => f.write_str("u32.divmod.unchecked"), + MasmOp::U32UncheckedDivModImm(imm) => write!(f, "u32.divmod.unchecked.{:#0x}", imm), + MasmOp::U32And => f.write_str("u32.and"), + MasmOp::U32Or => f.write_str("u32.or"), + MasmOp::U32Xor => f.write_str("u32.xor"), + MasmOp::U32Not => f.write_str("u32.not"), + MasmOp::U32CheckedShl => f.write_str("u32.shl.checked"), + MasmOp::U32CheckedShlImm(imm) => write!(f, "u32.shl.checked.{}", imm), + MasmOp::U32UncheckedShl => f.write_str("u32.shl.unchecked"), + MasmOp::U32UncheckedShlImm(imm) => write!(f, "u32.shl.unchecked.{}", imm), + MasmOp::U32CheckedShr => f.write_str("u32.shr.checked"), + MasmOp::U32CheckedShrImm(imm) => write!(f, "u32.shr.checked.{}", imm), + MasmOp::U32UncheckedShr => f.write_str("u32.shr.unchecked"), + MasmOp::U32UncheckedShrImm(imm) => write!(f, "u32.shr.unchecked.{}", imm), + MasmOp::U32CheckedRotl => f.write_str("u32.rotl.checked"), + MasmOp::U32CheckedRotlImm(imm) => write!(f, "u32.rotl.checked.{}", imm), + MasmOp::U32UncheckedRotl => f.write_str("u32.rotl.unchecked"), + MasmOp::U32UncheckedRotlImm(imm) => write!(f, "u32.rotl.unchecked.{}", imm), + MasmOp::U32CheckedRotr => f.write_str("u32.rotr.checked"), + MasmOp::U32CheckedRotrImm(imm) => write!(f, "u32.rotr.checked.{}", imm), + MasmOp::U32UncheckedRotr => f.write_str("u32.rotr.unchecked"), + MasmOp::U32UncheckedRotrImm(imm) => write!(f, "u32.rotr.unchecked.{}", imm), + MasmOp::U32CheckedPopcnt => f.write_str("u32.popcnt.checked"), + MasmOp::U32UncheckedPopcnt => f.write_str("u32.popcnt.unchecked"), + MasmOp::U32Eq => f.write_str("u32.eq"), + MasmOp::U32EqImm(imm) => write!(f, "u32.eq.{:#0x}", imm), + MasmOp::U32Neq => f.write_str("u32.neq"), + MasmOp::U32NeqImm(imm) => write!(f, "u32.neq.{:#0x}", imm), + MasmOp::U32CheckedLt => f.write_str("u32.lt.checked"), + MasmOp::U32UncheckedLt => f.write_str("u32.lt.unchecked"), + MasmOp::U32CheckedLte => f.write_str("u32.lte.checked"), + MasmOp::U32UncheckedLte => f.write_str("u32.lte.unchecked"), + MasmOp::U32CheckedGt => f.write_str("u32.gt.checked"), + MasmOp::U32UncheckedGt => f.write_str("u32.gt.unchecked"), + MasmOp::U32CheckedGte => f.write_str("u32.gte.checked"), + MasmOp::U32UncheckedGte => f.write_str("u32.gte.unchecked"), + MasmOp::U32CheckedMin => f.write_str("u32.min.checked"), + MasmOp::U32UncheckedMin => f.write_str("u32.min.unchecked"), + MasmOp::U32CheckedMax => f.write_str("u32.max.checked"), + MasmOp::U32UncheckedMax => f.write_str("u32.max.unchecked"), + } + } +} diff --git a/hir/src/asm/isa.rs b/hir/src/asm/isa.rs new file mode 100644 index 00000000..0c95f975 --- /dev/null +++ b/hir/src/asm/isa.rs @@ -0,0 +1,717 @@ +use std::fmt; + +use cranelift_entity::entity_impl; + +use crate::{Felt, FunctionIdent, LocalId}; + +/// A handle that refers to a MASM code block +#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct MasmBlockId(u32); +entity_impl!(MasmBlockId, "blk"); + +/// Represents a single code block in Miden Assembly +#[derive(Debug, Clone)] +pub struct MasmBlock { + pub id: MasmBlockId, + pub ops: Vec, +} +impl MasmBlock { + /// Returns true if there are no instructions in this block + #[inline(always)] + pub fn is_empty(&self) -> bool { + self.ops.is_empty() + } + + /// Returns the instructions contained in this block as a slice + #[inline(always)] + pub fn ops(&self) -> &[MasmOp] { + self.ops.as_slice() + } + + /// Appends `op` to this code block + #[inline(always)] + pub fn push(&mut self, op: MasmOp) { + self.ops.push(op); + } + + /// Appends instructions from `other` to the end of this block + #[inline] + pub fn append(&mut self, other: &mut Vec) { + self.ops.append(other); + } +} + +/// This enum represents the Miden Assembly (MASM) instruction set. +/// +/// Not all MASM instructions are necessarily represented here, only those we +/// actually use, or intend to use, when compiling from Miden IR. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum MasmOp { + /// Pushes a null word on the stack, i.e. four 0 values + Padw, + /// Pushes the given field element constant on top of the stack + Push(Felt), + /// Pushes the given word constant on top of the stack + Pushw([Felt; 4]), + /// Pushes the given 8-bit constant on top of the stack + PushU8(u8), + /// Pushes the given 16-bit constant on top of the stack + PushU16(u16), + /// Pushes the given 32-bit constant on top of the stack + PushU32(u32), + /// Removes the item on the top of the stack + Drop, + /// Removes the top 4 items on the stack + Dropw, + /// Copies the `n`th item on the stack to the top of stack + /// + /// * `Dup(0)` duplicates the item on top of the stack + Dup(u8), + /// Copies the `n`th word on the stack, to the top of the stack + /// + /// The only values of `n` which are valid, are 0, 1, 2, 3; or + /// in other words, the 4 words which make up the top 16 elements + /// of the stack. + Dupw(u8), + /// Swaps the 1st and `n`th items on the stack + /// + /// * `Swap(1)` swaps the top two elements of the stack + Swap(u8), + /// Swaps the 1st and `n`th words on the stack + /// + /// The only values of `n` which are valid, are 1, 2, 3; or + /// in other words, the 3 words which make up the last 12 elements + /// of the stack. + Swapw(u8), + /// Moves the `n`th stack item to top of stack + /// + /// * `Movup(1)` is equivalent to `Swap(1)` + Movup(u8), + /// Moves the `n`th stack word to the top of the stack + /// + /// The only values of `n` which are valid are 2 and 3. Use `Swapw(1)` + /// if you want to move the second word to the top. + Movupw(u8), + /// Moves the top of stack to the `n`th index of the stack + /// + /// * `Movdn(1)` is equivalent to `Swap(1)` + Movdn(u8), + /// Moves the top word of the stack, into position as the `n`th word on the stack. + /// + /// The only values of `n` which are valid are 2 and 3. Use `Swapw(1)` + /// if you want to make the top word the second word. + Movdnw(u8), + /// Pops `c, b, a` off the stack, and swaps `b` and `a` if `c` is 1, or leaves + /// them as-is when 0. + /// + /// Traps if `c` is > 1. + Cswap, + /// Pops `c, B, A` off the stack, where `B` and `A` are words, and swaps `B` and `A` + /// if `c` is 1, or leaves them as-is when 0. + /// + /// Traps if `c` is > 1. + Cswapw, + /// Pops `c, b, a` off the stack, and pushes back `b` if `c` is 1, and `a` if 0. + /// + /// Traps if `c` is > 1. + Cdrop, + /// Pops `c, B, A` off the stack, where `B` and `A` are words, and pushes back `B` + /// if `c` is 1, and `A` if 0. + /// + /// Traps if `c` is > 1. + Cdropw, + /// Pops a value off the stack and asserts that it is equal to 1 + Assert, + /// Pops a value off the stack and asserts that it is equal to 0 + Assertz, + /// Pops two values off the stack and asserts that they are equal + AssertEq, + /// Pops two words off the stack and asserts that they are equal + AssertEqw, + /// Places the memory address of the given local index on top of the stack + LocAddr(LocalId), + /// Pops `a`, representing a memory address, from the top of the stack, then loads the + /// first element of the word starting at that address, placing it on top of the stack. + /// + /// Traps if `a` >= 2^32 + MemLoad, + /// Same as above, but the address is given as an immediate + MemLoadImm(u32), + /// Pops `a`, representing a memory address + offset pair, from the top of the stack, then loads the + /// element at the given offset from the base of the word starting at that address, placing it on top + /// of the stack. + /// + /// Traps if `a` >= 2^32 + /// + /// NOTE: This instruction doesn't actually exist in Miden Assembly yet, it is a proposed extension of + /// `MemLoad` which allows addressing all field elements of a word individually. It is here for testing. + MemLoadOffset, + /// Same as above, but the address and offset are given as a immediates + MemLoadOffsetImm(u32, u8), + /// Pops `a`, representing a memory address, from the top of the stack, then overwrites + /// the top word of the stack with the word starting at that address. + /// + /// Traps if `a` >= 2^32 + MemLoadw, + /// Same as above, but the address is given as an immediate + MemLoadwImm(u32), + /// Pops `a, v` from the stack, where `a` represents a memory address, and `v` the value + /// to be stored, and stores `v` as the element as the first element of the word starting + /// at that address. The remaining elements of the word are not modified. + /// + /// Traps if `a` >= 2^32 + MemStore, + /// Same as above, but the address is given as an immediate + MemStoreImm(u32), + /// Pops `a, v` from the stack, where `a` represents a memory address + offset pair, and `v` the value + /// to be stored, and stores `v` as the element at the given offset from the base of the word starting + /// at that address. The remaining elements of the word are not modified. + /// + /// Traps if `a` >= 2^32 + /// + /// NOTE: This instruction doesn't actually exist in Miden Assembly yet, it is a proposed extension of + /// `MemStore` which allows addressing all field elements of a word individually. It is here for testing. + MemStoreOffset, + /// Same as above, but the address and offset are given as a immediates + MemStoreOffsetImm(u32, u8), + /// Pops `a, V` from the stack, where `a` represents a memory address, and `V` is a word to be stored + /// at that location, and overwrites the word located at `a`. + /// + /// Traps if `a` >= 2^32 + MemStorew, + /// Same as above, but the address is given as an immediate + MemStorewImm(u32), + /// Pops the top of the stack, and evaluates the ops in + /// the block of code corresponding to the branch taken. + /// + /// If the value is `1`, corresponding to `true`, the first block + /// is evaluated. Otherwise, the value must be `0`, corresponding to + /// `false`, and the second block is evaluated. + If(MasmBlockId, MasmBlockId), + /// Pops the top of the stack, and evaluates the given block of + /// code if the value is `1`, corresponding to `true`. + /// + /// Otherwise, the value must be `0`, corresponding to `false`, + /// and the block is skipped. + While(MasmBlockId), + /// Repeatedly evaluates the given block, `n` times. + Repeat(u8, MasmBlockId), + /// Pops `N` args off the stack, executes the procedure, results will be placed on the stack + Exec(FunctionIdent), + /// Pops `N` args off the stack, executes the procedure in the root context, results will be placed on the stack + Syscall(FunctionIdent), + /// Pops `b, a` off the stack, and places the result of `(a + b) mod p` on the stack + Add, + /// Same as above, but the immediate is used for `b` + AddImm(Felt), + /// Pops `b, a` off the stack, and places the result of `(a - b) mod p` on the stack + Sub, + /// Same as above, but the immediate is used for `b` + SubImm(Felt), + /// Pops `b, a` off the stack, and places the result of `(a * b) mod p` on the stack + Mul, + /// Same as above, but the immediate is used for `b` + MulImm(Felt), + /// Pops `b, a` off the stack, and places the result of `(a * b^-1) mod p` on the stack + /// + /// NOTE: `b` must not be 0 + Div, + /// Same as above, but the immediate is used for `b` + DivImm(Felt), + /// Pops `a` off the stack, and places the result of `-a mod p` on the stack + Neg, + /// Pops `a` off the stack, and places the result of `a^-1 mod p` on the stack + /// + /// NOTE: `a` must not be equal to 0 + Inv, + /// Pops `a` off the stack, and places the result of incrementing it by 1 back on the stack + Incr, + /// Pops `a` off the stack, and places the result of `2^a` on the stack + /// + /// NOTE: `a` must not be > 63 + Pow2, + /// Pops `a` and `b` off the stack, and places the result of `a^b` on the stack + /// + /// NOTE: `b` must not be > 63 + Exp, + /// Pops `a` off the stack, and places the result of `a^` on the stack + /// + /// NOTE: `imm` must not be > 63 + ExpImm(u8), + /// Pops `a` off the stack, and places the result of `1 - a` on the stack + /// + /// NOTE: `a` must be boolean + Not, + /// Pops `b, a` off the stack, and places the result of `a * b` on the stack + /// + /// NOTE: `a` must be boolean + And, + /// Same as above, but `a` is taken from the stack, and `b` is the immediate. + /// + /// NOTE: `a` must be boolean + AndImm(bool), + /// Pops `b, a` off the stack, and places the result of `a + b - a * b` on the stack + /// + /// NOTE: `a` must be boolean + Or, + /// Same as above, but `a` is taken from the stack, and `b` is the immediate. + /// + /// NOTE: `a` must be boolean + OrImm(bool), + /// Pops `b, a` off the stack, and places the result of `a + b - 2 * a * b` on the stack + /// + /// NOTE: `a` and `b` must be boolean + Xor, + /// Same as above, but `a` is taken from the stack, and `b` is the immediate. + /// + /// NOTE: `a` must be boolean + XorImm(bool), + /// Pops `b, a` off the stack, and places the result of `a == b` on the stack + Eq, + /// Same as above, but `b` is provided by the immediate + EqImm(Felt), + /// Pops `b, a` off the stack, and places the result of `a != b` on the stack + Neq, + /// Same as above, but `b` is provided by the immediate + NeqImm(Felt), + /// Pops `b, a` off the stack, and places the result of `a > b` on the stack + Gt, + /// Same as above, but `b` is provided by the immediate + GtImm(Felt), + /// Pops `b, a` off the stack, and places the result of `a >= b` on the stack + Gte, + /// Same as above, but `b` is provided by the immediate + GteImm(Felt), + /// Pops `b, a` off the stack, and places the result of `a < b` on the stack + Lt, + /// Same as above, but `b` is provided by the immediate + LtImm(Felt), + /// Pops `b, a` off the stack, and places the result of `a <= b` on the stack + Lte, + /// Same as above, but `b` is provided by the immediate + LteImm(Felt), + /// Pops `a` off the stack, and places the 1 on the stack if `a` is odd, else 0 + IsOdd, + /// Pops `B, A` off the stack, and places the result of `A == B` on the stack, + /// where the uppercase variables here represent words, rather than field elements. + /// + /// The comparison works by comparing pairs of elements from each word + Eqw, + /// Pushes the current value of the cycle counter (clock) on the stack + Clk, + /// Peeks `a` from the top of the stack, and places the 1 on the stack if `a < 2^32`, else 0 + U32Test, + /// Peeks `A` from the top of the stack, and places the 1 on the stack if `forall a : A, a < 2^32`, else 0 + U32Testw, + /// Peeks `a` from the top of the stack, and traps if `a >= 2^32` + U32Assert, + /// Peeks `b, a` from the top of the stack, and traps if either `a` or `b` is >= 2^32 + U32Assert2, + /// Peeks `A` from the top of the stack, and traps unless `forall a : A, a < 2^32`, else 0 + U32Assertw, + /// Pops `a` from the top of the stack, and places the result of `a mod 2^32` on the stack + /// + /// This is used to cast a field element to the u32 range + U32Cast, + /// Pops `a` from the top of the stack, and splits it into upper and lower 32-bit values, + /// placing them back on the stack. The lower part is calculated as `a mod 2^32`, + /// and the higher part as `a / 2^32`. The higher part will be on top of the stack after. + U32Split, + /// Pops `b, a` from the stack, and places the result of `a + b` on the stack, + /// trapping if the result, or either operand, are >= 2^32 + U32CheckedAdd, + /// Same as above, but with `b` provided by the immediate + U32CheckedAddImm(u32), + /// Pops `b, a` from the stack, and places the result of `(a + b) mod 2^32` on the stack, + /// followed by 1 if `(a + b) >= 2^32`, else 0. Thus the first item on the stack will be + /// a boolean indicating whether the arithmetic overflowed, and the second will be the + /// result of the addition. + /// + /// The behavior is undefined if either `b` or `a` are >= 2^32 + U32OverflowingAdd, + /// Same as above, but with `b` provided by the immediate + U32OverflowingAddImm(u32), + /// Pops `b, a` from the stack, and places the result of `(a + b) mod 2^32` on the stack. + /// + /// The behavior is undefined if either `b` or `a` are >= 2^32 + U32WrappingAdd, + /// Same as above, but with `b` provided by the immediate + U32WrappingAddImm(u32), + /// Pops `c, b, a` from the stack, adds them together, and splits the result into higher + /// and lower parts. The lower part is calculated as `(a + b + c) mod 2^32`, + /// the higher part as `(a + b + c) / 2^32`. + /// + /// The behavior is undefined if any of `c`, `b` or `a` are >= 2^32 + U32OverflowingAdd3, + /// Pops `c, b, a` from the stack, adds them together, and splits the result into higher + /// and lower parts. The lower part is calculated as `(a + b + c) mod 2^32`, + /// the higher part as `(a + b + c) / 2^32`. + /// + /// The behavior is undefined if any of `c`, `b` or `a` are >= 2^32 + U32WrappingAdd3, + /// Pops `b, a` from the stack, and places the result of `a - b` on the stack, + /// trapping if the result, or either operand, are >= 2^32; OR if `a < b`. + U32CheckedSub, + /// Same as above, but with `b` provided by the immediate + U32CheckedSubImm(u32), + /// Pops `b, a` from the stack, and places the result of `(a - b) mod 2^32` on the stack, + /// followed by 1 if `a < b`, else 0. Thus the first item on the stack will be + /// a boolean indicating whether the arithmetic underflowed, and the second will be the + /// result of the subtraction. + /// + /// The behavior is undefined if either `b` or `a` are >= 2^32 + U32OverflowingSub, + /// Same as above, but with `b` provided by the immediate + U32OverflowingSubImm(u32), + /// Pops `b, a` from the stack, and places the result of `(a - b) mod 2^32` on the stack. + /// + /// The behavior is undefined if either `b` or `a` are >= 2^32 + U32WrappingSub, + /// Same as above, but with `b` provided by the immediate + U32WrappingSubImm(u32), + /// Pops `b, a` from the stack, and places the result of `a * b` on the stack, + /// trapping if the result, or either operand, are >= 2^32. + U32CheckedMul, + /// Same as above, but with `b` provided by the immediate + U32CheckedMulImm(u32), + /// Pops `b, a` from the stack, and places the result of `(a * b) mod 2^32` on the stack, + /// followed by `(a * b) / 2^32`. Thus the first item on the stack will be the number + /// of times the multiplication overflowed, followed by the result. + /// + /// The behavior is undefined if either `b` or `a` are >= 2^32 + U32OverflowingMul, + /// Same as above, but with `b` provided by the immediate + U32OverflowingMulImm(u32), + /// Pops `b, a` from the stack, and places the result of `(a * b) mod 2^32` on the stack. + /// + /// The behavior is undefined if either `b` or `a` are >= 2^32 + U32WrappingMul, + /// Same as above, but with `b` provided by the immediate + U32WrappingMulImm(u32), + /// Pops `c, b, a` off the stack, and calculates `d = c * b + a`, then splits the result + /// into higher and lower parts, the lower given by `d mod 2^32`, the higher by `d / 2^32`, + /// and pushes them back on the stack, with the higher part on top of the stack at the end. + /// + /// Behavior is undefined if any of `a`, `b`, or `c` are >= 2^32 + U32OverflowingMadd, + /// Pops `c, b, a` off the stack, and pushes `(c * a + b) mod 2^32` on the stack. + /// + /// Behavior is undefined if any of `a`, `b`, or `c` are >= 2^32 + U32WrappingMadd, + /// Pops `b, a` off the stack, and pushes `a / b` on the stack. + /// + /// Traps if `b` is 0, or if `a` or `b` >= 2^32 + U32CheckedDiv, + /// Same as above, except `b` is provided by the immediate + U32CheckedDivImm(u32), + /// Pops `b, a` off the stack, and pushes `a / b` on the stack. + /// + /// Traps if `b` is 0. + /// + /// Behavior is undefined if `a` or `b` >= 2^32 + U32UncheckedDiv, + /// Same as above, except `b` is provided by the immediate + U32UncheckedDivImm(u32), + /// Pops `b, a` off the stack, and pushes `a mod b` on the stack. + /// + /// Traps if `b` is 0, or if `a` or `b` >= 2^32 + U32CheckedMod, + /// Same as above, except `b` is provided by the immediate + U32CheckedModImm(u32), + /// Pops `b, a` off the stack, and pushes `a mod b` on the stack. + /// + /// Traps if `b` is 0. + /// + /// Behavior is undefined if `a` or `b` >= 2^32 + U32UncheckedMod, + /// Same as above, except `b` is provided by the immediate + U32UncheckedModImm(u32), + /// Pops `b, a` off the stack, and first pushes `a / b` on the stack, followed by `a mod b`. + /// + /// Traps if `b` is 0, or if `a` or `b` >= 2^32 + U32CheckedDivMod, + /// Same as above, except `b` is provided by the immediate + U32CheckedDivModImm(u32), + /// Pops `b, a` off the stack, and first pushes `a / b` on the stack, followed by `a mod b`. + /// + /// Traps if `b` is 0. + /// + /// Behavior is undefined if `a` or `b` >= 2^32 + U32UncheckedDivMod, + /// Same as above, except `b` is provided by the immediate + U32UncheckedDivModImm(u32), + /// Pops `b, a` off the stack, and places the bitwise AND of `a` and `b` on the stack. + /// + /// Traps if either `a` or `b` >= 2^32 + U32And, + /// Pops `b, a` off the stack, and places the bitwise OR of `a` and `b` on the stack. + /// + /// Traps if either `a` or `b` >= 2^32 + U32Or, + /// Pops `b, a` off the stack, and places the bitwise XOR of `a` and `b` on the stack. + /// + /// Traps if either `a` or `b` >= 2^32 + U32Xor, + /// Pops `a` off the stack, and places the bitwise NOT of `a` on the stack. + /// + /// Traps if `a >= 2^32` + U32Not, + /// Pops `b, a` off the stack, and places the result of `(a * 2^b) mod 2^32` on the stack. + /// + /// Traps if `a >= 2^32` or `b > 31` + U32CheckedShl, + /// Same as above, except `b` is provided by the immediate + U32CheckedShlImm(u32), + /// Pops `b, a` off the stack, and places the result of `(a * 2^b) mod 2^32` on the stack. + /// + /// Behavior is undefined if `a >= 2^32` or `b > 31` + U32UncheckedShl, + /// Same as above, except `b` is provided by the immediate + U32UncheckedShlImm(u32), + /// Pops `b, a` off the stack, and places the result of `a / 2^b` on the stack. + /// + /// Traps if `a >= 2^32` or `b > 31` + U32CheckedShr, + /// Same as above, except `b` is provided by the immediate + U32CheckedShrImm(u32), + /// Pops `b, a` off the stack, and places the result of `a / 2^b` on the stack. + /// + /// Behavior is undefined if `a >= 2^32` or `b > 31` + U32UncheckedShr, + /// Same as above, except `b` is provided by the immediate + U32UncheckedShrImm(u32), + /// Pops `b, a` off the stack, and places the result of rotating the 32-bit + /// representation of `a` to the left by `b` bits. + /// + /// Traps if `a` >= 2^32, or `b` > 31 + U32CheckedRotl, + /// Same as above, except `b` is provided by the immediate + U32CheckedRotlImm(u32), + /// Pops `b, a` off the stack, and places the result of rotating the 32-bit + /// representation of `a` to the left by `b` bits. + /// + /// Behavior is undefined if `a` >= 2^32, or `b` > 31 + U32UncheckedRotl, + /// Same as above, except `b` is provided by the immediate + U32UncheckedRotlImm(u32), + /// Pops `b, a` off the stack, and places the result of rotating the 32-bit + /// representation of `a` to the right by `b` bits. + /// + /// Traps if `a` >= 2^32, or `b` > 31 + U32CheckedRotr, + /// Same as above, except `b` is provided by the immediate + U32CheckedRotrImm(u32), + /// Pops `b, a` off the stack, and places the result of rotating the 32-bit + /// representation of `a` to the right by `b` bits. + /// + /// Behavior is undefined if `a` >= 2^32, or `b` > 31 + U32UncheckedRotr, + /// Same as above, except `b` is provided by the immediate + U32UncheckedRotrImm(u32), + /// Pops `a` off the stack, and places the number of set bits in `a` (it's hamming weight). + /// + /// Traps if `a` >= 2^32 + U32CheckedPopcnt, + /// Pops `a` off the stack, and places the number of set bits in `a` (it's hamming weight). + /// + /// Behavior is undefined if `a` >= 2^32 + U32UncheckedPopcnt, + /// Pops `b, a` from the stack, and places 1 on the stack if `a == b`, else 0 + /// + /// Traps if either `a` or `b` are >= 2^32 + U32Eq, + /// Same as above, except `b` is provided by the immediate + U32EqImm(u32), + /// Pops `b, a` from the stack, and places 1 on the stack if `a != b`, else 0 + /// + /// Traps if either `a` or `b` are >= 2^32 + U32Neq, + /// Same as above, except `b` is provided by the immediate + U32NeqImm(u32), + /// Pops `b, a` from the stack, and places 1 on the stack if `a < b`, else 0 + /// + /// Traps if either `a` or `b` are >= 2^32 + U32CheckedLt, + /// Pops `b, a` from the stack, and places 1 on the stack if `a < b`, else 0 + /// + /// Traps if either `a` or `b` are >= 2^32 + U32UncheckedLt, + /// Pops `b, a` from the stack, and places 1 on the stack if `a <= b`, else 0 + /// + /// Traps if either `a` or `b` are >= 2^32 + U32CheckedLte, + /// Pops `b, a` from the stack, and places 1 on the stack if `a <= b`, else 0 + /// + /// Traps if either `a` or `b` are >= 2^32 + U32UncheckedLte, + /// Pops `b, a` from the stack, and places 1 on the stack if `a > b`, else 0 + /// + /// Traps if either `a` or `b` are >= 2^32 + U32CheckedGt, + /// Pops `b, a` from the stack, and places 1 on the stack if `a > b`, else 0 + /// + /// The behavior is undefined if either `a` or `b` are >= 2^32 + U32UncheckedGt, + /// Pops `b, a` from the stack, and places 1 on the stack if `a >= b`, else 0 + /// + /// Traps if either `a` or `b` are >= 2^32 + U32CheckedGte, + /// Pops `b, a` from the stack, and places 1 on the stack if `a >= b`, else 0 + /// + /// The behavior is undefined if either `a` or `b` are >= 2^32 + U32UncheckedGte, + /// Pops `b, a` from the stack, and places `a` back on the stack if `a < b`, else `b` + /// + /// Traps if either `a` or `b` are >= 2^32 + U32CheckedMin, + /// Pops `b, a` from the stack, and places `a` back on the stack if `a < b`, else `b` + /// + /// The behavior is undefined if either `a` or `b` are >= 2^32 + U32UncheckedMin, + /// Pops `b, a` from the stack, and places `a` back on the stack if `a > b`, else `b` + /// + /// Traps if either `a` or `b` are >= 2^32 + U32CheckedMax, + /// Pops `b, a` from the stack, and places `a` back on the stack if `a > b`, else `b` + /// + /// The behavior is undefined if either `a` or `b` are >= 2^32 + U32UncheckedMax, +} + +/// This implementation displays the opcode name for the given [MasmOp] +impl fmt::Display for MasmOp { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Padw => f.write_str("padw"), + Self::Push(_) + | Self::Pushw(_) + | Self::PushU8(_) + | Self::PushU16(_) + | Self::PushU32(_) => f.write_str("push"), + Self::Drop => f.write_str("drop"), + Self::Dropw => f.write_str("dropw"), + Self::Dup(_) => f.write_str("dup"), + Self::Dupw(_) => f.write_str("dupw"), + Self::Swap(_) => f.write_str("swap"), + Self::Swapw(_) => f.write_str("swapw"), + Self::Movup(_) => f.write_str("movup"), + Self::Movupw(_) => f.write_str("movupw"), + Self::Movdn(_) => f.write_str("movdn"), + Self::Movdnw(_) => f.write_str("movdnw"), + Self::Cswap => f.write_str("cswap"), + Self::Cswapw => f.write_str("cswapw"), + Self::Cdrop => f.write_str("cdrop"), + Self::Cdropw => f.write_str("cdropw"), + Self::Assert => f.write_str("assert"), + Self::Assertz => f.write_str("assertz"), + Self::AssertEq => f.write_str("assert_eq"), + Self::AssertEqw => f.write_str("assert_eqw"), + Self::LocAddr(_) => f.write_str("locaddr"), + Self::MemLoad + | Self::MemLoadOffset + | Self::MemLoadImm(_) + | Self::MemLoadOffsetImm(_, _) => f.write_str("mem_load"), + Self::MemLoadw | Self::MemLoadwImm(_) => f.write_str("mem_loadw"), + Self::MemStore + | Self::MemStoreOffset + | Self::MemStoreImm(_) + | Self::MemStoreOffsetImm(_, _) => f.write_str("mem_store"), + Self::MemStorew | Self::MemStorewImm(_) => f.write_str("mem_storew"), + Self::If(_, _) => f.write_str("if.true"), + Self::While(_) => f.write_str("while.true"), + Self::Repeat(_, _) => f.write_str("repeat"), + Self::Exec(_) => f.write_str("exec"), + Self::Syscall(_) => f.write_str("syscall"), + Self::Add | Self::AddImm(_) => f.write_str("add"), + Self::Sub | Self::SubImm(_) => f.write_str("sub"), + Self::Mul | Self::MulImm(_) => f.write_str("mul"), + Self::Div | Self::DivImm(_) => f.write_str("div"), + Self::Neg => f.write_str("neg"), + Self::Inv => f.write_str("inv"), + Self::Incr => f.write_str("incr"), + Self::Pow2 => f.write_str("pow2"), + Self::Exp | Self::ExpImm(_) => f.write_str("exp.u64"), + Self::Not => f.write_str("not"), + Self::And | Self::AndImm(_) => f.write_str("and"), + Self::Or | Self::OrImm(_) => f.write_str("or"), + Self::Xor | Self::XorImm(_) => f.write_str("xor"), + Self::Eq | Self::EqImm(_) => f.write_str("eq"), + Self::Neq | Self::NeqImm(_) => f.write_str("neq"), + Self::Gt | Self::GtImm(_) => f.write_str("gt"), + Self::Gte | Self::GteImm(_) => f.write_str("gte"), + Self::Lt | Self::LtImm(_) => f.write_str("lt"), + Self::Lte | Self::LteImm(_) => f.write_str("lte"), + Self::IsOdd => f.write_str("is_odd"), + Self::Eqw => f.write_str("eqw"), + Self::Clk => f.write_str("clk"), + Self::U32Test => f.write_str("u32.test"), + Self::U32Testw => f.write_str("u32.testw"), + Self::U32Assert => f.write_str("u32.assert"), + Self::U32Assert2 => f.write_str("u32.assert2"), + Self::U32Assertw => f.write_str("u32.assertw"), + Self::U32Cast => f.write_str("u23.cast"), + Self::U32Split => f.write_str("u32.split"), + Self::U32CheckedAdd | Self::U32CheckedAddImm(_) => f.write_str("u32.add.checked"), + Self::U32OverflowingAdd | Self::U32OverflowingAddImm(_) => { + f.write_str("u32.add.overflowing") + } + Self::U32WrappingAdd | Self::U32WrappingAddImm(_) => f.write_str("u32.add.wrapping"), + Self::U32OverflowingAdd3 => f.write_str("u32.add3.overflowing"), + Self::U32WrappingAdd3 => f.write_str("u32.add3.wrapping"), + Self::U32CheckedSub | Self::U32CheckedSubImm(_) => f.write_str("u32.sub.checked"), + Self::U32OverflowingSub | Self::U32OverflowingSubImm(_) => { + f.write_str("u32.sub.overflowing") + } + Self::U32WrappingSub | Self::U32WrappingSubImm(_) => f.write_str("u32.sub.wrapping"), + Self::U32CheckedMul | Self::U32CheckedMulImm(_) => f.write_str("u32.mul.checked"), + Self::U32OverflowingMul | Self::U32OverflowingMulImm(_) => { + f.write_str("u32.mul.overflowing") + } + Self::U32WrappingMul | Self::U32WrappingMulImm(_) => f.write_str("u32.mul.wrapping"), + Self::U32OverflowingMadd => f.write_str("u32.madd.overflowing"), + Self::U32WrappingMadd => f.write_str("u32.madd.wrapping"), + Self::U32CheckedDiv | Self::U32CheckedDivImm(_) => f.write_str("u32.div.checked"), + Self::U32UncheckedDiv | Self::U32UncheckedDivImm(_) => f.write_str("u32.div.unchecked"), + Self::U32CheckedMod | Self::U32CheckedModImm(_) => f.write_str("u32.mod.checked"), + Self::U32UncheckedMod | Self::U32UncheckedModImm(_) => f.write_str("u32.mod.unchecked"), + Self::U32CheckedDivMod | Self::U32CheckedDivModImm(_) => { + f.write_str("u32.divmod.checked") + } + Self::U32UncheckedDivMod | Self::U32UncheckedDivModImm(_) => { + f.write_str("u32.divmod.unchecked") + } + Self::U32And => f.write_str("u32.and"), + Self::U32Or => f.write_str("u32.or"), + Self::U32Xor => f.write_str("u32.xor"), + Self::U32Not => f.write_str("u32.not"), + Self::U32CheckedShl | Self::U32CheckedShlImm(_) => f.write_str("u32.shl.checked"), + Self::U32UncheckedShl | Self::U32UncheckedShlImm(_) => f.write_str("u32.shl.unchecked"), + Self::U32CheckedShr | Self::U32CheckedShrImm(_) => f.write_str("u32.shr.checked"), + Self::U32UncheckedShr | Self::U32UncheckedShrImm(_) => f.write_str("u32.shr.unchecked"), + Self::U32CheckedRotl | Self::U32CheckedRotlImm(_) => f.write_str("u32.rotl.checked"), + Self::U32UncheckedRotl | Self::U32UncheckedRotlImm(_) => { + f.write_str("u32.rotl.unchecked") + } + Self::U32CheckedRotr | Self::U32CheckedRotrImm(_) => f.write_str("u32.rotr.checked"), + Self::U32UncheckedRotr | Self::U32UncheckedRotrImm(_) => { + f.write_str("u32.rotr.unchecked") + } + Self::U32CheckedPopcnt => f.write_str("u32.popcnt.checked"), + Self::U32UncheckedPopcnt => f.write_str("u32.popcnt.unchecked"), + Self::U32Eq | Self::U32EqImm(_) => f.write_str("u32.eq"), + Self::U32Neq | Self::U32NeqImm(_) => f.write_str("u32.neq"), + Self::U32CheckedLt => f.write_str("u32.lt.checked"), + Self::U32UncheckedLt => f.write_str("u32.lt.unchecked"), + Self::U32CheckedLte => f.write_str("u32.lte.checked"), + Self::U32UncheckedLte => f.write_str("u32.lte.unchecked"), + Self::U32CheckedGt => f.write_str("u32.gt.checked"), + Self::U32UncheckedGt => f.write_str("u32.gt.unchecked"), + Self::U32CheckedGte => f.write_str("u32.gte.checked"), + Self::U32UncheckedGte => f.write_str("u32.gte.unchecked"), + Self::U32CheckedMin => f.write_str("u32.min.checked"), + Self::U32UncheckedMin => f.write_str("u32.min.unchecked"), + Self::U32CheckedMax => f.write_str("u32.max.checked"), + Self::U32UncheckedMax => f.write_str("u32.max.unchecked"), + } + } +} diff --git a/hir/src/asm/mod.rs b/hir/src/asm/mod.rs new file mode 100644 index 00000000..b65123e6 --- /dev/null +++ b/hir/src/asm/mod.rs @@ -0,0 +1,82 @@ +mod builder; +mod display; +mod isa; +mod stack; + +pub use self::builder::*; +pub use self::display::DisplayInlineAsm; +pub use self::isa::*; +pub use self::stack::{OperandStack, Stack}; + +use cranelift_entity::PrimaryMap; + +use super::{DataFlowGraph, Opcode, Type, ValueList}; + +/// Represents Miden Assembly (MASM) directly in the IR +/// +/// Each block of inline assembly executes in its own pseudo-isolated environment, +/// i.e. other than arguments provided to the inline assembly, and values introduced +/// within the inline assembly, it is not permitted to access anything else on the +/// operand stack. +/// +/// In addition to arguments, inline assembly can produce zero or more results, +/// see [MasmBuilder] for more info. +/// +/// Inline assembly can be built using [InstBuilder::inline_asm]. +#[derive(Debug, Clone)] +pub struct InlineAsm { + pub op: Opcode, + /// Arguments on which the inline assembly can operate + /// + /// The operand stack will be set up such that the given arguments + /// will appear in LIFO order, i.e. the first argument will be on top + /// of the stack, and so on. + /// + /// The inline assembly will be validated so that all other values on + /// the operand stack below the given arguments will remain on the stack + /// when the inline assembly finishes executing. + pub args: ValueList, + /// The types of the results produced by this inline assembly block + pub results: Vec, + /// The main code block + pub body: MasmBlockId, + /// The set of all code blocks contained in this inline assembly + /// + /// This is necessary to support control flow operations within asm blocks + pub blocks: PrimaryMap, +} +impl InlineAsm { + /// Constructs a new, empty inline assembly block with the given result type(s). + pub fn new(results: Vec) -> Self { + let mut blocks = PrimaryMap::::new(); + let id = blocks.next_key(); + let body = blocks.push(MasmBlock { id, ops: vec![] }); + Self { + op: Opcode::InlineAsm, + args: ValueList::default(), + results, + body, + blocks, + } + } + + /// Create a new code block for use with this inline assembly + pub fn create_block(&mut self) -> MasmBlockId { + let id = self.blocks.next_key(); + self.blocks.push(MasmBlock { id, ops: vec![] }); + id + } + + /// Appends `op` to the end of `block` + pub fn push(&mut self, block: MasmBlockId, op: MasmOp) { + self.blocks[block].push(op); + } + + pub fn display<'a, 'b: 'a>( + &'b self, + dfg: &'b DataFlowGraph, + indent: usize, + ) -> DisplayInlineAsm<'a> { + DisplayInlineAsm::new(self, dfg, indent) + } +} diff --git a/hir/src/asm/stack.rs b/hir/src/asm/stack.rs new file mode 100644 index 00000000..81d75451 --- /dev/null +++ b/hir/src/asm/stack.rs @@ -0,0 +1,440 @@ +use std::{ + fmt, + ops::{Index, IndexMut}, +}; + +use winter_math::FieldElement; + +use crate::{Felt, Type}; + +/// This trait is used to represent the basic plumbing of the operand stack in +/// Miden Assembly. +/// +/// Implementations of this trait may attach different semantics to the meaning of +/// elements on the stack. As a result, certain operations which are contingent on the +/// specific value of an element, may behave differently depending on the specific +/// implementation. +/// +/// In general however, it is expected that use of this trait in a generic context will +/// be rare, if ever the case. As mentioned above, it is meant to handle the common +/// plumbing of an operand stack implementation, but in practice users will be working +/// with a concrete implementation with this trait in scope to provide access to the +/// basic functionality of the stack. +/// +/// It is expected that implementations will override functions in this trait as necessary +/// to implement custom behavior above and beyond what is provided by the default implementation. +pub trait Stack: IndexMut::Element> { + type Element: StackElement; + + /// Return a reference to the underlying "raw" stack data structure, a vector + fn stack(&self) -> &Vec; + /// Return a mutable reference to the underlying "raw" stack data structure, a vector + fn stack_mut(&mut self) -> &mut Vec; + + /// Display this stack using its debugging representation + fn display(&self) -> DebugStack { + DebugStack(self) + } + + /// Returns true if the operand stack is empty + #[inline(always)] + fn is_empty(&self) -> bool { + self.stack().is_empty() + } + + /// Returns the number of elements on the stack + #[inline] + fn len(&self) -> usize { + self.stack().len() + } + + /// Returns the value on top of the stack, without consuming it + #[inline] + fn peek(&self) -> Option { + self.stack().last().cloned() + } + + /// Returns the word on top of the stack, without consuming it + #[inline] + fn peekw(&self) -> Option<[Self::Element; 4]> { + let stack = self.stack(); + let end = stack.len().checked_sub(1)?; + Some([ + stack[end].clone(), + stack[end - 1].clone(), + stack[end - 2].clone(), + stack[end - 3].clone(), + ]) + } + + /// Pushes a word of zeroes on top of the stack + fn padw(&mut self) { + self.stack_mut().extend([ + Self::Element::DEFAULT, + Self::Element::DEFAULT, + Self::Element::DEFAULT, + Self::Element::DEFAULT, + ]); + } + + /// Pushes `value` on top of the stac + fn push(&mut self, value: Self::Element) { + self.stack_mut().push(value); + } + + /// Pushes `word` on top of the stack + fn pushw(&mut self, word: [Self::Element; 4]) { + let stack = self.stack_mut(); + for value in word.into_iter().rev() { + stack.push(value); + } + } + + /// Pops the value on top of the stack + fn pop(&mut self) -> Option { + self.stack_mut().pop() + } + + /// Pops the first word on top of the stack + fn popw(&mut self) -> Option<[Self::Element; 4]> { + let stack = self.stack_mut(); + let a = stack.pop()?; + let b = stack.pop()?; + let c = stack.pop()?; + let d = stack.pop()?; + Some([a, b, c, d]) + } + + /// Drops the top item on the stack + fn drop(&mut self) { + self.dropn(1); + } + + /// Drops the top word on the stack + fn dropw(&mut self) { + self.dropn(4); + } + + #[inline] + fn dropn(&mut self, n: usize) { + let stack = self.stack_mut(); + let len = stack.len(); + assert!( + n <= len, + "unable to drop {} elements, operand stack only has {}", + n, + len + ); + stack.truncate(len - n); + } + + /// Duplicates the value in the `n`th position on the stack + /// + /// If `n` is 0, duplicates the top of the stack. + fn dup(&mut self, n: usize) { + let value = self[n].clone(); + self.stack_mut().push(value); + } + + /// Duplicates the `n`th word on the stack, to the top of the stack. + /// + /// Valid values for `n` are 0, 1, 2, or 3. + /// + /// If `n` is 0, duplicates the top word of the stack. + fn dupw(&mut self, n: usize) { + assert!(n < 4, "invalid word index: must be in the range 0..=3"); + let len = self.stack().len(); + let index = n * 4; + assert!( + index < len, + "invalid operand stack index ({}), only {} elements are available", + index, + len + ); + match index { + 0 => { + let word = self.peekw().expect("operand stack is empty"); + self.pushw(word); + } + n => { + let end = len - n - 1; + let word = { + let stack = self.stack(); + [ + stack[end].clone(), + stack[end - 1].clone(), + stack[end - 2].clone(), + stack[end - 3].clone(), + ] + }; + self.pushw(word); + } + } + } + + /// Swaps the `n`th value from the top of the stack, with the top of the stack + /// + /// If `n` is 1, it swaps the first two elements on the stack. + /// + /// NOTE: This function will panic if `n` is 0, or out of bounds. + fn swap(&mut self, n: usize) { + assert_ne!(n, 0, "invalid swap, index must be in the range 1..=15"); + let stack = self.stack_mut(); + let len = stack.len(); + assert!( + n < len, + "invalid operand stack index ({}), only {} elements are available", + n, + len + ); + let a = len - 1; + let b = a - n; + stack.swap(a, b); + } + + /// Swaps the `n`th word from the top of the stack, with the word on top of the stack + /// + /// If `n` is 1, it swaps the first two words on the stack. + /// + /// Valid values for `n` are: 1, 2, 3. + fn swapw(&mut self, n: usize) { + assert_ne!(n, 0, "invalid swap, index must be in the range 1..=3"); + let stack = self.stack_mut(); + let len = stack.len(); + let index = n * 4; + assert!( + index < len, + "invalid operand stack index ({}), only {} elements are available", + index, + len + ); + for offset in 0..4 { + // The index of the element in the top word + let a = len - 1 - offset; + // The index of the element in the `n`th word + let b = len - index - offset; + stack.swap(a, b); + } + } + + /// Moves the `n`th value to the top of the stack + /// + /// If `n` is 1, this is equivalent to `swap(1)`. + /// + /// NOTE: This function will panic if `n` is 0, or out of bounds. + fn movup(&mut self, n: usize) { + assert_ne!(n, 0, "invalid move, index must be in the range 1..=15"); + let stack = self.stack_mut(); + let len = stack.len(); + assert!( + n < len, + "invalid operand stack index ({}), only {} elements are available", + n, + len + ); + // Pick the midpoint by counting backwards from the end + let end = len - 1; + let mid = end - n; + // Split the stack, and rotate the half that + // contains our desired value to place it on top. + let (_, r) = stack.split_at_mut(mid); + r.rotate_left(1); + } + + /// Moves the `n`th word to the top of the stack + /// + /// If `n` is 1, this is equivalent to `swapw(1)`. + /// + /// Valid values for `n` are: 1, 2, 3 + fn movupw(&mut self, n: usize) { + assert_ne!(n, 0, "invalid move, index must be in the range 1..=3"); + let stack = self.stack_mut(); + let len = stack.len(); + let index = n * 4; + let last_index = index - 4; + assert!( + last_index < len, + "invalid operand stack index ({}), only {} elements are available", + last_index, + len + ); + // Pick the midpoint by counting backwards from the end + let end = len - 1; + let mid = end - last_index; + // Split the stack, and rotate the half that + // contains our desired word to place it on top. + let (_, r) = stack.split_at_mut(mid); + r.rotate_left(4); + } + + /// Makes the value on top of the stack, the `n`th value on the stack + /// + /// If `n` is 1, this is equivalent to `swap(1)`. + /// + /// NOTE: This function will panic if `n` is 0, or out of bounds. + fn movdn(&mut self, n: usize) { + assert_ne!(n, 0, "invalid move, index must be in the range 1..=15"); + let stack = self.stack_mut(); + let len = stack.len(); + assert!( + n < len, + "invalid operand stack index ({}), only {} elements are available", + n, + len + ); + // Split the stack so that the desired position is in the top half + let end = len - 1; + let mid = end - n; + let (_, r) = stack.split_at_mut(mid); + // Move all elements above the `n`th position up by one, moving the top element to the `n`th position + r.rotate_right(1); + } + + /// Makes the word on top of the stack, the `n`th word on the stack + /// + /// If `n` is 1, this is equivalent to `swapw(1)`. + /// + /// Valid values for `n` are: 1, 2, 3 + fn movdnw(&mut self, n: usize) { + assert_ne!(n, 0, "invalid move, index must be in the range 1..=3"); + let stack = self.stack_mut(); + let len = stack.len(); + let index = n * 4; + let last_index = index - 4; + assert!( + last_index < len, + "invalid operand stack index ({}), only {} elements are available", + last_index, + len + ); + // Split the stack so that the desired position is in the top half + let end = len - 1; + let mid = end - last_index; + let (_, r) = stack.split_at_mut(mid); + // Move all elements above the `n`th word up by one word, moving the top word to the `n`th position + r.rotate_right(4); + } +} + +/// This trait is used to represent expected behavior/properties of elements +/// that can be used in conjunction with the [Stack] trait. +pub trait StackElement: Clone + fmt::Debug { + /// A value of this type which represents the "zero" value for the type + const DEFAULT: Self; +} + +impl StackElement for Felt { + const DEFAULT: Self = Felt::ZERO; +} +impl StackElement for Type { + const DEFAULT: Self = Type::Felt; +} + +/// This structure is a concrete implementation of the [Stack] trait, implemented +/// for use with two different element types: +/// +/// * [Felt], for actual emulation of the Miden VM operand stack +/// * [Type], for tracking the state of the operand stack in abstract +pub struct OperandStack { + stack: Vec, +} +impl Clone for OperandStack { + fn clone(&self) -> Self { + Self { + stack: self.stack.clone(), + } + } +} +impl Default for OperandStack { + fn default() -> Self { + Self { stack: vec![] } + } +} +impl Stack for OperandStack { + type Element = T; + + #[inline(always)] + fn stack(&self) -> &Vec { + &self.stack + } + #[inline(always)] + fn stack_mut(&mut self) -> &mut Vec { + &mut self.stack + } +} +impl OperandStack { + /// Pushes `value` on top of the stack, with an optional set of aliases + pub fn push_u8(&mut self, value: u8) { + self.stack.push(Felt::new(value as u64)); + } + + /// Pushes `value` on top of the stack, with an optional set of aliases + pub fn push_u16(&mut self, value: u16) { + self.stack.push(Felt::new(value as u64)); + } + + /// Pushes `value` on top of the stack, with an optional set of aliases + pub fn push_u32(&mut self, value: u32) { + self.stack.push(Felt::new(value as u64)); + } +} +impl Index for OperandStack { + type Output = T; + + fn index(&self, index: usize) -> &Self::Output { + let len = self.stack.len(); + assert!( + index < 16, + "invalid operand stack index ({}), only the top 16 elements are directly accessible", + index + ); + assert!( + index < len, + "invalid operand stack index ({}), only {} elements are available", + index, + len + ); + &self.stack[len - index - 1] + } +} +impl IndexMut for OperandStack { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + let len = self.stack.len(); + assert!( + index < 16, + "invalid operand stack index ({}), only the top 16 elements are directly accessible", + index + ); + assert!( + index < len, + "invalid operand stack index ({}), only {} elements are available", + index, + len + ); + &mut self.stack[len - index - 1] + } +} + +#[doc(hidden)] +pub struct DebugStack<'a, T: ?Sized + Stack>(&'a T); +impl<'a, T: ?Sized + Stack> fmt::Debug for DebugStack<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + #[derive(Debug)] + #[allow(unused)] + struct StackEntry<'a, E: fmt::Debug> { + index: usize, + value: &'a E, + } + + f.debug_list() + .entries( + self.0 + .stack() + .iter() + .rev() + .enumerate() + .map(|(index, value)| StackEntry { index, value }), + ) + .finish() + } +} diff --git a/hir/src/builder.rs b/hir/src/builder.rs index 7ec7235c..3c84503b 100644 --- a/hir/src/builder.rs +++ b/hir/src/builder.rs @@ -941,6 +941,15 @@ pub trait InstBuilder<'f>: InstBuilderBase<'f> { self.build(data, Type::Never, span).0 } + fn inline_asm( + self, + args: &[Value], + results: impl IntoIterator, + span: SourceSpan, + ) -> MasmBuilder { + MasmBuilder::new(self, args, results.into_iter().collect(), span) + } + #[allow(non_snake_case)] fn CondBr( self, diff --git a/hir/src/dataflow.rs b/hir/src/dataflow.rs index 5ad48a8f..a22b7b40 100644 --- a/hir/src/dataflow.rs +++ b/hir/src/dataflow.rs @@ -1,4 +1,4 @@ -use std::ops::{Index, IndexMut}; +use std::ops::{Deref, Index, IndexMut}; use cranelift_entity::{PrimaryMap, SecondaryMap}; use intrusive_collections::UnsafeRef; @@ -320,8 +320,18 @@ impl DataFlowGraph { self.append_result(inst, ty); } } else { - for ty in opcode.results(ctrl_ty).into_iter() { - self.append_result(inst, ty); + match self.insts[inst].data.deref() { + Instruction::InlineAsm(ref asm) => { + let results = asm.results.clone(); + for ty in results.into_iter() { + self.append_result(inst, ty); + } + } + _ => { + for ty in opcode.results(ctrl_ty).into_iter() { + self.append_result(inst, ty); + } + } } } } @@ -334,7 +344,14 @@ impl DataFlowGraph { if let Some(fdata) = self.call_signature(inst) { new_results.extend(fdata.results().iter().map(|p| p.ty.clone())); } else { - new_results = opcode.results(ctrl_ty); + match self.insts[inst].data.deref() { + Instruction::InlineAsm(ref asm) => { + new_results.extend(asm.results.as_slice().iter().cloned()); + } + _ => { + new_results = opcode.results(ctrl_ty); + } + } } let old_results_len = old_results.len(); let new_results_len = new_results.len(); diff --git a/hir/src/function.rs b/hir/src/function.rs index 7c8d5472..b9b5bb9e 100644 --- a/hir/src/function.rs +++ b/hir/src/function.rs @@ -121,6 +121,11 @@ impl AbiParam { extension: ArgumentExtension::default(), } } + + /// Returns the [TypeRepr] for this function parameter + pub fn repr(&self) -> Option { + self.ty.repr() + } } /// A [Signature] represents the type, ABI, and linkage of a function. diff --git a/hir/src/ident.rs b/hir/src/ident.rs index c94cd66a..46e64dbe 100644 --- a/hir/src/ident.rs +++ b/hir/src/ident.rs @@ -2,8 +2,10 @@ use core::{ cmp::Ordering, fmt, hash::{Hash, Hasher}, + str::FromStr, }; +use anyhow::anyhow; use miden_diagnostics::{SourceSpan, Spanned}; use super::{symbols, Symbol}; @@ -15,6 +17,23 @@ pub struct FunctionIdent { #[span] pub function: Ident, } +impl FromStr for FunctionIdent { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + match s.rsplit_once("::") { + Some((ns, id)) => { + let module = Ident::with_empty_span(Symbol::intern(ns)); + let function = Ident::with_empty_span(Symbol::intern(id)); + Ok(Self { + module, + function, + }) + } + None => Err(anyhow!("invalid function name, expected fully-qualified identifier, e.g. 'std::math::u64::checked_add'")), + } + } +} impl fmt::Debug for FunctionIdent { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("FunctionIdent") diff --git a/hir/src/instruction.rs b/hir/src/instruction.rs index 61165668..618c8b43 100644 --- a/hir/src/instruction.rs +++ b/hir/src/instruction.rs @@ -542,8 +542,7 @@ impl Opcode { | Self::Br | Self::CondBr | Self::Switch - | Self::Unreachable - | Self::InlineAsm => smallvec![], + | Self::Unreachable => smallvec![], // These ops have fixed result types Self::Test | Self::IsOdd @@ -605,7 +604,7 @@ impl Opcode { smallvec![ctrl_ty.pointee().expect("expected pointer type").clone()] } // Call results are handled separately - Self::Call | Self::Syscall => unreachable!(), + Self::Call | Self::Syscall | Self::InlineAsm => unreachable!(), } } } @@ -682,19 +681,40 @@ impl fmt::Display for Opcode { } } +/// This enumeration represents the various ways in which arithmetic operations +/// can be configured to behave when either the operands or results over/underflow +/// the range of the integral type. +/// +/// Always check the documentation of the specific instruction involved to see if there +/// are any specific differences in how this enum is interpreted compared to the default +/// meaning of each variant. #[derive(Copy, Clone, Default, Debug)] pub enum Overflow { + /// Typically, this means the operation is performed using the equivalent field element operation, rather + /// than a dedicated operation for the given type. Because of this, the result of the operation may exceed + /// that of the integral type expected, but this will not be caught right away. + /// + /// It is the callers responsibility to ensure that resulting value is in range. #[default] Unchecked, + /// The operation will trap if the operands, or the result, is not valid for the range of the integral + /// type involved, e.g. u32. Checked, + /// The operation will wrap around, depending on the range of the integral type. For example, + /// given a u32 value, this is done by applying `mod 2^32` to the result. Wrapping, + /// The result of the operation will be computed as in [Wrapping], however in addition to the + /// result, this variant also pushes a value on the stack which represents whether or not the + /// operation over/underflowed; either 1 if over/underflow occurred, or 0 otherwise. Overflowing, } impl Overflow { + /// Returns true if overflow is unchecked pub fn is_unchecked(&self) -> bool { matches!(self, Self::Unchecked) } + /// Returns true if overflow will cause a trap pub fn is_checked(&self) -> bool { matches!(self, Self::Checked) } @@ -820,15 +840,3 @@ pub struct PrimOpImm { pub imm: Immediate, pub args: ValueList, } - -#[derive(Debug, Clone)] -pub struct InlineAsm { - pub op: Opcode, - pub body: Vec, - pub args: ValueList, -} - -#[derive(Debug, Clone)] -pub struct AsmInstruction { - pub name: String, -} diff --git a/hir/src/lib.rs b/hir/src/lib.rs index 880a4842..2c9dc690 100644 --- a/hir/src/lib.rs +++ b/hir/src/lib.rs @@ -3,10 +3,51 @@ pub use intrusive_collections::UnsafeRef; pub use miden_diagnostics::SourceSpan; pub use miden_hir_symbol::{symbols, Symbol}; -pub use miden_hir_type::{FunctionType, Type}; +pub use miden_hir_type::{FunctionType, Type, TypeRepr}; pub type Felt = winter_math::fields::f64::BaseElement; +macro_rules! assert_matches { + ($left:expr, $(|)? $( $pattern:pat_param )|+ $( if $guard: expr )? $(,)?) => { + match $left { + $( $pattern )|+ $( if $guard )? => {} + ref left_val => { + panic!(r#" +assertion failed: `(left matches right)` + left: `{:?}`, + right: `{}`"#, left_val, stringify!($($pattern)|+ $(if $guard)?)); + } + } + }; + + ($left:expr, $(|)? $( $pattern:pat_param )|+ $( if $guard: expr )?, $msg:literal $(,)?) => { + match $left { + $( $pattern )|+ $( if $guard )? => {} + ref left_val => { + panic!(concat!(r#" +assertion failed: `(left matches right)` + left: `{:?}`, + right: `{}` +"#, $msg), left_val, stringify!($($pattern)|+ $(if $guard)?)); + } + } + }; + + ($left:expr, $(|)? $( $pattern:pat_param )|+ $( if $guard: expr )?, $msg:literal, $($arg:tt)+) => { + match $left { + $( $pattern )|+ $( if $guard )? => {} + ref left_val => { + panic!(concat!(r#" +assertion failed: `(left matches right)` + left: `{:?}`, + right: `{}` +"#, $msg), left_val, stringify!($($pattern)|+ $(if $guard)?), $($arg)+); + } + } + } +} + +mod asm; mod block; mod builder; mod constants; @@ -19,6 +60,7 @@ mod immediates; mod insert; mod instruction; mod layout; +mod locals; mod module; mod program; #[cfg(test)] @@ -26,6 +68,7 @@ mod tests; mod value; mod write; +pub use self::asm::*; pub use self::block::{Block, BlockData}; pub use self::builder::{ DefaultInstBuilder, FunctionBuilder, InstBuilder, InstBuilderBase, ReplaceBuilder, @@ -40,6 +83,7 @@ pub use self::immediates::Immediate; pub use self::insert::{Insert, InsertionPoint}; pub use self::instruction::*; pub use self::layout::{ArenaMap, LayoutAdapter, LayoutNode, OrderedArenaMap}; +pub use self::locals::{Local, LocalId}; pub use self::module::*; pub use self::program::{Linker, LinkerError, Program}; pub use self::value::{Value, ValueData, ValueList, ValueListPool}; diff --git a/hir/src/locals.rs b/hir/src/locals.rs new file mode 100644 index 00000000..5e69dad7 --- /dev/null +++ b/hir/src/locals.rs @@ -0,0 +1,86 @@ +use std::{alloc::Layout, fmt}; + +use super::Type; + +/// A strongly typed identifier for referencing locals associated with a function +#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct LocalId(u8); +impl LocalId { + /// Create a new instance from a `u32`. + #[inline] + pub fn from_u8(x: u8) -> Self { + debug_assert!(x < u8::MAX, "invalid raw local id"); + Self(x) + } + + /// Return the underlying index value as a `usize`. + #[inline] + pub fn as_usize(self) -> usize { + self.0 as usize + } +} +impl cranelift_entity::EntityRef for LocalId { + #[inline] + fn new(index: usize) -> Self { + debug_assert!(index < (u8::MAX as usize)); + Self(index as u8) + } + + #[inline] + fn index(self) -> usize { + self.0 as usize + } +} +impl cranelift_entity::packed_option::ReservedValue for LocalId { + #[inline] + fn reserved_value() -> LocalId { + Self(u8::MAX) + } + + #[inline] + fn is_reserved_value(&self) -> bool { + self.0 == u8::MAX + } +} +impl fmt::Display for LocalId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "local{}", self.0) + } +} +impl fmt::Debug for LocalId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + +/// Represents a local allocated on the heap statically +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Local { + /// The unique identifier associated with this local + /// + /// It also represents the offset in the set of locals of a function + /// where this local will be allocated. + /// + /// NOTE: If a local's size is larger than a word, multiple consecutive + /// local allocations may be made to ensure there is enough memory starting + /// at the offset represented by `id` to hold the entire value + pub id: LocalId, + /// The type of the value stored in this local + pub ty: Type, +} +impl Local { + /// Returns the [Layout] for this local in memory + pub fn layout(&self) -> Layout { + self.ty.layout() + } + + /// Returns the size in bytes for this local, including necessary alignment padding + pub fn size_in_bytes(&self) -> usize { + self.ty.size_in_bytes() + } + + /// Returns the size in words for this local, including necessary alignment padding + pub fn size_in_words(&self) -> usize { + self.ty.size_in_words() + } +} diff --git a/hir/src/tests.rs b/hir/src/tests.rs index da8be9c7..a28ead8c 100644 --- a/hir/src/tests.rs +++ b/hir/src/tests.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use miden_diagnostics::{ term::termcolor::ColorChoice, CodeMap, DefaultEmitter, DiagnosticsHandler, }; +use winter_math::FieldElement; use super::*; @@ -147,3 +148,125 @@ fn integration_test_builders() { // Finalize the module builder.build(); } + +#[test] +fn inline_asm_builders() { + let codemap = Arc::new(CodeMap::new()); + let emitter = Arc::new(DefaultEmitter::new(ColorChoice::Auto)); + let diagnostics = DiagnosticsHandler::new(Default::default(), codemap.clone(), emitter); + + // Define the 'test' module + let mut builder = ModuleBuilder::new("test"); + + // Declare the `sum` function, with the appropriate type signature + let sig = Signature { + params: vec![ + AbiParam::new(Type::Ptr(Box::new(Type::Felt))), + AbiParam::new(Type::U32), + ], + results: vec![AbiParam::new(Type::Felt)], + cc: CallConv::SystemV, + linkage: Linkage::External, + }; + let mut fb = builder + .build_function("sum", sig, SourceSpan::UNKNOWN) + .expect("unexpected symbol conflict"); + + let entry = fb.current_block(); + let (ptr, len) = { + let args = fb.block_params(entry); + (args[0], args[1]) + }; + + let mut asm_builder = fb + .ins() + .inline_asm(&[ptr, len], [Type::Felt], SourceSpan::UNKNOWN); + asm_builder.ins().push(Felt::ZERO); // [sum, ptr, len] + asm_builder.ins().push_u32(0); // [i, sum, ptr, len] + asm_builder.ins().dup(0); // [i, i, sum, ptr, len] + asm_builder.ins().dup(4); // [len, i, i, sum, ptr, len] + asm_builder.ins().lt_u32(); // [i < len, i, sum, ptr, len] + + // Now, build the loop body + // + // The state of the stack on entry is: [i, sum, ptr, len] + let mut lb = asm_builder.ins().while_true(); + + // Calculate `i / 4` + lb.ins().dup(0); // [i, i, sum, ptr, len] + lb.ins().div_imm_u32(4); // [word_offset, i, sum, ptr, len] + + // Calculate the address for `array[i / 4]` + lb.ins().dup(3); // [ptr, word_offset, ..] + lb.ins().swap(1); + lb.ins().add_u32(Overflow::Checked); // [ptr + word_offset, i, sum, ptr, len] + + // Calculate the `i % 4` + lb.ins().dup(1); // [i, ptr + word_offset, i, sum, ptr, len] + lb.ins().mod_imm_u32(4); // [element_offset, ptr + word_offset, ..] + + // Precalculate what elements of the word to drop, so that + // we are only left with the specific element we wanted + lb.ins().push_u32(4); // [n, element_offset, ..] + let mut rb = lb.ins().repeat(3); + rb.ins().sub_imm_u32(1, Overflow::Checked); // [n = n - 1, element_offset] + rb.ins().dup(1); // [element_offset, n, element_offset, ..] + rb.ins().dup(1); // [n, element_offset, n, element_offset, ..] + rb.ins().lt_u32(); // [element_offset < n, n, element_offset, ..] + rb.ins().movdn(2); // [n, element_offset, element_offset < n] + rb.build(); // [0, element_offset, element_offset < 1, element_offset < 2, ..] + + // Clean up the now unused operands we used to calculate which element we want + lb.ins().drop(); // [element_offset, ..] + lb.ins().drop(); // [element_offset < 1, ..] + + // Load the word + lb.ins().movup(3); // [ptr + word_offset, element_offset < 1] + lb.ins().loadw(); // [word[0], word[1], word[2], word[3], element_offset < 1] + + // Select the element, `E`, that we want by conditionally dropping + // elements on the operand stack with a carefully chosen sequence + // of conditionals: E < N forall N in 0..=3 + lb.ins().movup(4); // [element_offset < 1, word[0], ..] + lb.ins().cdrop(); // [word[0 or 1], word[2], word[3], element_offset < 2] + lb.ins().movup(3); // [element_offset < 2, word[0 or 1], ..] + lb.ins().cdrop(); // [word[0 or 1 or 2], word[3], element_offset < 3] + lb.ins().movup(2); // [element_offset < 3, ..] + lb.ins().cdrop(); // [array[i], i, sum, ptr, len] + lb.ins().movup(2); // [sum, array[i], i, ptr, len] + lb.ins().add(); // [sum + array[i], i, ptr, len] + lb.ins().swap(1); // [i, sum + array[i], ptr, len] + + // We've reached the end of the loop, but we need a copy of the + // loop header here in order to use the expression `i < len` as + // the condition for the loop + lb.ins().dup(0); // [i, i, sum + array[i], ptr, len] + lb.ins().dup(4); // [len, i, i, sum + array[i], ptr, len] + lb.ins().lt_u32(); // [i < len, i, sum + array[i], ptr, len] + + // Finalize, it is at this point that validation will occur + lb.build(); + + // Clean up the operand stack and return the sum + // + // The stack here is: [i, sum, ptr, len] + asm_builder.ins().swap(1); // [sum, i, ptr, len] + asm_builder.ins().movdn(3); // [i, ptr, len, sum] + let mut rb = asm_builder.ins().repeat(3); + rb.ins().drop(); + rb.build(); // [sum] + + // Finish the inline assembly block + let asm = asm_builder.build(); + // Extract the result from the inline assembly block + let sum = fb.data_flow_graph().first_result(asm); + fb.ins().ret(Some(sum), SourceSpan::default()); + + // Finish building the function, getting back the function identifier + let _sum = fb + .build(&diagnostics) + .expect("unexpected validation error, see diagnostics output"); + + // Finalize the module + builder.build(); +} diff --git a/hir/src/write.rs b/hir/src/write.rs index a062f188..d4bfe798 100644 --- a/hir/src/write.rs +++ b/hir/src/write.rs @@ -134,7 +134,7 @@ fn write_instruction(w: &mut dyn Write, func: &Function, inst: Inst, indent: usi let opcode = func.dfg[inst].opcode(); write!(w, "{}", opcode)?; - write_operands(w, &func.dfg, inst)?; + write_operands(w, &func.dfg, inst, indent)?; if has_results { write!(w, " : ")?; @@ -153,7 +153,12 @@ fn write_instruction(w: &mut dyn Write, func: &Function, inst: Inst, indent: usi Ok(()) } -fn write_operands(w: &mut dyn Write, dfg: &DataFlowGraph, inst: Inst) -> fmt::Result { +fn write_operands( + w: &mut dyn Write, + dfg: &DataFlowGraph, + inst: Inst, + indent: usize, +) -> fmt::Result { let pool = &dfg.value_lists; match dfg[inst].as_ref() { Instruction::BinaryOp(BinaryOp { args, .. }) => write!(w, " {}, {}", args[0], args[1]), @@ -221,21 +226,8 @@ fn write_operands(w: &mut dyn Write, dfg: &DataFlowGraph, inst: Inst) -> fmt::Re }) => { write!(w, ".{} {}, {}, {}", ty, src, dst, count) } - Instruction::InlineAsm(InlineAsm { ref body, args, .. }) => { - write!(w, " \"")?; - for (i, ix) in body.iter().enumerate() { - if i == 0 { - write!(w, "{}", &ix.name)?; - } else { - write!(w, " {}", &ix.name)?; - } - } - let args = args.as_slice(pool); - if args.is_empty() { - write!(w, "\"") - } else { - write!(w, "\", {}", DisplayValues(args)) - } + Instruction::InlineAsm(ref asm) => { + write!(w, " {}", asm.display(dfg, indent)) } Instruction::GlobalValue(GlobalValueOp { global, .. }) => { write_global_value(w, dfg, *global, false) @@ -333,7 +325,18 @@ impl fmt::Display for DisplayOffset { } } -struct DisplayValues<'a>(&'a [Value]); +pub struct DisplayIndent(pub usize); +impl fmt::Display for DisplayIndent { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + const INDENT: &'static str = " "; + for _ in 0..self.0 { + f.write_str(INDENT)?; + } + Ok(()) + } +} + +pub struct DisplayValues<'a>(pub &'a [Value]); impl<'a> fmt::Display for DisplayValues<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { for (i, val) in self.0.iter().enumerate() { @@ -347,7 +350,7 @@ impl<'a> fmt::Display for DisplayValues<'a> { } } -struct DisplayValuesWithImmediate<'a>(&'a [Value], Immediate); +pub struct DisplayValuesWithImmediate<'a>(&'a [Value], Immediate); impl<'a> fmt::Display for DisplayValuesWithImmediate<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { for (i, val) in self.0.iter().enumerate() {