diff --git a/hir/src/asm/builder.rs b/hir/src/asm/builder.rs index 76fe25053..ce299e6f4 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 3f310704c..0c95f9759 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 f71cc8976..a28ead8c1 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();