From 840eacda0a9d9a900208653c02c97f267836516d Mon Sep 17 00:00:00 2001 From: Paul Schoenfelder Date: Thu, 7 Sep 2023 17:15:53 -0400 Subject: [PATCH 01/15] feat: implement local variables This commit introduces a new data type to represent local variable declarations. These are not currently attached to anything, but are used downstream during code generation. The local identifiers are also used in the in-crate representation of Miden Assembly, coming in a follow-on commit. --- hir/src/lib.rs | 2 ++ hir/src/locals.rs | 86 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 hir/src/locals.rs diff --git a/hir/src/lib.rs b/hir/src/lib.rs index 880a4842..6906e8e6 100644 --- a/hir/src/lib.rs +++ b/hir/src/lib.rs @@ -19,6 +19,7 @@ mod immediates; mod insert; mod instruction; mod layout; +mod locals; mod module; mod program; #[cfg(test)] @@ -40,6 +41,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() + } +} From 0b907c5e13e69d2d29cd57f6dd22223fcc434557 Mon Sep 17 00:00:00 2001 From: Paul Schoenfelder Date: Thu, 7 Sep 2023 17:19:51 -0400 Subject: [PATCH 02/15] feat: implement inline assembly This commit fleshes out the implementation of inline assembly that we had essentially stubbed out prior to this. The following are provided as part of this implementation: * A representation of the Miden Assembly instruction set, in crate, with the ops we intend to support in the near term. This is used to represent the contents of inline assembly, and is also used in the upcoming codegen backend as the target IR for Miden Assembly, as it is a flatter representation similar to Miden IR. The intent is to also implement a tiny evaluator for this subset of Miden Assembly for use in tests without having to emit MASM files, and run the Miden VM which is much more expensive. It also allows us to track the subset of Miden Assembly which we support in the compiler, as well as represent experimental changes that have not yet been implemented upstream. * `MasmBuilder`, which is constructed from an `InstBuilder` via `inline_asm`, and plays a similar role to `FunctionBuilder`+`InstBuilder`, but in terms of Miden Assembly, in the context of constructing a single inline assembly block. * Improved pretty printing of inline assembly. Now, an inline assembly block is printed like so (this block adds the two inputs, and squares the result, which is returned as the result of the inline assembly block): ```hir asm (v1, v2) { add mul.2 } : felt ``` * A generic data structure for use in representing the operand stack, and the common operations which are performed on it. This is used to validate inline assembly, as well as keep track of values during code generation. It will also likely be used for whatever evaluator we throw together for use in testing. --- hir-type/src/lib.rs | 8 + hir/src/asm.rs | 2447 ++++++++++++++++++++++++++++++++++++++++ hir/src/builder.rs | 4 + hir/src/instruction.rs | 18 +- hir/src/lib.rs | 2 + hir/src/write.rs | 41 +- 6 files changed, 2486 insertions(+), 34 deletions(-) create mode 100644 hir/src/asm.rs diff --git a/hir-type/src/lib.rs b/hir-type/src/lib.rs index 06989967..a3f08aad 100644 --- a/hir-type/src/lib.rs +++ b/hir-type/src/lib.rs @@ -5,6 +5,7 @@ extern crate alloc; use alloc::{alloc::Layout, boxed::Box, vec::Vec}; use core::fmt; +const FELT_SIZE: usize = core::mem::size_of::(); const WORD_SIZE: usize = core::mem::size_of::<[u64; 4]>(); /// Represents the type of a value @@ -179,6 +180,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.rs b/hir/src/asm.rs new file mode 100644 index 00000000..526a6272 --- /dev/null +++ b/hir/src/asm.rs @@ -0,0 +1,2447 @@ +use std::fmt; + +use cranelift_entity::{entity_impl, PrimaryMap}; +use winter_math::FieldElement; + +use super::{write::DisplayIndent, *}; + +/// 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 +#[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 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 + pub fn new() -> 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(), + 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 { + asm: self, + dfg, + indent, + } + } +} + +pub struct DisplayInlineAsm<'a> { + asm: &'a InlineAsm, + dfg: &'a DataFlowGraph, + indent: usize, +} +impl<'a> fmt::Display for DisplayInlineAsm<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use super::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)) + } +} + +/// 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); + } +} + +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(()) + } +} + +/// Used to construct an [InlineAssembly] instruction, while checking various safety invariants. +pub struct MasmBuilder { + builder: B, + span: SourceSpan, + asm: InlineAsm, + ty: Type, + current_block: MasmBlockId, + 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 type given by `ty` represents the expected result type for this inline assembly block. If the inline assembly + /// will not produce a result, use `Type::Unit`. It is expected that the value(s) remaining on the operand stack upon + /// exit from the inline assembly block, are a match for `ty`. For example, if `Type::Unit` is given, no values should + /// remain on the operand stack; if `Type::Felt` is given, then a single value should be on the operand stack; if + /// `Type::Array[Type::Felt; 2]` is given, then two values should be on the operand stack, and so on. + /// + /// NOTE: Not all types are permitted as inline assembly results. The type must be "loadable", i.e. no larger than a word. + /// + /// 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], ty: Type, span: SourceSpan) -> Self { + assert!( + ty.is_loadable(), + "invalid inline assembly block type: type must be loadable, but got {}", + &ty + ); + // 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(); + { + 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, + ty, + current_block, + stack, + } + } + + #[inline] + pub fn create_block(&mut self) -> MasmBlockId { + self.asm.create_block() + } + + #[inline(always)] + pub fn switch_to_block(&mut self, block: MasmBlockId) { + self.current_block = block; + } + + pub fn ins<'a, 'b: 'a>(&'b mut self) -> MasmOpBuilder<'a> { + MasmOpBuilder { + asm: &mut self.asm, + stack: &mut self.stack, + ip: self.current_block, + } + } + + pub fn build(self) -> (Inst, &'f mut DataFlowGraph) { + let ty = self.ty; + match &ty { + Type::Unit => assert!(self.stack.is_empty(), "invalid inline assembly: expected operand stack to be empty upon exit, found: {:?}", self.stack.display()), + ty => { + let len = ty.size_in_felts(); + assert_eq!(len, self.stack.len(), "invalid inline assembly: expected operand stack to have {} elements upon exit, found: {:?}", len, self.stack.display()); + } + } + + let span = self.span; + let data = Instruction::InlineAsm(self.asm); + self.builder.build(data, ty, span) + } +} + +/// Used to construct a single MASM opcode +pub struct MasmOpBuilder<'a> { + asm: &'a mut InlineAsm, + stack: &'a mut OperandStack, + ip: MasmBlockId, +} +impl<'a> MasmOpBuilder<'a> { + /// Pads the stack with four zero elements + pub fn padw(self) { + self.stack.padw(); + self.asm.push(self.ip, MasmOp::Padw); + } + + /// Pushes an element on the stack + pub fn push(self, imm: Felt) { + self.stack.push(Type::Felt); + self.asm.push(self.ip, MasmOp::Push(imm)); + } + + /// Pushes a word on the stack + pub fn pushw(self, word: [Felt; 4]) { + self.stack + .pushw([Type::Felt, Type::Felt, Type::Felt, Type::Felt]); + self.asm.push(self.ip, MasmOp::Pushw(word)); + } + + /// Pushes an element representing an unsigned 8-bit integer on the stack + pub fn push_u8(self, imm: u8) { + self.stack.push(Type::U8); + self.asm.push(self.ip, MasmOp::PushU8(imm)); + } + + /// Pushes an element representing an unsigned 16-bit integer on the stack + pub fn push_u16(self, imm: u16) { + self.stack.push(Type::U16); + self.asm.push(self.ip, MasmOp::PushU16(imm)); + } + + /// Pushes an element representing an unsigned 32-bit integer on the stack + pub fn push_u32(self, imm: u32) { + self.stack.push(Type::U32); + self.asm.push(self.ip, MasmOp::PushU32(imm)); + } + + /// Drops the element on the top of the stack + pub fn drop(self) { + self.stack.drop(); + self.asm.push(self.ip, MasmOp::Drop); + } + + /// Drops the word (first four elements) on the top of the stack + pub fn dropw(self) { + self.stack.dropw(); + self.asm.push(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(self, n: usize) { + self.stack.dup(n); + self.asm.push(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(self, n: usize) { + self.stack.dupw(n); + self.asm.push(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(self, n: usize) { + self.stack.swap(n); + self.asm.push(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(self, n: usize) { + self.stack.swapw(n); + self.asm.push(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(self, idx: usize) { + self.stack.movup(idx); + self.asm.push(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(self, idx: usize) { + self.stack.movupw(idx); + self.asm.push(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(self, idx: usize) { + self.stack.movdn(idx); + self.asm.push(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(self, idx: usize) { + self.stack.movdnw(idx); + self.asm.push(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(self) { + self.stack.drop(); + self.asm.push(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(self) { + self.stack.drop(); + self.asm.push(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(self) { + self.stack.dropn(2); + self.asm.push(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(self) { + self.stack.dropn(5); + self.asm.push(self.ip, MasmOp::Cdropw); + } + + /// Pops the top element on the stack, and traps if that element is != 1. + pub fn assert(self) { + self.stack.drop(); + self.asm.push(self.ip, MasmOp::Assert); + } + + /// Pops the top element on the stack, and traps if that element is != 0. + pub fn assertz(self) { + self.stack.drop(); + self.asm.push(self.ip, MasmOp::Assertz); + } + + /// Pops the top two elements on the stack, and traps if they are not equal. + pub fn assert_eq(self) { + self.stack.dropn(2); + self.asm.push(self.ip, MasmOp::AssertEq); + } + + /// Pops the top two words on the stack, and traps if they are not equal. + pub fn assert_eqw(self) { + self.stack.dropn(8); + self.asm.push(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(self) { + self.stack.drop(); + self.asm.push(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(self, addr: u32) { + self.stack.drop(); + self.asm.push(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(self) { + self.stack.drop(); + self.asm.push(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(self, addr: u32, offset: u8) { + assert!( + offset < 4, + "invalid element offset, must be in the range 0..=3, got {}", + offset + ); + self.stack.drop(); + self.asm + .push(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(self) { + self.stack.drop(); + self.asm.push(self.ip, MasmOp::MemLoadw); + } + + /// Loads the word at the given address to the top of the stack. + pub fn loadw_imm(self, addr: u32) { + self.stack.drop(); + self.asm.push(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(self) { + self.stack.drop(); + self.asm.push(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(self, addr: u32) { + self.stack.drop(); + self.asm.push(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(self) { + self.stack.drop(); + self.asm.push(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(self, addr: u32, offset: u8) { + assert!( + offset < 4, + "invalid element offset, must be in the range 0..=3, got {}", + offset + ); + self.stack.drop(); + self.asm + .push(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(self) { + self.stack.drop(); + self.asm.push(self.ip, MasmOp::MemStorew); + } + + /// Pops a word from the stack and stores it as the word at the given address. + pub fn storew_imm(self, addr: u32) { + self.stack.drop(); + self.asm.push(self.ip, MasmOp::MemStorewImm(addr)); + } + + /// Pops a boolean value from the stack, and executes the first block if it is true, + /// otherwise the second block. + pub fn if_true(self, then_blk: MasmBlockId, else_blk: MasmBlockId) { + self.stack.drop(); + self.asm.push(self.ip, MasmOp::If(then_blk, else_blk)) + } + + /// Pops a boolean value from the stack, and executes the given block if it is true, + /// otherwise it is skipped. The given block will continue to execute for as long as + /// the top value on the stack at the end of the block is true. + pub fn while_true(self, body: MasmBlockId) { + self.stack.drop(); + self.asm.push(self.ip, MasmOp::While(body)); + } + + /// Repeatedly executes `body`, `n` times. + pub fn repeat(self, n: u8, body: MasmBlockId) { + self.asm.push(self.ip, MasmOp::Repeat(n, body)); + } + + /// Executes the named procedure as a regular function. + pub fn exec(self, id: FunctionIdent) { + self.asm.push(self.ip, MasmOp::Exec(id)); + } + + /// Executes the named procedure as a syscall. + pub fn syscall(self, id: FunctionIdent) { + self.asm.push(self.ip, MasmOp::Syscall(id)); + } + + /// Pops two field elements from the stack, adds them, and places the result on the stack. + pub fn add(self) { + self.asm.push(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(self, imm: Felt) { + self.asm.push(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(self) { + self.asm.push(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(self, imm: Felt) { + self.asm.push(self.ip, MasmOp::SubImm(imm)); + } + + /// Pops two field elements from the stack, multiplies them, and places the result on the stack. + pub fn mul(self) { + self.asm.push(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(self, imm: Felt) { + self.asm.push(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(self) { + self.asm.push(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(self, imm: Felt) { + self.asm.push(self.ip, MasmOp::DivImm(imm)); + } + + /// Negates the field element on top of the stack + pub fn neg(self) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::Inv); + } + + /// Increments the field element on top of the stack + pub fn incr(self) { + self.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(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(self, exponent: u8) { + self.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(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(self, imm: bool) { + self.asm.push(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(self) { + self.asm.push(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(self, imm: bool) { + self.asm.push(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(self) { + self.asm.push(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(self, imm: bool) { + self.asm.push(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(self) { + self.asm.push(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(self, imm: Felt) { + self.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(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(self, imm: Felt) { + self.asm.push(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(self) { + self.asm.push(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(self, imm: Felt) { + self.asm.push(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(self) { + self.asm.push(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(self, imm: Felt) { + self.asm.push(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(self) { + self.asm.push(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(self, imm: Felt) { + self.asm.push(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(self) { + self.asm.push(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(self, imm: Felt) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::IsOdd); + } + + /// Pushes the current value of the cycle counter (clock) on the stack + pub fn clk(self) { + self.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(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. + /// + /// The specific behavior of the addition depends on the given `overflow` flags: + /// + /// * `Overflow::Unchecked` - the addition is performed using the `add` op for field elements, which may + /// produce a value that is outside of the u32 range, it is the callers responsibility to ensure that the + /// resulting value is in range. + /// * `Overflow::Checked` - the operation will trap if either operand, or the result, is not a valid u32 + /// * `Overflow::Wrapping` - computes the result as `(a + b) mod 2^32`, behavior is undefined if either operand + /// is not a valid u32 + /// * `Overflow::Overflowing` - similar to above, the result is computed as `(a + b) mod 2^32`, however a boolean + /// is also pushed on the stack after the result, which is 1 if the result of `a + b` overflowed, else 0. + /// + pub fn add_u32(self, overflow: Overflow) { + let op = match overflow { + Overflow::Unchecked => MasmOp::Add, + Overflow::Checked => MasmOp::U32CheckedAdd, + Overflow::Overflowing => MasmOp::U32OverflowingAdd, + Overflow::Wrapping => MasmOp::U32WrappingAdd, + }; + self.asm.push(self.ip, op); + } + + /// Same as above, but `a` is provided by the given immediate. + pub fn add_imm_u32(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.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(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. + /// + /// The specific behavior of the subtraction depends on the given `overflow` flags: + /// + /// * `Overflow::Unchecked` - the subtraction is performed using the `sub` op for field elements, which may + /// produce a value that is outside of the u32 range, it is the callers responsibility to ensure that the + /// resulting value is in range. + /// * `Overflow::Checked` - the operation will trap if either operand, or the result, is not a valid u32 + /// * `Overflow::Wrapping` - computes the result as `(a - b) mod 2^32`, behavior is undefined if either operand + /// is not a valid u32 + /// * `Overflow::Overflowing` - similar to above, the result is computed as `(a - b) mod 2^32`, however a boolean + /// is also pushed on the stack after the result, which is 1 if the result of `a - b` underflowed, else 0. + /// + pub fn sub_u32(self, overflow: Overflow) { + let op = match overflow { + Overflow::Unchecked => MasmOp::Sub, + Overflow::Checked => MasmOp::U32CheckedSub, + Overflow::Overflowing => MasmOp::U32OverflowingSub, + Overflow::Wrapping => MasmOp::U32WrappingSub, + }; + self.asm.push(self.ip, op); + } + + /// Same as above, but `a` is provided by the given immediate. + pub fn sub_imm_u32(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.asm.push(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. + /// + /// The specific behavior of the subtraction depends on the given `overflow` flags: + /// + /// * `Overflow::Unchecked` - the multiplication is performed using the `mul` op for field elements, which may + /// produce a value that is outside of the u32 range, it is the callers responsibility to ensure that the + /// resulting value is in range. + /// * `Overflow::Checked` - the operation will trap if either operand, or the result, is not a valid u32 + /// * `Overflow::Wrapping` - computes the result as `(a * b) mod 2^32`, behavior is undefined if either operand + /// is not a valid u32 + /// * `Overflow::Overflowing` - similar to above, the result is computed as `(a * b) mod 2^32`, however a boolean + /// is also pushed on the stack after the result, which is 1 if the result of `a * b` underflowed, else 0. + /// + pub fn mul_u32(self, overflow: Overflow) { + let op = match overflow { + Overflow::Unchecked => MasmOp::Mul, + Overflow::Checked => MasmOp::U32CheckedMul, + Overflow::Overflowing => MasmOp::U32OverflowingMul, + Overflow::Wrapping => MasmOp::U32WrappingMul, + }; + self.asm.push(self.ip, op); + } + + /// Same as above, but `a` is provided by the given immediate. + pub fn mul_imm_u32(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.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32CheckedDiv); + } + + /// Same as above, but `b` is provided by the given immediate + pub fn div_imm_u32(self, imm: u32) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32UncheckedDiv); + } + + /// Same as above, but `b` is provided by the given immediate + pub fn div_imm_unchecked_u32(self, imm: u32) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32CheckedMod); + } + + /// Same as above, but `b` is provided by the given immediate + pub fn mod_imm_u32(self, imm: u32) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32UncheckedMod); + } + + /// Same as above, but `b` is provided by the given immediate + pub fn mod_imm_unchecked_u32(self, imm: u32) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32CheckedDivMod); + } + + /// Same as above, but `b` is provided by the given immediate + pub fn divmod_imm_u32(self, imm: u32) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32UncheckedDivMod); + } + + /// Same as above, but `b` is provided by the given immediate + pub fn divmod_imm_unchecked_u32(self, imm: u32) { + self.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32CheckedShl); + } + + /// Same as `shl_u32`, but `b` is provided by immediate. + pub fn shl_imm_u32(self, imm: u32) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32UncheckedShl); + } + + /// Same as `shl_unchecked_u32`, but `b` is provided by immediate. + pub fn shl_unchecked_imm_u32(self, imm: u32) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32CheckedShr); + } + + /// Same as `shr_u32`, but `b` is provided by immediate. + pub fn shr_imm_u32(self, imm: u32) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32UncheckedShr); + } + + /// Same as `shr_unchecked_u32`, but `b` is provided by immediate. + pub fn shr_unchecked_imm_u32(self, imm: u32) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32CheckedRotl); + } + + /// Same as `rotl_u32`, but `b` is provided by immediate. + pub fn rotl_imm_u32(self, imm: u32) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32UncheckedRotl); + } + + /// Same as `rotl_unchecked_u32`, but `b` is provided by immediate. + pub fn rotl_unchecked_imm_u32(self, imm: u32) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32CheckedRotr); + } + + /// Same as `rotr_u32`, but `b` is provided by immediate. + pub fn rotr_imm_u32(self, imm: u32) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32UncheckedRotr); + } + + /// Same as `rotr_unchecked_u32`, but `b` is provided by immediate. + pub fn rotr_unchecked_imm_u32(self, imm: u32) { + self.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32UncheckedPopcnt); + } + + /// This is the same as `eq`, but also asserts that both operands are valid u32 values. + pub fn eq_u32(self) { + self.asm.push(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(self, imm: u32) { + self.asm.push(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(self) { + self.asm.push(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(self, imm: u32) { + self.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32UncheckedLt); + } + + /// This is the same as `lte`, but also asserts that both operands are valid u32 values. + pub fn lte_u32(self) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32UncheckedLte); + } + + /// This is the same as `gt`, but also asserts that both operands are valid u32 values. + pub fn gt_u32(self) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32UncheckedGt); + } + + /// This is the same as `gte`, but also asserts that both operands are valid u32 values. + pub fn gte_u32(self) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32UncheckedGte); + } + + /// This is the same as `min`, but also asserts that both operands are valid u32 values. + pub fn min_u32(self) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32UncheckedMin); + } + + /// This is the same as `max`, but also asserts that both operands are valid u32 values. + pub fn max_u32(self) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32UncheckedMax); + } +} + +/// 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, +} + +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"), + } + } +} + +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; +} + +pub trait Stack: std::ops::IndexMut::Element> { + type Element: StackElement; + + fn storage(&self) -> &Vec; + fn storage_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.storage().is_empty() + } + + /// Returns the number of elements on the stack + #[inline] + fn len(&self) -> usize { + self.storage().len() + } + + /// Returns the value on top of the stack, without consuming it + #[inline] + fn peek(&self) -> Self::Element { + self.storage() + .last() + .cloned() + .expect("operand stack is empty") + } + + /// Returns the word on top of the stack, without consuming it + #[inline] + fn peekw(&self) -> [Self::Element; 4] { + let stack = self.storage(); + let end = stack.len().checked_sub(1).expect("operand stack is empty"); + [ + 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.storage_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.storage_mut().push(value); + } + + /// Pushes `word` on top of the stack + fn pushw(&mut self, word: [Self::Element; 4]) { + let stack = self.storage_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.storage_mut().pop() + } + + /// Pops the first word on top of the stack + fn popw(&mut self) -> Option<[Self::Element; 4]> { + let stack = self.storage_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.storage_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.storage_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.storage().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(); + self.pushw(word); + } + n => { + let end = len - n - 1; + let word = { + let stack = self.storage(); + [ + 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.storage_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.storage_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.storage_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.storage_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.storage_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.storage_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 structure emulates the Miden VM operand stack +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 storage(&self) -> &Vec { + &self.stack + } + #[inline(always)] + fn storage_mut(&mut self) -> &mut Vec { + &mut self.stack + } + + fn padw(&mut self) { + self.stack.extend([::DEFAULT; 4]); + } +} +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 std::ops::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 std::ops::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] + } +} + +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 + .storage() + .iter() + .rev() + .enumerate() + .map(|(index, value)| StackEntry { index, value }), + ) + .finish() + } +} diff --git a/hir/src/builder.rs b/hir/src/builder.rs index 7ec7235c..93662d2f 100644 --- a/hir/src/builder.rs +++ b/hir/src/builder.rs @@ -941,6 +941,10 @@ pub trait InstBuilder<'f>: InstBuilderBase<'f> { self.build(data, Type::Never, span).0 } + fn inline_asm(self, args: &[Value], ty: Type, span: SourceSpan) -> MasmBuilder { + MasmBuilder::new(self, args, ty, span) + } + #[allow(non_snake_case)] fn CondBr( self, diff --git a/hir/src/instruction.rs b/hir/src/instruction.rs index 61165668..2ed7d53f 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 @@ -597,7 +596,8 @@ impl Opcode { | Self::Shl | Self::Shr | Self::Rotl - | Self::Rotr => { + | Self::Rotr + | Self::InlineAsm => { smallvec![ctrl_ty] } // The result type of a load is derived from the pointee type @@ -820,15 +820,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 6906e8e6..6536c768 100644 --- a/hir/src/lib.rs +++ b/hir/src/lib.rs @@ -7,6 +7,7 @@ pub use miden_hir_type::{FunctionType, Type}; pub type Felt = winter_math::fields::f64::BaseElement; +mod asm; mod block; mod builder; mod constants; @@ -27,6 +28,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, 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() { From 6a3f542ae788c41161a10374b6e61573ba5c299c Mon Sep 17 00:00:00 2001 From: Paul Schoenfelder Date: Fri, 8 Sep 2023 15:00:26 -0400 Subject: [PATCH 03/15] wip: split up asm module --- hir/src/asm.rs | 2447 ---------------------------------------- hir/src/asm/builder.rs | 1097 ++++++++++++++++++ hir/src/asm/display.rs | 307 +++++ hir/src/asm/isa.rs | 577 ++++++++++ hir/src/asm/mod.rs | 74 ++ hir/src/asm/stack.rs | 443 ++++++++ 6 files changed, 2498 insertions(+), 2447 deletions(-) delete mode 100644 hir/src/asm.rs create mode 100644 hir/src/asm/builder.rs create mode 100644 hir/src/asm/display.rs create mode 100644 hir/src/asm/isa.rs create mode 100644 hir/src/asm/mod.rs create mode 100644 hir/src/asm/stack.rs diff --git a/hir/src/asm.rs b/hir/src/asm.rs deleted file mode 100644 index 526a6272..00000000 --- a/hir/src/asm.rs +++ /dev/null @@ -1,2447 +0,0 @@ -use std::fmt; - -use cranelift_entity::{entity_impl, PrimaryMap}; -use winter_math::FieldElement; - -use super::{write::DisplayIndent, *}; - -/// 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 -#[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 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 - pub fn new() -> 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(), - 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 { - asm: self, - dfg, - indent, - } - } -} - -pub struct DisplayInlineAsm<'a> { - asm: &'a InlineAsm, - dfg: &'a DataFlowGraph, - indent: usize, -} -impl<'a> fmt::Display for DisplayInlineAsm<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use super::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)) - } -} - -/// 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); - } -} - -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(()) - } -} - -/// Used to construct an [InlineAssembly] instruction, while checking various safety invariants. -pub struct MasmBuilder { - builder: B, - span: SourceSpan, - asm: InlineAsm, - ty: Type, - current_block: MasmBlockId, - 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 type given by `ty` represents the expected result type for this inline assembly block. If the inline assembly - /// will not produce a result, use `Type::Unit`. It is expected that the value(s) remaining on the operand stack upon - /// exit from the inline assembly block, are a match for `ty`. For example, if `Type::Unit` is given, no values should - /// remain on the operand stack; if `Type::Felt` is given, then a single value should be on the operand stack; if - /// `Type::Array[Type::Felt; 2]` is given, then two values should be on the operand stack, and so on. - /// - /// NOTE: Not all types are permitted as inline assembly results. The type must be "loadable", i.e. no larger than a word. - /// - /// 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], ty: Type, span: SourceSpan) -> Self { - assert!( - ty.is_loadable(), - "invalid inline assembly block type: type must be loadable, but got {}", - &ty - ); - // 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(); - { - 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, - ty, - current_block, - stack, - } - } - - #[inline] - pub fn create_block(&mut self) -> MasmBlockId { - self.asm.create_block() - } - - #[inline(always)] - pub fn switch_to_block(&mut self, block: MasmBlockId) { - self.current_block = block; - } - - pub fn ins<'a, 'b: 'a>(&'b mut self) -> MasmOpBuilder<'a> { - MasmOpBuilder { - asm: &mut self.asm, - stack: &mut self.stack, - ip: self.current_block, - } - } - - pub fn build(self) -> (Inst, &'f mut DataFlowGraph) { - let ty = self.ty; - match &ty { - Type::Unit => assert!(self.stack.is_empty(), "invalid inline assembly: expected operand stack to be empty upon exit, found: {:?}", self.stack.display()), - ty => { - let len = ty.size_in_felts(); - assert_eq!(len, self.stack.len(), "invalid inline assembly: expected operand stack to have {} elements upon exit, found: {:?}", len, self.stack.display()); - } - } - - let span = self.span; - let data = Instruction::InlineAsm(self.asm); - self.builder.build(data, ty, span) - } -} - -/// Used to construct a single MASM opcode -pub struct MasmOpBuilder<'a> { - asm: &'a mut InlineAsm, - stack: &'a mut OperandStack, - ip: MasmBlockId, -} -impl<'a> MasmOpBuilder<'a> { - /// Pads the stack with four zero elements - pub fn padw(self) { - self.stack.padw(); - self.asm.push(self.ip, MasmOp::Padw); - } - - /// Pushes an element on the stack - pub fn push(self, imm: Felt) { - self.stack.push(Type::Felt); - self.asm.push(self.ip, MasmOp::Push(imm)); - } - - /// Pushes a word on the stack - pub fn pushw(self, word: [Felt; 4]) { - self.stack - .pushw([Type::Felt, Type::Felt, Type::Felt, Type::Felt]); - self.asm.push(self.ip, MasmOp::Pushw(word)); - } - - /// Pushes an element representing an unsigned 8-bit integer on the stack - pub fn push_u8(self, imm: u8) { - self.stack.push(Type::U8); - self.asm.push(self.ip, MasmOp::PushU8(imm)); - } - - /// Pushes an element representing an unsigned 16-bit integer on the stack - pub fn push_u16(self, imm: u16) { - self.stack.push(Type::U16); - self.asm.push(self.ip, MasmOp::PushU16(imm)); - } - - /// Pushes an element representing an unsigned 32-bit integer on the stack - pub fn push_u32(self, imm: u32) { - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::PushU32(imm)); - } - - /// Drops the element on the top of the stack - pub fn drop(self) { - self.stack.drop(); - self.asm.push(self.ip, MasmOp::Drop); - } - - /// Drops the word (first four elements) on the top of the stack - pub fn dropw(self) { - self.stack.dropw(); - self.asm.push(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(self, n: usize) { - self.stack.dup(n); - self.asm.push(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(self, n: usize) { - self.stack.dupw(n); - self.asm.push(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(self, n: usize) { - self.stack.swap(n); - self.asm.push(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(self, n: usize) { - self.stack.swapw(n); - self.asm.push(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(self, idx: usize) { - self.stack.movup(idx); - self.asm.push(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(self, idx: usize) { - self.stack.movupw(idx); - self.asm.push(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(self, idx: usize) { - self.stack.movdn(idx); - self.asm.push(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(self, idx: usize) { - self.stack.movdnw(idx); - self.asm.push(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(self) { - self.stack.drop(); - self.asm.push(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(self) { - self.stack.drop(); - self.asm.push(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(self) { - self.stack.dropn(2); - self.asm.push(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(self) { - self.stack.dropn(5); - self.asm.push(self.ip, MasmOp::Cdropw); - } - - /// Pops the top element on the stack, and traps if that element is != 1. - pub fn assert(self) { - self.stack.drop(); - self.asm.push(self.ip, MasmOp::Assert); - } - - /// Pops the top element on the stack, and traps if that element is != 0. - pub fn assertz(self) { - self.stack.drop(); - self.asm.push(self.ip, MasmOp::Assertz); - } - - /// Pops the top two elements on the stack, and traps if they are not equal. - pub fn assert_eq(self) { - self.stack.dropn(2); - self.asm.push(self.ip, MasmOp::AssertEq); - } - - /// Pops the top two words on the stack, and traps if they are not equal. - pub fn assert_eqw(self) { - self.stack.dropn(8); - self.asm.push(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(self) { - self.stack.drop(); - self.asm.push(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(self, addr: u32) { - self.stack.drop(); - self.asm.push(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(self) { - self.stack.drop(); - self.asm.push(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(self, addr: u32, offset: u8) { - assert!( - offset < 4, - "invalid element offset, must be in the range 0..=3, got {}", - offset - ); - self.stack.drop(); - self.asm - .push(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(self) { - self.stack.drop(); - self.asm.push(self.ip, MasmOp::MemLoadw); - } - - /// Loads the word at the given address to the top of the stack. - pub fn loadw_imm(self, addr: u32) { - self.stack.drop(); - self.asm.push(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(self) { - self.stack.drop(); - self.asm.push(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(self, addr: u32) { - self.stack.drop(); - self.asm.push(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(self) { - self.stack.drop(); - self.asm.push(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(self, addr: u32, offset: u8) { - assert!( - offset < 4, - "invalid element offset, must be in the range 0..=3, got {}", - offset - ); - self.stack.drop(); - self.asm - .push(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(self) { - self.stack.drop(); - self.asm.push(self.ip, MasmOp::MemStorew); - } - - /// Pops a word from the stack and stores it as the word at the given address. - pub fn storew_imm(self, addr: u32) { - self.stack.drop(); - self.asm.push(self.ip, MasmOp::MemStorewImm(addr)); - } - - /// Pops a boolean value from the stack, and executes the first block if it is true, - /// otherwise the second block. - pub fn if_true(self, then_blk: MasmBlockId, else_blk: MasmBlockId) { - self.stack.drop(); - self.asm.push(self.ip, MasmOp::If(then_blk, else_blk)) - } - - /// Pops a boolean value from the stack, and executes the given block if it is true, - /// otherwise it is skipped. The given block will continue to execute for as long as - /// the top value on the stack at the end of the block is true. - pub fn while_true(self, body: MasmBlockId) { - self.stack.drop(); - self.asm.push(self.ip, MasmOp::While(body)); - } - - /// Repeatedly executes `body`, `n` times. - pub fn repeat(self, n: u8, body: MasmBlockId) { - self.asm.push(self.ip, MasmOp::Repeat(n, body)); - } - - /// Executes the named procedure as a regular function. - pub fn exec(self, id: FunctionIdent) { - self.asm.push(self.ip, MasmOp::Exec(id)); - } - - /// Executes the named procedure as a syscall. - pub fn syscall(self, id: FunctionIdent) { - self.asm.push(self.ip, MasmOp::Syscall(id)); - } - - /// Pops two field elements from the stack, adds them, and places the result on the stack. - pub fn add(self) { - self.asm.push(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(self, imm: Felt) { - self.asm.push(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(self) { - self.asm.push(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(self, imm: Felt) { - self.asm.push(self.ip, MasmOp::SubImm(imm)); - } - - /// Pops two field elements from the stack, multiplies them, and places the result on the stack. - pub fn mul(self) { - self.asm.push(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(self, imm: Felt) { - self.asm.push(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(self) { - self.asm.push(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(self, imm: Felt) { - self.asm.push(self.ip, MasmOp::DivImm(imm)); - } - - /// Negates the field element on top of the stack - pub fn neg(self) { - self.asm.push(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(self) { - self.asm.push(self.ip, MasmOp::Inv); - } - - /// Increments the field element on top of the stack - pub fn incr(self) { - self.asm.push(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(self) { - self.asm.push(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(self) { - self.asm.push(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(self, exponent: u8) { - self.asm.push(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(self) { - self.asm.push(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(self) { - self.asm.push(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(self, imm: bool) { - self.asm.push(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(self) { - self.asm.push(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(self, imm: bool) { - self.asm.push(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(self) { - self.asm.push(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(self, imm: bool) { - self.asm.push(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(self) { - self.asm.push(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(self, imm: Felt) { - self.asm.push(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(self) { - self.asm.push(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(self) { - self.asm.push(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(self, imm: Felt) { - self.asm.push(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(self) { - self.asm.push(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(self, imm: Felt) { - self.asm.push(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(self) { - self.asm.push(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(self, imm: Felt) { - self.asm.push(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(self) { - self.asm.push(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(self, imm: Felt) { - self.asm.push(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(self) { - self.asm.push(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(self, imm: Felt) { - self.asm.push(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(self) { - self.asm.push(self.ip, MasmOp::IsOdd); - } - - /// Pushes the current value of the cycle counter (clock) on the stack - pub fn clk(self) { - self.asm.push(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(self) { - self.asm.push(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(self) { - self.asm.push(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(self) { - self.asm.push(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(self) { - self.asm.push(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(self) { - self.asm.push(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(self) { - self.asm.push(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(self) { - self.asm.push(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. - /// - /// The specific behavior of the addition depends on the given `overflow` flags: - /// - /// * `Overflow::Unchecked` - the addition is performed using the `add` op for field elements, which may - /// produce a value that is outside of the u32 range, it is the callers responsibility to ensure that the - /// resulting value is in range. - /// * `Overflow::Checked` - the operation will trap if either operand, or the result, is not a valid u32 - /// * `Overflow::Wrapping` - computes the result as `(a + b) mod 2^32`, behavior is undefined if either operand - /// is not a valid u32 - /// * `Overflow::Overflowing` - similar to above, the result is computed as `(a + b) mod 2^32`, however a boolean - /// is also pushed on the stack after the result, which is 1 if the result of `a + b` overflowed, else 0. - /// - pub fn add_u32(self, overflow: Overflow) { - let op = match overflow { - Overflow::Unchecked => MasmOp::Add, - Overflow::Checked => MasmOp::U32CheckedAdd, - Overflow::Overflowing => MasmOp::U32OverflowingAdd, - Overflow::Wrapping => MasmOp::U32WrappingAdd, - }; - self.asm.push(self.ip, op); - } - - /// Same as above, but `a` is provided by the given immediate. - pub fn add_imm_u32(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.asm.push(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(self) { - self.asm.push(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(self) { - self.asm.push(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. - /// - /// The specific behavior of the subtraction depends on the given `overflow` flags: - /// - /// * `Overflow::Unchecked` - the subtraction is performed using the `sub` op for field elements, which may - /// produce a value that is outside of the u32 range, it is the callers responsibility to ensure that the - /// resulting value is in range. - /// * `Overflow::Checked` - the operation will trap if either operand, or the result, is not a valid u32 - /// * `Overflow::Wrapping` - computes the result as `(a - b) mod 2^32`, behavior is undefined if either operand - /// is not a valid u32 - /// * `Overflow::Overflowing` - similar to above, the result is computed as `(a - b) mod 2^32`, however a boolean - /// is also pushed on the stack after the result, which is 1 if the result of `a - b` underflowed, else 0. - /// - pub fn sub_u32(self, overflow: Overflow) { - let op = match overflow { - Overflow::Unchecked => MasmOp::Sub, - Overflow::Checked => MasmOp::U32CheckedSub, - Overflow::Overflowing => MasmOp::U32OverflowingSub, - Overflow::Wrapping => MasmOp::U32WrappingSub, - }; - self.asm.push(self.ip, op); - } - - /// Same as above, but `a` is provided by the given immediate. - pub fn sub_imm_u32(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.asm.push(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. - /// - /// The specific behavior of the subtraction depends on the given `overflow` flags: - /// - /// * `Overflow::Unchecked` - the multiplication is performed using the `mul` op for field elements, which may - /// produce a value that is outside of the u32 range, it is the callers responsibility to ensure that the - /// resulting value is in range. - /// * `Overflow::Checked` - the operation will trap if either operand, or the result, is not a valid u32 - /// * `Overflow::Wrapping` - computes the result as `(a * b) mod 2^32`, behavior is undefined if either operand - /// is not a valid u32 - /// * `Overflow::Overflowing` - similar to above, the result is computed as `(a * b) mod 2^32`, however a boolean - /// is also pushed on the stack after the result, which is 1 if the result of `a * b` underflowed, else 0. - /// - pub fn mul_u32(self, overflow: Overflow) { - let op = match overflow { - Overflow::Unchecked => MasmOp::Mul, - Overflow::Checked => MasmOp::U32CheckedMul, - Overflow::Overflowing => MasmOp::U32OverflowingMul, - Overflow::Wrapping => MasmOp::U32WrappingMul, - }; - self.asm.push(self.ip, op); - } - - /// Same as above, but `a` is provided by the given immediate. - pub fn mul_imm_u32(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.asm.push(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(self) { - self.asm.push(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(self) { - self.asm.push(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(self) { - self.asm.push(self.ip, MasmOp::U32CheckedDiv); - } - - /// Same as above, but `b` is provided by the given immediate - pub fn div_imm_u32(self, imm: u32) { - self.asm.push(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(self) { - self.asm.push(self.ip, MasmOp::U32UncheckedDiv); - } - - /// Same as above, but `b` is provided by the given immediate - pub fn div_imm_unchecked_u32(self, imm: u32) { - self.asm.push(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(self) { - self.asm.push(self.ip, MasmOp::U32CheckedMod); - } - - /// Same as above, but `b` is provided by the given immediate - pub fn mod_imm_u32(self, imm: u32) { - self.asm.push(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(self) { - self.asm.push(self.ip, MasmOp::U32UncheckedMod); - } - - /// Same as above, but `b` is provided by the given immediate - pub fn mod_imm_unchecked_u32(self, imm: u32) { - self.asm.push(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(self) { - self.asm.push(self.ip, MasmOp::U32CheckedDivMod); - } - - /// Same as above, but `b` is provided by the given immediate - pub fn divmod_imm_u32(self, imm: u32) { - self.asm.push(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(self) { - self.asm.push(self.ip, MasmOp::U32UncheckedDivMod); - } - - /// Same as above, but `b` is provided by the given immediate - pub fn divmod_imm_unchecked_u32(self, imm: u32) { - self.asm.push(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(self) { - self.asm.push(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(self) { - self.asm.push(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(self) { - self.asm.push(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(self) { - self.asm.push(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(self) { - self.asm.push(self.ip, MasmOp::U32CheckedShl); - } - - /// Same as `shl_u32`, but `b` is provided by immediate. - pub fn shl_imm_u32(self, imm: u32) { - self.asm.push(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(self) { - self.asm.push(self.ip, MasmOp::U32UncheckedShl); - } - - /// Same as `shl_unchecked_u32`, but `b` is provided by immediate. - pub fn shl_unchecked_imm_u32(self, imm: u32) { - self.asm.push(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(self) { - self.asm.push(self.ip, MasmOp::U32CheckedShr); - } - - /// Same as `shr_u32`, but `b` is provided by immediate. - pub fn shr_imm_u32(self, imm: u32) { - self.asm.push(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(self) { - self.asm.push(self.ip, MasmOp::U32UncheckedShr); - } - - /// Same as `shr_unchecked_u32`, but `b` is provided by immediate. - pub fn shr_unchecked_imm_u32(self, imm: u32) { - self.asm.push(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(self) { - self.asm.push(self.ip, MasmOp::U32CheckedRotl); - } - - /// Same as `rotl_u32`, but `b` is provided by immediate. - pub fn rotl_imm_u32(self, imm: u32) { - self.asm.push(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(self) { - self.asm.push(self.ip, MasmOp::U32UncheckedRotl); - } - - /// Same as `rotl_unchecked_u32`, but `b` is provided by immediate. - pub fn rotl_unchecked_imm_u32(self, imm: u32) { - self.asm.push(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(self) { - self.asm.push(self.ip, MasmOp::U32CheckedRotr); - } - - /// Same as `rotr_u32`, but `b` is provided by immediate. - pub fn rotr_imm_u32(self, imm: u32) { - self.asm.push(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(self) { - self.asm.push(self.ip, MasmOp::U32UncheckedRotr); - } - - /// Same as `rotr_unchecked_u32`, but `b` is provided by immediate. - pub fn rotr_unchecked_imm_u32(self, imm: u32) { - self.asm.push(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(self) { - self.asm.push(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(self) { - self.asm.push(self.ip, MasmOp::U32UncheckedPopcnt); - } - - /// This is the same as `eq`, but also asserts that both operands are valid u32 values. - pub fn eq_u32(self) { - self.asm.push(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(self, imm: u32) { - self.asm.push(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(self) { - self.asm.push(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(self, imm: u32) { - self.asm.push(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(self) { - self.asm.push(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(self) { - self.asm.push(self.ip, MasmOp::U32UncheckedLt); - } - - /// This is the same as `lte`, but also asserts that both operands are valid u32 values. - pub fn lte_u32(self) { - self.asm.push(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(self) { - self.asm.push(self.ip, MasmOp::U32UncheckedLte); - } - - /// This is the same as `gt`, but also asserts that both operands are valid u32 values. - pub fn gt_u32(self) { - self.asm.push(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(self) { - self.asm.push(self.ip, MasmOp::U32UncheckedGt); - } - - /// This is the same as `gte`, but also asserts that both operands are valid u32 values. - pub fn gte_u32(self) { - self.asm.push(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(self) { - self.asm.push(self.ip, MasmOp::U32UncheckedGte); - } - - /// This is the same as `min`, but also asserts that both operands are valid u32 values. - pub fn min_u32(self) { - self.asm.push(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(self) { - self.asm.push(self.ip, MasmOp::U32UncheckedMin); - } - - /// This is the same as `max`, but also asserts that both operands are valid u32 values. - pub fn max_u32(self) { - self.asm.push(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(self) { - self.asm.push(self.ip, MasmOp::U32UncheckedMax); - } -} - -/// 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, -} - -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"), - } - } -} - -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; -} - -pub trait Stack: std::ops::IndexMut::Element> { - type Element: StackElement; - - fn storage(&self) -> &Vec; - fn storage_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.storage().is_empty() - } - - /// Returns the number of elements on the stack - #[inline] - fn len(&self) -> usize { - self.storage().len() - } - - /// Returns the value on top of the stack, without consuming it - #[inline] - fn peek(&self) -> Self::Element { - self.storage() - .last() - .cloned() - .expect("operand stack is empty") - } - - /// Returns the word on top of the stack, without consuming it - #[inline] - fn peekw(&self) -> [Self::Element; 4] { - let stack = self.storage(); - let end = stack.len().checked_sub(1).expect("operand stack is empty"); - [ - 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.storage_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.storage_mut().push(value); - } - - /// Pushes `word` on top of the stack - fn pushw(&mut self, word: [Self::Element; 4]) { - let stack = self.storage_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.storage_mut().pop() - } - - /// Pops the first word on top of the stack - fn popw(&mut self) -> Option<[Self::Element; 4]> { - let stack = self.storage_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.storage_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.storage_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.storage().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(); - self.pushw(word); - } - n => { - let end = len - n - 1; - let word = { - let stack = self.storage(); - [ - 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.storage_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.storage_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.storage_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.storage_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.storage_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.storage_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 structure emulates the Miden VM operand stack -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 storage(&self) -> &Vec { - &self.stack - } - #[inline(always)] - fn storage_mut(&mut self) -> &mut Vec { - &mut self.stack - } - - fn padw(&mut self) { - self.stack.extend([::DEFAULT; 4]); - } -} -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 std::ops::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 std::ops::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] - } -} - -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 - .storage() - .iter() - .rev() - .enumerate() - .map(|(index, value)| StackEntry { index, value }), - ) - .finish() - } -} diff --git a/hir/src/asm/builder.rs b/hir/src/asm/builder.rs new file mode 100644 index 00000000..1935bfba --- /dev/null +++ b/hir/src/asm/builder.rs @@ -0,0 +1,1097 @@ +use crate::{ + DataFlowGraph, Felt, FunctionIdent, Inst, InstBuilder, Instruction, Overflow, SourceSpan, Type, + Value, +}; + +use super::*; + +/// Used to construct an [InlineAssembly] instruction, while checking various safety invariants. +pub struct MasmBuilder { + builder: B, + span: SourceSpan, + asm: InlineAsm, + ty: Type, + current_block: MasmBlockId, + 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 type given by `ty` represents the expected result type for this inline assembly block. If the inline assembly + /// will not produce a result, use `Type::Unit`. It is expected that the value(s) remaining on the operand stack upon + /// exit from the inline assembly block, are a match for `ty`. For example, if `Type::Unit` is given, no values should + /// remain on the operand stack; if `Type::Felt` is given, then a single value should be on the operand stack; if + /// `Type::Array[Type::Felt; 2]` is given, then two values should be on the operand stack, and so on. + /// + /// NOTE: Not all types are permitted as inline assembly results. The type must be "loadable", i.e. no larger than a word. + /// + /// 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], ty: Type, span: SourceSpan) -> Self { + assert!( + ty.is_loadable(), + "invalid inline assembly block type: type must be loadable, but got {}", + &ty + ); + // 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(); + { + 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, + ty, + current_block, + stack, + } + } + + #[inline] + pub fn create_block(&mut self) -> MasmBlockId { + self.asm.create_block() + } + + #[inline(always)] + pub fn switch_to_block(&mut self, block: MasmBlockId) { + self.current_block = block; + } + + pub fn ins<'a, 'b: 'a>(&'b mut self) -> MasmOpBuilder<'a> { + MasmOpBuilder { + asm: &mut self.asm, + stack: &mut self.stack, + ip: self.current_block, + } + } + + pub fn build(self) -> (Inst, &'f mut DataFlowGraph) { + let ty = self.ty; + match &ty { + Type::Unit => assert!(self.stack.is_empty(), "invalid inline assembly: expected operand stack to be empty upon exit, found: {:?}", self.stack.display()), + ty => { + let len = ty.size_in_felts(); + assert_eq!(len, self.stack.len(), "invalid inline assembly: expected operand stack to have {} elements upon exit, found: {:?}", len, self.stack.display()); + } + } + + let span = self.span; + let data = Instruction::InlineAsm(self.asm); + self.builder.build(data, ty, span) + } +} + +/// Used to construct a single MASM opcode +pub struct MasmOpBuilder<'a> { + asm: &'a mut InlineAsm, + stack: &'a mut OperandStack, + ip: MasmBlockId, +} +impl<'a> MasmOpBuilder<'a> { + /// Pads the stack with four zero elements + pub fn padw(self) { + self.stack.padw(); + self.asm.push(self.ip, MasmOp::Padw); + } + + /// Pushes an element on the stack + pub fn push(self, imm: Felt) { + self.stack.push(Type::Felt); + self.asm.push(self.ip, MasmOp::Push(imm)); + } + + /// Pushes a word on the stack + pub fn pushw(self, word: [Felt; 4]) { + self.stack + .pushw([Type::Felt, Type::Felt, Type::Felt, Type::Felt]); + self.asm.push(self.ip, MasmOp::Pushw(word)); + } + + /// Pushes an element representing an unsigned 8-bit integer on the stack + pub fn push_u8(self, imm: u8) { + self.stack.push(Type::U8); + self.asm.push(self.ip, MasmOp::PushU8(imm)); + } + + /// Pushes an element representing an unsigned 16-bit integer on the stack + pub fn push_u16(self, imm: u16) { + self.stack.push(Type::U16); + self.asm.push(self.ip, MasmOp::PushU16(imm)); + } + + /// Pushes an element representing an unsigned 32-bit integer on the stack + pub fn push_u32(self, imm: u32) { + self.stack.push(Type::U32); + self.asm.push(self.ip, MasmOp::PushU32(imm)); + } + + /// Drops the element on the top of the stack + pub fn drop(self) { + self.stack.drop(); + self.asm.push(self.ip, MasmOp::Drop); + } + + /// Drops the word (first four elements) on the top of the stack + pub fn dropw(self) { + self.stack.dropw(); + self.asm.push(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(self, n: usize) { + self.stack.dup(n); + self.asm.push(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(self, n: usize) { + self.stack.dupw(n); + self.asm.push(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(self, n: usize) { + self.stack.swap(n); + self.asm.push(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(self, n: usize) { + self.stack.swapw(n); + self.asm.push(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(self, idx: usize) { + self.stack.movup(idx); + self.asm.push(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(self, idx: usize) { + self.stack.movupw(idx); + self.asm.push(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(self, idx: usize) { + self.stack.movdn(idx); + self.asm.push(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(self, idx: usize) { + self.stack.movdnw(idx); + self.asm.push(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(self) { + self.stack.drop(); + self.asm.push(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(self) { + self.stack.drop(); + self.asm.push(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(self) { + self.stack.dropn(2); + self.asm.push(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(self) { + self.stack.dropn(5); + self.asm.push(self.ip, MasmOp::Cdropw); + } + + /// Pops the top element on the stack, and traps if that element is != 1. + pub fn assert(self) { + self.stack.drop(); + self.asm.push(self.ip, MasmOp::Assert); + } + + /// Pops the top element on the stack, and traps if that element is != 0. + pub fn assertz(self) { + self.stack.drop(); + self.asm.push(self.ip, MasmOp::Assertz); + } + + /// Pops the top two elements on the stack, and traps if they are not equal. + pub fn assert_eq(self) { + self.stack.dropn(2); + self.asm.push(self.ip, MasmOp::AssertEq); + } + + /// Pops the top two words on the stack, and traps if they are not equal. + pub fn assert_eqw(self) { + self.stack.dropn(8); + self.asm.push(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(self) { + self.stack.drop(); + self.asm.push(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(self, addr: u32) { + self.stack.drop(); + self.asm.push(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(self) { + self.stack.drop(); + self.asm.push(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(self, addr: u32, offset: u8) { + assert!( + offset < 4, + "invalid element offset, must be in the range 0..=3, got {}", + offset + ); + self.stack.drop(); + self.asm + .push(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(self) { + self.stack.drop(); + self.asm.push(self.ip, MasmOp::MemLoadw); + } + + /// Loads the word at the given address to the top of the stack. + pub fn loadw_imm(self, addr: u32) { + self.stack.drop(); + self.asm.push(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(self) { + self.stack.drop(); + self.asm.push(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(self, addr: u32) { + self.stack.drop(); + self.asm.push(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(self) { + self.stack.drop(); + self.asm.push(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(self, addr: u32, offset: u8) { + assert!( + offset < 4, + "invalid element offset, must be in the range 0..=3, got {}", + offset + ); + self.stack.drop(); + self.asm + .push(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(self) { + self.stack.drop(); + self.asm.push(self.ip, MasmOp::MemStorew); + } + + /// Pops a word from the stack and stores it as the word at the given address. + pub fn storew_imm(self, addr: u32) { + self.stack.drop(); + self.asm.push(self.ip, MasmOp::MemStorewImm(addr)); + } + + /// Pops a boolean value from the stack, and executes the first block if it is true, + /// otherwise the second block. + pub fn if_true(self, then_blk: MasmBlockId, else_blk: MasmBlockId) { + self.stack.drop(); + self.asm.push(self.ip, MasmOp::If(then_blk, else_blk)) + } + + /// Pops a boolean value from the stack, and executes the given block if it is true, + /// otherwise it is skipped. The given block will continue to execute for as long as + /// the top value on the stack at the end of the block is true. + pub fn while_true(self, body: MasmBlockId) { + self.stack.drop(); + self.asm.push(self.ip, MasmOp::While(body)); + } + + /// Repeatedly executes `body`, `n` times. + pub fn repeat(self, n: u8, body: MasmBlockId) { + self.asm.push(self.ip, MasmOp::Repeat(n, body)); + } + + /// Executes the named procedure as a regular function. + pub fn exec(self, id: FunctionIdent) { + self.asm.push(self.ip, MasmOp::Exec(id)); + } + + /// Executes the named procedure as a syscall. + pub fn syscall(self, id: FunctionIdent) { + self.asm.push(self.ip, MasmOp::Syscall(id)); + } + + /// Pops two field elements from the stack, adds them, and places the result on the stack. + pub fn add(self) { + self.asm.push(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(self, imm: Felt) { + self.asm.push(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(self) { + self.asm.push(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(self, imm: Felt) { + self.asm.push(self.ip, MasmOp::SubImm(imm)); + } + + /// Pops two field elements from the stack, multiplies them, and places the result on the stack. + pub fn mul(self) { + self.asm.push(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(self, imm: Felt) { + self.asm.push(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(self) { + self.asm.push(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(self, imm: Felt) { + self.asm.push(self.ip, MasmOp::DivImm(imm)); + } + + /// Negates the field element on top of the stack + pub fn neg(self) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::Inv); + } + + /// Increments the field element on top of the stack + pub fn incr(self) { + self.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(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(self, exponent: u8) { + self.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(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(self, imm: bool) { + self.asm.push(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(self) { + self.asm.push(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(self, imm: bool) { + self.asm.push(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(self) { + self.asm.push(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(self, imm: bool) { + self.asm.push(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(self) { + self.asm.push(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(self, imm: Felt) { + self.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(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(self, imm: Felt) { + self.asm.push(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(self) { + self.asm.push(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(self, imm: Felt) { + self.asm.push(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(self) { + self.asm.push(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(self, imm: Felt) { + self.asm.push(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(self) { + self.asm.push(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(self, imm: Felt) { + self.asm.push(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(self) { + self.asm.push(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(self, imm: Felt) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::IsOdd); + } + + /// Pushes the current value of the cycle counter (clock) on the stack + pub fn clk(self) { + self.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(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. + /// + /// The specific behavior of the addition depends on the given `overflow` flags: + /// + /// * `Overflow::Unchecked` - the addition is performed using the `add` op for field elements, which may + /// produce a value that is outside of the u32 range, it is the callers responsibility to ensure that the + /// resulting value is in range. + /// * `Overflow::Checked` - the operation will trap if either operand, or the result, is not a valid u32 + /// * `Overflow::Wrapping` - computes the result as `(a + b) mod 2^32`, behavior is undefined if either operand + /// is not a valid u32 + /// * `Overflow::Overflowing` - similar to above, the result is computed as `(a + b) mod 2^32`, however a boolean + /// is also pushed on the stack after the result, which is 1 if the result of `a + b` overflowed, else 0. + /// + pub fn add_u32(self, overflow: Overflow) { + let op = match overflow { + Overflow::Unchecked => MasmOp::Add, + Overflow::Checked => MasmOp::U32CheckedAdd, + Overflow::Overflowing => MasmOp::U32OverflowingAdd, + Overflow::Wrapping => MasmOp::U32WrappingAdd, + }; + self.asm.push(self.ip, op); + } + + /// Same as above, but `a` is provided by the given immediate. + pub fn add_imm_u32(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.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(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. + /// + /// The specific behavior of the subtraction depends on the given `overflow` flags: + /// + /// * `Overflow::Unchecked` - the subtraction is performed using the `sub` op for field elements, which may + /// produce a value that is outside of the u32 range, it is the callers responsibility to ensure that the + /// resulting value is in range. + /// * `Overflow::Checked` - the operation will trap if either operand, or the result, is not a valid u32 + /// * `Overflow::Wrapping` - computes the result as `(a - b) mod 2^32`, behavior is undefined if either operand + /// is not a valid u32 + /// * `Overflow::Overflowing` - similar to above, the result is computed as `(a - b) mod 2^32`, however a boolean + /// is also pushed on the stack after the result, which is 1 if the result of `a - b` underflowed, else 0. + /// + pub fn sub_u32(self, overflow: Overflow) { + let op = match overflow { + Overflow::Unchecked => MasmOp::Sub, + Overflow::Checked => MasmOp::U32CheckedSub, + Overflow::Overflowing => MasmOp::U32OverflowingSub, + Overflow::Wrapping => MasmOp::U32WrappingSub, + }; + self.asm.push(self.ip, op); + } + + /// Same as above, but `a` is provided by the given immediate. + pub fn sub_imm_u32(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.asm.push(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. + /// + /// The specific behavior of the subtraction depends on the given `overflow` flags: + /// + /// * `Overflow::Unchecked` - the multiplication is performed using the `mul` op for field elements, which may + /// produce a value that is outside of the u32 range, it is the callers responsibility to ensure that the + /// resulting value is in range. + /// * `Overflow::Checked` - the operation will trap if either operand, or the result, is not a valid u32 + /// * `Overflow::Wrapping` - computes the result as `(a * b) mod 2^32`, behavior is undefined if either operand + /// is not a valid u32 + /// * `Overflow::Overflowing` - similar to above, the result is computed as `(a * b) mod 2^32`, however a boolean + /// is also pushed on the stack after the result, which is 1 if the result of `a * b` underflowed, else 0. + /// + pub fn mul_u32(self, overflow: Overflow) { + let op = match overflow { + Overflow::Unchecked => MasmOp::Mul, + Overflow::Checked => MasmOp::U32CheckedMul, + Overflow::Overflowing => MasmOp::U32OverflowingMul, + Overflow::Wrapping => MasmOp::U32WrappingMul, + }; + self.asm.push(self.ip, op); + } + + /// Same as above, but `a` is provided by the given immediate. + pub fn mul_imm_u32(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.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32CheckedDiv); + } + + /// Same as above, but `b` is provided by the given immediate + pub fn div_imm_u32(self, imm: u32) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32UncheckedDiv); + } + + /// Same as above, but `b` is provided by the given immediate + pub fn div_imm_unchecked_u32(self, imm: u32) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32CheckedMod); + } + + /// Same as above, but `b` is provided by the given immediate + pub fn mod_imm_u32(self, imm: u32) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32UncheckedMod); + } + + /// Same as above, but `b` is provided by the given immediate + pub fn mod_imm_unchecked_u32(self, imm: u32) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32CheckedDivMod); + } + + /// Same as above, but `b` is provided by the given immediate + pub fn divmod_imm_u32(self, imm: u32) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32UncheckedDivMod); + } + + /// Same as above, but `b` is provided by the given immediate + pub fn divmod_imm_unchecked_u32(self, imm: u32) { + self.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32CheckedShl); + } + + /// Same as `shl_u32`, but `b` is provided by immediate. + pub fn shl_imm_u32(self, imm: u32) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32UncheckedShl); + } + + /// Same as `shl_unchecked_u32`, but `b` is provided by immediate. + pub fn shl_unchecked_imm_u32(self, imm: u32) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32CheckedShr); + } + + /// Same as `shr_u32`, but `b` is provided by immediate. + pub fn shr_imm_u32(self, imm: u32) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32UncheckedShr); + } + + /// Same as `shr_unchecked_u32`, but `b` is provided by immediate. + pub fn shr_unchecked_imm_u32(self, imm: u32) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32CheckedRotl); + } + + /// Same as `rotl_u32`, but `b` is provided by immediate. + pub fn rotl_imm_u32(self, imm: u32) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32UncheckedRotl); + } + + /// Same as `rotl_unchecked_u32`, but `b` is provided by immediate. + pub fn rotl_unchecked_imm_u32(self, imm: u32) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32CheckedRotr); + } + + /// Same as `rotr_u32`, but `b` is provided by immediate. + pub fn rotr_imm_u32(self, imm: u32) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32UncheckedRotr); + } + + /// Same as `rotr_unchecked_u32`, but `b` is provided by immediate. + pub fn rotr_unchecked_imm_u32(self, imm: u32) { + self.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32UncheckedPopcnt); + } + + /// This is the same as `eq`, but also asserts that both operands are valid u32 values. + pub fn eq_u32(self) { + self.asm.push(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(self, imm: u32) { + self.asm.push(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(self) { + self.asm.push(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(self, imm: u32) { + self.asm.push(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(self) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32UncheckedLt); + } + + /// This is the same as `lte`, but also asserts that both operands are valid u32 values. + pub fn lte_u32(self) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32UncheckedLte); + } + + /// This is the same as `gt`, but also asserts that both operands are valid u32 values. + pub fn gt_u32(self) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32UncheckedGt); + } + + /// This is the same as `gte`, but also asserts that both operands are valid u32 values. + pub fn gte_u32(self) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32UncheckedGte); + } + + /// This is the same as `min`, but also asserts that both operands are valid u32 values. + pub fn min_u32(self) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32UncheckedMin); + } + + /// This is the same as `max`, but also asserts that both operands are valid u32 values. + pub fn max_u32(self) { + self.asm.push(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(self) { + self.asm.push(self.ip, MasmOp::U32UncheckedMax); + } +} 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..3f310704 --- /dev/null +++ b/hir/src/asm/isa.rs @@ -0,0 +1,577 @@ +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, +} diff --git a/hir/src/asm/mod.rs b/hir/src/asm/mod.rs new file mode 100644 index 00000000..f2c7a984 --- /dev/null +++ b/hir/src/asm/mod.rs @@ -0,0 +1,74 @@ +mod builder; +mod display; +mod isa; +mod stack; + +pub use self::builder::{MasmBuilder, MasmOpBuilder}; +pub use self::display::DisplayInlineAsm; +pub use self::isa::*; +pub use self::stack::{OperandStack, Stack}; + +use cranelift_entity::PrimaryMap; + +use super::{DataFlowGraph, Opcode, 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 +#[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 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 + pub fn new() -> 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(), + 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..98e900b5 --- /dev/null +++ b/hir/src/asm/stack.rs @@ -0,0 +1,443 @@ +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) -> Self::Element { + self.stack() + .last() + .cloned() + .expect("operand stack is empty") + } + + /// Returns the word on top of the stack, without consuming it + #[inline] + fn peekw(&self) -> [Self::Element; 4] { + let stack = self.stack(); + let end = stack.len().checked_sub(1).expect("operand stack is empty"); + [ + 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(); + 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() + } +} From ce274491dc0f66b00360e7ec807fbabbf074b19b Mon Sep 17 00:00:00 2001 From: Paul Schoenfelder Date: Sat, 9 Sep 2023 03:43:32 -0400 Subject: [PATCH 04/15] feat: provide type representation enum The [TypeRepr] enum is intended to represent how a specific [Type] will be encoded in terms of field elements/words on the operand stack in Miden. See the doc comments for details. This is meant to provide a single source for information regarding type layout in Miden. For types in linear memory, the encoding is slightly different, as we must preserve the semantics of byte-addressability in the IR. However, when placing values on the operand stack, the encoding is modified to provide a more natural alignment between individual terms and elements on the stack. The specific representations in this commit are just an initial draft based on details I'm aware of at this time, but we may make adjustments as necessary. --- hir-type/src/lib.rs | 128 +++++++++++++++++++++++++++++++++++++++++++- hir/src/function.rs | 5 ++ hir/src/lib.rs | 2 +- 3 files changed, 133 insertions(+), 2 deletions(-) diff --git a/hir-type/src/lib.rs b/hir-type/src/lib.rs index a3f08aad..896f0ded 100644 --- a/hir-type/src/lib.rs +++ b/hir-type/src/lib.rs @@ -3,11 +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 { @@ -143,6 +214,61 @@ impl Type { } } + /// 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; 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/lib.rs b/hir/src/lib.rs index 6536c768..a9b00421 100644 --- a/hir/src/lib.rs +++ b/hir/src/lib.rs @@ -3,7 +3,7 @@ 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; From e57d3d49d7d9bbfffe077eb763adcd3aa7f3173e Mon Sep 17 00:00:00 2001 From: Paul Schoenfelder Date: Sat, 9 Sep 2023 03:52:55 -0400 Subject: [PATCH 05/15] fix: improve handling of multi-value results in inline asm --- hir/src/asm/builder.rs | 52 +++++++++++++++++++++--------------------- hir/src/asm/mod.rs | 16 +++++++++---- hir/src/builder.rs | 9 ++++++-- hir/src/dataflow.rs | 25 ++++++++++++++++---- hir/src/instruction.rs | 5 ++-- 5 files changed, 68 insertions(+), 39 deletions(-) diff --git a/hir/src/asm/builder.rs b/hir/src/asm/builder.rs index 1935bfba..0dc7b2ee 100644 --- a/hir/src/asm/builder.rs +++ b/hir/src/asm/builder.rs @@ -1,6 +1,6 @@ use crate::{ - DataFlowGraph, Felt, FunctionIdent, Inst, InstBuilder, Instruction, Overflow, SourceSpan, Type, - Value, + CallConv, DataFlowGraph, Felt, FunctionIdent, Inst, InstBuilder, Instruction, Overflow, + SourceSpan, Type, TypeRepr, Value, }; use super::*; @@ -10,7 +10,6 @@ pub struct MasmBuilder { builder: B, span: SourceSpan, asm: InlineAsm, - ty: Type, current_block: MasmBlockId, stack: OperandStack, } @@ -19,22 +18,12 @@ impl<'f, B: InstBuilder<'f>> MasmBuilder { /// /// The `args` list represents the arguments which will be visible on the operand stack in this inline assembly block. /// - /// The type given by `ty` represents the expected result type for this inline assembly block. If the inline assembly - /// will not produce a result, use `Type::Unit`. It is expected that the value(s) remaining on the operand stack upon - /// exit from the inline assembly block, are a match for `ty`. For example, if `Type::Unit` is given, no values should - /// remain on the operand stack; if `Type::Felt` is given, then a single value should be on the operand stack; if - /// `Type::Array[Type::Felt; 2]` is given, then two values should be on the operand stack, and so on. - /// - /// NOTE: Not all types are permitted as inline assembly results. The type must be "loadable", i.e. no larger than a word. + /// 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], ty: Type, span: SourceSpan) -> Self { - assert!( - ty.is_loadable(), - "invalid inline assembly block type: type must be loadable, but got {}", - &ty - ); + 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(); { @@ -45,7 +34,7 @@ impl<'f, B: InstBuilder<'f>> MasmBuilder { } // Construct an empty inline assembly block with the given arguments - let mut asm = InlineAsm::new(); + let mut asm = InlineAsm::new(results); { let dfg = builder.data_flow_graph_mut(); let mut vlist = ValueList::default(); @@ -58,7 +47,6 @@ impl<'f, B: InstBuilder<'f>> MasmBuilder { builder, span, asm, - ty, current_block, stack, } @@ -82,19 +70,31 @@ impl<'f, B: InstBuilder<'f>> MasmBuilder { } } - pub fn build(self) -> (Inst, &'f mut DataFlowGraph) { - let ty = self.ty; - match &ty { - Type::Unit => assert!(self.stack.is_empty(), "invalid inline assembly: expected operand stack to be empty upon exit, found: {:?}", self.stack.display()), - ty => { - let len = ty.size_in_felts(); - assert_eq!(len, self.stack.len(), "invalid inline assembly: expected operand stack to have {} elements upon exit, found: {:?}", len, self.stack.display()); + /// 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, ty, span) + self.builder.build(data, Type::Unit, span).0 } } diff --git a/hir/src/asm/mod.rs b/hir/src/asm/mod.rs index f2c7a984..32833bb1 100644 --- a/hir/src/asm/mod.rs +++ b/hir/src/asm/mod.rs @@ -10,14 +10,19 @@ pub use self::stack::{OperandStack, Stack}; use cranelift_entity::PrimaryMap; -use super::{DataFlowGraph, Opcode, ValueList}; +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 +/// 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, @@ -31,6 +36,8 @@ pub struct InlineAsm { /// 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 @@ -39,14 +46,15 @@ pub struct InlineAsm { pub blocks: PrimaryMap, } impl InlineAsm { - /// Constructs a new, empty inline assembly block - pub fn new() -> Self { + /// 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, } diff --git a/hir/src/builder.rs b/hir/src/builder.rs index 93662d2f..3c84503b 100644 --- a/hir/src/builder.rs +++ b/hir/src/builder.rs @@ -941,8 +941,13 @@ pub trait InstBuilder<'f>: InstBuilderBase<'f> { self.build(data, Type::Never, span).0 } - fn inline_asm(self, args: &[Value], ty: Type, span: SourceSpan) -> MasmBuilder { - MasmBuilder::new(self, args, ty, span) + 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)] 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/instruction.rs b/hir/src/instruction.rs index 2ed7d53f..a4220cfc 100644 --- a/hir/src/instruction.rs +++ b/hir/src/instruction.rs @@ -596,8 +596,7 @@ impl Opcode { | Self::Shl | Self::Shr | Self::Rotl - | Self::Rotr - | Self::InlineAsm => { + | Self::Rotr => { smallvec![ctrl_ty] } // The result type of a load is derived from the pointee type @@ -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!(), } } } From 038a18de97372edb635d059e41817b4343e17757 Mon Sep 17 00:00:00 2001 From: Paul Schoenfelder Date: Sat, 9 Sep 2023 03:56:41 -0400 Subject: [PATCH 06/15] fix: missing stack adjustments in multiple masm op builders --- hir/src/asm/builder.rs | 213 +++++++++++++++++++++++++++++++++++++++-- hir/src/asm/stack.rs | 17 ++-- 2 files changed, 212 insertions(+), 18 deletions(-) diff --git a/hir/src/asm/builder.rs b/hir/src/asm/builder.rs index 0dc7b2ee..f7a68a29 100644 --- a/hir/src/asm/builder.rs +++ b/hir/src/asm/builder.rs @@ -119,8 +119,7 @@ impl<'a> MasmOpBuilder<'a> { /// Pushes a word on the stack pub fn pushw(self, word: [Felt; 4]) { - self.stack - .pushw([Type::Felt, Type::Felt, Type::Felt, Type::Felt]); + self.stack.padw(); self.asm.push(self.ip, MasmOp::Pushw(word)); } @@ -286,12 +285,13 @@ impl<'a> MasmOpBuilder<'a> { /// and loads the first element of the word at that address to the top of the stack. pub fn load(self) { self.stack.drop(); + self.stack.push(Type::Felt); self.asm.push(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(self, addr: u32) { - self.stack.drop(); + self.stack.push(Type::Felt); self.asm.push(self.ip, MasmOp::MemLoadImm(addr)); } @@ -301,6 +301,7 @@ impl<'a> MasmOpBuilder<'a> { /// NOTE: This is an experimental instruction which is not implemented in Miden VM yet. pub fn load_offset(self) { self.stack.drop(); + self.stack.push(Type::Felt); self.asm.push(self.ip, MasmOp::MemLoadOffset); } @@ -313,7 +314,7 @@ impl<'a> MasmOpBuilder<'a> { "invalid element offset, must be in the range 0..=3, got {}", offset ); - self.stack.drop(); + self.stack.push(Type::Felt); self.asm .push(self.ip, MasmOp::MemLoadOffsetImm(addr, offset)); } @@ -322,19 +323,20 @@ impl<'a> MasmOpBuilder<'a> { /// and loads the word at that address to the top of the stack. pub fn loadw(self) { self.stack.drop(); + self.stack.padw(); self.asm.push(self.ip, MasmOp::MemLoadw); } /// Loads the word at the given address to the top of the stack. pub fn loadw_imm(self, addr: u32) { - self.stack.drop(); + self.stack.padw(); self.asm.push(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(self) { - self.stack.drop(); + self.stack.dropn(2); self.asm.push(self.ip, MasmOp::MemStore); } @@ -369,13 +371,13 @@ impl<'a> MasmOpBuilder<'a> { /// 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(self) { - self.stack.drop(); + self.stack.dropn(5); self.asm.push(self.ip, MasmOp::MemStorew); } /// Pops a word from the stack and stores it as the word at the given address. pub fn storew_imm(self, addr: u32) { - self.stack.drop(); + self.stack.dropw(); self.asm.push(self.ip, MasmOp::MemStorewImm(addr)); } @@ -411,6 +413,7 @@ impl<'a> MasmOpBuilder<'a> { /// Pops two field elements from the stack, adds them, and places the result on the stack. pub fn add(self) { + self.stack.drop(); self.asm.push(self.ip, MasmOp::Add); } @@ -421,6 +424,7 @@ impl<'a> MasmOpBuilder<'a> { /// Pops two field elements from the stack, subtracts the second from the first, and places the result on the stack. pub fn sub(self) { + self.stack.drop(); self.asm.push(self.ip, MasmOp::Sub); } @@ -431,6 +435,7 @@ impl<'a> MasmOpBuilder<'a> { /// Pops two field elements from the stack, multiplies them, and places the result on the stack. pub fn mul(self) { + self.stack.drop(); self.asm.push(self.ip, MasmOp::Mul); } @@ -441,6 +446,7 @@ impl<'a> MasmOpBuilder<'a> { /// Pops two field elements from the stack, divides the first by the second, and places the result on the stack. pub fn div(self) { + self.stack.drop(); self.asm.push(self.ip, MasmOp::Div); } @@ -475,6 +481,7 @@ impl<'a> MasmOpBuilder<'a> { /// /// Traps if `b` is not in the range 0..=63 pub fn exp(self) { + self.stack.drop(); self.asm.push(self.ip, MasmOp::Exp); } @@ -490,6 +497,11 @@ impl<'a> MasmOpBuilder<'a> { /// /// Traps if the value is not 0 or 1. pub fn not(self) { + assert_eq!( + self.stack.peek(), + Some(Type::I1), + "expected a boolean operand on the stack" + ); self.asm.push(self.ip, MasmOp::Not); } @@ -497,6 +509,10 @@ impl<'a> MasmOpBuilder<'a> { /// /// Traps if either value is not 0 or 1. pub fn and(self) { + let rhs = self.stack.pop().expect("operand stack is empty"); + let lhs = self.stack.peek().expect("operand stack is empty"); + assert_eq!(lhs, rhs, "expected both operands to be the same type"); + assert_eq!(lhs, Type::I1, "expected boolean operands"); self.asm.push(self.ip, MasmOp::And); } @@ -504,6 +520,11 @@ impl<'a> MasmOpBuilder<'a> { /// /// Traps if the value is not 0 or 1. pub fn and_imm(self, imm: bool) { + assert_eq!( + self.stack.peek(), + Some(Type::I1), + "expected a boolean operand on the stack" + ); self.asm.push(self.ip, MasmOp::AndImm(imm)); } @@ -511,6 +532,10 @@ impl<'a> MasmOpBuilder<'a> { /// /// Traps if either value is not 0 or 1. pub fn or(self) { + let rhs = self.stack.pop().expect("operand stack is empty"); + let lhs = self.stack.peek().expect("operand stack is empty"); + assert_eq!(lhs, rhs, "expected both operands to be the same type"); + assert_eq!(lhs, Type::I1, "expected boolean operands"); self.asm.push(self.ip, MasmOp::Or); } @@ -518,6 +543,11 @@ impl<'a> MasmOpBuilder<'a> { /// /// Traps if the value is not 0 or 1. pub fn or_imm(self, imm: bool) { + assert_eq!( + self.stack.peek(), + Some(Type::I1), + "expected a boolean operand on the stack" + ); self.asm.push(self.ip, MasmOp::OrImm(imm)); } @@ -525,6 +555,10 @@ impl<'a> MasmOpBuilder<'a> { /// /// Traps if either value is not 0 or 1. pub fn xor(self) { + let rhs = self.stack.pop().expect("operand stack is empty"); + let lhs = self.stack.peek().expect("operand stack is empty"); + assert_eq!(lhs, rhs, "expected both operands to be the same type"); + assert_eq!(lhs, Type::I1, "expected boolean operands"); self.asm.push(self.ip, MasmOp::Xor); } @@ -532,91 +566,128 @@ impl<'a> MasmOpBuilder<'a> { /// /// Traps if the value is not 0 or 1. pub fn xor_imm(self, imm: bool) { + assert_eq!( + self.stack.peek(), + Some(Type::I1), + "expected a boolean operand on the stack" + ); self.asm.push(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(self) { + self.stack.dropn(2); + self.stack.push(Type::I1); self.asm.push(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(self, imm: Felt) { + self.stack.drop(); + self.stack.push(Type::I1); self.asm.push(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(self) { + self.stack.dropw(); + self.stack.dropw(); + self.stack.push(Type::I1); self.asm.push(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(self) { + self.stack.dropn(2); + self.stack.push(Type::I1); self.asm.push(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(self, imm: Felt) { + self.stack.drop(); + self.stack.push(Type::I1); self.asm.push(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(self) { + self.stack.dropn(2); + self.stack.push(Type::I1); self.asm.push(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(self, imm: Felt) { + self.stack.drop(); + self.stack.push(Type::I1); self.asm.push(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(self) { + self.stack.dropn(2); + self.stack.push(Type::I1); self.asm.push(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(self, imm: Felt) { + self.stack.drop(); + self.stack.push(Type::I1); self.asm.push(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(self) { + self.stack.dropn(2); + self.stack.push(Type::I1); self.asm.push(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(self, imm: Felt) { + self.stack.drop(); + self.stack.push(Type::I1); self.asm.push(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(self) { + self.stack.dropn(2); + self.stack.push(Type::I1); self.asm.push(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(self, imm: Felt) { + self.stack.drop(); + self.stack.push(Type::I1); self.asm.push(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(self) { + self.stack.drop(); + self.stack.push(Type::I1); self.asm.push(self.ip, MasmOp::IsOdd); } /// Pushes the current value of the cycle counter (clock) on the stack pub fn clk(self) { + self.stack.push(Type::Felt); self.asm.push(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(self) { + self.stack.push(Type::I1); self.asm.push(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(self) { + self.stack.push(Type::I1); self.asm.push(self.ip, MasmOp::U32Testw); } @@ -637,6 +708,8 @@ impl<'a> MasmOpBuilder<'a> { /// Casts the element on top of the stack, `a`, to a valid u32 value, by computing `a mod 2^32` pub fn cast_u32(self) { + self.stack.drop(); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32Cast); } @@ -645,6 +718,9 @@ impl<'a> MasmOpBuilder<'a> { /// 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(self) { + self.stack.drop(); + self.stack.push(Type::U32); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32Split); } @@ -663,6 +739,8 @@ impl<'a> MasmOpBuilder<'a> { /// is also pushed on the stack after the result, which is 1 if the result of `a + b` overflowed, else 0. /// pub fn add_u32(self, overflow: Overflow) { + self.stack.dropn(2); + self.stack.push(Type::U32); let op = match overflow { Overflow::Unchecked => MasmOp::Add, Overflow::Checked => MasmOp::U32CheckedAdd, @@ -674,6 +752,8 @@ impl<'a> MasmOpBuilder<'a> { /// Same as above, but `a` is provided by the given immediate. pub fn add_imm_u32(self, imm: u32, overflow: Overflow) { + self.stack.drop(); + self.stack.push(Type::U32); let op = match overflow { Overflow::Unchecked => MasmOp::AddImm(Felt::new(imm as u64)), Overflow::Checked => MasmOp::U32CheckedAddImm(imm), @@ -687,12 +767,16 @@ impl<'a> MasmOpBuilder<'a> { /// 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(self) { + self.stack.dropn(3); + self.stack.push(Type::U32); self.asm.push(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(self) { + self.stack.dropn(3); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32WrappingAdd3); } @@ -711,6 +795,8 @@ impl<'a> MasmOpBuilder<'a> { /// is also pushed on the stack after the result, which is 1 if the result of `a - b` underflowed, else 0. /// pub fn sub_u32(self, overflow: Overflow) { + self.stack.dropn(2); + self.stack.push(Type::U32); let op = match overflow { Overflow::Unchecked => MasmOp::Sub, Overflow::Checked => MasmOp::U32CheckedSub, @@ -722,6 +808,8 @@ impl<'a> MasmOpBuilder<'a> { /// Same as above, but `a` is provided by the given immediate. pub fn sub_imm_u32(self, imm: u32, overflow: Overflow) { + self.stack.drop(); + self.stack.push(Type::U32); let op = match overflow { Overflow::Unchecked => MasmOp::SubImm(Felt::new(imm as u64)), Overflow::Checked => MasmOp::U32CheckedSubImm(imm), @@ -746,6 +834,8 @@ impl<'a> MasmOpBuilder<'a> { /// is also pushed on the stack after the result, which is 1 if the result of `a * b` underflowed, else 0. /// pub fn mul_u32(self, overflow: Overflow) { + self.stack.dropn(2); + self.stack.push(Type::U32); let op = match overflow { Overflow::Unchecked => MasmOp::Mul, Overflow::Checked => MasmOp::U32CheckedMul, @@ -757,6 +847,8 @@ impl<'a> MasmOpBuilder<'a> { /// Same as above, but `a` is provided by the given immediate. pub fn mul_imm_u32(self, imm: u32, overflow: Overflow) { + self.stack.drop(); + self.stack.push(Type::U32); let op = match overflow { Overflow::Unchecked => MasmOp::MulImm(Felt::new(imm as u64)), Overflow::Checked => MasmOp::U32CheckedMulImm(imm), @@ -770,12 +862,17 @@ impl<'a> MasmOpBuilder<'a> { /// 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(self) { + self.stack.dropn(3); + self.stack.push(Type::U32); + self.stack.push(Type::I1); self.asm.push(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(self) { + self.stack.dropn(3); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32WrappingMadd); } @@ -786,11 +883,15 @@ impl<'a> MasmOpBuilder<'a> { /// /// Traps if `b` is 0. pub fn div_u32(self) { + self.stack.dropn(2); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32CheckedDiv); } /// Same as above, but `b` is provided by the given immediate pub fn div_imm_u32(self, imm: u32) { + self.stack.drop(); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32CheckedDivImm(imm)); } @@ -801,11 +902,15 @@ impl<'a> MasmOpBuilder<'a> { /// /// Traps if `b` is 0. pub fn div_unchecked_u32(self) { + self.stack.dropn(2); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32UncheckedDiv); } /// Same as above, but `b` is provided by the given immediate pub fn div_imm_unchecked_u32(self, imm: u32) { + self.stack.drop(); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32UncheckedDivImm(imm)); } @@ -815,11 +920,15 @@ impl<'a> MasmOpBuilder<'a> { /// /// Traps if `b` is 0. pub fn mod_u32(self) { + self.stack.dropn(2); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32CheckedMod); } /// Same as above, but `b` is provided by the given immediate pub fn mod_imm_u32(self, imm: u32) { + self.stack.drop(); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32CheckedModImm(imm)); } @@ -829,11 +938,15 @@ impl<'a> MasmOpBuilder<'a> { /// /// Traps if `b` is 0. pub fn mod_unchecked_u32(self) { + self.stack.dropn(2); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32UncheckedMod); } /// Same as above, but `b` is provided by the given immediate pub fn mod_imm_unchecked_u32(self, imm: u32) { + self.stack.drop(); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32UncheckedModImm(imm)); } @@ -844,11 +957,15 @@ impl<'a> MasmOpBuilder<'a> { /// /// Traps if `b` is 0. pub fn divmod_u32(self) { + self.stack.dropn(2); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32CheckedDivMod); } /// Same as above, but `b` is provided by the given immediate pub fn divmod_imm_u32(self, imm: u32) { + self.stack.drop(); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32CheckedDivModImm(imm)); } @@ -859,11 +976,15 @@ impl<'a> MasmOpBuilder<'a> { /// /// Traps if `b` is 0. pub fn divmod_unchecked_u32(self) { + self.stack.dropn(2); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32UncheckedDivMod); } /// Same as above, but `b` is provided by the given immediate pub fn divmod_imm_unchecked_u32(self, imm: u32) { + self.stack.drop(); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32UncheckedDivModImm(imm)); } @@ -871,6 +992,8 @@ impl<'a> MasmOpBuilder<'a> { /// /// Traps if either element is not a valid u32 value. pub fn band_u32(self) { + self.stack.dropn(2); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32And); } @@ -878,6 +1001,8 @@ impl<'a> MasmOpBuilder<'a> { /// /// Traps if either element is not a valid u32 value. pub fn bor_u32(self) { + self.stack.dropn(2); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32Or); } @@ -885,6 +1010,8 @@ impl<'a> MasmOpBuilder<'a> { /// /// Traps if either element is not a valid u32 value. pub fn bxor_u32(self) { + self.stack.dropn(2); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32Xor); } @@ -892,6 +1019,8 @@ impl<'a> MasmOpBuilder<'a> { /// /// Traps if the element is not a valid u32 value. pub fn bnot_u32(self) { + self.stack.dropn(2); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32Not); } @@ -900,11 +1029,15 @@ impl<'a> MasmOpBuilder<'a> { /// /// Traps if `a` is not a valid u32, or `b` > 31. pub fn shl_u32(self) { + self.stack.dropn(2); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32CheckedShl); } /// Same as `shl_u32`, but `b` is provided by immediate. pub fn shl_imm_u32(self, imm: u32) { + self.stack.drop(); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32CheckedShlImm(imm)); } @@ -913,11 +1046,15 @@ impl<'a> MasmOpBuilder<'a> { /// /// The result is undefined if `a` is not a valid u32, or `b` is > 31. pub fn shl_unchecked_u32(self) { + self.stack.dropn(2); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32UncheckedShl); } /// Same as `shl_unchecked_u32`, but `b` is provided by immediate. pub fn shl_unchecked_imm_u32(self, imm: u32) { + self.stack.drop(); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32UncheckedShlImm(imm)); } @@ -926,11 +1063,15 @@ impl<'a> MasmOpBuilder<'a> { /// /// Traps if `a` is not a valid u32, or `b` > 31. pub fn shr_u32(self) { + self.stack.dropn(2); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32CheckedShr); } /// Same as `shr_u32`, but `b` is provided by immediate. pub fn shr_imm_u32(self, imm: u32) { + self.stack.drop(); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32CheckedShrImm(imm)); } @@ -939,11 +1080,15 @@ impl<'a> MasmOpBuilder<'a> { /// /// The result is undefined if `a` is not a valid u32, or `b` is > 31. pub fn shr_unchecked_u32(self) { + self.stack.dropn(2); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32UncheckedShr); } /// Same as `shr_unchecked_u32`, but `b` is provided by immediate. pub fn shr_unchecked_imm_u32(self, imm: u32) { + self.stack.drop(); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32UncheckedShrImm(imm)); } @@ -952,11 +1097,15 @@ impl<'a> MasmOpBuilder<'a> { /// /// Traps if `a` is not a valid u32, or `b` > 31 pub fn rotl_u32(self) { + self.stack.dropn(2); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32CheckedRotl); } /// Same as `rotl_u32`, but `b` is provided by immediate. pub fn rotl_imm_u32(self, imm: u32) { + self.stack.drop(); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32CheckedRotlImm(imm)); } @@ -965,11 +1114,15 @@ impl<'a> MasmOpBuilder<'a> { /// /// The result is undefined if `a` is not a valid u32, or `b` is > 31. pub fn rotl_unchecked_u32(self) { + self.stack.dropn(2); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32UncheckedRotl); } /// Same as `rotl_unchecked_u32`, but `b` is provided by immediate. pub fn rotl_unchecked_imm_u32(self, imm: u32) { + self.stack.drop(); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32UncheckedRotlImm(imm)); } @@ -978,11 +1131,15 @@ impl<'a> MasmOpBuilder<'a> { /// /// Traps if `a` is not a valid u32, or `b` > 31 pub fn rotr_u32(self) { + self.stack.dropn(2); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32CheckedRotr); } /// Same as `rotr_u32`, but `b` is provided by immediate. pub fn rotr_imm_u32(self, imm: u32) { + self.stack.drop(); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32CheckedRotrImm(imm)); } @@ -991,11 +1148,15 @@ impl<'a> MasmOpBuilder<'a> { /// /// The result is undefined if `a` is not a valid u32, or `b` is > 31. pub fn rotr_unchecked_u32(self) { + self.stack.dropn(2); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32UncheckedRotr); } /// Same as `rotr_unchecked_u32`, but `b` is provided by immediate. pub fn rotr_unchecked_imm_u32(self, imm: u32) { + self.stack.drop(); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32UncheckedRotrImm(imm)); } @@ -1004,6 +1165,8 @@ impl<'a> MasmOpBuilder<'a> { /// /// Traps if the input value is not a valid u32. pub fn popcnt_u32(self) { + self.stack.dropn(2); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32CheckedPopcnt); } @@ -1012,86 +1175,120 @@ impl<'a> MasmOpBuilder<'a> { /// /// The result is undefined if the input value is not a valid u32. pub fn popcnt_unchecked_u32(self) { + self.stack.dropn(2); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32UncheckedPopcnt); } /// This is the same as `eq`, but also asserts that both operands are valid u32 values. pub fn eq_u32(self) { + self.stack.dropn(2); + self.stack.push(Type::I1); self.asm.push(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(self, imm: u32) { + self.stack.drop(); + self.stack.push(Type::I1); self.asm.push(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(self) { + self.stack.dropn(2); + self.stack.push(Type::I1); self.asm.push(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(self, imm: u32) { + self.stack.drop(); + self.stack.push(Type::I1); self.asm.push(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(self) { + self.stack.dropn(2); + self.stack.push(Type::I1); self.asm.push(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(self) { + self.stack.dropn(2); + self.stack.push(Type::I1); self.asm.push(self.ip, MasmOp::U32UncheckedLt); } /// This is the same as `lte`, but also asserts that both operands are valid u32 values. pub fn lte_u32(self) { + self.stack.dropn(2); + self.stack.push(Type::I1); self.asm.push(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(self) { + self.stack.dropn(2); + self.stack.push(Type::I1); self.asm.push(self.ip, MasmOp::U32UncheckedLte); } /// This is the same as `gt`, but also asserts that both operands are valid u32 values. pub fn gt_u32(self) { + self.stack.dropn(2); + self.stack.push(Type::I1); self.asm.push(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(self) { + self.stack.dropn(2); + self.stack.push(Type::I1); self.asm.push(self.ip, MasmOp::U32UncheckedGt); } /// This is the same as `gte`, but also asserts that both operands are valid u32 values. pub fn gte_u32(self) { + self.stack.dropn(2); + self.stack.push(Type::I1); self.asm.push(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(self) { + self.stack.dropn(2); + self.stack.push(Type::I1); self.asm.push(self.ip, MasmOp::U32UncheckedGte); } /// This is the same as `min`, but also asserts that both operands are valid u32 values. pub fn min_u32(self) { + self.stack.dropn(2); + self.stack.push(Type::U32); self.asm.push(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(self) { + self.stack.dropn(2); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32UncheckedMin); } /// This is the same as `max`, but also asserts that both operands are valid u32 values. pub fn max_u32(self) { + self.stack.dropn(2); + self.stack.push(Type::U32); self.asm.push(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(self) { + self.stack.dropn(2); + self.stack.push(Type::U32); self.asm.push(self.ip, MasmOp::U32UncheckedMax); } } diff --git a/hir/src/asm/stack.rs b/hir/src/asm/stack.rs index 98e900b5..81d75451 100644 --- a/hir/src/asm/stack.rs +++ b/hir/src/asm/stack.rs @@ -50,24 +50,21 @@ pub trait Stack: IndexMut::Element> { /// Returns the value on top of the stack, without consuming it #[inline] - fn peek(&self) -> Self::Element { - self.stack() - .last() - .cloned() - .expect("operand stack is empty") + fn peek(&self) -> Option { + self.stack().last().cloned() } /// Returns the word on top of the stack, without consuming it #[inline] - fn peekw(&self) -> [Self::Element; 4] { + fn peekw(&self) -> Option<[Self::Element; 4]> { let stack = self.stack(); - let end = stack.len().checked_sub(1).expect("operand stack is empty"); - [ + 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 @@ -156,7 +153,7 @@ pub trait Stack: IndexMut::Element> { ); match index { 0 => { - let word = self.peekw(); + let word = self.peekw().expect("operand stack is empty"); self.pushw(word); } n => { From f23d96ff791ff1bcaa02d6fd6dd5d76a99b11ac0 Mon Sep 17 00:00:00 2001 From: Paul Schoenfelder Date: Sat, 9 Sep 2023 03:57:51 -0400 Subject: [PATCH 07/15] feat: support parsing function idents from strings --- hir/src/ident.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) 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") From f73ab07a97bbab3a352cafba5065243c46aae122 Mon Sep 17 00:00:00 2001 From: Paul Schoenfelder Date: Sat, 9 Sep 2023 03:59:41 -0400 Subject: [PATCH 08/15] docs: improve docs related to inline asm --- hir/src/asm/builder.rs | 59 ++++++++++++++++++------------------------ hir/src/instruction.rs | 21 +++++++++++++++ 2 files changed, 46 insertions(+), 34 deletions(-) diff --git a/hir/src/asm/builder.rs b/hir/src/asm/builder.rs index f7a68a29..c90a7814 100644 --- a/hir/src/asm/builder.rs +++ b/hir/src/asm/builder.rs @@ -5,12 +5,27 @@ use crate::{ use super::*; -/// Used to construct an [InlineAssembly] instruction, while checking various safety invariants. +/// 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 { @@ -52,16 +67,19 @@ impl<'f, B: InstBuilder<'f>> MasmBuilder { } } + /// 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 { asm: &mut self.asm, @@ -100,8 +118,11 @@ impl<'f, B: InstBuilder<'f>> MasmBuilder { /// Used to construct a single MASM opcode pub struct MasmOpBuilder<'a> { + /// 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> { @@ -727,17 +748,7 @@ impl<'a> MasmOpBuilder<'a> { /// Performs unsigned addition of the top two elements on the stack, `b` and `a` respectively, which /// are expected to be valid u32 values. /// - /// The specific behavior of the addition depends on the given `overflow` flags: - /// - /// * `Overflow::Unchecked` - the addition is performed using the `add` op for field elements, which may - /// produce a value that is outside of the u32 range, it is the callers responsibility to ensure that the - /// resulting value is in range. - /// * `Overflow::Checked` - the operation will trap if either operand, or the result, is not a valid u32 - /// * `Overflow::Wrapping` - computes the result as `(a + b) mod 2^32`, behavior is undefined if either operand - /// is not a valid u32 - /// * `Overflow::Overflowing` - similar to above, the result is computed as `(a + b) mod 2^32`, however a boolean - /// is also pushed on the stack after the result, which is 1 if the result of `a + b` overflowed, else 0. - /// + /// See the [Overflow] enum for how `overflow` modifies the semantics of this instruction. pub fn add_u32(self, overflow: Overflow) { self.stack.dropn(2); self.stack.push(Type::U32); @@ -783,17 +794,7 @@ impl<'a> MasmOpBuilder<'a> { /// Performs unsigned subtraction of the top two elements on the stack, `b` and `a` respectively, which /// are expected to be valid u32 values. /// - /// The specific behavior of the subtraction depends on the given `overflow` flags: - /// - /// * `Overflow::Unchecked` - the subtraction is performed using the `sub` op for field elements, which may - /// produce a value that is outside of the u32 range, it is the callers responsibility to ensure that the - /// resulting value is in range. - /// * `Overflow::Checked` - the operation will trap if either operand, or the result, is not a valid u32 - /// * `Overflow::Wrapping` - computes the result as `(a - b) mod 2^32`, behavior is undefined if either operand - /// is not a valid u32 - /// * `Overflow::Overflowing` - similar to above, the result is computed as `(a - b) mod 2^32`, however a boolean - /// is also pushed on the stack after the result, which is 1 if the result of `a - b` underflowed, else 0. - /// + /// See the [Overflow] enum for how `overflow` modifies the semantics of this instruction. pub fn sub_u32(self, overflow: Overflow) { self.stack.dropn(2); self.stack.push(Type::U32); @@ -822,17 +823,7 @@ impl<'a> MasmOpBuilder<'a> { /// Performs unsigned multiplication of the top two elements on the stack, `b` and `a` respectively, which /// are expected to be valid u32 values. /// - /// The specific behavior of the subtraction depends on the given `overflow` flags: - /// - /// * `Overflow::Unchecked` - the multiplication is performed using the `mul` op for field elements, which may - /// produce a value that is outside of the u32 range, it is the callers responsibility to ensure that the - /// resulting value is in range. - /// * `Overflow::Checked` - the operation will trap if either operand, or the result, is not a valid u32 - /// * `Overflow::Wrapping` - computes the result as `(a * b) mod 2^32`, behavior is undefined if either operand - /// is not a valid u32 - /// * `Overflow::Overflowing` - similar to above, the result is computed as `(a * b) mod 2^32`, however a boolean - /// is also pushed on the stack after the result, which is 1 if the result of `a * b` underflowed, else 0. - /// + /// See the [Overflow] enum for how `overflow` modifies the semantics of this instruction. pub fn mul_u32(self, overflow: Overflow) { self.stack.dropn(2); self.stack.push(Type::U32); diff --git a/hir/src/instruction.rs b/hir/src/instruction.rs index a4220cfc..618c8b43 100644 --- a/hir/src/instruction.rs +++ b/hir/src/instruction.rs @@ -681,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) } From b52776d90bc9afd84d83d28c6cd03660c4e8e846 Mon Sep 17 00:00:00 2001 From: Paul Schoenfelder Date: Sat, 9 Sep 2023 04:00:55 -0400 Subject: [PATCH 09/15] fix: validate exec/syscall in inline asm --- hir/src/asm/builder.rs | 66 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/hir/src/asm/builder.rs b/hir/src/asm/builder.rs index c90a7814..e7d2230c 100644 --- a/hir/src/asm/builder.rs +++ b/hir/src/asm/builder.rs @@ -82,6 +82,7 @@ impl<'f, B: InstBuilder<'f>> MasmBuilder { /// 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, @@ -118,6 +119,7 @@ impl<'f, B: InstBuilder<'f>> MasmBuilder { /// 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 @@ -423,15 +425,75 @@ impl<'a> MasmOpBuilder<'a> { } /// Executes the named procedure as a regular function. - pub fn exec(self, id: FunctionIdent) { + pub fn exec(mut self, id: FunctionIdent) { + self.execute_call(&id, false); self.asm.push(self.ip, MasmOp::Exec(id)); } /// Executes the named procedure as a syscall. - pub fn syscall(self, id: FunctionIdent) { + pub fn syscall(mut self, id: FunctionIdent) { + self.execute_call(&id, true); self.asm.push(self.ip, MasmOp::Syscall(id)); } + /// 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(&mut self, id: &FunctionIdent, is_syscall: bool) { + let import = self + .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 = self.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); + self.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) => self.stack.push(ty), + TypeRepr::Sparse(_, n) => { + for _ in 0..n.get() { + self.stack.push(Type::Felt); + } + } + TypeRepr::Packed(ty) => { + for _ in 0..ty.size_in_felts() { + self.stack.push(Type::Felt); + } + } + } + } + } + } + } + /// Pops two field elements from the stack, adds them, and places the result on the stack. pub fn add(self) { self.stack.drop(); From 0485488283222d48b6284f9775c49c721922c96f Mon Sep 17 00:00:00 2001 From: Paul Schoenfelder Date: Sat, 9 Sep 2023 04:06:06 -0400 Subject: [PATCH 10/15] fix: reimplement builders for inline asm control flow The previous implementation did not provide us with an opportunity to validate the state of the operand stack after the control flow instruction is constructed. It also failed to account for changes to the operand stack within the control flow instruction body. This commit introduces a new implementation, one for if.true, and one for the loop instructions, which provides a builder API for those instructions. These builders ensure that upon construction, the operand stack is consistent across all control flow edges leaving the instruction, as well as checking other important invariants, such as while.true requiring a boolean on top of the stack when entering/exiting. --- hir/src/asm/builder.rs | 427 +++++++++++++++++++++++++++++++++++++++-- hir/src/asm/mod.rs | 2 +- 2 files changed, 414 insertions(+), 15 deletions(-) diff --git a/hir/src/asm/builder.rs b/hir/src/asm/builder.rs index e7d2230c..76fe2505 100644 --- a/hir/src/asm/builder.rs +++ b/hir/src/asm/builder.rs @@ -404,24 +404,99 @@ impl<'a> MasmOpBuilder<'a> { self.asm.push(self.ip, MasmOp::MemStorewImm(addr)); } - /// Pops a boolean value from the stack, and executes the first block if it is true, - /// otherwise the second block. - pub fn if_true(self, then_blk: MasmBlockId, else_blk: MasmBlockId) { - self.stack.drop(); - self.asm.push(self.ip, MasmOp::If(then_blk, else_blk)) + /// 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, + } } - /// Pops a boolean value from the stack, and executes the given block if it is true, - /// otherwise it is skipped. The given block will continue to execute for as long as - /// the top value on the stack at the end of the block is true. - pub fn while_true(self, body: MasmBlockId) { - self.stack.drop(); - self.asm.push(self.ip, MasmOp::While(body)); + /// 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, + } } - /// Repeatedly executes `body`, `n` times. - pub fn repeat(self, n: u8, body: MasmBlockId) { - self.asm.push(self.ip, MasmOp::Repeat(n, body)); + /// 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. @@ -1345,3 +1420,327 @@ impl<'a> MasmOpBuilder<'a> { self.asm.push(self.ip, MasmOp::U32UncheckedMax); } } + +#[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(n) => { + // No special validation is needed, we're done + 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()); + } +} diff --git a/hir/src/asm/mod.rs b/hir/src/asm/mod.rs index 32833bb1..b65123e6 100644 --- a/hir/src/asm/mod.rs +++ b/hir/src/asm/mod.rs @@ -3,7 +3,7 @@ mod display; mod isa; mod stack; -pub use self::builder::{MasmBuilder, MasmOpBuilder}; +pub use self::builder::*; pub use self::display::DisplayInlineAsm; pub use self::isa::*; pub use self::stack::{OperandStack, Stack}; From f30185c9cd368a6fba27a538470d8b9001dd13ec Mon Sep 17 00:00:00 2001 From: Paul Schoenfelder Date: Sat, 9 Sep 2023 04:10:24 -0400 Subject: [PATCH 11/15] test: implement integration test for inline-asm builders --- hir/src/tests.rs | 116 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/hir/src/tests.rs b/hir/src/tests.rs index da8be9c7..f71cc897 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,118 @@ 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::Felt), + ], + 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(Felt::ZERO); // [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(); // [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(Felt::new(4)); // [word_offset, i, sum, ptr, len] + + // Calculate the address for `array[i / 4]` + lb.ins().dup(3); // [ptr, word_offset, ..] + 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().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().movup(2); // [element_offset, element_offset < 2, ..] + lb.ins().lt_imm(Felt::new(1)); // [element_offset < 1, element_offset < 2, ..] + + // 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(); // [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] + asm_builder.ins().drop(); // [ptr, len, sum] + asm_builder.ins().drop(); // [len, sum] + asm_builder.ins().drop(); // [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(); +} From 79fc777aa6a4c098e026836534eb1d4f7ba84660 Mon Sep 17 00:00:00 2001 From: Paul Schoenfelder Date: Wed, 13 Sep 2023 15:36:36 -0400 Subject: [PATCH 12/15] feat: define type compatibility for operators This implements a function on `Type` called `is_compatible_operand`, whose purpose is to determine if a type is compatible with another type in the context of a binary operator. Type compatibility is defined here in terms of what types can be implicitly coerced to the controlling type of the operator safely. For example, one can safely apply `u32.add` to `u32` and `u8`, where `u32` is the operand whose type "controls" the expected type produced by the operation. Similarly, applying `u32.add` to `u8` and `u8` is safe, albeit with no guarantees about catching overflow of the `u8` range, both operands are `u32` compatible. On the other hand, applying `u32.add` to `u32` and `i32` is not compatible, because `u32.add` does not handle signed integers, not to mention there is no way to handle negative results. This commit also has a couple small tweaks to how a few of the predicate functions are expressed to suppress some clippy warnings. --- hir-type/src/lib.rs | 123 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 99 insertions(+), 24 deletions(-) diff --git a/hir-type/src/lib.rs b/hir-type/src/lib.rs index 896f0ded..a3febf9b 100644 --- a/hir-type/src/lib.rs +++ b/hir-type/src/lib.rs @@ -166,50 +166,125 @@ 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, } } From 2b8d905324fede96aa87c89850ba5654221f9413 Mon Sep 17 00:00:00 2001 From: Paul Schoenfelder Date: Wed, 13 Sep 2023 23:58:20 -0400 Subject: [PATCH 13/15] feat: add assert_matches macro --- hir/src/lib.rs | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/hir/src/lib.rs b/hir/src/lib.rs index a9b00421..2c9dc690 100644 --- a/hir/src/lib.rs +++ b/hir/src/lib.rs @@ -7,6 +7,46 @@ 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; From c7760df331773feec03d12ef418730028566bf4a Mon Sep 17 00:00:00 2001 From: Paul Schoenfelder Date: Wed, 13 Sep 2023 23:59:28 -0400 Subject: [PATCH 14/15] refactor: improve handling of stack effects in inline assembly This commit extracts out the handle of stack affects for the inline assembly builders, and addresses a number of places in which the types were either not validated, or were incorrectly validated/applied. --- hir/src/asm/builder.rs | 1449 ++++++++++++++++++++++++---------------- hir/src/asm/isa.rs | 140 ++++ hir/src/tests.rs | 35 +- 3 files changed, 1034 insertions(+), 590 deletions(-) diff --git a/hir/src/asm/builder.rs b/hir/src/asm/builder.rs index 76fe2505..ce299e6f 100644 --- a/hir/src/asm/builder.rs +++ b/hir/src/asm/builder.rs @@ -129,51 +129,43 @@ pub struct MasmOpBuilder<'a> { } impl<'a> MasmOpBuilder<'a> { /// Pads the stack with four zero elements - pub fn padw(self) { - self.stack.padw(); - self.asm.push(self.ip, MasmOp::Padw); + pub fn padw(mut self) { + self.build(self.ip, MasmOp::Padw); } /// Pushes an element on the stack - pub fn push(self, imm: Felt) { - self.stack.push(Type::Felt); - self.asm.push(self.ip, MasmOp::Push(imm)); + pub fn push(mut self, imm: Felt) { + self.build(self.ip, MasmOp::Push(imm)); } /// Pushes a word on the stack - pub fn pushw(self, word: [Felt; 4]) { - self.stack.padw(); - self.asm.push(self.ip, MasmOp::Pushw(word)); + 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(self, imm: u8) { - self.stack.push(Type::U8); - self.asm.push(self.ip, MasmOp::PushU8(imm)); + 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(self, imm: u16) { - self.stack.push(Type::U16); - self.asm.push(self.ip, MasmOp::PushU16(imm)); + 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(self, imm: u32) { - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::PushU32(imm)); + 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(self) { - self.stack.drop(); - self.asm.push(self.ip, MasmOp::Drop); + 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(self) { - self.stack.dropw(); - self.asm.push(self.ip, MasmOp::Dropw); + 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 @@ -181,9 +173,8 @@ impl<'a> MasmOpBuilder<'a> { /// A `n` of zero, duplicates the element on top of the stack /// /// The valid range for `n` is 0..=15 - pub fn dup(self, n: usize) { - self.stack.dup(n); - self.asm.push(self.ip, MasmOp::Dup(n as u8)); + 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 @@ -191,217 +182,185 @@ impl<'a> MasmOpBuilder<'a> { /// A `n` of zero, duplicates the word on top of the stack /// /// The valid range for `n` is 0..=3 - pub fn dupw(self, n: usize) { - self.stack.dupw(n); - self.asm.push(self.ip, MasmOp::Dupw(n as u8)); + 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(self, n: usize) { - self.stack.swap(n); - self.asm.push(self.ip, MasmOp::Swap(n as u8)); + 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(self, n: usize) { - self.stack.swapw(n); - self.asm.push(self.ip, MasmOp::Swapw(n as u8)); + 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(self, idx: usize) { - self.stack.movup(idx); - self.asm.push(self.ip, MasmOp::Movup(idx as u8)); + 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(self, idx: usize) { - self.stack.movupw(idx); - self.asm.push(self.ip, MasmOp::Movupw(idx as u8)); + 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(self, idx: usize) { - self.stack.movdn(idx); - self.asm.push(self.ip, MasmOp::Movdn(idx as u8)); + 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(self, idx: usize) { - self.stack.movdnw(idx); - self.asm.push(self.ip, MasmOp::Movdnw(idx as u8)); + 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(self) { - self.stack.drop(); - self.asm.push(self.ip, MasmOp::Cswap); + 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(self) { - self.stack.drop(); - self.asm.push(self.ip, MasmOp::Cswapw); + 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(self) { - self.stack.dropn(2); - self.asm.push(self.ip, MasmOp::Cdrop); + 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(self) { - self.stack.dropn(5); - self.asm.push(self.ip, MasmOp::Cdropw); + 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(self) { - self.stack.drop(); - self.asm.push(self.ip, MasmOp::Assert); + 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(self) { - self.stack.drop(); - self.asm.push(self.ip, MasmOp::Assertz); + 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(self) { - self.stack.dropn(2); - self.asm.push(self.ip, MasmOp::AssertEq); + 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(self) { - self.stack.dropn(8); - self.asm.push(self.ip, MasmOp::AssertEq); + 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(self) { - self.stack.drop(); - self.stack.push(Type::Felt); - self.asm.push(self.ip, MasmOp::MemLoad); + 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(self, addr: u32) { - self.stack.push(Type::Felt); - self.asm.push(self.ip, MasmOp::MemLoadImm(addr)); + 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(self) { - self.stack.drop(); - self.stack.push(Type::Felt); - self.asm.push(self.ip, MasmOp::MemLoadOffset); + 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(self, addr: u32, offset: u8) { + 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.stack.push(Type::Felt); - self.asm - .push(self.ip, MasmOp::MemLoadOffsetImm(addr, 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(self) { - self.stack.drop(); - self.stack.padw(); - self.asm.push(self.ip, MasmOp::MemLoadw); + 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(self, addr: u32) { - self.stack.padw(); - self.asm.push(self.ip, MasmOp::MemLoadwImm(addr)); + 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(self) { - self.stack.dropn(2); - self.asm.push(self.ip, MasmOp::MemStore); + 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(self, addr: u32) { - self.stack.drop(); - self.asm.push(self.ip, MasmOp::MemStoreImm(addr)); + 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(self) { - self.stack.drop(); - self.asm.push(self.ip, MasmOp::MemStoreOffset); + 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(self, addr: u32, offset: u8) { + 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.stack.drop(); - self.asm - .push(self.ip, MasmOp::MemStoreOffsetImm(addr, 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(self) { - self.stack.dropn(5); - self.asm.push(self.ip, MasmOp::MemStorew); + 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(self, addr: u32) { - self.stack.dropw(); - self.asm.push(self.ip, MasmOp::MemStorewImm(addr)); + pub fn storew_imm(mut self, addr: u32) { + self.build(self.ip, MasmOp::MemStorewImm(addr)); } /// Begins construction of a `if.true` statement. @@ -501,507 +460,352 @@ impl<'a> MasmOpBuilder<'a> { /// Executes the named procedure as a regular function. pub fn exec(mut self, id: FunctionIdent) { - self.execute_call(&id, false); - self.asm.push(self.ip, MasmOp::Exec(id)); + self.build(self.ip, MasmOp::Exec(id)); } /// Executes the named procedure as a syscall. pub fn syscall(mut self, id: FunctionIdent) { - self.execute_call(&id, true); - self.asm.push(self.ip, MasmOp::Syscall(id)); - } - - /// 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(&mut self, id: &FunctionIdent, is_syscall: bool) { - let import = self - .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 = self.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); - self.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) => self.stack.push(ty), - TypeRepr::Sparse(_, n) => { - for _ in 0..n.get() { - self.stack.push(Type::Felt); - } - } - TypeRepr::Packed(ty) => { - for _ in 0..ty.size_in_felts() { - self.stack.push(Type::Felt); - } - } - } - } - } - } + 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(self) { - self.stack.drop(); - self.asm.push(self.ip, MasmOp::Add); + 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(self, imm: Felt) { - self.asm.push(self.ip, MasmOp::AddImm(imm)); + 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(self) { - self.stack.drop(); - self.asm.push(self.ip, MasmOp::Sub); + 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(self, imm: Felt) { - self.asm.push(self.ip, MasmOp::SubImm(imm)); + 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(self) { - self.stack.drop(); - self.asm.push(self.ip, MasmOp::Mul); + 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(self, imm: Felt) { - self.asm.push(self.ip, MasmOp::MulImm(imm)); + 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(self) { - self.stack.drop(); - self.asm.push(self.ip, MasmOp::Div); + 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(self, imm: Felt) { - self.asm.push(self.ip, MasmOp::DivImm(imm)); + 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(self) { - self.asm.push(self.ip, MasmOp::Neg); + 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(self) { - self.asm.push(self.ip, MasmOp::Inv); + pub fn inv(mut self) { + self.build(self.ip, MasmOp::Inv); } /// Increments the field element on top of the stack - pub fn incr(self) { - self.asm.push(self.ip, MasmOp::Incr); + 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(self) { - self.asm.push(self.ip, MasmOp::Pow2); + 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(self) { - self.stack.drop(); - self.asm.push(self.ip, MasmOp::Exp); + 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(self, exponent: u8) { - self.asm.push(self.ip, MasmOp::ExpImm(exponent)); + 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(self) { - assert_eq!( - self.stack.peek(), - Some(Type::I1), - "expected a boolean operand on the stack" - ); - self.asm.push(self.ip, MasmOp::Not); + 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(self) { - let rhs = self.stack.pop().expect("operand stack is empty"); - let lhs = self.stack.peek().expect("operand stack is empty"); - assert_eq!(lhs, rhs, "expected both operands to be the same type"); - assert_eq!(lhs, Type::I1, "expected boolean operands"); - self.asm.push(self.ip, MasmOp::And); + 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(self, imm: bool) { - assert_eq!( - self.stack.peek(), - Some(Type::I1), - "expected a boolean operand on the stack" - ); - self.asm.push(self.ip, MasmOp::AndImm(imm)); + 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(self) { - let rhs = self.stack.pop().expect("operand stack is empty"); - let lhs = self.stack.peek().expect("operand stack is empty"); - assert_eq!(lhs, rhs, "expected both operands to be the same type"); - assert_eq!(lhs, Type::I1, "expected boolean operands"); - self.asm.push(self.ip, MasmOp::Or); + 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(self, imm: bool) { - assert_eq!( - self.stack.peek(), - Some(Type::I1), - "expected a boolean operand on the stack" - ); - self.asm.push(self.ip, MasmOp::OrImm(imm)); + 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(self) { - let rhs = self.stack.pop().expect("operand stack is empty"); - let lhs = self.stack.peek().expect("operand stack is empty"); - assert_eq!(lhs, rhs, "expected both operands to be the same type"); - assert_eq!(lhs, Type::I1, "expected boolean operands"); - self.asm.push(self.ip, MasmOp::Xor); + 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(self, imm: bool) { - assert_eq!( - self.stack.peek(), - Some(Type::I1), - "expected a boolean operand on the stack" - ); - self.asm.push(self.ip, MasmOp::XorImm(imm)); + 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(self) { - self.stack.dropn(2); - self.stack.push(Type::I1); - self.asm.push(self.ip, MasmOp::Eq); + 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(self, imm: Felt) { - self.stack.drop(); - self.stack.push(Type::I1); - self.asm.push(self.ip, MasmOp::EqImm(imm)); + 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(self) { - self.stack.dropw(); - self.stack.dropw(); - self.stack.push(Type::I1); - self.asm.push(self.ip, MasmOp::Eqw); + 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(self) { - self.stack.dropn(2); - self.stack.push(Type::I1); - self.asm.push(self.ip, MasmOp::Neq); + 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(self, imm: Felt) { - self.stack.drop(); - self.stack.push(Type::I1); - self.asm.push(self.ip, MasmOp::NeqImm(imm)); + 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(self) { - self.stack.dropn(2); - self.stack.push(Type::I1); - self.asm.push(self.ip, MasmOp::Gt); + 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(self, imm: Felt) { - self.stack.drop(); - self.stack.push(Type::I1); - self.asm.push(self.ip, MasmOp::GtImm(imm)); + 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(self) { - self.stack.dropn(2); - self.stack.push(Type::I1); - self.asm.push(self.ip, MasmOp::Gte); + 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(self, imm: Felt) { - self.stack.drop(); - self.stack.push(Type::I1); - self.asm.push(self.ip, MasmOp::GteImm(imm)); + 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(self) { - self.stack.dropn(2); - self.stack.push(Type::I1); - self.asm.push(self.ip, MasmOp::Lt); + 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(self, imm: Felt) { - self.stack.drop(); - self.stack.push(Type::I1); - self.asm.push(self.ip, MasmOp::LtImm(imm)); + 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(self) { - self.stack.dropn(2); - self.stack.push(Type::I1); - self.asm.push(self.ip, MasmOp::Lte); + 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(self, imm: Felt) { - self.stack.drop(); - self.stack.push(Type::I1); - self.asm.push(self.ip, MasmOp::LteImm(imm)); + 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(self) { - self.stack.drop(); - self.stack.push(Type::I1); - self.asm.push(self.ip, MasmOp::IsOdd); + 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(self) { - self.stack.push(Type::Felt); - self.asm.push(self.ip, MasmOp::Clk); + 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(self) { - self.stack.push(Type::I1); - self.asm.push(self.ip, MasmOp::U32Test); + 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(self) { - self.stack.push(Type::I1); - self.asm.push(self.ip, MasmOp::U32Testw); + 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(self) { - self.asm.push(self.ip, MasmOp::U32Assert); + 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(self) { - self.asm.push(self.ip, MasmOp::U32Assert2); + 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(self) { - self.asm.push(self.ip, MasmOp::U32Assertw); + 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(self) { - self.stack.drop(); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32Cast); + 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(self) { - self.stack.drop(); - self.stack.push(Type::U32); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32Split); + 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(self, overflow: Overflow) { - self.stack.dropn(2); - self.stack.push(Type::U32); + 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.asm.push(self.ip, op); + self.build(self.ip, op); } /// Same as above, but `a` is provided by the given immediate. - pub fn add_imm_u32(self, imm: u32, overflow: Overflow) { - self.stack.drop(); - self.stack.push(Type::U32); + 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.asm.push(self.ip, op); + 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(self) { - self.stack.dropn(3); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32OverflowingAdd3); + 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(self) { - self.stack.dropn(3); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32WrappingAdd3); + 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(self, overflow: Overflow) { - self.stack.dropn(2); - self.stack.push(Type::U32); + 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.asm.push(self.ip, op); + self.build(self.ip, op); } /// Same as above, but `a` is provided by the given immediate. - pub fn sub_imm_u32(self, imm: u32, overflow: Overflow) { - self.stack.drop(); - self.stack.push(Type::U32); + 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.asm.push(self.ip, op); + 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(self, overflow: Overflow) { - self.stack.dropn(2); - self.stack.push(Type::U32); + 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.asm.push(self.ip, op); + self.build(self.ip, op); } /// Same as above, but `a` is provided by the given immediate. - pub fn mul_imm_u32(self, imm: u32, overflow: Overflow) { - self.stack.drop(); - self.stack.push(Type::U32); + 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.asm.push(self.ip, op); + 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(self) { - self.stack.dropn(3); - self.stack.push(Type::U32); - self.stack.push(Type::I1); - self.asm.push(self.ip, MasmOp::U32OverflowingMadd); + 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(self) { - self.stack.dropn(3); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32WrappingMadd); + 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 @@ -1010,17 +814,13 @@ impl<'a> MasmOpBuilder<'a> { /// 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(self) { - self.stack.dropn(2); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32CheckedDiv); + 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(self, imm: u32) { - self.stack.drop(); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32CheckedDivImm(imm)); + 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 @@ -1029,17 +829,13 @@ impl<'a> MasmOpBuilder<'a> { /// 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(self) { - self.stack.dropn(2); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32UncheckedDiv); + 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(self, imm: u32) { - self.stack.drop(); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32UncheckedDivImm(imm)); + 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`. @@ -1047,17 +843,13 @@ impl<'a> MasmOpBuilder<'a> { /// 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(self) { - self.stack.dropn(2); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32CheckedMod); + 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(self, imm: u32) { - self.stack.drop(); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32CheckedModImm(imm)); + 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`. @@ -1065,17 +857,13 @@ impl<'a> MasmOpBuilder<'a> { /// 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(self) { - self.stack.dropn(2); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32UncheckedMod); + 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(self, imm: u32) { - self.stack.drop(); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32UncheckedModImm(imm)); + 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`, @@ -1084,17 +872,13 @@ impl<'a> MasmOpBuilder<'a> { /// 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(self) { - self.stack.dropn(2); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32CheckedDivMod); + 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(self, imm: u32) { - self.stack.drop(); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32CheckedDivModImm(imm)); + 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`, @@ -1103,321 +887,247 @@ impl<'a> MasmOpBuilder<'a> { /// 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(self) { - self.stack.dropn(2); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32UncheckedDivMod); + 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(self, imm: u32) { - self.stack.drop(); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32UncheckedDivModImm(imm)); + 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(self) { - self.stack.dropn(2); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32And); + 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(self) { - self.stack.dropn(2); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32Or); + 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(self) { - self.stack.dropn(2); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32Xor); + 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(self) { - self.stack.dropn(2); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32Not); + 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(self) { - self.stack.dropn(2); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32CheckedShl); + 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(self, imm: u32) { - self.stack.drop(); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32CheckedShlImm(imm)); + 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(self) { - self.stack.dropn(2); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32UncheckedShl); + 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(self, imm: u32) { - self.stack.drop(); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32UncheckedShlImm(imm)); + 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(self) { - self.stack.dropn(2); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32CheckedShr); + 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(self, imm: u32) { - self.stack.drop(); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32CheckedShrImm(imm)); + 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(self) { - self.stack.dropn(2); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32UncheckedShr); + 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(self, imm: u32) { - self.stack.drop(); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32UncheckedShrImm(imm)); + 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(self) { - self.stack.dropn(2); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32CheckedRotl); + 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(self, imm: u32) { - self.stack.drop(); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32CheckedRotlImm(imm)); + 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(self) { - self.stack.dropn(2); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32UncheckedRotl); + 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(self, imm: u32) { - self.stack.drop(); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32UncheckedRotlImm(imm)); + 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(self) { - self.stack.dropn(2); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32CheckedRotr); + 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(self, imm: u32) { - self.stack.drop(); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32CheckedRotrImm(imm)); + 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(self) { - self.stack.dropn(2); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32UncheckedRotr); + 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(self, imm: u32) { - self.stack.drop(); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32UncheckedRotrImm(imm)); + 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(self) { - self.stack.dropn(2); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32CheckedPopcnt); + 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(self) { - self.stack.dropn(2); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32UncheckedPopcnt); + 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(self) { - self.stack.dropn(2); - self.stack.push(Type::I1); - self.asm.push(self.ip, MasmOp::U32Eq); + 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(self, imm: u32) { - self.stack.drop(); - self.stack.push(Type::I1); - self.asm.push(self.ip, MasmOp::U32EqImm(imm)); + 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(self) { - self.stack.dropn(2); - self.stack.push(Type::I1); - self.asm.push(self.ip, MasmOp::U32Neq); + 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(self, imm: u32) { - self.stack.drop(); - self.stack.push(Type::I1); - self.asm.push(self.ip, MasmOp::U32NeqImm(imm)); + 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(self) { - self.stack.dropn(2); - self.stack.push(Type::I1); - self.asm.push(self.ip, MasmOp::U32CheckedLt); + 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(self) { - self.stack.dropn(2); - self.stack.push(Type::I1); - self.asm.push(self.ip, MasmOp::U32UncheckedLt); + 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(self) { - self.stack.dropn(2); - self.stack.push(Type::I1); - self.asm.push(self.ip, MasmOp::U32CheckedLte); + 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(self) { - self.stack.dropn(2); - self.stack.push(Type::I1); - self.asm.push(self.ip, MasmOp::U32UncheckedLte); + 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(self) { - self.stack.dropn(2); - self.stack.push(Type::I1); - self.asm.push(self.ip, MasmOp::U32CheckedGt); + 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(self) { - self.stack.dropn(2); - self.stack.push(Type::I1); - self.asm.push(self.ip, MasmOp::U32UncheckedGt); + 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(self) { - self.stack.dropn(2); - self.stack.push(Type::I1); - self.asm.push(self.ip, MasmOp::U32CheckedGte); + 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(self) { - self.stack.dropn(2); - self.stack.push(Type::I1); - self.asm.push(self.ip, MasmOp::U32UncheckedGte); + 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(self) { - self.stack.dropn(2); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32CheckedMin); + 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(self) { - self.stack.dropn(2); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32UncheckedMin); + 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(self) { - self.stack.dropn(2); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32CheckedMax); + 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(self) { - self.stack.dropn(2); - self.stack.push(Type::U32); - self.asm.push(self.ip, MasmOp::U32UncheckedMax); + 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); } } @@ -1744,3 +1454,590 @@ impl<'f> LoopBuilder<'f> { 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/isa.rs b/hir/src/asm/isa.rs index 3f310704..0c95f975 100644 --- a/hir/src/asm/isa.rs +++ b/hir/src/asm/isa.rs @@ -1,3 +1,5 @@ +use std::fmt; + use cranelift_entity::entity_impl; use crate::{Felt, FunctionIdent, LocalId}; @@ -575,3 +577,141 @@ pub enum MasmOp { /// 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/tests.rs b/hir/src/tests.rs index f71cc897..a28ead8c 100644 --- a/hir/src/tests.rs +++ b/hir/src/tests.rs @@ -162,7 +162,7 @@ fn inline_asm_builders() { let sig = Signature { params: vec![ AbiParam::new(Type::Ptr(Box::new(Type::Felt))), - AbiParam::new(Type::Felt), + AbiParam::new(Type::U32), ], results: vec![AbiParam::new(Type::Felt)], cc: CallConv::SystemV, @@ -182,10 +182,10 @@ fn inline_asm_builders() { .ins() .inline_asm(&[ptr, len], [Type::Felt], SourceSpan::UNKNOWN); asm_builder.ins().push(Felt::ZERO); // [sum, ptr, len] - asm_builder.ins().push(Felt::ZERO); // [i, 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(); // [i < len, i, sum, ptr, len] + asm_builder.ins().lt_u32(); // [i < len, i, sum, ptr, len] // Now, build the loop body // @@ -194,10 +194,11 @@ fn inline_asm_builders() { // Calculate `i / 4` lb.ins().dup(0); // [i, i, sum, ptr, len] - lb.ins().div_imm(Felt::new(4)); // [word_offset, 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` @@ -206,12 +207,18 @@ fn inline_asm_builders() { // 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().movup(2); // [element_offset, element_offset < 2, ..] - lb.ins().lt_imm(Felt::new(1)); // [element_offset < 1, element_offset < 2, ..] + 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] @@ -235,7 +242,7 @@ fn inline_asm_builders() { // 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(); // [i < len, 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(); @@ -245,9 +252,9 @@ fn inline_asm_builders() { // 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] - asm_builder.ins().drop(); // [ptr, len, sum] - asm_builder.ins().drop(); // [len, sum] - asm_builder.ins().drop(); // [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(); From 15a104c963dd6844e1575e7ad60a34f5064dae99 Mon Sep 17 00:00:00 2001 From: Paul Schoenfelder Date: Thu, 14 Sep 2023 00:01:42 -0400 Subject: [PATCH 15/15] fix: validate repeat loops in inline assembly --- hir/src/asm/builder.rs | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/hir/src/asm/builder.rs b/hir/src/asm/builder.rs index ce299e6f..85f7ebeb 100644 --- a/hir/src/asm/builder.rs +++ b/hir/src/asm/builder.rs @@ -1442,8 +1442,38 @@ impl<'f> LoopBuilder<'f> { 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) => { - // No special validation is needed, we're done + // 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)); } }