From 9fe6acef9b10a18abb853b77c129810804c45d01 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Mon, 7 Mar 2022 14:41:53 -0800 Subject: [PATCH 01/26] feat: add DivResultU64 decorator --- core/src/operations/advice.rs | 4 ++ processor/src/operations/decorators/mod.rs | 48 ++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/core/src/operations/advice.rs b/core/src/operations/advice.rs index f49d7314cc..1780809387 100644 --- a/core/src/operations/advice.rs +++ b/core/src/operations/advice.rs @@ -9,12 +9,16 @@ pub enum AdviceInjector { /// - index of the node, 1 element /// - root of the tree, 4 elements MerkleNode, + + /// TODO: add comments + DivResultU64, } impl fmt::Display for AdviceInjector { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::MerkleNode => write!(f, "merkle_node"), + Self::DivResultU64 => write!(f, "div_result_u64"), } } } diff --git a/processor/src/operations/decorators/mod.rs b/processor/src/operations/decorators/mod.rs index 79a29191c0..b70e375bcf 100644 --- a/processor/src/operations/decorators/mod.rs +++ b/processor/src/operations/decorators/mod.rs @@ -104,6 +104,7 @@ impl Process { pub fn op_advice(&mut self, injector: AdviceInjector) -> Result<(), ExecutionError> { match injector { AdviceInjector::MerkleNode => self.inject_merkle_node(), + AdviceInjector::DivResultU64 => self.inject_div_result_u64(), } } @@ -148,6 +149,53 @@ impl Process { Ok(()) } + + /// Injects the result of u64 division (both the quotient and the remainder) at the head of + /// the advice tape. The stack is expected to be arranged as follows (from the top): + /// - divisor split into two 32-bit elements + /// - dividend split into two 32-bit elements + /// + /// The result is injected into the advice tape as follows: first the remainder is injected, + /// then the quotient is injected. This guarantees that when reading values from the advice + /// tape, first the quotient will be read, and then the remainder. + /// + /// # Errors + /// Returns an error if the divisor is ZERO. + fn inject_div_result_u64(&mut self) -> Result<(), ExecutionError> { + let divisor_hi = self.stack.get(0).as_int(); + let divisor_lo = self.stack.get(1).as_int(); + let divisor = (divisor_hi << 32) + divisor_lo; + + if divisor == 0 { + return Err(ExecutionError::DivideByZero(self.system.clk())); + } + + let dividend_hi = self.stack.get(2).as_int(); + let dividend_lo = self.stack.get(3).as_int(); + let dividend = (dividend_hi << 32) + dividend_lo; + + let quotient = dividend / divisor; + let remainder = dividend - quotient * divisor; + + let (q_hi, q_lo) = u64_to_u32_elements(quotient); + let (r_hi, r_lo) = u64_to_u32_elements(remainder); + + self.advice.write_tape(r_hi); + self.advice.write_tape(r_lo); + self.advice.write_tape(q_hi); + self.advice.write_tape(q_lo); + + Ok(()) + } +} + +// HELPER FUNCTIONS +// ================================================================================================ + +fn u64_to_u32_elements(value: u64) -> (Felt, Felt) { + let hi = Felt::new(value >> 32); + let lo = Felt::new((value as u32) as u64); + (hi, lo) } // TESTS From 8b88f7dc95cfbcfa7815e43e456928f0b409724c Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Mon, 7 Mar 2022 21:26:03 -0800 Subject: [PATCH 02/26] feat: implement adv.u64div parser --- assembly/src/parsers/io_ops/mod.rs | 16 +++++++++++- assembly/src/parsers/mod.rs | 6 ++++- processor/src/tests/io_ops/adv_ops.rs | 35 +++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/assembly/src/parsers/io_ops/mod.rs b/assembly/src/parsers/io_ops/mod.rs index 7ad17d5c34..dab25361d8 100644 --- a/assembly/src/parsers/io_ops/mod.rs +++ b/assembly/src/parsers/io_ops/mod.rs @@ -1,6 +1,6 @@ use super::{ super::validate_operation, parse_decimal_param, parse_element_param, parse_hex_param, - parse_int_param, push_value, AssemblyError, Felt, Operation, Token, + parse_int_param, push_value, AdviceInjector, AssemblyError, Felt, Operation, Token, }; mod adv_ops; @@ -244,6 +244,20 @@ pub fn parse_storew( } } +// ADVICE INJECTORS +// ================================================================================================ + +/// TODO: add doc comments +pub fn parse_adv_inject(span_ops: &mut Vec, op: &Token) -> Result<(), AssemblyError> { + validate_operation!(op, "adv.u64div"); + match op.parts()[1] { + "u64div" => span_ops.push(Operation::Advice(AdviceInjector::DivResultU64)), + _ => return Err(AssemblyError::invalid_op(op)), + } + + Ok(()) +} + // TESTS // ================================================================================================ diff --git a/assembly/src/parsers/mod.rs b/assembly/src/parsers/mod.rs index 6c75db6d16..9251ca2cfb 100644 --- a/assembly/src/parsers/mod.rs +++ b/assembly/src/parsers/mod.rs @@ -1,6 +1,8 @@ use super::{AssemblyContext, AssemblyError, Token, TokenStream}; pub use blocks::{combine_blocks, parse_code_blocks}; -use vm_core::{program::blocks::CodeBlock, Felt, FieldElement, Operation, StarkField}; +use vm_core::{ + program::blocks::CodeBlock, AdviceInjector, Felt, FieldElement, Operation, StarkField, +}; mod blocks; mod crypto_ops; @@ -103,6 +105,8 @@ fn parse_op_token( "loadw" => io_ops::parse_loadw(span_ops, op, num_proc_locals), "storew" => io_ops::parse_storew(span_ops, op, num_proc_locals), + "adv" => io_ops::parse_adv_inject(span_ops, op), + // ----- cryptographic operations --------------------------------------------------------- "rphash" => crypto_ops::parse_rphash(span_ops, op), "rpperm" => crypto_ops::parse_rpperm(span_ops, op), diff --git a/processor/src/tests/io_ops/adv_ops.rs b/processor/src/tests/io_ops/adv_ops.rs index e86f1e803d..5b6aaede97 100644 --- a/processor/src/tests/io_ops/adv_ops.rs +++ b/processor/src/tests/io_ops/adv_ops.rs @@ -1,4 +1,5 @@ use super::{compile, execute, push_to_stack, test_execution_failure, ProgramInputs}; +use rand_utils::rand_value; // PUSHING VALUES ONTO THE STACK (PUSH) // ================================================================================================ @@ -54,3 +55,37 @@ fn loadw_adv_invalid() { // attempting to read from empty advice tape should throw an error test_execution_failure("loadw.adv", &[0, 0, 0, 0], "EmptyAdviceTape"); } + +// ADVICE INJECTORS +// ================================================================================================ + +#[test] +fn adv_inject_u64div() { + let script = compile("begin adv.u64div push.adv.4 end"); + + // get two random 64-bit integers and split them into 32-bit limbs + let a = rand_value::(); + let a_hi = a >> 32; + let a_lo = a as u32 as u64; + + let b = rand_value::(); + let b_hi = b >> 32; + let b_lo = b as u32 as u64; + + // compute expected quotient + let q = a / b; + let q_hi = q >> 32; + let q_lo = q as u32 as u64; + + // compute expected remainder + let r = a % b; + let r_hi = r >> 32; + let r_lo = r as u32 as u64; + + // inject a/b into the advice tape and then read these values from the tape + let inputs = ProgramInputs::new(&[a_lo, a_hi, b_lo, b_hi], &[], vec![]).unwrap(); + let trace = execute(&script, &inputs).unwrap(); + + let expected = push_to_stack(&[a_lo, a_hi, b_lo, b_hi, q_lo, q_hi, r_lo, r_hi]); + assert_eq!(expected, trace.last_stack_state()); +} From 1d938e37907f81023028933689a1ef6e7a0f0381 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Tue, 8 Mar 2022 00:51:19 -0800 Subject: [PATCH 03/26] feat: implement u64 division in stdlib --- processor/src/tests/stdlib/u64_mod.rs | 25 ++++++++++++ stdlib/asm/math/u64.masm | 54 +++++++++++++++++++++++++- stdlib/src/asm.rs | 55 ++++++++++++++++++++++++++- 3 files changed, 132 insertions(+), 2 deletions(-) diff --git a/processor/src/tests/stdlib/u64_mod.rs b/processor/src/tests/stdlib/u64_mod.rs index ca8d0594bf..5b1da1211b 100644 --- a/processor/src/tests/stdlib/u64_mod.rs +++ b/processor/src/tests/stdlib/u64_mod.rs @@ -43,6 +43,31 @@ fn mul_unsafe() { test_script_execution(&script, &[a0, a1, b0, b1], &[c1, c0]); } +#[test] +fn div_unsafe() { + let a: u64 = rand_value(); + let b: u64 = rand_value(); + let c = a / b; + + let script = compile( + " + use.std::math::u64 + begin + exec.u64::div_unsafe + end", + ); + + let (a1, a0) = split_u64(a); + let (b1, b0) = split_u64(b); + let (c1, c0) = split_u64(c); + + test_script_execution(&script, &[a0, a1, b0, b1], &[c1, c0]); + + let d = a / b0; + let (d1, d0) = split_u64(d); + test_script_execution(&script, &[a0, a1, b0, 0], &[d1, d0]); +} + // HELPER FUNCTIONS // ================================================================================================ diff --git a/stdlib/asm/math/u64.masm b/stdlib/asm/math/u64.masm index 47b06dbb5e..a65c186358 100644 --- a/stdlib/asm/math/u64.masm +++ b/stdlib/asm/math/u64.masm @@ -28,4 +28,56 @@ export.mul_unsafe movup.3 u32madd drop -end \ No newline at end of file +end + +# ===== DIVISION ================================================================================ # + +# Performs division of two unsigned 64 bit integers discarding the remainder. # +# The input values are assumed to be represented using 32 bit limbs, but this is not checked. # +# Stack transition looks as follows: # +# [b_hi, b_lo, a_hi, a_lo, ...] -> [c_hi, c_lo, ...], where c = a // b # +export.div_unsafe + adv.u64div # inject the quotient and the remainder into the advice tape # + + push.adv.1 # read the values from the advice tape and make sure they are u32's # + u32assert # TODO: this can be optimized once we have u32assert2 instruction # + push.adv.1 + u32assert + push.adv.1 + u32assert + push.adv.1 + u32assert + + dup.5 # multiply quotient by the divisor; this also consumes the divisor # + dup.4 + u32mul.unsafe + dup.6 + dup.6 + u32madd + eq.0 + assert + movup.7 + dup.5 + u32madd + eq.0 + assert + movup.6 + dup.5 + mul + eq.0 + assert + swap + + movup.3 # add remainder to the previous result; this also consumes the remainder # + u32add.unsafe + movup.3 + movup.3 + u32addc + eq.0 + assert + + movup.4 # make sure the result we got is equal to the dividend # + assert.eq + movup.3 + assert.eq # quotient remains on the stack # +end diff --git a/stdlib/src/asm.rs b/stdlib/src/asm.rs index 61320c75df..e7119967fd 100644 --- a/stdlib/src/asm.rs +++ b/stdlib/src/asm.rs @@ -239,5 +239,58 @@ export.mul_unsafe movup.3 u32madd drop -end"), +end + +# ===== DIVISION ================================================================================ # + +# Performs division of two unsigned 64 bit integers discarding the remainder. # +# The input values are assumed to be represented using 32 bit limbs, but this is not checked. # +# Stack transition looks as follows: # +# [b_hi, b_lo, a_hi, a_lo, ...] -> [c_hi, c_lo, ...], where c = a // b # +export.div_unsafe + adv.u64div # inject the quotient and the remainder into the advice tape # + + push.adv.1 # read the values from the advice tape and make sure they are u32's # + u32assert # TODO: this can be optimized once we have u32assert2 instruction # + push.adv.1 + u32assert + push.adv.1 + u32assert + push.adv.1 + u32assert + + dup.5 # multiply quotient by the divisor; this also consumes the divisor # + dup.4 + u32mul.unsafe + dup.6 + dup.6 + u32madd + eq.0 + assert + movup.7 + dup.5 + u32madd + eq.0 + assert + movup.6 + dup.5 + mul + eq.0 + assert + swap + + movup.3 # add remainder to the previous result; this also consumes the remainder # + u32add.unsafe + movup.3 + movup.3 + u32addc + eq.0 + assert + + movup.4 # make sure the result we got is equal to the dividend # + assert.eq + movup.3 + assert.eq # quotient remains on the stack # +end +"), ]; From 205778d8813676ea2ad5b5887cdb1ccd8bdfbed6 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Tue, 8 Mar 2022 01:09:40 -0800 Subject: [PATCH 04/26] doc: add comments for u64div advice injector --- assembly/src/parsers/io_ops/mod.rs | 9 ++++++++- core/src/operations/advice.rs | 8 +++++++- stdlib/asm/math/u64.masm | 4 ++-- stdlib/src/asm.rs | 4 ++-- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/assembly/src/parsers/io_ops/mod.rs b/assembly/src/parsers/io_ops/mod.rs index dab25361d8..eeb3adb65e 100644 --- a/assembly/src/parsers/io_ops/mod.rs +++ b/assembly/src/parsers/io_ops/mod.rs @@ -247,7 +247,14 @@ pub fn parse_storew( // ADVICE INJECTORS // ================================================================================================ -/// TODO: add doc comments +/// Appends the appropriate advice injector operation to `span_ops`. +/// +/// Advice injector operations insert one or more values at the head of the advice tape, but do +/// not modify the VM state and do not advance the clock cycles. Currently, the following advice +/// injectors can be invoked explicitly: +/// - adv.u64div: this operation interprets four elements at the top of the stack as two 64-bit +/// values (represented by 32-bit limbs), divides one value by another, and injects the quotient +/// and the remainder into the advice tape. pub fn parse_adv_inject(span_ops: &mut Vec, op: &Token) -> Result<(), AssemblyError> { validate_operation!(op, "adv.u64div"); match op.parts()[1] { diff --git a/core/src/operations/advice.rs b/core/src/operations/advice.rs index 1780809387..6f514dc7bb 100644 --- a/core/src/operations/advice.rs +++ b/core/src/operations/advice.rs @@ -10,7 +10,13 @@ pub enum AdviceInjector { /// - root of the tree, 4 elements MerkleNode, - /// TODO: add comments + /// Injects the result of u64 division (both the quotient and the remainder) at the head of + /// the advice tape. The stack is expected to be arranged as follows (from the top): + /// - divisor split into two 32-bit elements + /// - dividend split into two 32-bit elements + /// + /// The result is injected into the advice tape as follows: first the remainder is injected, + /// then the quotient is injected. DivResultU64, } diff --git a/stdlib/asm/math/u64.masm b/stdlib/asm/math/u64.masm index a65c186358..6ff375f04e 100644 --- a/stdlib/asm/math/u64.masm +++ b/stdlib/asm/math/u64.masm @@ -66,9 +66,9 @@ export.div_unsafe mul eq.0 assert - swap - movup.3 # add remainder to the previous result; this also consumes the remainder # + swap # add remainder to the previous result; this also consumes the remainder # + movup.3 u32add.unsafe movup.3 movup.3 diff --git a/stdlib/src/asm.rs b/stdlib/src/asm.rs index e7119967fd..ffd3212454 100644 --- a/stdlib/src/asm.rs +++ b/stdlib/src/asm.rs @@ -277,9 +277,9 @@ export.div_unsafe mul eq.0 assert - swap - movup.3 # add remainder to the previous result; this also consumes the remainder # + swap # add remainder to the previous result; this also consumes the remainder # + movup.3 u32add.unsafe movup.3 movup.3 From 3052273e9160996b336f318b0c889302cac57435 Mon Sep 17 00:00:00 2001 From: grjte Date: Tue, 8 Mar 2022 16:17:19 +0000 Subject: [PATCH 05/26] test: refactor integration tests to a Test struct and builder macro --- processor/src/tests/aux_table_trace.rs | 60 +- processor/src/tests/crypto_ops.rs | 139 +++-- processor/src/tests/field_ops.rs | 87 +-- processor/src/tests/flow_control.rs | 109 ++-- processor/src/tests/io_ops/adv_ops.rs | 57 +- processor/src/tests/io_ops/constant_ops.rs | 76 +-- processor/src/tests/io_ops/env_ops.rs | 83 ++- processor/src/tests/io_ops/local_ops.rs | 233 ++++---- processor/src/tests/io_ops/mem_ops.rs | 146 +++-- processor/src/tests/io_ops/mod.rs | 32 +- processor/src/tests/mod.rs | 305 +++++++---- processor/src/tests/stdlib/mod.rs | 2 +- processor/src/tests/stdlib/u64_mod.rs | 33 +- processor/src/tests/u32_ops/arithmetic_ops.rs | 514 ++++++++++++------ processor/src/tests/u32_ops/bitwise_ops.rs | 253 +++++---- processor/src/tests/u32_ops/comparison_ops.rs | 231 +++++--- processor/src/tests/u32_ops/conversion_ops.rs | 114 ++-- processor/src/tests/u32_ops/mod.rs | 26 +- 18 files changed, 1422 insertions(+), 1078 deletions(-) diff --git a/processor/src/tests/aux_table_trace.rs b/processor/src/tests/aux_table_trace.rs index 4e6cb1945d..9bd71536f5 100644 --- a/processor/src/tests/aux_table_trace.rs +++ b/processor/src/tests/aux_table_trace.rs @@ -1,38 +1,31 @@ use super::{ super::{ bitwise::BITWISE_OR, + build_op_test, build_test, hasher::{LINEAR_HASH, RETURN_STATE}, - AuxiliaryTableTrace, ExecutionTrace, FieldElement, Process, + AuxiliaryTableTrace, FieldElement, }, - build_inputs, compile, Felt, + Felt, }; #[test] fn trace_len() { // --- final trace lengths are equal when stack trace is longer than aux trace ---------------- - let script = compile("begin popw.mem.2 end"); - let inputs = build_inputs(&[1, 2, 3, 4]); - let mut process = Process::new(inputs); - process.execute_code_block(script.root()).unwrap(); - let trace = ExecutionTrace::new(process); + let test = build_op_test!("popw.mem.2", &[1, 2, 3, 4]); + let trace = test.execute().unwrap(); assert_eq!(trace.aux_table()[0].len(), trace.stack()[0].len()); // --- final trace lengths are equal when aux trace is longer than stack trace ---------------- - let script = compile("begin u32and end"); - let inputs = build_inputs(&[4, 8]); - let mut process = Process::new(inputs); - process.execute_code_block(script.root()).unwrap(); - let trace = ExecutionTrace::new(process); + let test = build_op_test!("u32and", &[4, 8]); + let trace = test.execute().unwrap(); assert_eq!(trace.aux_table()[0].len(), trace.stack()[0].len()); // --- stack and aux trace lengths are equal after multi-processor aux trace ------------------ - let script = compile("begin u32and pop.mem.0 u32or end"); - let inputs = build_inputs(&[1, 2, 3, 4]); - let mut process = Process::new(inputs); - process.execute_code_block(script.root()).unwrap(); - let trace = ExecutionTrace::new(process); + let source = "begin u32and pop.mem.0 u32or end"; + let test = build_test!(source, &[1, 2, 3, 4]); + let trace = test.execute().unwrap(); assert_eq!(trace.aux_table()[0].len(), trace.stack()[0].len()); @@ -43,12 +36,8 @@ fn trace_len() { #[test] fn hasher_aux_trace() { // --- single hasher permutation with no stack manipulation ----------------------------------- - let script = compile("begin rpperm end"); - let inputs = build_inputs(&[2, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0]); - let mut process = Process::new(inputs); - - process.execute_code_block(script.root()).unwrap(); - let trace = ExecutionTrace::new(process); + let test = build_op_test!("rpperm", &[2, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0]); + let trace = test.execute().unwrap(); let aux_table = trace.aux_table(); let expected_len = 8; @@ -62,12 +51,8 @@ fn hasher_aux_trace() { #[test] fn bitwise_aux_trace() { // --- single bitwise operation with no stack manipulation ------------------------------------ - let script = compile("begin u32or end"); - let inputs = build_inputs(&[4, 8]); - let mut process = Process::new(inputs); - - process.execute_code_block(script.root()).unwrap(); - let trace = ExecutionTrace::new(process); + let test = build_op_test!("u32or", &[4, 8]); + let trace = test.execute().unwrap(); let aux_table = trace.aux_table(); let expected_len = 8; @@ -81,12 +66,8 @@ fn bitwise_aux_trace() { #[test] fn memory_aux_trace() { // --- single memory operation with no stack manipulation ------------------------------------- - let script = compile("begin storew.mem.2 end"); - let inputs = build_inputs(&[1, 2, 3, 4]); - let mut process = Process::new(inputs); - - process.execute_code_block(script.root()).unwrap(); - let trace = ExecutionTrace::new(process); + let test = build_op_test!("storew.mem.2", &[1, 2, 3, 4]); + let trace = test.execute().unwrap(); let aux_table = trace.aux_table(); // the memory trace is only one row, so the length should match the stack trace length @@ -106,12 +87,9 @@ fn memory_aux_trace() { #[test] fn stacked_aux_trace() { // --- operations in hasher, bitwise, and memory processors without stack manipulation -------- - let script = compile("begin u32or storew.mem.0 rpperm end"); - let inputs = build_inputs(&[8, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 1]); - let mut process = Process::new(inputs); - - process.execute_code_block(script.root()).unwrap(); - let trace = ExecutionTrace::new(process); + let source = "begin u32or storew.mem.0 rpperm end"; + let test = build_test!(source, &[8, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 1]); + let trace = test.execute().unwrap(); let aux_table = trace.aux_table(); // expect 8 rows of hasher trace diff --git a/processor/src/tests/crypto_ops.rs b/processor/src/tests/crypto_ops.rs index a5a1e92dee..83bcab4545 100644 --- a/processor/src/tests/crypto_ops.rs +++ b/processor/src/tests/crypto_ops.rs @@ -1,8 +1,8 @@ -use super::{build_inputs, compile, execute, push_to_stack, Felt, FieldElement, Word}; +use super::{super::build_op_test, Felt, FieldElement, Word}; use rand_utils::rand_vector; use vm_core::{ hasher::{apply_permutation, hash_elements, STATE_WIDTH}, - AdviceSet, ProgramInputs, StarkField, + AdviceSet, StarkField, }; // TESTS @@ -10,25 +10,26 @@ use vm_core::{ #[test] fn rpperm() { - let script = compile("begin rpperm end"); + let asm_op = "rpperm"; + // --- test hashing [ONE, ONE] ---------------------------------------------------------------- let values: Vec = vec![2, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0]; - let inputs = build_inputs(&values); let expected = build_expected_perm(&values); - let trace = execute(&script, &inputs).unwrap(); - let last_state = trace.last_stack_state(); + let test = build_op_test!(asm_op, &values); + let last_state = test.get_last_stack_state(); + assert_eq!(expected, &last_state[0..12]); // --- test hashing 8 random values ----------------------------------------------------------- let mut values = rand_vector::(8); let capacity: Vec = vec![0, 0, 0, 8]; values.extend_from_slice(&capacity); - let inputs = build_inputs(&values); let expected = build_expected_perm(&values); - let trace = execute(&script, &inputs).unwrap(); - let last_state = trace.last_stack_state(); + let test = build_op_test!(asm_op, &values); + let last_state = test.get_last_stack_state(); + assert_eq!(expected, &last_state[0..12]); // --- test that the rest of the stack isn't affected ----------------------------------------- @@ -41,33 +42,33 @@ fn rpperm() { let values_to_hash: Vec = vec![2, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0]; stack_inputs.extend_from_slice(&values_to_hash); - let inputs = build_inputs(&stack_inputs); - let trace = execute(&script, &inputs).unwrap(); - let last_state = trace.last_stack_state(); + let test = build_op_test!(asm_op, &stack_inputs); + let last_state = test.get_last_stack_state(); + assert_eq!(expected_stack_slice, &last_state[12..16]); } #[test] fn rphash() { - let script = compile("begin rphash end"); + let asm_op = "rphash"; // --- test hashing [ONE, ONE, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO] ---------------------------- let values = [1, 1, 0, 0, 0, 0, 0, 0]; - let inputs = build_inputs(&values); let expected = build_expected_hash(&values); - let trace = execute(&script, &inputs).unwrap(); - let last_state = trace.last_stack_state(); + let test = build_op_test!(asm_op, &values); + let last_state = test.get_last_stack_state(); + assert_eq!(expected, &last_state[..4]); // --- test hashing 8 random values ----------------------------------------------------------- let values = rand_vector::(8); - let inputs = build_inputs(&values); let expected = build_expected_hash(&values); - let trace = execute(&script, &inputs).unwrap(); - let last_state = trace.last_stack_state(); + let test = build_op_test!(asm_op, &values); + let last_state = test.get_last_stack_state(); + assert_eq!(expected, &last_state[..4]); // --- test that the rest of the stack isn't affected ----------------------------------------- @@ -80,16 +81,16 @@ fn rphash() { let values_to_hash: Vec = vec![1, 1, 0, 0, 0, 0, 0, 0]; stack_inputs.extend_from_slice(&values_to_hash); - let inputs = build_inputs(&stack_inputs); - let trace = execute(&script, &inputs).unwrap(); - let last_state = trace.last_stack_state(); + let test = build_op_test!(asm_op, &stack_inputs); + let last_state = test.get_last_stack_state(); + assert_eq!(expected_stack_slice, &last_state[4..8]); } #[test] fn mtree_get() { - let script = compile("begin mtree.get end"); + let asm_op = "mtree.get"; let index = 3usize; let leaves = init_leaves(&[1, 2, 3, 4, 5, 6, 7, 8]); @@ -104,30 +105,23 @@ fn mtree_get() { tree.depth() as u64, ]; - let inputs = ProgramInputs::new(&stack_inputs, &[], vec![tree.clone()]).unwrap(); - - let trace = execute(&script, &inputs).unwrap(); - let last_state = trace.last_stack_state(); - - let expected_stack = push_to_stack(&[ - tree.root()[0].as_int(), - tree.root()[1].as_int(), - tree.root()[2].as_int(), - tree.root()[3].as_int(), - leaves[index][0].as_int(), - leaves[index][1].as_int(), - leaves[index][2].as_int(), + let final_stack = [ leaves[index][3].as_int(), - ]); - assert_eq!(expected_stack, last_state); + leaves[index][2].as_int(), + leaves[index][1].as_int(), + leaves[index][0].as_int(), + tree.root()[3].as_int(), + tree.root()[2].as_int(), + tree.root()[1].as_int(), + tree.root()[0].as_int(), + ]; + + let test = build_op_test!(asm_op, &stack_inputs, &[], vec![tree]); + test.expect_stack(&final_stack); } #[test] fn mtree_update() { - // --- mtree.set ---------------------------------------------------------------------- - // update a node value and replace the old root - let script = compile("begin mtree.set end"); - let index = 5usize; let leaves = init_leaves(&[1, 2, 3, 4, 5, 6, 7, 8]); let tree = AdviceSet::new_merkle_tree(leaves.clone()).unwrap(); @@ -150,48 +144,47 @@ fn mtree_update() { tree.depth() as u64, ]; - let inputs = ProgramInputs::new(&stack_inputs, &[], vec![tree.clone()]).unwrap(); - let trace = execute(&script, &inputs).unwrap(); - let last_state = trace.last_stack_state(); + // --- mtree.set ---------------------------------------------------------------------- + // update a node value and replace the old root + let asm_op = "mtree.set"; // expected state has the new leaf and the new root of the tree - let expected_stack = push_to_stack(&[ - new_tree.root()[0].as_int(), - new_tree.root()[1].as_int(), - new_tree.root()[2].as_int(), - new_tree.root()[3].as_int(), - new_node[0].as_int(), - new_node[1].as_int(), - new_node[2].as_int(), + let final_stack = [ new_node[3].as_int(), - ]); + new_node[2].as_int(), + new_node[1].as_int(), + new_node[0].as_int(), + new_tree.root()[3].as_int(), + new_tree.root()[2].as_int(), + new_tree.root()[1].as_int(), + new_tree.root()[0].as_int(), + ]; - assert_eq!(expected_stack, last_state); + let test = build_op_test!(asm_op, &stack_inputs, &[], vec![tree.clone()]); + test.expect_stack(&final_stack); // --- mtree.cwm ---------------------------------------------------------------------- // update a node value and replace the old root - let script = compile("begin mtree.cwm end"); - let inputs = ProgramInputs::new(&stack_inputs, &[], vec![tree.clone()]).unwrap(); - let trace = execute(&script, &inputs).unwrap(); - let last_state = trace.last_stack_state(); + let asm_op = "mtree.cwm"; // expected state has the new leaf, the new root of the tree, and the root of the old tree - let expected_stack = push_to_stack(&[ - tree.root()[0].as_int(), - tree.root()[1].as_int(), - tree.root()[2].as_int(), - tree.root()[3].as_int(), - new_tree.root()[0].as_int(), - new_tree.root()[1].as_int(), - new_tree.root()[2].as_int(), - new_tree.root()[3].as_int(), - new_node[0].as_int(), - new_node[1].as_int(), - new_node[2].as_int(), + let final_stack = [ new_node[3].as_int(), - ]); + new_node[2].as_int(), + new_node[1].as_int(), + new_node[0].as_int(), + new_tree.root()[3].as_int(), + new_tree.root()[2].as_int(), + new_tree.root()[1].as_int(), + new_tree.root()[0].as_int(), + tree.root()[3].as_int(), + tree.root()[2].as_int(), + tree.root()[1].as_int(), + tree.root()[0].as_int(), + ]; - assert_eq!(expected_stack, last_state); + let test = build_op_test!(asm_op, &stack_inputs, &[], vec![tree]); + test.expect_stack(&final_stack); } // HELPER FUNCTIONS diff --git a/processor/src/tests/field_ops.rs b/processor/src/tests/field_ops.rs index de4b18d7c6..813b0a7344 100644 --- a/processor/src/tests/field_ops.rs +++ b/processor/src/tests/field_ops.rs @@ -1,6 +1,4 @@ -use super::{ - build_inputs, compile, execute, push_to_stack, test_op_execution, test_op_execution_proptest, -}; +use super::{super::build_op_test, prop_randw}; use proptest::prelude::*; use vm_core::{Felt, StarkField}; @@ -9,19 +7,21 @@ use vm_core::{Felt, StarkField}; #[test] fn eq() { - // let script = compile("begin eq end"); let asm_op = "eq"; // --- test when two elements are equal ------------------------------------------------------ - test_op_execution(asm_op, &[100, 100], &[1]); + let test = build_op_test!(asm_op, &[100, 100]); + test.expect_stack(&[1]); // --- test when two elements are unequal ---------------------------------------------------- - test_op_execution(asm_op, &[25, 100], &[0]); + let test = build_op_test!(asm_op, &[25, 100]); + test.expect_stack(&[0]); // --- test when two u64s are unequal but their felts are equal ------------------------------ let a = Felt::MODULUS + 1; let b = 1; - test_op_execution(asm_op, &[a, b], &[1]); + let test = build_op_test!(asm_op, &[a, b]); + test.expect_stack(&[1]); } #[test] @@ -35,7 +35,8 @@ fn eqw() { expected.push(1); // put it in stack order expected.reverse(); - test_op_execution(asm_op, &values, &expected); + let test = build_op_test!(asm_op, &values); + test.expect_stack(&expected); // --- test when top two words are not equal -------------------------------------------------- let values = vec![8, 7, 6, 5, 4, 3, 2, 1]; @@ -44,7 +45,8 @@ fn eqw() { expected.push(0); // put it in stack order expected.reverse(); - test_op_execution(asm_op, &values, &expected); + let test = build_op_test!(asm_op, &values); + test.expect_stack(&expected); } #[test] @@ -106,36 +108,46 @@ fn test_felt_comparison_op(asm_op: &str, expect_if_lt: u64, expect_if_eq: u64, e // --- a < b ---------------------------------------------------------------------------------- // a is smaller in the low bits (equal in high bits) - test_op_execution(asm_op, &[smaller, hi_eq_lo_gt], &[expect_if_lt]); + let test = build_op_test!(asm_op, &[smaller, hi_eq_lo_gt]); + test.expect_stack(&[expect_if_lt]); // a is smaller in the high bits and equal in the low bits - test_op_execution(asm_op, &[smaller, hi_gt_lo_eq], &[expect_if_lt]); + let test = build_op_test!(asm_op, &[smaller, hi_gt_lo_eq]); + test.expect_stack(&[expect_if_lt]); // a is smaller in the high bits but bigger in the low bits - test_op_execution(asm_op, &[smaller, hi_gt_lo_lt], &[expect_if_lt]); + let test = build_op_test!(asm_op, &[smaller, hi_gt_lo_lt]); + test.expect_stack(&[expect_if_lt]); // compare values above and below the field modulus - test_op_execution(asm_op, &[a_mod, a + 1], &[expect_if_lt]); + let test = build_op_test!(asm_op, &[a_mod, a + 1]); + test.expect_stack(&[expect_if_lt]); // --- a = b ---------------------------------------------------------------------------------- // high and low bits are both set - test_op_execution(asm_op, &[hi_gt_lo_eq, hi_gt_lo_eq], &[expect_if_eq]); + let test = build_op_test!(asm_op, &[hi_gt_lo_eq, hi_gt_lo_eq]); + test.expect_stack(&[expect_if_eq]); // compare values above and below the field modulus - test_op_execution(asm_op, &[a_mod, a], &[expect_if_eq]); + let test = build_op_test!(asm_op, &[a_mod, a]); + test.expect_stack(&[expect_if_eq]); // --- a > b ---------------------------------------------------------------------------------- // a is bigger in the low bits (equal in high bits) - test_op_execution(asm_op, &[hi_eq_lo_gt, smaller], &[expect_if_gt]); + let test = build_op_test!(asm_op, &[hi_eq_lo_gt, smaller]); + test.expect_stack(&[expect_if_gt]); // a is bigger in the high bits and equal in the low bits - test_op_execution(asm_op, &[hi_gt_lo_eq, smaller], &[expect_if_gt]); + let test = build_op_test!(asm_op, &[hi_gt_lo_eq, smaller]); + test.expect_stack(&[expect_if_gt]); // a is bigger in the high bits but smaller in the low bits - test_op_execution(asm_op, &[hi_gt_lo_lt, smaller], &[expect_if_gt]); + let test = build_op_test!(asm_op, &[hi_gt_lo_lt, smaller]); + test.expect_stack(&[expect_if_gt]); // compare values above and below the field modulus - test_op_execution(asm_op, &[a_mod + 1, a], &[expect_if_gt]); + let test = build_op_test!(asm_op, &[a_mod + 1, a]); + test.expect_stack(&[expect_if_gt]); } // FIELD OPS COMPARISON - RANDOMIZED TESTS @@ -143,24 +155,21 @@ fn test_felt_comparison_op(asm_op: &str, expect_if_lt: u64, expect_if_eq: u64, e const WORD_LEN: usize = 4; -// This is a proptest strategy for generating a random word of u64 values. -fn rand_word() -> impl Strategy> { - prop::collection::vec(any::(), WORD_LEN) -} - proptest! { #[test] fn eq_proptest(a in any::(), b in any::()) { let asm_op = "eq"; // compare the random a & b values modulo the field modulus to get the expected result let expected_result = if a % Felt::MODULUS == b % Felt::MODULUS { 1 } else { 0 }; - test_op_execution_proptest(asm_op, &[a, b], &[expected_result])?; + + let test = build_op_test!(asm_op, &[a,b]); + test.prop_expect_stack(&[expected_result])?; } #[test] - fn eqw_proptest(w1 in rand_word(), w2 in rand_word()) { + fn eqw_proptest(w1 in prop_randw(), w2 in prop_randw()) { // test the eqw assembly operation with randomized inputs - let script = compile("begin eqw end"); + let asm_op = "eqw"; // 2 words (8 values) for comparison and 1 for the result let mut values = vec![0; 2 * WORD_LEN + 1]; @@ -177,16 +186,14 @@ proptest! { values[i + WORD_LEN] = *b; } - let inputs = build_inputs(&values); - let trace = execute(&script, &inputs).unwrap(); - let last_state = trace.last_stack_state(); + let test = build_op_test!(asm_op, &values); // add the expected result to get the expected state let expected_result = if inputs_equal { 1 } else { 0 }; values.push(expected_result); - let expected_state = push_to_stack(&values); + values.reverse(); - prop_assert_eq!(expected_state, last_state); + test.prop_expect_stack(&values)?; } #[test] @@ -195,7 +202,9 @@ proptest! { let asm_op = "lt"; // compare the random a & b values modulo the field modulus to get the expected result let expected_result = if a % Felt::MODULUS < b % Felt::MODULUS { 1 } else { 0 }; - test_op_execution_proptest(asm_op, &[a, b], &[expected_result])?; + + let test = build_op_test!(asm_op, &[a,b]); + test.prop_expect_stack(&[expected_result])?; } #[test] @@ -204,7 +213,9 @@ proptest! { let asm_op = "lte"; // compare the random a & b values modulo the field modulus to get the expected result let expected_result = if a % Felt::MODULUS <= b % Felt::MODULUS { 1 } else { 0 }; - test_op_execution_proptest(asm_op, &[a, b], &[expected_result])?; + + let test = build_op_test!(asm_op, &[a,b]); + test.prop_expect_stack(&[expected_result])?; } #[test] @@ -213,7 +224,9 @@ proptest! { let asm_op = "gt"; // compare the random a & b values modulo the field modulus to get the expected result let expected_result = if a % Felt::MODULUS > b % Felt::MODULUS { 1 } else { 0 }; - test_op_execution_proptest(asm_op, &[a, b], &[expected_result])?; + + let test = build_op_test!(asm_op, &[a,b]); + test.prop_expect_stack(&[expected_result])?; } #[test] @@ -222,6 +235,8 @@ proptest! { let asm_op = "gte"; // compare the random a & b values modulo the field modulus to get the expected result let expected_result = if a % Felt::MODULUS >= b % Felt::MODULUS { 1 } else { 0 }; - test_op_execution_proptest(asm_op, &[a, b], &[expected_result])?; + + let test = build_op_test!(asm_op, &[a,b]); + test.prop_expect_stack(&[expected_result])?; } } diff --git a/processor/src/tests/flow_control.rs b/processor/src/tests/flow_control.rs index 24d9dde36b..3a2b5b4ff4 100644 --- a/processor/src/tests/flow_control.rs +++ b/processor/src/tests/flow_control.rs @@ -1,4 +1,4 @@ -use super::{compile, test_script_execution}; +use super::super::build_test; // SIMPLE FLOW CONTROL TESTS // ================================================================================================ @@ -6,47 +6,52 @@ use super::{compile, test_script_execution}; #[test] fn conditional_execution() { // --- if without else ------------------------------------------------------------------------ - let script = compile("begin dup.1 dup.1 eq if.true add end end"); + let source = "begin dup.1 dup.1 eq if.true add end end"; - test_script_execution(&script, &[1, 2], &[2, 1]); - test_script_execution(&script, &[3, 3], &[6]); + let test = build_test!(source, &[1, 2]); + test.expect_stack(&[2, 1]); + + let test = build_test!(source, &[3, 3]); + test.expect_stack(&[6]); // --- if with else ------------------------------------------------------------------------ - let script = compile("begin dup.1 dup.1 eq if.true add else mul end end"); + let source = "begin dup.1 dup.1 eq if.true add else mul end end"; + + let test = build_test!(source, &[2, 3]); + test.expect_stack(&[6]); - test_script_execution(&script, &[2, 3], &[6]); - test_script_execution(&script, &[3, 3], &[6]); + let test = build_test!(source, &[3, 3]); + test.expect_stack(&[6]); } #[test] fn conditional_loop() { // --- entering the loop ---------------------------------------------------------------------- // computes sum of values from 0 to the value at the top of the stack - let script = compile( - " + let source = " begin dup push.0 movdn.2 neq.0 while.true dup movup.2 add swap push.1 sub dup neq.0 end drop - end", - ); + end"; - test_script_execution(&script, &[10], &[55]); + let test = build_test!(source, &[10]); + test.expect_stack(&[55]); // --- skipping the loop ---------------------------------------------------------------------- - let script = compile("begin dup eq.0 while.true add end end"); + let source = "begin dup eq.0 while.true add end end"; - test_script_execution(&script, &[10], &[10]); + let test = build_test!(source, &[10]); + test.expect_stack(&[10]); } #[test] fn counter_controlled_loop() { // --- entering the loop ---------------------------------------------------------------------- // compute 2^10 - let script = compile( - " + let source = " begin push.2 push.1 @@ -54,10 +59,10 @@ fn counter_controlled_loop() { dup.1 mul end swap drop - end", - ); + end"; - test_script_execution(&script, &[], &[1024]); + let test = build_test!(source); + test.expect_stack(&[1024]); } // NESTED CONTROL FLOW @@ -65,9 +70,31 @@ fn counter_controlled_loop() { #[test] fn if_in_loop() { - let script = compile( - " - begin + let source = " + begin + dup push.0 movdn.2 neq.0 + while.true + dup movup.2 dup.1 eq.5 + if.true + mul + else + add + end + swap push.1 sub dup neq.0 + end + drop + end"; + + let test = build_test!(source, &[10]); + test.expect_stack(&[210]); +} + +#[test] +fn if_in_loop_in_if() { + let source = " + begin + dup eq.10 + if.true dup push.0 movdn.2 neq.0 while.true dup movup.2 dup.1 eq.5 @@ -79,36 +106,14 @@ fn if_in_loop() { swap push.1 sub dup neq.0 end drop - end", - ); - - test_script_execution(&script, &[10], &[210]); -} + else + dup mul + end + end"; -#[test] -fn if_in_loop_in_if() { - let script = compile( - " - begin - dup eq.10 - if.true - dup push.0 movdn.2 neq.0 - while.true - dup movup.2 dup.1 eq.5 - if.true - mul - else - add - end - swap push.1 sub dup neq.0 - end - drop - else - dup mul - end - end", - ); + let test = build_test!(source, &[10]); + test.expect_stack(&[210]); - test_script_execution(&script, &[10], &[210]); - test_script_execution(&script, &[11], &[121]); + let test = build_test!(source, &[11]); + test.expect_stack(&[121]); } diff --git a/processor/src/tests/io_ops/adv_ops.rs b/processor/src/tests/io_ops/adv_ops.rs index 5b6aaede97..19421368de 100644 --- a/processor/src/tests/io_ops/adv_ops.rs +++ b/processor/src/tests/io_ops/adv_ops.rs @@ -1,4 +1,4 @@ -use super::{compile, execute, push_to_stack, test_execution_failure, ProgramInputs}; +use super::{build_op_test, build_test, TestError}; use rand_utils::rand_value; // PUSHING VALUES ONTO THE STACK (PUSH) @@ -6,33 +6,30 @@ use rand_utils::rand_value; #[test] fn push_adv() { + let asm_op = "push.adv"; let advice_tape = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + let test_n = |n: usize| { + let source = format!("{}.{}", asm_op, n); + let mut final_stack = vec![0; n]; + final_stack.copy_from_slice(&advice_tape[..n]); + final_stack.reverse(); - // --- push 1 --------------------------------------------------------------------------------- - let n = 1; - let script = compile(format!("begin push.adv.{} end", n).as_str()); - - let inputs = ProgramInputs::new(&[], &advice_tape, vec![]).unwrap(); - let trace = execute(&script, &inputs).unwrap(); - let expected = push_to_stack(&advice_tape[..n]); + let test = build_op_test!(source, &[], &advice_tape, vec![]); + test.expect_stack(&final_stack); + }; - assert_eq!(expected, trace.last_stack_state()); + // --- push 1 --------------------------------------------------------------------------------- + test_n(1); // --- push max ------------------------------------------------------------------------------- - let n = 16; - let script = compile(format!("begin push.adv.{} end", n).as_str()); - - let inputs = ProgramInputs::new(&[], &advice_tape, vec![]).unwrap(); - let trace = execute(&script, &inputs).unwrap(); - let expected = push_to_stack(&advice_tape[..n]); - - assert_eq!(expected, trace.last_stack_state()); + test_n(16); } #[test] fn push_adv_invalid() { // attempting to read from empty advice tape should throw an error - test_execution_failure("push.adv.1", &[], "EmptyAdviceTape"); + let test = build_op_test!("push.adv.1"); + test.expect_error(TestError::ExecutionError("EmptyAdviceTape")); } // OVERWRITING VALUES ON THE STACK (LOAD) @@ -40,20 +37,20 @@ fn push_adv_invalid() { #[test] fn loadw_adv() { - let script = compile("begin loadw.adv end"); + let asm_op = "loadw.adv"; let advice_tape = [1, 2, 3, 4]; + let mut final_stack = advice_tape; + final_stack.reverse(); - let inputs = ProgramInputs::new(&[8, 7, 6, 5], &advice_tape, vec![]).unwrap(); - let trace = execute(&script, &inputs).unwrap(); - let expected = push_to_stack(&advice_tape); - - assert_eq!(expected, trace.last_stack_state()); + let test = build_op_test!(asm_op, &[8, 7, 6, 5], &advice_tape, vec![]); + test.expect_stack(&final_stack); } #[test] fn loadw_adv_invalid() { // attempting to read from empty advice tape should throw an error - test_execution_failure("loadw.adv", &[0, 0, 0, 0], "EmptyAdviceTape"); + let test = build_op_test!("loadw.adv", &[0, 0, 0, 0]); + test.expect_error(TestError::ExecutionError("EmptyAdviceTape")); } // ADVICE INJECTORS @@ -61,7 +58,7 @@ fn loadw_adv_invalid() { #[test] fn adv_inject_u64div() { - let script = compile("begin adv.u64div push.adv.4 end"); + let source = "begin adv.u64div push.adv.4 end"; // get two random 64-bit integers and split them into 32-bit limbs let a = rand_value::(); @@ -83,9 +80,7 @@ fn adv_inject_u64div() { let r_lo = r as u32 as u64; // inject a/b into the advice tape and then read these values from the tape - let inputs = ProgramInputs::new(&[a_lo, a_hi, b_lo, b_hi], &[], vec![]).unwrap(); - let trace = execute(&script, &inputs).unwrap(); - - let expected = push_to_stack(&[a_lo, a_hi, b_lo, b_hi, q_lo, q_hi, r_lo, r_hi]); - assert_eq!(expected, trace.last_stack_state()); + let test = build_test!(source, &[a_lo, a_hi, b_lo, b_hi]); + let expected = [r_hi, r_lo, q_hi, q_lo, b_hi, b_lo, a_hi, a_lo]; + test.expect_stack(&expected); } diff --git a/processor/src/tests/io_ops/constant_ops.rs b/processor/src/tests/io_ops/constant_ops.rs index 9fed403b00..a59ee098e8 100644 --- a/processor/src/tests/io_ops/constant_ops.rs +++ b/processor/src/tests/io_ops/constant_ops.rs @@ -1,86 +1,88 @@ -use super::test_op_execution; +use super::build_op_test; // PUSHING VALUES ONTO THE STACK (PUSH) // ================================================================================================ #[test] fn push_one() { - let asm_op = "push"; + let asm_op_base = "push"; // --- test zero ------------------------------------------------------------------------------ - test_op_execution(format!("{}.{}", asm_op, "0").as_str(), &[], &[0]); + let asm_op = format!("{}.{}", asm_op_base, "0"); + let test = build_op_test!(&asm_op); + test.expect_stack(&[0]); // --- single decimal input ------------------------------------------------------------------- - test_op_execution(format!("{}.{}", asm_op, "5").as_str(), &[], &[5]); + let asm_op = format!("{}.{}", asm_op_base, "5"); + let test = build_op_test!(&asm_op); + test.expect_stack(&[5]); // --- single hexadecimal input --------------------------------------------------------------- - test_op_execution(format!("{}.{}", asm_op, "0xAF").as_str(), &[], &[175]); + let asm_op = format!("{}.{}", asm_op_base, "0xAF"); + let test = build_op_test!(&asm_op); + test.expect_stack(&[175]); } #[test] fn push_many() { - let asm_op = "push"; + let base_op = "push"; // --- multiple values with separators -------------------------------------------------------- - test_op_execution( - format!("{}.17.0x13.23", asm_op).as_str(), - &[], - &[23, 19, 17], - ); + let asm_op = format!("{}.17.0x13.23", base_op); + let test = build_op_test!(asm_op); + test.expect_stack(&[23, 19, 17]); // --- push the maximum number of decimal values (16) ------------------------------------- + let asm_op = format!( + "{}.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31", + base_op + ); let mut expected = Vec::with_capacity(16); for i in (16..32).rev() { expected.push(i); } - test_op_execution( - format!("{}.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31", asm_op).as_str(), - &[], - &expected, - ); + + let test = build_op_test!(asm_op); + test.expect_stack(&expected); // --- push hexadecimal values with period separators between values ---------------------- + let asm_op = format!("{}.0xA.0x64.0x3E8.0x2710.0x186A0", base_op); let mut expected = Vec::with_capacity(5); for i in (1..=5).rev() { expected.push(10_u64.pow(i)); } - test_op_execution( - format!("{}.0xA.0x64.0x3E8.0x2710.0x186A0", asm_op).as_str(), - &[], - &expected, - ); + + let test = build_op_test!(asm_op); + test.expect_stack(&expected); // --- push a mixture of decimal and single-element hexadecimal values -------------------- + let asm_op = format!("{}.2.4.8.0x10.0x20.0x40.128.0x100", base_op); let mut expected = Vec::with_capacity(8); for i in (1_u32..=8).rev() { expected.push(2_u64.pow(i)); } - test_op_execution( - format!("{}.2.4.8.0x10.0x20.0x40.128.0x100", asm_op).as_str(), - &[], - &expected, - ); + + let test = build_op_test!(asm_op); + test.expect_stack(&expected); } #[test] fn push_without_separator() { - let asm_op = "push"; + let base_op = "push"; // --- multiple values as a hexadecimal string ------------------------------------------------ - test_op_execution( - format!("{}.0x0000000000004321000000000000dcba", asm_op).as_str(), - &[], - &[56506, 17185], - ); + let asm_op = format!("{}.0x0000000000004321000000000000dcba", base_op); + + let test = build_op_test!(asm_op); + test.expect_stack(&[56506, 17185]); // --- push the maximum number of hexadecimal values without separators (16) ------------------ + let asm_op = format!("{}.0x0000000000000000000000000000000100000000000000020000000000000003000000000000000400000000000000050000000000000006000000000000000700000000000000080000000000000009000000000000000A000000000000000B000000000000000C000000000000000D000000000000000E000000000000000F", base_op); let mut expected = Vec::with_capacity(16); for i in (0..16).rev() { expected.push(i); } - test_op_execution( - format!("{}.0x0000000000000000000000000000000100000000000000020000000000000003000000000000000400000000000000050000000000000006000000000000000700000000000000080000000000000009000000000000000A000000000000000B000000000000000C000000000000000D000000000000000E000000000000000F", asm_op).as_str(), - &[], - &expected, - ) + + let test = build_op_test!(asm_op); + test.expect_stack(&expected); } diff --git a/processor/src/tests/io_ops/env_ops.rs b/processor/src/tests/io_ops/env_ops.rs index dfd4893867..bfb9a56cd5 100644 --- a/processor/src/tests/io_ops/env_ops.rs +++ b/processor/src/tests/io_ops/env_ops.rs @@ -1,4 +1,4 @@ -use super::{compile, test_op_execution, test_script_execution}; +use super::{build_op_test, build_test}; use crate::system::FMP_MIN; // PUSHING VALUES ONTO THE STACK (PUSH) @@ -9,39 +9,37 @@ fn push_env_sdepth() { let test_op = "push.env.sdepth"; // --- empty stack ---------------------------------------------------------------------------- - test_op_execution(test_op, &[], &[0]); + let test = build_op_test!(test_op); + test.expect_stack(&[0]); // --- multi-element stack -------------------------------------------------------------------- - test_op_execution(test_op, &[2, 4, 6, 8, 10], &[5, 10, 8, 6, 4, 2]); + let test = build_op_test!(test_op, &[2, 4, 6, 8, 10]); + test.expect_stack(&[5, 10, 8, 6, 4, 2]); // --- overflowed stack ----------------------------------------------------------------------- // push 2 values to increase the lenth of the stack beyond 16 - let setup_ops = "push.1 push.1"; - test_op_execution( - format!("{} {}", setup_ops, test_op).as_str(), - &[0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7], - &[18, 1, 1, 7, 6, 5, 4, 3, 2, 1, 0, 7, 6, 5, 4, 3], - ); + let source = format!("begin push.1 push.1 {} end", test_op); + let test = build_test!(&source, &[0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7]); + test.expect_stack(&[18, 1, 1, 7, 6, 5, 4, 3, 2, 1, 0, 7, 6, 5, 4, 3]); } #[test] fn push_env_locaddr() { // --- locaddr returns expected address ------------------------------------------------------- - let script = compile( - " + let source = " proc.foo.2 push.env.locaddr.0 push.env.locaddr.1 end begin exec.foo - end", - ); - test_script_execution(&script, &[10], &[FMP_MIN + 1, FMP_MIN + 2, 10]); + end"; + + let test = build_test!(source, &[10]); + test.expect_stack(&[FMP_MIN + 1, FMP_MIN + 2, 10]); // --- accessing mem via locaddr updates the correct variables -------------------------------- - let script = compile( - " + let source = " proc.foo.2 push.env.locaddr.0 pop.mem @@ -52,13 +50,13 @@ fn push_env_locaddr() { end begin exec.foo - end", - ); - test_script_execution(&script, &[10, 1, 2, 3, 4, 5], &[4, 3, 2, 1, 5, 10]); + end"; + + let test = build_test!(source, &[10, 1, 2, 3, 4, 5]); + test.expect_stack(&[4, 3, 2, 1, 5, 10]); // --- locaddr returns expected addresses in nested procedures -------------------------------- - let script = compile( - " + let source = " proc.foo.3 push.env.locaddr.0 push.env.locaddr.1 @@ -72,28 +70,23 @@ fn push_env_locaddr() { begin exec.bar exec.foo - end", - ); + end"; - test_script_execution( - &script, - &[10], - &[ - FMP_MIN + 1, - FMP_MIN + 2, - FMP_MIN + 3, - FMP_MIN + 1, - FMP_MIN + 3, - FMP_MIN + 4, - FMP_MIN + 5, - FMP_MIN + 2, - 10, - ], - ); + let test = build_test!(source, &[10]); + test.expect_stack(&[ + FMP_MIN + 1, + FMP_MIN + 2, + FMP_MIN + 3, + FMP_MIN + 1, + FMP_MIN + 3, + FMP_MIN + 4, + FMP_MIN + 5, + FMP_MIN + 2, + 10, + ]); // --- accessing mem via locaddr in nested procedures updates the correct variables ----------- - let script = compile( - " + let source = " proc.foo.2 push.env.locaddr.0 pop.mem @@ -113,12 +106,8 @@ fn push_env_locaddr() { end begin exec.bar - end", - ); + end"; - test_script_execution( - &script, - &[10, 1, 2, 3, 4, 5, 6, 7], - &[7, 6, 5, 4, 3, 2, 1, 10], - ); + let test = build_test!(source, &[10, 1, 2, 3, 4, 5, 6, 7]); + test.expect_stack(&[7, 6, 5, 4, 3, 2, 1, 10]); } diff --git a/processor/src/tests/io_ops/local_ops.rs b/processor/src/tests/io_ops/local_ops.rs index 6b6366ba1a..b15a08c2fb 100644 --- a/processor/src/tests/io_ops/local_ops.rs +++ b/processor/src/tests/io_ops/local_ops.rs @@ -1,4 +1,4 @@ -use super::{compile, test_memory_write, test_script_execution, test_script_execution_failure}; +use super::{build_test, TestError}; // PUSHING VALUES ONTO THE STACK (PUSH) // ================================================================================================ @@ -6,39 +6,39 @@ use super::{compile, test_memory_write, test_script_execution, test_script_execu #[test] fn push_local() { // --- read from uninitialized memory --------------------------------------------------------- - let script = compile( - " + let source = " proc.foo.1 push.local.0 end begin exec.foo - end", - ); + end"; - test_script_execution(&script, &[], &[0]); + let test = build_test!(source); + test.expect_stack(&[0]); // --- the rest of the stack is unchanged ----------------------------------------------------- - test_script_execution(&script, &[1, 2, 3, 4], &[0, 4, 3, 2, 1]); + let test = build_test!(source, &[1, 2, 3, 4]); + test.expect_stack(&[0, 4, 3, 2, 1]); } #[test] fn pushw_local() { // --- read from uninitialized memory --------------------------------------------------------- - let script = compile( - " + let source = " proc.foo.1 pushw.local.0 end begin exec.foo - end", - ); + end"; - test_script_execution(&script, &[], &[0, 0, 0, 0]); + let test = build_test!(source); + test.expect_stack(&[0, 0, 0, 0]); // --- the rest of the stack is unchanged ----------------------------------------------------- - test_script_execution(&script, &[1, 2, 3, 4], &[0, 0, 0, 0, 4, 3, 2, 1]); + let test = build_test!(source, &[1, 2, 3, 4]); + test.expect_stack(&[0, 0, 0, 0, 4, 3, 2, 1]); } // REMOVING VALUES FROM THE STACK (POP) @@ -47,8 +47,7 @@ fn pushw_local() { #[test] fn pop_local() { // --- test write to local memory ------------------------------------------------------------- - let script = compile( - " + let source = " proc.foo.2 pop.local.0 pop.local.1 @@ -57,14 +56,13 @@ fn pop_local() { end begin exec.foo - end", - ); + end"; - test_script_execution(&script, &[1, 2, 3, 4], &[3, 4, 2, 1]); + let test = build_test!(source, &[1, 2, 3, 4]); + test.expect_stack(&[3, 4, 2, 1]); // --- test existing memory is not affected --------------------------------------------------- - let script = compile( - " + let source = " proc.foo.1 pop.local.0 end @@ -72,34 +70,32 @@ fn pop_local() { pop.mem.0 pop.mem.1 exec.foo - end", - ); + end"; let mem_addr = 1; - test_memory_write(&script, &[1, 2, 3, 4], &[1], mem_addr, &[3, 0, 0, 0]); + let test = build_test!(source, &[1, 2, 3, 4]); + test.expect_stack_and_memory(&[1], mem_addr, &[3, 0, 0, 0]); } #[test] fn pop_local_invalid() { - let script = compile( - " + let source = " proc.foo.1 pop.local.0 end begin exec.foo - end", - ); + end"; // --- pop fails when stack is empty ---------------------------------------------------------- - test_script_execution_failure(&script, &[], "StackUnderflow"); + let test = build_test!(source); + test.expect_error(TestError::ExecutionError("StackUnderflow")); } #[test] fn popw_local() { // --- test write to local memory ------------------------------------------------------------- - let script = compile( - " + let source = " proc.foo.2 popw.local.0 popw.local.1 @@ -108,18 +104,13 @@ fn popw_local() { end begin exec.foo - end", - ); + end"; - test_script_execution( - &script, - &[1, 2, 3, 4, 5, 6, 7, 8], - &[4, 3, 2, 1, 8, 7, 6, 5], - ); + let test = build_test!(source, &[1, 2, 3, 4, 5, 6, 7, 8]); + test.expect_stack(&[4, 3, 2, 1, 8, 7, 6, 5]); // --- test existing memory is not affected --------------------------------------------------- - let script = compile( - " + let source = " proc.foo.1 popw.local.0 end @@ -127,33 +118,26 @@ fn popw_local() { popw.mem.0 popw.mem.1 exec.foo - end", - ); + end"; let mem_addr = 1; - test_memory_write( - &script, - &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], - &[], - mem_addr, - &[5, 6, 7, 8], - ); + let test = build_test!(source, &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); + test.expect_stack_and_memory(&[], mem_addr, &[5, 6, 7, 8]); } #[test] fn popw_local_invalid() { - let script = compile( - " + let source = " proc.foo.1 popw.local.0 end begin exec.foo - end", - ); + end"; // --- pop fails when stack is empty ---------------------------------------------------------- - test_script_execution_failure(&script, &[1, 2], "StackUnderflow"); + let test = build_test!(source, &[1, 2]); + test.expect_error(TestError::ExecutionError("StackUnderflow")); } // OVERWRITING VALUES ON THE STACK (LOAD) @@ -162,24 +146,20 @@ fn popw_local_invalid() { #[test] fn loadw_local() { // --- read from uninitialized memory --------------------------------------------------------- - let script = compile( - " + let source = " proc.foo.1 loadw.local.0 end begin exec.foo - end", - ); + end"; - test_script_execution(&script, &[5, 6, 7, 8], &[0, 0, 0, 0]); + let test = build_test!(source, &[5, 6, 7, 8]); + test.expect_stack(&[0, 0, 0, 0]); // --- the rest of the stack is unchanged ----------------------------------------------------- - test_script_execution( - &script, - &[1, 2, 3, 4, 5, 6, 7, 8], - &[0, 0, 0, 0, 4, 3, 2, 1], - ); + let test = build_test!(source, &[1, 2, 3, 4, 5, 6, 7, 8]); + test.expect_stack(&[0, 0, 0, 0, 4, 3, 2, 1]); } // SAVING STACK VALUES WITHOUT REMOVING THEM (STORE) @@ -188,8 +168,7 @@ fn loadw_local() { #[test] fn storew_local() { // --- test write to local memory ------------------------------------------------------------- - let script = compile( - " + let source = " proc.foo.2 storew.local.0 swapw @@ -200,18 +179,13 @@ fn storew_local() { end begin exec.foo - end", - ); + end"; - test_script_execution( - &script, - &[1, 2, 3, 4, 5, 6, 7, 8], - &[4, 3, 2, 1, 8, 7, 6, 5, 8, 7, 6, 5, 4, 3, 2, 1], - ); + let test = build_test!(source, &[1, 2, 3, 4, 5, 6, 7, 8]); + test.expect_stack(&[4, 3, 2, 1, 8, 7, 6, 5, 8, 7, 6, 5, 4, 3, 2, 1]); // --- test existing memory is not affected --------------------------------------------------- - let script = compile( - " + let source = " proc.foo.1 storew.local.0 end @@ -219,33 +193,26 @@ fn storew_local() { popw.mem.0 popw.mem.1 exec.foo - end", - ); + end"; let mem_addr = 1; - test_memory_write( - &script, - &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], - &[4, 3, 2, 1], - mem_addr, - &[5, 6, 7, 8], - ); + let test = build_test!(source, &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); + test.expect_stack_and_memory(&[4, 3, 2, 1], mem_addr, &[5, 6, 7, 8]); } #[test] fn storew_local_invalid() { - let script = compile( - " + let source = " proc.foo.1 storew.local.0 end begin exec.foo - end", - ); + end"; // --- pop fails when stack is empty ---------------------------------------------------------- - test_script_execution_failure(&script, &[1, 2], "StackUnderflow"); + let test = build_test!(source, &[1, 2]); + test.expect_error(TestError::ExecutionError("StackUnderflow")); } // NESTED PROCEDURES & PAIRED OPERATIONS (push/pop, pushw/popw, loadw/storew) @@ -254,107 +221,100 @@ fn storew_local_invalid() { #[test] fn inverse_operations() { // --- pop and push are inverse operations, so the stack should be left unchanged ------------- - let script = compile( - " + let source = " proc.foo.1 pop.local.0 push.local.0 end begin exec.foo - end", - ); + end"; let inputs = [0, 1, 2, 3, 4]; let mut final_stack = inputs; final_stack.reverse(); - test_script_execution(&script, &inputs, &final_stack); + let test = build_test!(source, &inputs); + test.expect_stack(&final_stack); // --- popw and pushw are inverse operations, so the stack should be left unchanged ----------- - let script = compile( - " + let source = " proc.foo.1 popw.local.0 pushw.local.0 end begin exec.foo - end", - ); + end"; let inputs = [0, 1, 2, 3, 4]; let mut final_stack = inputs; final_stack.reverse(); - test_script_execution(&script, &inputs, &final_stack); + let test = build_test!(source, &inputs); + test.expect_stack(&final_stack); // --- storew and loadw are inverse operations, so the stack should be left unchanged --------- - let script = compile( - " + let source = " proc.foo.1 storew.local.0 loadw.local.0 end begin exec.foo - end", - ); + end"; let inputs = [0, 1, 2, 3, 4]; let mut final_stack = inputs; final_stack.reverse(); - test_script_execution(&script, &inputs, &final_stack); + let test = build_test!(source, &inputs); + test.expect_stack(&final_stack); } #[test] fn read_after_write() { // --- write to memory first, then test read with push -------------------------------------- - let script = compile( - " + let source = " proc.foo.1 storew.local.0 push.local.0 end begin exec.foo - end", - ); + end"; - test_script_execution(&script, &[1, 2, 3, 4], &[1, 4, 3, 2, 1]); + let test = build_test!(source, &[1, 2, 3, 4]); + test.expect_stack(&[1, 4, 3, 2, 1]); // --- write to memory first, then test read with pushw -------------------------------------- - let script = compile( - " + let source = " proc.foo.1 storew.local.0 pushw.local.0 end begin exec.foo - end", - ); + end"; - test_script_execution(&script, &[1, 2, 3, 4], &[4, 3, 2, 1, 4, 3, 2, 1]); + let test = build_test!(source, &[1, 2, 3, 4]); + test.expect_stack(&[4, 3, 2, 1, 4, 3, 2, 1]); // --- write to memory first, then test read with loadw -------------------------------------- - let script = compile( - " + let source = " proc.foo.1 popw.local.0 loadw.local.0 end begin exec.foo - end", - ); + end"; - test_script_execution(&script, &[1, 2, 3, 4, 5, 6, 7, 8], &[8, 7, 6, 5]); + let test = build_test!(source, &[1, 2, 3, 4, 5, 6, 7, 8]); + test.expect_stack(&[8, 7, 6, 5]); } #[test] fn nested_procedures() { // --- test nested procedures - pop/push ------------------------------------------------------ - let script = compile( - " + let source = " proc.foo.1 pop.local.0 end @@ -365,15 +325,14 @@ fn nested_procedures() { end begin exec.bar - end", - ); + end"; let inputs = [0, 1, 2, 3]; - test_script_execution(&script, &inputs, &[3, 1, 0]); + let test = build_test!(source, &inputs); + test.expect_stack(&[3, 1, 0]); // --- test nested procedures - popw/pushw ---------------------------------------------------- - let script = compile( - " + let source = " proc.foo.1 popw.local.0 end @@ -384,15 +343,14 @@ fn nested_procedures() { end begin exec.bar - end", - ); + end"; let inputs = [0, 1, 2, 3, 4, 5, 6, 7]; - test_script_execution(&script, &inputs, &[7, 6, 5, 4]); + let test = build_test!(source, &inputs); + test.expect_stack(&[7, 6, 5, 4]); // --- test nested procedures - storew/loadw -------------------------------------------------- - let script = compile( - " + let source = " proc.foo.1 push.0 push.0 storew.local.0 @@ -404,18 +362,17 @@ fn nested_procedures() { end begin exec.bar - end", - ); + end"; let inputs = [0, 1, 2, 3]; - test_script_execution(&script, &inputs, &[3, 2, 1, 0, 1, 0]); + let test = build_test!(source, &inputs); + test.expect_stack(&[3, 2, 1, 0, 1, 0]); } #[test] fn free_memory_pointer() { // ensure local procedure memory doesn't overwrite memory from outer scope - let script = compile( - " + let source = " proc.bar.2 pop.local.0 pop.local.1 @@ -430,9 +387,9 @@ fn free_memory_pointer() { push.mem.2 push.mem.1 push.mem.0 - end", - ); + end"; let inputs = [1, 2, 3, 4, 5, 6, 7]; - test_script_execution(&script, &inputs, &[7, 6, 5, 4, 1]); + let test = build_test!(source, &inputs); + test.expect_stack(&[7, 6, 5, 4, 1]); } diff --git a/processor/src/tests/io_ops/mem_ops.rs b/processor/src/tests/io_ops/mem_ops.rs index 0a2ea2f19f..260c7c1241 100644 --- a/processor/src/tests/io_ops/mem_ops.rs +++ b/processor/src/tests/io_ops/mem_ops.rs @@ -1,4 +1,4 @@ -use super::{compile, test_execution_failure, test_memory_write, test_script_execution}; +use super::{build_op_test, build_test, TestError}; // PUSHING VALUES ONTO THE STACK (PUSH) // ================================================================================================ @@ -9,15 +9,17 @@ fn push_mem() { let asm_op = "push.mem"; // --- read from uninitialized memory - address provided via the stack ------------------------ - let script = compile(format!("begin {} end", asm_op).as_str()); - test_script_execution(&script, &[addr], &[0]); + let test = build_op_test!(asm_op, &[addr]); + test.expect_stack(&[0]); // --- read from uninitialized memory - address provided as a parameter ----------------------- - let script = compile(format!("begin {}.{} end", asm_op, addr).as_str()); - test_script_execution(&script, &[], &[0]); + let asm_op = format!("{}.{}", asm_op, addr); + let test = build_op_test!(&asm_op); + test.expect_stack(&[0]); // --- the rest of the stack is unchanged ----------------------------------------------------- - test_script_execution(&script, &[1, 2, 3, 4], &[0, 4, 3, 2, 1]); + let test = build_op_test!(&asm_op, &[1, 2, 3, 4]); + test.expect_stack(&[0, 4, 3, 2, 1]); } #[test] @@ -26,15 +28,18 @@ fn pushw_mem() { let asm_op = "pushw.mem"; // --- read from uninitialized memory - address provided via the stack ------------------------ - let script = compile(format!("begin {} end", asm_op).as_str()); - test_script_execution(&script, &[addr], &[0, 0, 0, 0]); + let test = build_op_test!(asm_op, &[addr]); + test.expect_stack(&[0, 0, 0, 0]); // --- read from uninitialized memory - address provided as a parameter ----------------------- - let script = compile(format!("begin {}.{} end", asm_op, addr).as_str()); - test_script_execution(&script, &[], &[0, 0, 0, 0]); + let asm_op = format!("{}.{}", asm_op, addr); + + let test = build_op_test!(asm_op); + test.expect_stack(&[0, 0, 0, 0]); // --- the rest of the stack is unchanged ----------------------------------------------------- - test_script_execution(&script, &[1, 2, 3, 4], &[0, 0, 0, 0, 4, 3, 2, 1]); + let test = build_op_test!(asm_op, &[1, 2, 3, 4]); + test.expect_stack(&[0, 0, 0, 0, 4, 3, 2, 1]); } // REMOVING VALUES FROM THE STACK (POP) @@ -46,18 +51,13 @@ fn pop_mem() { let addr = 0; // --- address provided via the stack --------------------------------------------------------- - let script = compile(format!("begin {} end", asm_op).as_str()); - test_memory_write( - &script, - &[1, 2, 3, 4, addr], - &[3, 2, 1], - addr, - &[4, 0, 0, 0], - ); + let test = build_op_test!(asm_op, &[1, 2, 3, 4, addr]); + test.expect_stack_and_memory(&[3, 2, 1], addr, &[4, 0, 0, 0]); // --- address provided as a parameter -------------------------------------------------------- - let script = compile(format!("begin {}.{} end", asm_op, addr).as_str()); - test_memory_write(&script, &[1, 2, 3, 4], &[3, 2, 1], addr, &[4, 0, 0, 0]); + let asm_op = format!("{}.{}", asm_op, addr); + let test = build_op_test!(&asm_op, &[1, 2, 3, 4]); + test.expect_stack_and_memory(&[3, 2, 1], addr, &[4, 0, 0, 0]); } #[test] @@ -65,7 +65,8 @@ fn pop_mem_invalid() { let asm_op = "pop.mem.0"; // --- pop fails when stack is empty ---------------------------------------------------------- - test_execution_failure(asm_op, &[], "StackUnderflow"); + let test = build_op_test!(asm_op); + test.expect_error(TestError::ExecutionError("StackUnderflow")); } #[test] @@ -74,16 +75,17 @@ fn popw_mem() { let addr = 0; // --- address provided via the stack --------------------------------------------------------- - let script = compile(format!("begin {} end", asm_op).as_str()); - test_memory_write(&script, &[1, 2, 3, 4, addr], &[], addr, &[1, 2, 3, 4]); + let test = build_op_test!(asm_op, &[1, 2, 3, 4, addr]); + test.expect_stack_and_memory(&[], addr, &[1, 2, 3, 4]); // --- address provided as a parameter -------------------------------------------------------- - let script = compile(format!("begin {}.{} end", asm_op, addr).as_str()); - test_memory_write(&script, &[1, 2, 3, 4], &[], addr, &[1, 2, 3, 4]); + let asm_op = format!("{}.{}", asm_op, addr); + let test = build_op_test!(&asm_op, &[1, 2, 3, 4]); + test.expect_stack_and_memory(&[], addr, &[1, 2, 3, 4]); // --- the rest of the stack is unchanged ----------------------------------------------------- - let script = compile(format!("begin {}.{} end", asm_op, addr).as_str()); - test_memory_write(&script, &[0, 1, 2, 3, 4], &[0], addr, &[1, 2, 3, 4]); + let test = build_op_test!(&asm_op, &[0, 1, 2, 3, 4]); + test.expect_stack_and_memory(&[0], addr, &[1, 2, 3, 4]); } #[test] @@ -91,7 +93,8 @@ fn popw_mem_invalid() { let asm_op = "popw.mem.0"; // --- popw fails when the stack doesn't contain a full word ---------------------------------------------------------- - test_execution_failure(asm_op, &[1, 2], "StackUnderflow"); + let test = build_op_test!(asm_op, &[1, 2]); + test.expect_error(TestError::ExecutionError("StackUnderflow")); } // OVERWRITING VALUES ON THE STACK (LOAD) @@ -103,19 +106,19 @@ fn loadw_mem() { let asm_op = "loadw.mem"; // --- read from uninitialized memory - address provided via the stack ------------------------ - let script = compile(format!("begin {} end", asm_op).as_str()); - test_script_execution(&script, &[addr, 5, 6, 7, 8], &[0, 0, 0, 0]); + let test = build_op_test!(asm_op, &[addr, 5, 6, 7, 8]); + test.expect_stack(&[0, 0, 0, 0]); // --- read from uninitialized memory - address provided as a parameter ----------------------- - let script = compile(format!("begin {}.{} end", asm_op, addr).as_str()); - test_script_execution(&script, &[5, 6, 7, 8], &[0, 0, 0, 0]); + let asm_op = format!("{}.{}", asm_op, addr); + + let test = build_op_test!(asm_op, &[5, 6, 7, 8]); + test.expect_stack(&[0, 0, 0, 0]); // --- the rest of the stack is unchanged ----------------------------------------------------- - test_script_execution( - &script, - &[1, 2, 3, 4, 5, 6, 7, 8], - &[0, 0, 0, 0, 4, 3, 2, 1], - ); + + let test = build_op_test!(asm_op, &[1, 2, 3, 4, 5, 6, 7, 8]); + test.expect_stack(&[0, 0, 0, 0, 4, 3, 2, 1]); } // SAVING STACK VALUES WITHOUT REMOVING THEM (STORE) @@ -127,28 +130,17 @@ fn storew_mem() { let addr = 0; // --- address provided via the stack --------------------------------------------------------- - let script = compile(format!("begin {} end", asm_op).as_str()); - test_memory_write( - &script, - &[1, 2, 3, 4, addr], - &[4, 3, 2, 1], - addr, - &[1, 2, 3, 4], - ); + let test = build_op_test!(asm_op, &[1, 2, 3, 4, addr]); + test.expect_stack_and_memory(&[4, 3, 2, 1], addr, &[1, 2, 3, 4]); // --- address provided as a parameter -------------------------------------------------------- - let script = compile(format!("begin {}.{} end", asm_op, addr).as_str()); - test_memory_write(&script, &[1, 2, 3, 4], &[4, 3, 2, 1], addr, &[1, 2, 3, 4]); + let asm_op = format!("{}.{}", asm_op, addr); + let test = build_op_test!(&asm_op, &[1, 2, 3, 4]); + test.expect_stack_and_memory(&[4, 3, 2, 1], addr, &[1, 2, 3, 4]); // --- the rest of the stack is unchanged ----------------------------------------------------- - let script = compile(format!("begin {}.{} end", asm_op, addr).as_str()); - test_memory_write( - &script, - &[0, 1, 2, 3, 4], - &[4, 3, 2, 1, 0], - addr, - &[1, 2, 3, 4], - ); + let test = build_op_test!(&asm_op, &[0, 1, 2, 3, 4]); + test.expect_stack_and_memory(&[4, 3, 2, 1, 0], addr, &[1, 2, 3, 4]); } // PAIRED OPERATIONS - ABSOLUTE MEMORY (push/pop, pushw/popw, loadw/storew) @@ -157,8 +149,7 @@ fn storew_mem() { #[test] fn inverse_operations() { // --- pop and push are inverse operations, so the stack should be left unchanged ------------- - let script = compile( - " + let source = " begin push.0 pop.mem @@ -166,17 +157,17 @@ fn inverse_operations() { push.1 push.mem push.mem.0 - end", - ); + end"; + let inputs = [0, 1, 2, 3, 4]; let mut final_stack = inputs; final_stack.reverse(); - test_script_execution(&script, &inputs, &final_stack); + let test = build_test!(source, &inputs); + test.expect_stack(&final_stack); // --- popw and pushw are inverse operations, so the stack should be left unchanged ----------- - let script = compile( - " + let source = " begin push.0 popw.mem @@ -184,17 +175,17 @@ fn inverse_operations() { push.1 pushw.mem pushw.mem.0 - end", - ); + end"; + let inputs = [0, 1, 2, 3, 4, 5, 6, 7, 8]; let mut final_stack = inputs; final_stack.reverse(); - test_script_execution(&script, &inputs, &final_stack); + let test = build_test!(source, &inputs); + test.expect_stack(&final_stack); // --- storew and loadw are inverse operations, so the stack should be left unchanged --------- - let script = compile( - " + let source = " begin push.0 storew.mem @@ -202,26 +193,27 @@ fn inverse_operations() { push.1 loadw.mem loadw.mem.0 - end", - ); + end"; + let inputs = [0, 1, 2, 3, 4]; let mut final_stack = inputs; final_stack.reverse(); - test_script_execution(&script, &inputs, &final_stack); + let test = build_test!(source, &inputs); + test.expect_stack(&final_stack); } #[test] fn read_after_write() { // --- write to memory first, then test read with push -------------------------------------- - let script = compile("begin storew.mem.0 push.mem.0 end"); - test_script_execution(&script, &[1, 2, 3, 4], &[1, 4, 3, 2, 1]); + let test = build_op_test!("storew.mem.0 push.mem.0", &[1, 2, 3, 4]); + test.expect_stack(&[1, 4, 3, 2, 1]); // --- write to memory first, then test read with pushw -------------------------------------- - let script = compile("begin storew.mem.0 pushw.mem.0 end"); - test_script_execution(&script, &[1, 2, 3, 4], &[4, 3, 2, 1, 4, 3, 2, 1]); + let test = build_op_test!("storew.mem.0 pushw.mem.0", &[1, 2, 3, 4]); + test.expect_stack(&[4, 3, 2, 1, 4, 3, 2, 1]); // --- write to memory first, then test read with loadw -------------------------------------- - let script = compile("begin popw.mem.0 loadw.mem.0 end"); - test_script_execution(&script, &[1, 2, 3, 4, 5, 6, 7, 8], &[8, 7, 6, 5]); + let test = build_op_test!("popw.mem.0 loadw.mem.0", &[1, 2, 3, 4, 5, 6, 7, 8]); + test.expect_stack(&[8, 7, 6, 5]); } diff --git a/processor/src/tests/io_ops/mod.rs b/processor/src/tests/io_ops/mod.rs index fd9df9343f..5828fb6d77 100644 --- a/processor/src/tests/io_ops/mod.rs +++ b/processor/src/tests/io_ops/mod.rs @@ -1,7 +1,6 @@ use super::{ - super::ExecutionTrace, super::Process, super::ProgramInputs, super::Script, build_inputs, - compile, convert_to_stack, execute, push_to_stack, test_execution_failure, test_op_execution, - test_script_execution, test_script_execution_failure, Felt, + super::{build_op_test, build_test}, + TestError, }; mod adv_ops; @@ -9,30 +8,3 @@ mod constant_ops; mod env_ops; mod local_ops; mod mem_ops; - -// HELPER FUNCTIONS -// ================================================================================================ - -fn test_memory_write( - script: &Script, - stack_inputs: &[u64], - final_stack: &[u64], - mem_addr: u64, - expected_mem: &[u64], -) { - let inputs = build_inputs(stack_inputs); - let mut process = Process::new(inputs); - - // execute the test - process.execute_code_block(script.root()).unwrap(); - - // validate the memory state - let mem_state = process.memory.get_value(mem_addr).unwrap(); - let expected_mem: Vec = expected_mem.iter().map(|&v| Felt::new(v)).collect(); - assert_eq!(expected_mem, mem_state); - - // validate the stack state - let stack_state = ExecutionTrace::new(process).last_stack_state(); - let expected_stack = convert_to_stack(final_stack); - assert_eq!(expected_stack, stack_state); -} diff --git a/processor/src/tests/mod.rs b/processor/src/tests/mod.rs index c87f4bae90..16d9ff15d8 100644 --- a/processor/src/tests/mod.rs +++ b/processor/src/tests/mod.rs @@ -1,5 +1,7 @@ -use super::{execute, Felt, FieldElement, ProgramInputs, Script, STACK_TOP_SIZE}; -use crate::Word; +use super::{ + execute, ExecutionError, ExecutionTrace, Felt, FieldElement, Process, ProgramInputs, Script, + Word, STACK_TOP_SIZE, +}; use proptest::prelude::*; mod aux_table_trace; @@ -15,139 +17,226 @@ mod u32_ops; #[test] fn simple_program() { - let script = compile("begin push.1 push.2 add end"); - - let inputs = ProgramInputs::none(); - let trace = super::execute(&script, &inputs).unwrap(); - - let last_state = trace.last_stack_state(); - let expected_state = convert_to_stack(&[3]); - assert_eq!(expected_state, last_state); + let test = Test::new("begin push.1 push.2 add end"); + test.expect_stack(&[3]); } -// HELPER FUNCTIONS +// TEST HANDLER // ================================================================================================ -fn compile(source: &str) -> Script { - let assembler = assembly::Assembler::new(); - assembler.compile_script(source).unwrap() +/// This is used to specify the expected error type when using Test to test errors. +/// `Test::expect_error` will try to either compile or execute the test data, according to the +/// provided TestError variant. Then it will validate that the resulting error contains the +/// TestError variant's string slice. +pub enum TestError<'a> { + AssemblyError(&'a str), + ExecutionError(&'a str), } -fn build_inputs(stack_init: &[u64]) -> ProgramInputs { - ProgramInputs::new(stack_init, &[], vec![]).unwrap() +/// This is a container for the data required to run tests, which allows for running several +/// different types of tests. +/// +/// Types of valid result tests: +/// * - Execution test: check that running a script compiled from the given source has the specified +/// results for the given (optional) inputs. +/// * - Proptest: run an execution test inside a proptest. +/// +/// Types of failure tests: +/// * - Assembly error test: check that attempting to compile the given source causes an +/// AssemblyError which contains the specified substring. +/// * - Execution error test: check that running a script compiled from the given source causes an +/// ExecutionError which contains the specified substring. +pub struct Test { + source: String, + inputs: ProgramInputs, } -/// Takes an array of u64 values and builds a stack, perserving their order and converting them to -/// field elements. -fn convert_to_stack(values: &[u64]) -> [Felt; STACK_TOP_SIZE] { - let mut result = [Felt::ZERO; STACK_TOP_SIZE]; - for (&value, result) in values.iter().zip(result.iter_mut()) { - *result = Felt::new(value); +impl Test { + // CONSTRUCTOR + // -------------------------------------------------------------------------------------------- + + /// Creates the simplest possible new test, with only a source string and no inputs. + fn new(source: &str) -> Self { + Test { + source: String::from(source), + inputs: ProgramInputs::none(), + } } - result -} -/// Takes an array of u64 values, converts them to elements, and pushes them onto a stack, -/// reversing their order. -fn push_to_stack(values: &[u64]) -> [Felt; STACK_TOP_SIZE] { - let mut result = [Felt::ZERO; STACK_TOP_SIZE]; - for (&value, result) in values.iter().rev().zip(result.iter_mut()) { - *result = Felt::new(value); + // TEST METHODS + // -------------------------------------------------------------------------------------------- + + /// Asserts that running the test for the expected TestError variant will result in an error + /// that contains the TestError's error substring in its error message. + fn expect_error(&self, error: TestError) { + match error { + TestError::AssemblyError(substr) => { + assert_eq!( + std::panic::catch_unwind(|| self.compile()) + .err() + .and_then(|a| { a.downcast_ref::().map(|s| s.contains(substr)) }), + Some(true) + ); + } + TestError::ExecutionError(substr) => { + assert_eq!( + std::panic::catch_unwind(|| self.execute().unwrap()) + .err() + .and_then(|a| { a.downcast_ref::().map(|s| s.contains(substr)) }), + Some(true) + ); + } + } } - result -} -/// This helper function tests that when the given assembly script is executed on the -/// the provided inputs, it results in the specified final stack state. -/// - `inputs` should be provided in "normal" order. They'll be pushed onto the stack, reversing -/// their order. -/// - `final_stack` should be ordered to match the expected order of the stack after execution, -/// starting from the top. -fn test_script_execution(script: &Script, inputs: &[u64], final_stack: &[u64]) { - let expected_stack = convert_to_stack(final_stack); - let last_state = run_test_execution(script, inputs); - assert_eq!(expected_stack, last_state); -} + /// Builds a final stack from the provided stack-ordered array and asserts that executing the + /// test will result in the expected final stack state. + fn expect_stack(&self, final_stack: &[u64]) { + let expected = convert_to_stack(final_stack); + let result = self.get_last_stack_state(); -/// This helper function tests that when the given assembly instruction is executed on the -/// the provided inputs, it results in the specified final stack state. -/// - `inputs` should be provided in "normal" order. They'll be pushed onto the stack, reversing -/// their order. -/// - `final_stack` should be ordered to match the expected order of the stack after execution, -/// starting from the top. -fn test_op_execution(asm_op: &str, inputs: &[u64], final_stack: &[u64]) { - let script = compile(format!("begin {} end", asm_op).as_str()); - test_script_execution(&script, inputs, final_stack); -} + assert_eq!(expected, result); + } -/// This helper function is the same as `test_op_execution`, except that when it is used inside a -/// proptest it will return a test failure instead of panicking if the assertion condition fails. -fn test_op_execution_proptest( - asm_op: &str, - inputs: &[u64], - final_stack: &[u64], -) -> Result<(), proptest::test_runner::TestCaseError> { - let script = compile(format!("begin {} end", asm_op).as_str()); - let expected_stack = convert_to_stack(final_stack); - let last_state = run_test_execution(&script, inputs); + /// Executes the test and validates that the process memory has the elements of `expected_mem` + /// at address `mem_addr` and that the end of the stack execution trace matches the + /// `final_stack`. + fn expect_stack_and_memory(&self, final_stack: &[u64], mem_addr: u64, expected_mem: &[u64]) { + let mut process = Process::new(self.inputs.clone()); - prop_assert_eq!(expected_stack, last_state); + // execute the test + process.execute_code_block(self.compile().root()).unwrap(); - Ok(()) -} + // validate the memory state + let mem_state = process.memory.get_value(mem_addr).unwrap(); + let expected_mem: Vec = expected_mem.iter().map(|&v| Felt::new(v)).collect(); + assert_eq!(expected_mem, mem_state); -/// Executes the given script over the provided inputs and returns the last state of the resulting -/// stack for validation. -fn run_test_execution(script: &Script, inputs: &[u64]) -> [Felt; STACK_TOP_SIZE] { - let inputs = build_inputs(inputs); - let trace = execute(script, &inputs).unwrap(); + // validate the stack state + let stack_state = ExecutionTrace::new(process).last_stack_state(); + let expected_stack = convert_to_stack(final_stack); + assert_eq!(expected_stack, stack_state); + } - trace.last_stack_state() -} + /// Asserts that executing the test inside a proptest results in the expected final stack state. + /// The proptest will return a test failure instead of panicking if the assertion condition + /// fails. + fn prop_expect_stack( + &self, + final_stack: &[u64], + ) -> Result<(), proptest::test_runner::TestCaseError> { + let expected = convert_to_stack(final_stack); + let result = self.get_last_stack_state(); -/// This helper function tests failures where the execution of a given assembly script with the -/// provided inputs is expected to panic. This function catches the panic and tests it against a -/// provided string to make sure it contains the expected error string. -fn test_script_execution_failure(script: &Script, inputs: &[u64], err_substr: &str) { - let inputs = build_inputs(inputs); - assert_eq!( - std::panic::catch_unwind(|| execute(script, &inputs).unwrap()) - .err() - .and_then(|a| { a.downcast_ref::().map(|s| s.contains(err_substr)) }), - Some(true) - ); -} + prop_assert_eq!(expected, result); -/// This helper function tests failures where the execution of a given assembly operation with the -/// provided inputs is expected to panic. This function catches the panic and tests it against a -/// provided string to make sure it contains the expected error string. -fn test_execution_failure(asm_op: &str, inputs: &[u64], err_substr: &str) { - let script = compile(format!("begin {} end", asm_op).as_str()); + Ok(()) + } - test_script_execution_failure(&script, inputs, err_substr); -} + // UTILITY METHODS + // -------------------------------------------------------------------------------------------- + + /// Compiles a test's source and returns the resulting Script. + fn compile(&self) -> Script { + let assembler = assembly::Assembler::new(); + assembler + .compile_script(&self.source) + .expect("Failed to compile test source.") + } + + /// Compiles the test's source to a Script and executes it with the tests inputs. Returns a + /// resulting execution trace or error. + fn execute(&self) -> Result { + let script = self.compile(); + execute(&script, &self.inputs) + } + + /// Returns the last state of the stack after executing a test. + fn get_last_stack_state(&self) -> [Felt; STACK_TOP_SIZE] { + let trace = self.execute().unwrap(); -/// This helper function tests failures where the compilation of a given assembly operation is -/// expected to panic. This function catches the panic and tests it against a provided string to -/// make sure it contains the expected error string. -fn test_compilation_failure(asm_op: &str, err_substr: &str) { - assert_eq!( - std::panic::catch_unwind(|| compile(format!("begin {} end", asm_op).as_str())) - .err() - .and_then(|a| { a.downcast_ref::().map(|s| s.contains(err_substr)) }), - Some(true) - ); + trace.last_stack_state() + } } -/// This helper function tests a provided assembly operation which takes a single parameter -/// to ensure that it fails when that parameter is over the maximum allowed value (out of bounds). -fn test_param_out_of_bounds(asm_op_base: &str, gt_max_value: u64) { - let build_asm_op = |param: u64| format!("{}.{}", asm_op_base, param); +// HELPER FUNCTIONS +// ================================================================================================ - test_compilation_failure(build_asm_op(gt_max_value).as_str(), "parameter"); +/// Takes an array of u64 values and builds a stack, perserving their order and converting them to +/// field elements. +fn convert_to_stack(values: &[u64]) -> [Felt; STACK_TOP_SIZE] { + let mut result = [Felt::ZERO; STACK_TOP_SIZE]; + for (&value, result) in values.iter().zip(result.iter_mut()) { + *result = Felt::new(value); + } + result } // This is a proptest strategy for generating a random word with 4 values of type T. -fn rand_word() -> impl Strategy> { +fn prop_randw() -> impl Strategy> { prop::collection::vec(any::(), 4) } + +// MACROS +// ================================================================================================ + +/// Returns a Test struct from the provided source string and any specified stack and advice inputs. +/// +/// Parameters are expected in the following order: +/// `source`, `stack_inputs` (optional), `advice_tape` (optional), `advice_sets` (optional) +/// +/// * `source`: a well-formed source string. +/// * `stack_inputs` (optional): the initial inputs which must be at the top of the stack before +/// executing the `source`. Stack inputs can be provided independently without any advice inputs. +/// * `advice_tape` (optional): the initial advice tape values. When provided, `stack_inputs` and +/// `advice_sets` are also expected. +/// * `advice_sets` (optional): the initial advice set values. When provided, `stack_inputs` and +/// `advice_tape` are also expected. +#[macro_export] +macro_rules! build_test { + ($source:expr) => {{ + $crate::tests::Test::new($source) + }}; + ($source:expr, $stack_inputs:expr) => {{ + let inputs = $crate::tests::ProgramInputs::new($stack_inputs, &[], vec![]).unwrap(); + + $crate::tests::Test { + source: String::from($source), + inputs, + } + }}; + ($source:expr, $stack_inputs:expr, $advice_tape:expr, $advice_sets:expr) => {{ + let inputs = + $crate::tests::ProgramInputs::new($stack_inputs, $advice_tape, $advice_sets).unwrap(); + + $crate::tests::Test { + source: String::from($source), + inputs, + } + }}; +} + +/// Returns a Test struct from a string of one or more operations and any specified stack and advice +/// inputs. +/// +/// Parameters are expected in the following order: +/// `source`, `stack_inputs` (optional), `advice_tape` (optional), `advice_sets` (optional) +/// +/// * `source`: a well-formed source string. +/// * `stack_inputs` (optional): the initial inputs which must be at the top of the stack before +/// executing the `source`. Stack inputs can be provided independently without any advice inputs. +/// * `advice_tape` (optional): the initial advice tape values. When provided, `stack_inputs` and +/// `advice_sets` are also expected. +/// * `advice_sets` (optional): the initial advice set values. When provided, `stack_inputs` and +/// `advice_tape` are also expected. +#[macro_export] +macro_rules! build_op_test { + ($op_str:expr) => {{ + let source = format!("begin {} end", $op_str); + $crate::build_test!(&source) + }}; + ($op_str:expr, $($tail:tt)+) => {{ + let source = format!("begin {} end", $op_str); + $crate::build_test!(&source, $($tail)+) + }}; +} diff --git a/processor/src/tests/stdlib/mod.rs b/processor/src/tests/stdlib/mod.rs index a1c07b5657..b011e3569e 100644 --- a/processor/src/tests/stdlib/mod.rs +++ b/processor/src/tests/stdlib/mod.rs @@ -1,4 +1,4 @@ -use super::{compile, test_script_execution}; +use super::super::build_test; mod crypto; mod u64_mod; diff --git a/processor/src/tests/stdlib/u64_mod.rs b/processor/src/tests/stdlib/u64_mod.rs index 5b1da1211b..2d1651d858 100644 --- a/processor/src/tests/stdlib/u64_mod.rs +++ b/processor/src/tests/stdlib/u64_mod.rs @@ -1,4 +1,4 @@ -use super::{compile, test_script_execution}; +use super::build_test; use rand_utils::rand_value; #[test] @@ -7,19 +7,18 @@ fn add_unsafe() { let b: u64 = rand_value(); let c = a.wrapping_add(b); - let script = compile( - " + let source = " use.std::math::u64 begin exec.u64::add_unsafe - end", - ); + end"; let (a1, a0) = split_u64(a); let (b1, b0) = split_u64(b); let (c1, c0) = split_u64(c); - test_script_execution(&script, &[a0, a1, b0, b1], &[c1, c0]); + let test = build_test!(source, &[a0, a1, b0, b1]); + test.expect_stack(&[c1, c0]); } #[test] @@ -28,19 +27,18 @@ fn mul_unsafe() { let b: u64 = rand_value(); let c = a.wrapping_mul(b); - let script = compile( - " + let source = " use.std::math::u64 begin exec.u64::mul_unsafe - end", - ); + end"; let (a1, a0) = split_u64(a); let (b1, b0) = split_u64(b); let (c1, c0) = split_u64(c); - test_script_execution(&script, &[a0, a1, b0, b1], &[c1, c0]); + let test = build_test!(source, &[a0, a1, b0, b1]); + test.expect_stack(&[c1, c0]); } #[test] @@ -49,23 +47,24 @@ fn div_unsafe() { let b: u64 = rand_value(); let c = a / b; - let script = compile( - " + let source = " use.std::math::u64 begin exec.u64::div_unsafe - end", - ); + end"; let (a1, a0) = split_u64(a); let (b1, b0) = split_u64(b); let (c1, c0) = split_u64(c); - test_script_execution(&script, &[a0, a1, b0, b1], &[c1, c0]); + let test = build_test!(source, &[a0, a1, b0, b1]); + test.expect_stack(&[c1, c0]); let d = a / b0; let (d1, d0) = split_u64(d); - test_script_execution(&script, &[a0, a1, b0, 0], &[d1, d0]); + + let test = build_test!(source, &[a0, a1, b0, 0]); + test.expect_stack(&[d1, d0]); } // HELPER FUNCTIONS diff --git a/processor/src/tests/u32_ops/arithmetic_ops.rs b/processor/src/tests/u32_ops/arithmetic_ops.rs index 3de65f39fe..8bf0d08287 100644 --- a/processor/src/tests/u32_ops/arithmetic_ops.rs +++ b/processor/src/tests/u32_ops/arithmetic_ops.rs @@ -1,8 +1,4 @@ -use super::{ - build_inputs, compile, execute, test_compilation_failure, test_execution_failure, - test_op_execution, test_op_execution_proptest, test_param_out_of_bounds, test_unsafe_execution, - U32_BOUND, -}; +use super::{build_op_test, test_param_out_of_bounds, test_unsafe_execution, TestError, U32_BOUND}; use proptest::prelude::*; use rand_utils::rand_value; @@ -14,7 +10,8 @@ fn u32add() { let asm_op = "u32add"; // --- simple case ---------------------------------------------------------------------------- - test_op_execution(asm_op, &[1, 2], &[3]); + let test = build_op_test!(asm_op, &[1, 2]); + test.expect_stack(&[3]); // --- random values -------------------------------------------------------------------------- // test using u16 values to ensure there's no overflow so the result is valid @@ -22,11 +19,13 @@ fn u32add() { let b = rand_value::() as u16; let expected = a as u64 + b as u64; - test_op_execution(asm_op, &[a as u64, b as u64], &[expected]); + let test = build_op_test!(asm_op, &[a as u64, b as u64]); + test.expect_stack(&[expected]); // --- test that the rest of the stack isn't affected ----------------------------------------- let c = rand_value::(); - test_op_execution(asm_op, &[c, a as u64, b as u64], &[expected, c]); + let test = build_op_test!(asm_op, &[c, a as u64, b as u64]); + test.expect_stack(&[expected, c]); } #[test] @@ -34,15 +33,18 @@ fn u32add_fail() { let asm_op = "u32add"; // should fail if a >= 2^32 - test_execution_failure(asm_op, &[U32_BOUND, 0], "FailedAssertion"); + let test = build_op_test!(asm_op, &[U32_BOUND, 0]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); // should fail if b >= 2^32 - test_execution_failure(asm_op, &[0, U32_BOUND], "FailedAssertion"); + let test = build_op_test!(asm_op, &[0, U32_BOUND]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); // should fail if a + b >= 2^32 let a = u32::MAX; let b = 1_u64; - test_execution_failure(asm_op, &[a as u64, b], "FailedAssertion"); + let test = build_op_test!(asm_op, &[a as u64, b]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); } #[test] @@ -50,18 +52,23 @@ fn u32add_b() { let build_asm_op = |param: u16| format!("u32add.{}", param); // --- simple case ---------------------------------------------------------------------------- - test_op_execution(build_asm_op(2).as_str(), &[1], &[3]); + let test = build_op_test!(build_asm_op(2).as_str(), &[1]); + test.expect_stack(&[3]); // --- random values -------------------------------------------------------------------------- // test using u16 values to ensure there's no overflow so the result is valid let a = rand_value::() as u16; let b = rand_value::() as u16; let expected = a as u64 + b as u64; - test_op_execution(build_asm_op(b).as_str(), &[a as u64], &[expected]); + + let test = build_op_test!(build_asm_op(b).as_str(), &[a as u64]); + test.expect_stack(&[expected]); // --- test that the rest of the stack isn't affected ----------------------------------------- let c = rand_value::(); - test_op_execution(build_asm_op(b).as_str(), &[c, a as u64], &[expected, c]); + + let test = build_op_test!(build_asm_op(b).as_str(), &[c, a as u64]); + test.expect_stack(&[expected, c]); } #[test] @@ -69,7 +76,8 @@ fn u32add_b_fail() { let build_asm_op = |param: u64| format!("u32add.{}", param); // should fail during execution if a >= 2^32 - test_execution_failure(build_asm_op(0).as_str(), &[U32_BOUND], "FailedAssertion"); + let test = build_op_test!(build_asm_op(0).as_str(), &[U32_BOUND]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); // should fail during compilation if b >= 2^32 test_param_out_of_bounds(build_asm_op(U32_BOUND).as_str(), U32_BOUND); @@ -77,7 +85,9 @@ fn u32add_b_fail() { // should fail if a + b >= 2^32 let a = u32::MAX; let b = 1_u64; - test_execution_failure(build_asm_op(b).as_str(), &[a as u64], "FailedAssertion"); + + let test = build_op_test!(build_asm_op(b).as_str(), &[a as u64]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); } #[test] @@ -94,10 +104,12 @@ fn u32add_full_fail() { let asm_op = "u32add.full"; // should fail if a >= 2^32 - test_execution_failure(asm_op, &[U32_BOUND, 0], "FailedAssertion"); + let test = build_op_test!(asm_op, &[U32_BOUND, 0]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); // should fail if b >= 2^32 - test_execution_failure(asm_op, &[0, U32_BOUND], "FailedAssertion"); + let test = build_op_test!(asm_op, &[0, U32_BOUND]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); } #[test] @@ -126,13 +138,16 @@ fn u32addc_fail() { let asm_op = "u32addc"; // should fail if a >= 2^32 - test_execution_failure(asm_op, &[0, 0, U32_BOUND, 0], "FailedAssertion"); + let test = build_op_test!(asm_op, &[0, 0, U32_BOUND, 0]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); // should fail if b >= 2^32 - test_execution_failure(asm_op, &[0, U32_BOUND, 0], "FailedAssertion"); + let test = build_op_test!(asm_op, &[0, U32_BOUND, 0]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); // should fail if c > 1 - test_execution_failure(asm_op, &[2, 0, 0], "NotBinaryValue"); + let test = build_op_test!(asm_op, &[2, 0, 0]); + test.expect_error(TestError::ExecutionError("NotBinaryValue")); } #[test] @@ -145,15 +160,14 @@ fn u32addc_unsafe() { test_addc(asm_op); // --- test that out of bounds inputs do not cause a failure ---------------------------------- - let script = compile(format!("begin {} end", asm_op).as_str()); // should not fail if a >= 2^32 - let inputs = build_inputs(&[0, 0, U32_BOUND]); - assert!(execute(&script, &inputs).is_ok()); + let test = build_op_test!(asm_op, &[0, 0, U32_BOUND]); + assert!(test.execute().is_ok()); // should not fail if b >= 2^32 - let inputs = build_inputs(&[0, U32_BOUND, 0]); - assert!(execute(&script, &inputs).is_ok()); + let test = build_op_test!(asm_op, &[0, U32_BOUND, 0]); + assert!(test.execute().is_ok()); } #[test] @@ -161,7 +175,8 @@ fn u32addc_unsafe_fail() { let asm_op = "u32addc.unsafe"; // should fail if c > 1 - test_execution_failure(asm_op, &[2, U32_BOUND, 0], "NotBinaryValue"); + let test = build_op_test!(asm_op, &[2, U32_BOUND, 0]); + test.expect_error(TestError::ExecutionError("NotBinaryValue")); } #[test] @@ -169,8 +184,11 @@ fn u32sub() { let asm_op = "u32sub"; // --- simple cases --------------------------------------------------------------------------- - test_op_execution(asm_op, &[1, 1], &[0]); - test_op_execution(asm_op, &[2, 1], &[1]); + let test = build_op_test!(asm_op, &[1, 1]); + test.expect_stack(&[0]); + + let test = build_op_test!(asm_op, &[2, 1]); + test.expect_stack(&[1]); // --- random u32 values ---------------------------------------------------------------------- let val1 = rand_value::() as u32; @@ -183,11 +201,14 @@ fn u32sub() { }; let expected = a - b; - test_op_execution(asm_op, &[a as u64, b as u64], &[expected as u64]); + let test = build_op_test!(asm_op, &[a as u64, b as u64]); + test.expect_stack(&[expected as u64]); // --- test that the rest of the stack isn't affected ----------------------------------------- let c = rand_value::(); - test_op_execution(asm_op, &[c, a as u64, b as u64], &[expected as u64, c]); + + let test = build_op_test!(asm_op, &[c, a as u64, b as u64]); + test.expect_stack(&[expected as u64, c]); } #[test] @@ -195,15 +216,18 @@ fn u32sub_fail() { let asm_op = "u32sub"; // should fail if a >= 2^32 - test_execution_failure(asm_op, &[U32_BOUND, 0], "FailedAssertion"); + let test = build_op_test!(asm_op, &[U32_BOUND, 0]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); // should fail if b >= 2^32 - test_execution_failure(asm_op, &[0, U32_BOUND], "FailedAssertion"); + let test = build_op_test!(asm_op, &[0, U32_BOUND]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); // should fail if a < b let a = 1_u64; let b = 2_u64; - test_execution_failure(asm_op, &[a, b], "FailedAssertion"); + let test = build_op_test!(asm_op, &[a, b]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); } #[test] @@ -211,8 +235,11 @@ fn u32sub_b() { let build_asm_op = |param: u32| format!("u32sub.{}", param); // --- simple cases --------------------------------------------------------------------------- - test_op_execution(build_asm_op(1).as_str(), &[2], &[1]); - test_op_execution(build_asm_op(1).as_str(), &[1], &[0]); + let test = build_op_test!(build_asm_op(1).as_str(), &[2]); + test.expect_stack(&[1]); + + let test = build_op_test!(build_asm_op(1).as_str(), &[1]); + test.expect_stack(&[0]); // --- random u32 values ---------------------------------------------------------------------- let val1 = rand_value::() as u32; @@ -224,15 +251,15 @@ fn u32sub_b() { (val2, val1) }; let expected = a - b; - test_op_execution(build_asm_op(b).as_str(), &[a as u64], &[expected as u64]); + + let test = build_op_test!(build_asm_op(b).as_str(), &[a as u64]); + test.expect_stack(&[expected as u64]); // --- test that the rest of the stack isn't affected ----------------------------------------- let c = rand_value::(); - test_op_execution( - build_asm_op(b).as_str(), - &[c, a as u64], - &[expected as u64, c], - ); + + let test = build_op_test!(build_asm_op(b).as_str(), &[c, a as u64]); + test.expect_stack(&[expected as u64, c]); } #[test] @@ -240,7 +267,8 @@ fn u32sub_b_fail() { let build_asm_op = |param: u64| format!("u32sub.{}", param); // should fail during execution if a >= 2^32 - test_execution_failure(build_asm_op(0).as_str(), &[U32_BOUND], "FailedAssertion"); + let test = build_op_test!(build_asm_op(0).as_str(), &[U32_BOUND]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); // should fail during compilation if b >= 2^32 test_param_out_of_bounds(build_asm_op(U32_BOUND).as_str(), U32_BOUND); @@ -248,7 +276,8 @@ fn u32sub_b_fail() { // should fail if a < b let a = 1_u64; let b = 2_u64; - test_execution_failure(build_asm_op(b).as_str(), &[a], "FailedAssertion"); + let test = build_op_test!(build_asm_op(b).as_str(), &[a]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); } #[test] @@ -265,10 +294,12 @@ fn u32sub_full_fail() { let asm_op = "u32sub.full"; // should fail if a >= 2^32 - test_execution_failure(asm_op, &[U32_BOUND, 0], "FailedAssertion"); + let test = build_op_test!(asm_op, &[U32_BOUND, 0]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); // should fail if b >= 2^32 - test_execution_failure(asm_op, &[0, U32_BOUND], "FailedAssertion"); + let test = build_op_test!(asm_op, &[0, U32_BOUND]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); } #[test] @@ -288,9 +319,14 @@ fn u32mul() { let asm_op = "u32mul"; // --- simple cases --------------------------------------------------------------------------- - test_op_execution(asm_op, &[1, 0], &[0]); - test_op_execution(asm_op, &[5, 1], &[5]); - test_op_execution(asm_op, &[2, 5], &[10]); + let test = build_op_test!(asm_op, &[1, 0]); + test.expect_stack(&[0]); + + let test = build_op_test!(asm_op, &[5, 1]); + test.expect_stack(&[5]); + + let test = build_op_test!(asm_op, &[2, 5]); + test.expect_stack(&[10]); // --- random values -------------------------------------------------------------------------- // test using u16 values to ensure there's no overflow so the result is valid @@ -298,11 +334,13 @@ fn u32mul() { let b = rand_value::() as u16; let expected: u64 = a as u64 * b as u64; - test_op_execution(asm_op, &[a as u64, b as u64], &[expected]); + let test = build_op_test!(asm_op, &[a as u64, b as u64]); + test.expect_stack(&[expected]); // --- test that the rest of the stack isn't affected ----------------------------------------- let c = rand_value::(); - test_op_execution(asm_op, &[c, a as u64, b as u64], &[expected, c]); + let test = build_op_test!(asm_op, &[c, a as u64, b as u64]); + test.expect_stack(&[expected, c]); } #[test] @@ -310,15 +348,18 @@ fn u32mul_fail() { let asm_op = "u32mul"; // should fail if a >= 2^32 - test_execution_failure(asm_op, &[U32_BOUND, 0], "FailedAssertion"); + let test = build_op_test!(asm_op, &[U32_BOUND, 0]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); // should fail if b >= 2^32 - test_execution_failure(asm_op, &[0, U32_BOUND], "FailedAssertion"); + let test = build_op_test!(asm_op, &[0, U32_BOUND]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); // should fail if a * b >= 2^32 let a = u32::MAX as u64; let b = 2_u64; - test_execution_failure(asm_op, &[a, b], "FailedAssertion"); + let test = build_op_test!(asm_op, &[a, b]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); } #[test] @@ -326,9 +367,14 @@ fn u32mul_b() { let build_asm_op = |param: u16| format!("u32mul.{}", param); // --- simple cases --------------------------------------------------------------------------- - test_op_execution(build_asm_op(0).as_str(), &[1], &[0]); - test_op_execution(build_asm_op(1).as_str(), &[5], &[5]); - test_op_execution(build_asm_op(5).as_str(), &[2], &[10]); + let test = build_op_test!(build_asm_op(0).as_str(), &[1]); + test.expect_stack(&[0]); + + let test = build_op_test!(build_asm_op(1).as_str(), &[5]); + test.expect_stack(&[5]); + + let test = build_op_test!(build_asm_op(5).as_str(), &[2]); + test.expect_stack(&[10]); // --- random values -------------------------------------------------------------------------- // test using u16 values to ensure there's no overflow so the result is valid @@ -336,11 +382,13 @@ fn u32mul_b() { let b = rand_value::() as u16; let expected: u64 = a as u64 * b as u64; - test_op_execution(build_asm_op(b).as_str(), &[a as u64], &[expected as u64]); + let test = build_op_test!(build_asm_op(b).as_str(), &[a as u64]); + test.expect_stack(&[expected as u64]); // --- test that the rest of the stack isn't affected ----------------------------------------- let c = rand_value::(); - test_op_execution(build_asm_op(5).as_str(), &[c, 10], &[50, c]); + let test = build_op_test!(build_asm_op(5).as_str(), &[c, 10]); + test.expect_stack(&[50, c]); } #[test] @@ -348,7 +396,8 @@ fn u32mul_b_fail() { let build_asm_op = |param: u64| format!("u32mul.{}", param); // should fail during execution if a >= 2^32 - test_execution_failure(build_asm_op(0).as_str(), &[U32_BOUND], "FailedAssertion"); + let test = build_op_test!(build_asm_op(0).as_str(), &[U32_BOUND]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); // should fail during compilation if b >= 2^32 test_param_out_of_bounds(build_asm_op(U32_BOUND).as_str(), U32_BOUND); @@ -356,7 +405,8 @@ fn u32mul_b_fail() { // should fail if a * b >= 2^32 let a = u32::MAX as u64; let b = u32::MAX as u64; - test_execution_failure(build_asm_op(b).as_str(), &[a], "FailedAssertion"); + let test = build_op_test!(build_asm_op(b).as_str(), &[a]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); } #[test] @@ -373,10 +423,12 @@ fn u32mul_full_fail() { let asm_op = "u32mul.full"; // should fail if a >= 2^32 - test_execution_failure(asm_op, &[U32_BOUND, 0], "FailedAssertion"); + let test = build_op_test!(asm_op, &[U32_BOUND, 0]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); // should fail if b >= 2^32 - test_execution_failure(asm_op, &[0, U32_BOUND], "FailedAssertion"); + let test = build_op_test!(asm_op, &[0, U32_BOUND]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); } #[test] @@ -405,13 +457,16 @@ fn u32madd_fail() { let asm_op = "u32madd"; // should fail if a >= 2^32 - test_execution_failure(asm_op, &[0, 0, U32_BOUND], "FailedAssertion"); + let test = build_op_test!(asm_op, &[0, 0, U32_BOUND]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); // should fail if b >= 2^32 - test_execution_failure(asm_op, &[0, U32_BOUND, 0], "FailedAssertion"); + let test = build_op_test!(asm_op, &[0, U32_BOUND, 0]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); // should fail if c >= 2^32 - test_execution_failure(asm_op, &[U32_BOUND, 0, 0], "FailedAssertion"); + let test = build_op_test!(asm_op, &[U32_BOUND, 0, 0]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); } #[test] @@ -432,21 +487,29 @@ fn u32div() { let asm_op = "u32div"; // --- simple cases --------------------------------------------------------------------------- - test_op_execution(asm_op, &[0, 1], &[0]); + let test = build_op_test!(asm_op, &[0, 1]); + test.expect_stack(&[0]); + // division with no remainder - test_op_execution(asm_op, &[2, 1], &[2]); + let test = build_op_test!(asm_op, &[2, 1]); + test.expect_stack(&[2]); + // division with remainder - test_op_execution(asm_op, &[1, 2], &[0]); + let test = build_op_test!(asm_op, &[1, 2]); + test.expect_stack(&[0]); // --- random u32 values ---------------------------------------------------------------------- let a = rand_value::() as u32; let b = rand_value::() as u32; let expected = a / b; - test_op_execution(asm_op, &[a as u64, b as u64], &[expected as u64]); + let test = build_op_test!(asm_op, &[a as u64, b as u64]); + test.expect_stack(&[expected as u64]); // --- test that the rest of the stack isn't affected ----------------------------------------- let c = rand_value::(); - test_op_execution(asm_op, &[c, a as u64, b as u64], &[expected as u64, c]); + + let test = build_op_test!(asm_op, &[c, a as u64, b as u64]); + test.expect_stack(&[expected as u64, c]); } #[test] @@ -454,13 +517,16 @@ fn u32div_fail() { let asm_op = "u32div"; // should fail if a >= 2^32 - test_execution_failure(asm_op, &[U32_BOUND, 1], "FailedAssertion"); + let test = build_op_test!(asm_op, &[U32_BOUND, 1]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); // should fail if b >= 2^32 - test_execution_failure(asm_op, &[1, U32_BOUND], "FailedAssertion"); + let test = build_op_test!(asm_op, &[1, U32_BOUND]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); // should fail if b == 0 - test_execution_failure(asm_op, &[1, 0], "DivideByZero"); + let test = build_op_test!(asm_op, &[1, 0]); + test.expect_error(TestError::ExecutionError("DivideByZero")); } #[test] @@ -469,25 +535,30 @@ fn u32div_b() { let build_asm_op = |param: u32| format!("u32div.{}", param); // --- simple cases --------------------------------------------------------------------------- - test_op_execution(build_asm_op(1).as_str(), &[0], &[0]); + let test = build_op_test!(build_asm_op(1).as_str(), &[0]); + test.expect_stack(&[0]); + // division with no remainder - test_op_execution(build_asm_op(1).as_str(), &[2], &[2]); + let test = build_op_test!(build_asm_op(1).as_str(), &[2]); + test.expect_stack(&[2]); + // division with remainder - test_op_execution(build_asm_op(2).as_str(), &[1], &[0]); + let test = build_op_test!(build_asm_op(2).as_str(), &[1]); + test.expect_stack(&[0]); // --- random u32 values ---------------------------------------------------------------------- let a = rand_value::() as u32; let b = rand_value::() as u32; let expected = a / b; - test_op_execution(build_asm_op(b).as_str(), &[a as u64], &[expected as u64]); + + let test = build_op_test!(build_asm_op(b).as_str(), &[a as u64]); + test.expect_stack(&[expected as u64]); // --- test that the rest of the stack isn't affected ----------------------------------------- let c = rand_value::(); - test_op_execution( - build_asm_op(b).as_str(), - &[c, a as u64], - &[expected as u64, c], - ); + + let test = build_op_test!(build_asm_op(b).as_str(), &[c, a as u64]); + test.expect_stack(&[expected as u64, c]); } #[test] @@ -496,13 +567,15 @@ fn u32div_b_fail() { let build_asm_op = |param: u64| format!("u32div.{}", param); // should fail during execution if a >= 2^32 - test_execution_failure(build_asm_op(1).as_str(), &[U32_BOUND], "FailedAssertion"); + let test = build_op_test!(build_asm_op(1).as_str(), &[U32_BOUND]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); // should fail during compilation if b >= 2^32 test_param_out_of_bounds(build_asm_op(U32_BOUND).as_str(), U32_BOUND); // should fail during compilation if b = 0 - test_compilation_failure(build_asm_op(0).as_str(), "parameter"); + let test = build_op_test!(build_asm_op(0).as_str()); + test.expect_error(TestError::AssemblyError("parameter")); } #[test] @@ -519,13 +592,16 @@ fn u32div_full_fail() { let asm_op = "u32div.full"; // should fail if a >= 2^32 - test_execution_failure(asm_op, &[U32_BOUND, 1], "FailedAssertion"); + let test = build_op_test!(asm_op, &[U32_BOUND, 1]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); // should fail if b >= 2^32 - test_execution_failure(asm_op, &[1, U32_BOUND], "FailedAssertion"); + let test = build_op_test!(asm_op, &[1, U32_BOUND]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); // should fail if b == 0 - test_execution_failure(asm_op, &[1, 0], "DivideByZero"); + let test = build_op_test!(asm_op, &[1, 0]); + test.expect_error(TestError::ExecutionError("DivideByZero")); } #[test] @@ -545,7 +621,8 @@ fn u32div_unsafe_fail() { let asm_op = "u32div.unsafe"; // should fail if b == 0 - test_execution_failure(asm_op, &[1, 0], "DivideByZero"); + let test = build_op_test!(asm_op, &[1, 0]); + test.expect_error(TestError::ExecutionError("DivideByZero")); } #[test] @@ -561,13 +638,16 @@ fn u32mod_fail() { let asm_op = "u32mod"; // should fail if a >= 2^32 - test_execution_failure(asm_op, &[U32_BOUND, 1], "FailedAssertion"); + let test = build_op_test!(asm_op, &[U32_BOUND, 1]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); // should fail if b >= 2^32 - test_execution_failure(asm_op, &[1, U32_BOUND], "FailedAssertion"); + let test = build_op_test!(asm_op, &[1, U32_BOUND]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); // should fail if b == 0 - test_execution_failure(asm_op, &[1, 0], "DivideByZero"); + let test = build_op_test!(asm_op, &[1, 0]); + test.expect_error(TestError::ExecutionError("DivideByZero")); } #[test] @@ -575,9 +655,14 @@ fn u32mod_b() { let build_asm_op = |param: u32| format!("u32mod.{}", param); // --- simple cases --------------------------------------------------------------------------- - test_op_execution(build_asm_op(5).as_str(), &[10], &[0]); - test_op_execution(build_asm_op(5).as_str(), &[11], &[1]); - test_op_execution(build_asm_op(11).as_str(), &[5], &[5]); + let test = build_op_test!(build_asm_op(5).as_str(), &[10]); + test.expect_stack(&[0]); + + let test = build_op_test!(build_asm_op(5).as_str(), &[11]); + test.expect_stack(&[1]); + + let test = build_op_test!(build_asm_op(11).as_str(), &[5]); + test.expect_stack(&[5]); // --- random u32 values ---------------------------------------------------------------------- let a = rand_value::() as u32; @@ -587,15 +672,15 @@ fn u32mod_b() { b += 1; } let expected = a % b; - test_op_execution(build_asm_op(b).as_str(), &[a as u64], &[expected as u64]); + + let test = build_op_test!(build_asm_op(b).as_str(), &[a as u64]); + test.expect_stack(&[expected as u64]); // --- test that the rest of the stack isn't affected ----------------------------------------- let c = rand_value::(); - test_op_execution( - build_asm_op(b).as_str(), - &[c, a as u64], - &[expected as u64, c], - ); + + let test = build_op_test!(build_asm_op(b).as_str(), &[c, a as u64]); + test.expect_stack(&[expected as u64, c]); } #[test] @@ -603,13 +688,15 @@ fn u32mod_b_fail() { let build_asm_op = |param: u64| format!("u32mod.{}", param); // should fail during exeuction if a >= 2^32 - test_execution_failure(build_asm_op(1).as_str(), &[U32_BOUND], "FailedAssertion"); + let test = build_op_test!(build_asm_op(1).as_str(), &[U32_BOUND]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); // should fail during compilation if b >= 2^32 test_param_out_of_bounds(build_asm_op(U32_BOUND).as_str(), U32_BOUND); // should fail during compilation if b = 0 - test_compilation_failure(build_asm_op(0).as_str(), "parameter"); + let test = build_op_test!(build_asm_op(0).as_str()); + test.expect_error(TestError::AssemblyError("parameter")); } #[test] @@ -628,7 +715,8 @@ fn u32mod_unsafe_fail() { let asm_op = "u32mod.unsafe"; // should fail if b == 0 - test_execution_failure(asm_op, &[1, 0], "DivideByZero"); + let test = build_op_test!(asm_op, &[1, 0]); + test.expect_error(TestError::ExecutionError("DivideByZero")); } // U32 OPERATIONS TESTS - RANDOMIZED - ARITHMETIC OPERATIONS @@ -641,9 +729,13 @@ proptest! { let expected = a as u64 + b as u64; // b provided via the stack - test_op_execution_proptest(asm_op, &[b as u64, a as u64], &[expected])?; + let test = build_op_test!(asm_op, &[a as u64, b as u64]); + test.prop_expect_stack(&[expected])?; + // b provided as a parameter - test_op_execution_proptest(format!("{}.{}", asm_op, b).as_str(), &[a as u64], &[expected])?; + let asm_op = format!("{}.{}", asm_op, b); + let test = build_op_test!(&asm_op, &[a as u64]); + test.prop_expect_stack(&[expected])?; } #[test] @@ -654,8 +746,11 @@ proptest! { let d = if overflow { 1 } else { 0 }; // full and unsafe should produce the same result for valid values - test_op_execution_proptest(format!("{}.full", asm_op).as_str(), &[a as u64, b as u64], &[d, c as u64])?; - test_op_execution_proptest(format!("{}.unsafe", asm_op).as_str(), &[a as u64, b as u64], &[d, c as u64])?; + let test = build_op_test!(format!("{}.full", asm_op).as_str(), &[a as u64, b as u64]); + test.prop_expect_stack(&[d, c as u64])?; + + let test = build_op_test!(format!("{}.unsafe", asm_op).as_str(), &[a as u64, b as u64]); + test.prop_expect_stack(&[d, c as u64])?; } #[test] @@ -667,8 +762,12 @@ proptest! { let e = if overflow_b || overflow_c { 1_u64 } else { 0_u64 }; // safe and unsafe should produce the same result for valid values - test_op_execution_proptest(asm_op, &[c as u64, a as u64, b as u64], &[e, d as u64])?; - test_op_execution_proptest(format!("{}.unsafe", asm_op).as_str(), &[c as u64, a as u64, b as u64], &[e, d as u64])?; + let test = build_op_test!(asm_op, &[c as u64, a as u64, b as u64]); + test.prop_expect_stack(&[e, d as u64])?; + + let asm_op = format!("{}.unsafe", asm_op); + let test = build_op_test!(&asm_op, &[c as u64, a as u64, b as u64]); + test.prop_expect_stack(&[e, d as u64])?; } #[test] @@ -684,9 +783,14 @@ proptest! { let expected = a - b; // b provided via the stack - test_op_execution_proptest(asm_op, &[a as u64, b as u64], &[expected as u64])?; + let test = build_op_test!(asm_op, &[a as u64, b as u64]); + test.prop_expect_stack(&[expected as u64])?; + // b provided as a parameter - test_op_execution_proptest(format!("{}.{}", asm_op, b).as_str(), &[a as u64], &[expected as u64])?; + let asm_op = format!("{}.{}", asm_op, b); + let test = build_op_test!(&asm_op, &[a as u64]); + test.prop_expect_stack(&[expected as u64])?; + } #[test] @@ -698,8 +802,11 @@ proptest! { let d = if overflow { 1 } else { 0 }; // full and unsafe should produce the same result for valid values - test_op_execution_proptest(format!("{}.full", asm_op).as_str(), &[a as u64, b as u64], &[d, c as u64])?; - test_op_execution_proptest(format!("{}.unsafe", asm_op).as_str(), &[a as u64, b as u64], &[d, c as u64])?; + let test = build_op_test!(format!("{}.full", asm_op).as_str(), &[a as u64, b as u64]); + test.prop_expect_stack(&[d, c as u64])?; + + let test = build_op_test!(format!("{}.unsafe", asm_op).as_str(), &[a as u64, b as u64]); + test.prop_expect_stack(&[d, c as u64])?; } #[test] @@ -709,9 +816,13 @@ proptest! { let expected = a as u64 * b as u64; // b provided via the stack - test_op_execution_proptest(asm_op, &[b as u64, a as u64], &[expected])?; + let test = build_op_test!(asm_op, &[a as u64, b as u64]); + test.prop_expect_stack(&[expected])?; + // b provided as a parameter - test_op_execution_proptest(format!("{}.{}", asm_op, b).as_str(), &[a as u64], &[expected])?; + let asm_op = format!("{}.{}", asm_op, b); + let test = build_op_test!(&asm_op, &[a as u64]); + test.prop_expect_stack(&[expected])?; } #[test] @@ -726,8 +837,11 @@ proptest! { }; // full and unsafe should produce the same result for valid values - test_op_execution_proptest(format!("{}.full", asm_op).as_str(), &[a as u64, b as u64], &[d, c as u64])?; - test_op_execution_proptest(format!("{}.unsafe", asm_op).as_str(), &[a as u64, b as u64], &[d, c as u64])?; + let test = build_op_test!(format!("{}.full", asm_op).as_str(), &[a as u64, b as u64]); + test.prop_expect_stack(&[d, c as u64])?; + + let test = build_op_test!(format!("{}.unsafe", asm_op).as_str(), &[a as u64, b as u64]); + test.prop_expect_stack(&[d, c as u64])?; } #[test] @@ -739,8 +853,12 @@ proptest! { let e = madd / U32_BOUND; // safe and unsafe should produce the same result for valid values - test_op_execution_proptest(asm_op, &[c as u64, a as u64, b as u64], &[e, d as u64])?; - test_op_execution_proptest(format!("{}.unsafe", asm_op).as_str(), &[c as u64, a as u64, b as u64], &[e, d as u64])?; + let test = build_op_test!(asm_op, &[c as u64, a as u64, b as u64]); + test.prop_expect_stack(&[e, d as u64])?; + + let asm_op = format!("{}.unsafe", asm_op); + let test = build_op_test!(&asm_op, &[c as u64, a as u64, b as u64]); + test.prop_expect_stack(&[e, d as u64])?; } #[test] @@ -751,9 +869,14 @@ proptest! { let expected = a / b; // b provided via the stack - test_op_execution_proptest(asm_op, &[a as u64, b as u64], &[expected as u64])?; + let test = build_op_test!(asm_op, &[a as u64, b as u64]); + test.prop_expect_stack(&[expected as u64])?; + // b provided as a parameter - test_op_execution_proptest(format!("{}.{}", asm_op, b).as_str(), &[a as u64], &[expected as u64])?; + let asm_op = format!("{}.{}", asm_op, b); + let test = build_op_test!(&asm_op, &[a as u64]); + test.prop_expect_stack(&[expected as u64])?; + } #[test] @@ -764,22 +887,32 @@ proptest! { let rem = (a % b) as u64; // full and unsafe should produce the same result for valid values - test_op_execution_proptest(format!("{}.full", asm_op).as_str(), &[a as u64, b as u64], &[rem, quot])?; - test_op_execution_proptest(format!("{}.unsafe", asm_op).as_str(), &[a as u64, b as u64], &[rem, quot])?; + let test = build_op_test!(format!("{}.full", asm_op).as_str(), &[a as u64, b as u64]); + test.prop_expect_stack(&[rem, quot])?; + + let test = build_op_test!(format!("{}.unsafe", asm_op).as_str(), &[a as u64, b as u64]); + test.prop_expect_stack(&[rem, quot])?; } #[test] fn u32mod_proptest(a in any::(), b in 1..u32::MAX) { - let asm_op = "u32mod"; + let base_op = "u32mod"; let expected = a % b; // b provided via the stack - test_op_execution_proptest(asm_op, &[a as u64, b as u64], &[expected as u64])?; + let test = build_op_test!(base_op, &[a as u64, b as u64]); + test.prop_expect_stack(&[expected as u64])?; + // b provided as a parameter - test_op_execution_proptest(format!("{}.{}", asm_op, b).as_str(), &[a as u64], &[expected as u64])?; + let asm_op = format!("{}.{}", base_op, b); + let test = build_op_test!(&asm_op, &[a as u64]); + test.prop_expect_stack(&[expected as u64])?; + // safe and unsafe should produce the same result for valid values - test_op_execution_proptest(format!("{}.unsafe", asm_op).as_str(), &[a as u64, b as u64], &[expected as u64])?; + let asm_op = format!("{}.unsafe", base_op); + let test = build_op_test!(&asm_op, &[a as u64, b as u64]); + test.prop_expect_stack(&[expected as u64])?; } } @@ -793,30 +926,35 @@ proptest! { fn test_add_full(asm_op: &str) { // --- (a + b) < 2^32 ------------------------------------------------------------------------- // c = a + b and d should be unset, since there was no overflow - test_op_execution(asm_op, &[1, 2], &[0, 3]); + let test = build_op_test!(asm_op, &[1, 2]); + test.expect_stack(&[0, 3]); // --- (a + b) = 2^32 ------------------------------------------------------------------------- let a = u32::MAX; let b = 1_u64; // c should be the sum mod 2^32 and d should be set to signal overflow - test_op_execution(asm_op, &[a as u64, b], &[1, 0]); + let test = build_op_test!(asm_op, &[a as u64, b]); + test.expect_stack(&[1, 0]); // --- (a + b) > 2^32 ------------------------------------------------------------------------- let a = 2_u64; let b = u32::MAX; // c should be the sum mod 2^32 and d should be set to signal overflow - test_op_execution(asm_op, &[a, b as u64], &[1, 1]); + let test = build_op_test!(asm_op, &[a, b as u64]); + test.expect_stack(&[1, 1]); // --- random u32 values ---------------------------------------------------------------------- let a = rand_value::() as u32; let b = rand_value::() as u32; let (c, overflow) = a.overflowing_add(b); let d = if overflow { 1 } else { 0 }; - test_op_execution(asm_op, &[a as u64, b as u64], &[d, c as u64]); + let test = build_op_test!(asm_op, &[a as u64, b as u64]); + test.expect_stack(&[d, c as u64]); // --- test that the rest of the stack isn't affected ----------------------------------------- let e = rand_value::(); - test_op_execution(asm_op, &[e, a as u64, b as u64], &[d, c as u64, e]); + let test = build_op_test!(asm_op, &[e, a as u64, b as u64]); + test.expect_stack(&[d, c as u64, e]); } /// This helper function tests overflowing add with carry for two u32 inputs a, b and one binary @@ -827,23 +965,27 @@ fn test_add_full(asm_op: &str) { fn test_addc(asm_op: &str) { // --- (a + b + c) < 2^32 where c = 0 --------------------------------------------------------- // d = a + b + c and e should be unset, since there was no overflow - test_op_execution(asm_op, &[0, 1, 2], &[0, 3]); + let test = build_op_test!(asm_op, &[0, 1, 2]); + test.expect_stack(&[0, 3]); // --- (a + b + c) < 2^32 where c = 1 --------------------------------------------------------- // d = a + b + c and e should be unset, since there was no overflow - test_op_execution(asm_op, &[1, 2, 3], &[0, 6]); + let test = build_op_test!(asm_op, &[1, 2, 3]); + test.expect_stack(&[0, 6]); // --- (a + b + c) = 2^32 --------------------------------------------------------------------- let a = u32::MAX; let b = 1_u64; // d should be the sum mod 2^32 and e should be set to signal overflow - test_op_execution(asm_op, &[0, a as u64, b], &[1, 0]); + let test = build_op_test!(asm_op, &[0, a as u64, b]); + test.expect_stack(&[1, 0]); // --- (a + b + c) > 2^32 --------------------------------------------------------------------- let a = 1_u64; let b = u32::MAX; // d should be the sum mod 2^32 and e should be set to signal overflow - test_op_execution(asm_op, &[1, a, b as u64], &[1, 1]); + let test = build_op_test!(asm_op, &[1, a, b as u64]); + test.expect_stack(&[1, 1]); // --- random u32 values with c = 0 ----------------------------------------------------------- let a = rand_value::() as u32; @@ -851,7 +993,8 @@ fn test_addc(asm_op: &str) { let c = 0_u64; let (d, overflow) = a.overflowing_add(b); let e = if overflow { 1 } else { 0 }; - test_op_execution(asm_op, &[c, a as u64, b as u64], &[e, d as u64]); + let test = build_op_test!(asm_op, &[c, a as u64, b as u64]); + test.expect_stack(&[e, d as u64]); // --- random u32 values with c = 1 ----------------------------------------------------------- let a = rand_value::() as u32; @@ -860,15 +1003,13 @@ fn test_addc(asm_op: &str) { let (d, overflow_b) = a.overflowing_add(b); let (d, overflow_c) = d.overflowing_add(c); let e = if overflow_b || overflow_c { 1 } else { 0 }; - test_op_execution(asm_op, &[c as u64, a as u64, b as u64], &[e, d as u64]); + let test = build_op_test!(asm_op, &[c as u64, a as u64, b as u64]); + test.expect_stack(&[e, d as u64]); // --- test that the rest of the stack isn't affected ----------------------------------------- let f = rand_value::(); - test_op_execution( - asm_op, - &[f, c as u64, a as u64, b as u64], - &[e, d as u64, f], - ); + let test = build_op_test!(asm_op, &[f, c as u64, a as u64, b as u64]); + test.expect_stack(&[e, d as u64, f]); } /// This helper function tests overflowing subtraction for two u32 inputs for a number of simple @@ -878,15 +1019,18 @@ fn test_addc(asm_op: &str) { fn test_sub_full(asm_op: &str) { // --- a > b ------------------------------------------------------------------------- // c = a - b and d should be unset, since there was no arithmetic overflow - test_op_execution(asm_op, &[2, 1], &[0, 1]); + let test = build_op_test!(asm_op, &[2, 1]); + test.expect_stack(&[0, 1]); // --- a = b ------------------------------------------------------------------------- // c = a - b and d should be unset, since there was no arithmetic overflow - test_op_execution(asm_op, &[1, 1], &[0, 0]); + let test = build_op_test!(asm_op, &[1, 1]); + test.expect_stack(&[0, 0]); // --- a < b ------------------------------------------------------------------------- // c = a - b % 2^32 and d should be set, since there was arithmetic overflow - test_op_execution(asm_op, &[1, 2], &[1, u32::MAX as u64]); + let test = build_op_test!(asm_op, &[1, 2]); + test.expect_stack(&[1, u32::MAX as u64]); // --- random u32 values: a >= b -------------------------------------------------------------- let val1 = rand_value::() as u32; @@ -897,7 +1041,8 @@ fn test_sub_full(asm_op: &str) { (val2, val1) }; let c = a - b; - test_op_execution(asm_op, &[a as u64, b as u64], &[0, c as u64]); + let test = build_op_test!(asm_op, &[a as u64, b as u64]); + test.expect_stack(&[0, c as u64]); // --- random u32 values: a < b --------------------------------------------------------------- let val1 = rand_value::() as u32; @@ -909,11 +1054,13 @@ fn test_sub_full(asm_op: &str) { }; let (c, _) = a.overflowing_sub(b); let d = 1; - test_op_execution(asm_op, &[a as u64, b as u64], &[d, c as u64]); + let test = build_op_test!(asm_op, &[a as u64, b as u64]); + test.expect_stack(&[d, c as u64]); // --- test that the rest of the stack isn't affected ----------------------------------------- let e = rand_value::(); - test_op_execution(asm_op, &[e, a as u64, b as u64], &[d, c as u64, e]); + let test = build_op_test!(asm_op, &[e, a as u64, b as u64]); + test.expect_stack(&[d, c as u64, e]); } /// This helper function tests overflowing multiplication for two u32 inputs for a number of simple @@ -923,15 +1070,18 @@ fn test_sub_full(asm_op: &str) { fn test_mul_full(asm_op: &str) { // --- no overflow ---------------------------------------------------------------------------- // c = a * b and d should be unset, since there was no arithmetic overflow - test_op_execution(asm_op, &[1, 2], &[0, 2]); + let test = build_op_test!(asm_op, &[1, 2]); + test.expect_stack(&[0, 2]); // --- overflow once -------------------------------------------------------------------------- // c = a * b and d = 1, since it overflows once - test_op_execution(asm_op, &[U32_BOUND / 2, 2], &[1, 0]); + let test = build_op_test!(asm_op, &[U32_BOUND / 2, 2]); + test.expect_stack(&[1, 0]); // --- multiple overflows --------------------------------------------------------------------- // c = a * b and d = 2, since it overflows twice - test_op_execution(asm_op, &[U32_BOUND / 2, 4], &[2, 0]); + let test = build_op_test!(asm_op, &[U32_BOUND / 2, 4]); + test.expect_stack(&[2, 0]); // --- random u32 values ---------------------------------------------------------------------- let a = rand_value::() as u32; @@ -942,11 +1092,13 @@ fn test_mul_full(asm_op: &str) { } else { (a as u64 * b as u64) / U32_BOUND }; - test_op_execution(asm_op, &[a as u64, b as u64], &[d, c as u64]); + let test = build_op_test!(asm_op, &[a as u64, b as u64]); + test.expect_stack(&[d, c as u64]); // --- test that the rest of the stack isn't affected ----------------------------------------- let e = rand_value::(); - test_op_execution(asm_op, &[e, a as u64, b as u64], &[d, c as u64, e]); + let test = build_op_test!(asm_op, &[e, a as u64, b as u64]); + test.expect_stack(&[d, c as u64, e]); } /// This helper function tests multiply and add for three u32 inputs for a number of simple cases @@ -956,16 +1108,21 @@ fn test_mul_full(asm_op: &str) { fn test_madd(asm_op: &str) { // --- no overflow ---------------------------------------------------------------------------- // d = a * b + c and e should be unset, since there was no arithmetic overflow - test_op_execution(asm_op, &[1, 0, 0], &[0, 1]); - test_op_execution(asm_op, &[3, 1, 2], &[0, 5]); + let test = build_op_test!(asm_op, &[1, 0, 0]); + test.expect_stack(&[0, 1]); + + let test = build_op_test!(asm_op, &[3, 1, 2]); + test.expect_stack(&[0, 5]); // --- overflow once -------------------------------------------------------------------------- // c = a * b and d = 1, since it overflows once - test_op_execution(asm_op, &[1, U32_BOUND / 2, 2], &[1, 1]); + let test = build_op_test!(asm_op, &[1, U32_BOUND / 2, 2]); + test.expect_stack(&[1, 1]); // --- multiple overflows --------------------------------------------------------------------- // c = a * b and d = 2, since it overflows twice - test_op_execution(asm_op, &[1, U32_BOUND / 2, 4], &[2, 1]); + let test = build_op_test!(asm_op, &[1, U32_BOUND / 2, 4]); + test.expect_stack(&[2, 1]); // --- random u32 values ---------------------------------------------------------------------- let a = rand_value::() as u32; @@ -974,11 +1131,13 @@ fn test_madd(asm_op: &str) { let madd = a as u64 * b as u64 + c as u64; let d = madd % U32_BOUND; let e = madd / U32_BOUND; - test_op_execution(asm_op, &[c as u64, a as u64, b as u64], &[e, d]); + let test = build_op_test!(asm_op, &[c as u64, a as u64, b as u64]); + test.expect_stack(&[e, d]); // --- test that the rest of the stack isn't affected ----------------------------------------- let f = rand_value::(); - test_op_execution(asm_op, &[f, c as u64, a as u64, b as u64], &[e, d, f]); + let test = build_op_test!(asm_op, &[f, c as u64, a as u64, b as u64]); + test.expect_stack(&[e, d, f]); } /// This helper function tests division with remainder for two u32 inputs for a number of simple @@ -988,21 +1147,27 @@ fn test_madd(asm_op: &str) { fn test_div_full(asm_op: &str) { // --- simple cases --------------------------------------------------------------------------- // division with no remainder - test_op_execution(asm_op, &[2, 1], &[0, 2]); + let test = build_op_test!(asm_op, &[2, 1]); + test.expect_stack(&[0, 2]); + // division with remainder - test_op_execution(asm_op, &[1, 2], &[1, 0]); - test_op_execution(asm_op, &[3, 2], &[1, 1]); + let test = build_op_test!(asm_op, &[1, 2]); + test.expect_stack(&[1, 0]); + let test = build_op_test!(asm_op, &[3, 2]); + test.expect_stack(&[1, 1]); // --- random u32 values ---------------------------------------------------------------------- let a = rand_value::() as u32; let b = rand_value::() as u32; let quot = (a / b) as u64; let rem = (a % b) as u64; - test_op_execution(asm_op, &[a as u64, b as u64], &[rem, quot]); + let test = build_op_test!(asm_op, &[a as u64, b as u64]); + test.expect_stack(&[rem, quot]); // --- test that the rest of the stack isn't affected ----------------------------------------- let e = rand_value::(); - test_op_execution(asm_op, &[e, a as u64, b as u64], &[rem, quot, e]); + let test = build_op_test!(asm_op, &[e, a as u64, b as u64]); + test.expect_stack(&[rem, quot, e]); } /// This helper function tests the modulus operation for two u32 inputs for a number of simple @@ -1010,9 +1175,14 @@ fn test_div_full(asm_op: &str) { /// ensures that the rest of the stack was unaffected. fn test_mod(asm_op: &str) { // --- simple cases --------------------------------------------------------------------------- - test_op_execution(asm_op, &[10, 5], &[0]); - test_op_execution(asm_op, &[11, 5], &[1]); - test_op_execution(asm_op, &[5, 11], &[5]); + let test = build_op_test!(asm_op, &[10, 5]); + test.expect_stack(&[0]); + + let test = build_op_test!(asm_op, &[11, 5]); + test.expect_stack(&[1]); + + let test = build_op_test!(asm_op, &[5, 11]); + test.expect_stack(&[5]); // --- random u32 values ---------------------------------------------------------------------- let a = rand_value::() as u32; @@ -1022,9 +1192,11 @@ fn test_mod(asm_op: &str) { b += 1; } let expected = a % b; - test_op_execution(asm_op, &[a as u64, b as u64], &[expected as u64]); + let test = build_op_test!(asm_op, &[a as u64, b as u64]); + test.expect_stack(&[expected as u64]); // --- test that the rest of the stack isn't affected ----------------------------------------- let c = rand_value::(); - test_op_execution(asm_op, &[c, a as u64, b as u64], &[expected as u64, c]); + let test = build_op_test!(asm_op, &[c, a as u64, b as u64]); + test.expect_stack(&[expected as u64, c]); } diff --git a/processor/src/tests/u32_ops/bitwise_ops.rs b/processor/src/tests/u32_ops/bitwise_ops.rs index 8d212bc633..2131572a19 100644 --- a/processor/src/tests/u32_ops/bitwise_ops.rs +++ b/processor/src/tests/u32_ops/bitwise_ops.rs @@ -1,6 +1,5 @@ use super::{ - test_execution_failure, test_input_out_of_bounds, test_op_execution, - test_op_execution_proptest, test_param_out_of_bounds, U32_BOUND, + build_op_test, test_input_out_of_bounds, test_param_out_of_bounds, TestError, U32_BOUND, }; use proptest::prelude::*; use rand_utils::rand_value; @@ -13,24 +12,31 @@ fn u32and() { let asm_op = "u32and"; // --- simple cases --------------------------------------------------------------------------- - test_op_execution(asm_op, &[1, 1], &[1]); - test_op_execution(asm_op, &[0, 1], &[0]); - test_op_execution(asm_op, &[1, 0], &[0]); - test_op_execution(asm_op, &[0, 0], &[0]); + let test = build_op_test!(asm_op, &[1, 1]); + test.expect_stack(&[1]); + + let test = build_op_test!(asm_op, &[0, 1]); + test.expect_stack(&[0]); + + let test = build_op_test!(asm_op, &[1, 0]); + test.expect_stack(&[0]); + + let test = build_op_test!(asm_op, &[0, 0]); + test.expect_stack(&[0]); // --- random u32 values ---------------------------------------------------------------------- let a = rand_value::() as u32; let b = rand_value::() as u32; - test_op_execution(asm_op, &[a as u64, b as u64], &[(a & b) as u64]); + + let test = build_op_test!(asm_op, &[a as u64, b as u64]); + test.expect_stack(&[(a & b) as u64]); // --- test that the rest of the stack isn't affected ----------------------------------------- let c = rand_value::() as u32; let d = rand_value::() as u32; - test_op_execution( - asm_op, - &[c as u64, d as u64, a as u64, b as u64], - &[(a & b) as u64, d as u64, c as u64], - ); + + let test = build_op_test!(asm_op, &[c as u64, d as u64, a as u64, b as u64]); + test.expect_stack(&[(a & b) as u64, d as u64, c as u64]); } #[test] @@ -38,8 +44,11 @@ fn u32and() { fn u32and_fail() { let asm_op = "u32and"; - test_execution_failure(asm_op, &[U32_BOUND, 0], "NotU32Value"); - test_execution_failure(asm_op, &[0, U32_BOUND], "NotU32Value"); + let test = build_op_test!(asm_op, &[U32_BOUND, 0]); + test.expect_error(TestError::ExecutionError("NotU32Value")); + + let test = build_op_test!(asm_op, &[0, U32_BOUND]); + test.expect_error(TestError::ExecutionError("NotU32Value")); } #[test] @@ -47,24 +56,31 @@ fn u32or() { let asm_op = "u32or"; // --- simple cases --------------------------------------------------------------------------- - test_op_execution(asm_op, &[1, 1], &[1]); - test_op_execution(asm_op, &[0, 1], &[1]); - test_op_execution(asm_op, &[1, 0], &[1]); - test_op_execution(asm_op, &[0, 0], &[0]); + let test = build_op_test!(asm_op, &[1, 1]); + test.expect_stack(&[1]); + + let test = build_op_test!(asm_op, &[0, 1]); + test.expect_stack(&[1]); + + let test = build_op_test!(asm_op, &[1, 0]); + test.expect_stack(&[1]); + + let test = build_op_test!(asm_op, &[0, 0]); + test.expect_stack(&[0]); // --- random u32 values ---------------------------------------------------------------------- let a = rand_value::() as u32; let b = rand_value::() as u32; - test_op_execution(asm_op, &[a as u64, b as u64], &[(a | b) as u64]); + + let test = build_op_test!(asm_op, &[a as u64, b as u64]); + test.expect_stack(&[(a | b) as u64]); // --- test that the rest of the stack isn't affected ----------------------------------------- let c = rand_value::() as u32; let d = rand_value::() as u32; - test_op_execution( - asm_op, - &[c as u64, d as u64, a as u64, b as u64], - &[(a | b) as u64, d as u64, c as u64], - ); + + let test = build_op_test!(asm_op, &[c as u64, d as u64, a as u64, b as u64]); + test.expect_stack(&[(a | b) as u64, d as u64, c as u64]); } #[test] @@ -72,8 +88,11 @@ fn u32or() { fn u32or_fail() { let asm_op = "u32or"; - test_execution_failure(asm_op, &[U32_BOUND, 0], "NotU32Value"); - test_execution_failure(asm_op, &[0, U32_BOUND], "NotU32Value"); + let test = build_op_test!(asm_op, &[U32_BOUND, 0]); + test.expect_error(TestError::ExecutionError("NotU32Value")); + + let test = build_op_test!(asm_op, &[0, U32_BOUND]); + test.expect_error(TestError::ExecutionError("NotU32Value")); } #[test] @@ -81,24 +100,30 @@ fn u32xor() { let asm_op = "u32xor"; // --- simple cases --------------------------------------------------------------------------- - test_op_execution(asm_op, &[1, 1], &[0]); - test_op_execution(asm_op, &[0, 1], &[1]); - test_op_execution(asm_op, &[1, 0], &[1]); - test_op_execution(asm_op, &[0, 0], &[0]); + let test = build_op_test!(asm_op, &[1, 1]); + test.expect_stack(&[0]); + + let test = build_op_test!(asm_op, &[0, 1]); + test.expect_stack(&[1]); + + let test = build_op_test!(asm_op, &[1, 0]); + test.expect_stack(&[1]); + + let test = build_op_test!(asm_op, &[0, 0]); + test.expect_stack(&[0]); // --- random u32 values ---------------------------------------------------------------------- let a = rand_value::() as u32; let b = rand_value::() as u32; - test_op_execution(asm_op, &[a as u64, b as u64], &[(a ^ b) as u64]); + + let test = build_op_test!(asm_op, &[a as u64, b as u64]); + test.expect_stack(&[(a ^ b) as u64]); // --- test that the rest of the stack isn't affected ----------------------------------------- let c = rand_value::() as u32; let d = rand_value::() as u32; - test_op_execution( - asm_op, - &[c as u64, d as u64, a as u64, b as u64], - &[(a ^ b) as u64, d as u64, c as u64], - ); + let test = build_op_test!(asm_op, &[c as u64, d as u64, a as u64, b as u64]); + test.expect_stack(&[(a ^ b) as u64, d as u64, c as u64]); } #[test] @@ -106,8 +131,11 @@ fn u32xor() { fn u32xor_fail() { let asm_op = "u32xor"; - test_execution_failure(asm_op, &[U32_BOUND, 0], "NotU32Value"); - test_execution_failure(asm_op, &[0, U32_BOUND], "NotU32Value"); + let test = build_op_test!(asm_op, &[U32_BOUND, 0]); + test.expect_error(TestError::ExecutionError("NotU32Value")); + + let test = build_op_test!(asm_op, &[0, U32_BOUND]); + test.expect_error(TestError::ExecutionError("NotU32Value")); } #[test] @@ -115,16 +143,23 @@ fn u32not() { let asm_op = "u32not"; // --- simple cases --------------------------------------------------------------------------- - test_op_execution(asm_op, &[U32_BOUND - 1], &[0]); - test_op_execution(asm_op, &[0], &[U32_BOUND - 1]); + let test = build_op_test!(asm_op, &[U32_BOUND - 1]); + test.expect_stack(&[0]); + + let test = build_op_test!(asm_op, &[0]); + test.expect_stack(&[U32_BOUND - 1]); // --- random u32 values ---------------------------------------------------------------------- let a = rand_value::() as u32; - test_op_execution(asm_op, &[a as u64], &[!a as u64]); + + let test = build_op_test!(asm_op, &[a as u64]); + test.expect_stack(&[!a as u64]); // --- test that the rest of the stack isn't affected ----------------------------------------- let b = rand_value::() as u32; - test_op_execution(asm_op, &[b as u64, a as u64], &[!a as u64, b as u64]); + + let test = build_op_test!(asm_op, &[b as u64, a as u64]); + test.expect_stack(&[!a as u64, b as u64]); } #[test] @@ -142,34 +177,29 @@ fn u32shl() { // --- test simple case ----------------------------------------------------------------------- let a = 1_u32; let b = 1_u32; - test_op_execution(get_asm_op(b).as_str(), &[a as u64], &[2]); + let test = build_op_test!(get_asm_op(b).as_str(), &[a as u64]); + test.expect_stack(&[2]); // --- test max values of a and b ------------------------------------------------------------- let a = (U32_BOUND - 1) as u32; let b = 31; - test_op_execution( - get_asm_op(b).as_str(), - &[U32_BOUND - 1], - &[a.wrapping_shl(b) as u64], - ); + + let test = build_op_test!(get_asm_op(b).as_str(), &[a as u64]); + test.expect_stack(&[a.wrapping_shl(b) as u64]); // --- test b = 0 ----------------------------------------------------------------------------- let a = rand_value::() as u32; let b = 0; - test_op_execution( - get_asm_op(b).as_str(), - &[a as u64], - &[a.wrapping_shl(b) as u64], - ); + + let test = build_op_test!(get_asm_op(b).as_str(), &[a as u64]); + test.expect_stack(&[a.wrapping_shl(b) as u64]); // --- test random values --------------------------------------------------------------------- let a = rand_value::() as u32; let b = (rand_value::() % 32) as u32; - test_op_execution( - get_asm_op(b).as_str(), - &[a as u64], - &[a.wrapping_shl(b) as u64], - ); + + let test = build_op_test!(get_asm_op(b).as_str(), &[a as u64]); + test.expect_stack(&[a.wrapping_shl(b) as u64]); } #[test] @@ -189,34 +219,29 @@ fn u32shr() { // --- test simple case ----------------------------------------------------------------------- let a = 4_u32; let b = 2_u32; - test_op_execution(get_asm_op(b).as_str(), &[a as u64], &[1]); + let test = build_op_test!(get_asm_op(b).as_str(), &[a as u64]); + test.expect_stack(&[1]); // --- test max values of a and b ------------------------------------------------------------- let a = (U32_BOUND - 1) as u32; let b = 31; - test_op_execution( - get_asm_op(b).as_str(), - &[U32_BOUND - 1], - &[a.wrapping_shr(b) as u64], - ); + + let test = build_op_test!(get_asm_op(b).as_str(), &[a as u64]); + test.expect_stack(&[a.wrapping_shr(b) as u64]); // --- test b = 0 --------------------------------------------------------------------------- let a = rand_value::() as u32; let b = 0; - test_op_execution( - get_asm_op(b).as_str(), - &[a as u64], - &[a.wrapping_shr(b) as u64], - ); + + let test = build_op_test!(get_asm_op(b).as_str(), &[a as u64]); + test.expect_stack(&[a.wrapping_shr(b) as u64]); // --- test random values --------------------------------------------------------------------- let a = rand_value::() as u32; let b = (rand_value::() % 32) as u32; - test_op_execution( - get_asm_op(b).as_str(), - &[a as u64], - &[a.wrapping_shr(b) as u64], - ); + + let test = build_op_test!(get_asm_op(b).as_str(), &[a as u64]); + test.expect_stack(&[a.wrapping_shr(b) as u64]); } #[test] @@ -236,40 +261,40 @@ fn u32rotl() { // --- test simple case ----------------------------------------------------------------------- let a = 1_u32; let b = 1_u32; - test_op_execution(get_asm_op(b).as_str(), &[a as u64], &[2]); + let test = build_op_test!(get_asm_op(b).as_str(), &[a as u64]); + test.expect_stack(&[2]); // --- test simple wraparound case with large a ----------------------------------------------- let a = (1_u64 << 31) as u32; let b: u32 = 1; - test_op_execution(get_asm_op(b).as_str(), &[a as u64], &[1]); + let test = build_op_test!(get_asm_op(b).as_str(), &[a as u64]); + test.expect_stack(&[1]); // --- test simple case wraparound case with max b -------------------------------------------- let a = 2_u32; let b: u32 = 31; - test_op_execution(get_asm_op(b).as_str(), &[a as u64], &[1]); + let test = build_op_test!(get_asm_op(b).as_str(), &[a as u64]); + test.expect_stack(&[1]); // --- no change when a is max value (all 1s) ------------------------------------------------- let a = (U32_BOUND - 1) as u32; let b = 2; - test_op_execution(get_asm_op(b).as_str(), &[a as u64], &[a as u64]); + let test = build_op_test!(get_asm_op(b).as_str(), &[a as u64]); + test.expect_stack(&[a as u64]); // --- test b = 0 --------------------------------------------------------------------------- let a = rand_value::() as u32; let b = 0; - test_op_execution( - get_asm_op(b).as_str(), - &[a as u64], - &[a.rotate_left(b) as u64], - ); + + let test = build_op_test!(get_asm_op(b).as_str(), &[a as u64]); + test.expect_stack(&[a.rotate_left(b) as u64]); // --- test random values --------------------------------------------------------------------- let a = rand_value::() as u32; let b = (rand_value::() % 32) as u32; - test_op_execution( - get_asm_op(b).as_str(), - &[a as u64], - &[a.rotate_left(b) as u64], - ); + + let test = build_op_test!(get_asm_op(b).as_str(), &[a as u64]); + test.expect_stack(&[a.rotate_left(b) as u64]); } #[test] @@ -289,40 +314,40 @@ fn u32rotr() { // --- test simple case ----------------------------------------------------------------------- let a = 2_u32; let b = 1_u32; - test_op_execution(get_asm_op(b).as_str(), &[a as u64], &[1]); + let test = build_op_test!(get_asm_op(b).as_str(), &[a as u64]); + test.expect_stack(&[1]); // --- test simple wraparound case with small a ----------------------------------------------- let a = 1_u32; let b = 1_u32; - test_op_execution(get_asm_op(b).as_str(), &[a as u64], &[U32_BOUND >> 1]); + let test = build_op_test!(get_asm_op(b).as_str(), &[a as u64]); + test.expect_stack(&[U32_BOUND >> 1]); // --- test simple case wraparound case with max b -------------------------------------------- let a = 1_u32; let b: u32 = 31; - test_op_execution(get_asm_op(b).as_str(), &[a as u64], &[2]); + let test = build_op_test!(get_asm_op(b).as_str(), &[a as u64]); + test.expect_stack(&[2]); // --- no change when a is max value (all 1s) ------------------------------------------------- let a = (U32_BOUND - 1) as u32; let b = 2; - test_op_execution(get_asm_op(b).as_str(), &[a as u64], &[a as u64]); + let test = build_op_test!(get_asm_op(b).as_str(), &[a as u64]); + test.expect_stack(&[a as u64]); // --- test b = 0 --------------------------------------------------------------------------- let a = rand_value::() as u32; let b = 0; - test_op_execution( - get_asm_op(b).as_str(), - &[a as u64], - &[a.rotate_right(b) as u64], - ); + + let test = build_op_test!(get_asm_op(b).as_str(), &[a as u64]); + test.expect_stack(&[a.rotate_right(b) as u64]); // --- test random values --------------------------------------------------------------------- let a = rand_value::() as u32; let b = (rand_value::() % 32) as u32; - test_op_execution( - get_asm_op(b).as_str(), - &[a as u64], - &[a.rotate_right(b) as u64], - ); + + let test = build_op_test!(get_asm_op(b).as_str(), &[a as u64]); + test.expect_stack(&[a.rotate_right(b) as u64]); } #[test] @@ -344,7 +369,8 @@ proptest! { // should result in bitwise AND let expected = (a & b) as u64; - test_op_execution_proptest(asm_opcode, &values, &[expected])?; + let test = build_op_test!(asm_opcode, &values); + test.prop_expect_stack(&[expected])?; } #[test] @@ -354,7 +380,8 @@ proptest! { // should result in bitwise OR let expected = (a | b) as u64; - test_op_execution_proptest(asm_opcode, &values, &[expected])?; + let test = build_op_test!(asm_opcode, &values); + test.prop_expect_stack(&[expected])?; } #[test] @@ -364,7 +391,8 @@ proptest! { // should result in bitwise XOR let expected = (a ^ b) as u64; - test_op_execution_proptest(asm_opcode, &values, &[expected])?; + let test = build_op_test!(asm_opcode, &values); + test.prop_expect_stack(&[expected])?; } #[test] @@ -372,7 +400,8 @@ proptest! { let asm_opcode = "u32not"; // should result in bitwise NOT - test_op_execution_proptest(asm_opcode, &[value as u64], &[!value as u64])?; + let test = build_op_test!(asm_opcode, &[value as u64]); + test.prop_expect_stack(&[!value as u64])?; } #[test] @@ -381,7 +410,8 @@ proptest! { // should execute left shift let expected = a << b; - test_op_execution_proptest(&asm_opcode, &[a as u64], &[expected as u64])?; + let test = build_op_test!(asm_opcode, &[a as u64]); + test.prop_expect_stack(&[expected as u64])?; } #[test] @@ -390,7 +420,8 @@ proptest! { // should execute right shift let expected = a >> b; - test_op_execution_proptest(&asm_opcode, &[a as u64], &[expected as u64])?; + let test = build_op_test!(asm_opcode, &[a as u64]); + test.prop_expect_stack(&[expected as u64])?; } #[test] @@ -399,7 +430,8 @@ proptest! { let asm_opcode = format!("{}.{}", op_base, b); // should execute left bit rotation - test_op_execution_proptest(&asm_opcode, &[a as u64], &[a.rotate_left(b) as u64])?; + let test = build_op_test!(asm_opcode, &[a as u64]); + test.prop_expect_stack(&[a.rotate_left(b) as u64])?; } #[test] @@ -408,6 +440,7 @@ proptest! { let asm_opcode = format!("{}.{}", op_base, b); // should execute right bit rotation - test_op_execution_proptest(&asm_opcode, &[a as u64], &[a.rotate_right(b) as u64])?; + let test = build_op_test!(asm_opcode, &[a as u64]); + test.prop_expect_stack(&[a.rotate_right(b) as u64])?; } } diff --git a/processor/src/tests/u32_ops/comparison_ops.rs b/processor/src/tests/u32_ops/comparison_ops.rs index 8b7ecf3e95..37ca49dead 100644 --- a/processor/src/tests/u32_ops/comparison_ops.rs +++ b/processor/src/tests/u32_ops/comparison_ops.rs @@ -1,8 +1,8 @@ use std::cmp::Ordering; use super::{ - test_execution_failure, test_inputs_out_of_bounds, test_op_execution, - test_op_execution_proptest, test_param_out_of_bounds, test_unsafe_execution, U32_BOUND, + build_op_test, test_inputs_out_of_bounds, test_param_out_of_bounds, test_unsafe_execution, + TestError, U32_BOUND, }; use proptest::prelude::*; use rand_utils::rand_value; @@ -15,21 +15,30 @@ fn u32eq() { let asm_op = "u32eq"; // --- simple cases --------------------------------------------------------------------------- - test_op_execution(asm_op, &[1, 1], &[1]); - test_op_execution(asm_op, &[0, 1], &[0]); + let test = build_op_test!(asm_op, &[1, 1]); + test.expect_stack(&[1]); + + let test = build_op_test!(asm_op, &[0, 1]); + test.expect_stack(&[0]); // --- random u32: equality ------------------------------------------------------------------- let a = rand_value::() as u32; - test_op_execution(asm_op, &[a as u64, a as u64], &[1]); + + let test = build_op_test!(asm_op, &[a as u64, a as u64]); + test.expect_stack(&[1]); // --- random u32: probable inequality -------------------------------------------------------- let b = rand_value::() as u32; let expected = if a == b { 1 } else { 0 }; - test_op_execution(asm_op, &[a as u64, b as u64], &[expected]); + + let test = build_op_test!(asm_op, &[a as u64, b as u64]); + test.expect_stack(&[expected]); // --- test that the rest of the stack isn't affected ----------------------------------------- let c = rand_value::(); - test_op_execution(asm_op, &[c, a as u64, b as u64], &[expected, c]); + + let test = build_op_test!(asm_op, &[c, a as u64, b as u64]); + test.expect_stack(&[expected, c]); } #[test] @@ -45,36 +54,43 @@ fn u32eq_b() { let build_asm_op = |param: u32| format!("u32eq.{}", param); // --- simple cases --------------------------------------------------------------------------- - test_op_execution(build_asm_op(1).as_str(), &[1], &[1]); - test_op_execution(build_asm_op(0).as_str(), &[1], &[0]); + let test = build_op_test!(build_asm_op(1).as_str(), &[1]); + test.expect_stack(&[1]); + + let test = build_op_test!(build_asm_op(0).as_str(), &[1]); + test.expect_stack(&[0]); // --- random u32: equality ------------------------------------------------------------------- let a = rand_value::() as u32; - test_op_execution(build_asm_op(a).as_str(), &[a as u64], &[1]); + + let test = build_op_test!(build_asm_op(a).as_str(), &[a as u64]); + test.expect_stack(&[1]); // --- random u32: probable inequality -------------------------------------------------------- let b = rand_value::() as u32; let expected = if a == b { 1 } else { 0 }; - test_op_execution(build_asm_op(b).as_str(), &[a as u64], &[expected]); + + let test = build_op_test!(build_asm_op(b).as_str(), &[a as u64]); + test.expect_stack(&[expected]); // --- test that the rest of the stack isn't affected ----------------------------------------- let c = rand_value::(); - test_op_execution(build_asm_op(b).as_str(), &[c, a as u64], &[expected, c]); + + let test = build_op_test!(build_asm_op(b).as_str(), &[c, a as u64]); + test.expect_stack(&[expected, c]); } #[test] fn u32eq_b_fail() { - let asm_op_base = "u32eq"; + let asm_op = "u32eq"; // should fail when b is out of bounds and provided as a parameter - test_param_out_of_bounds(asm_op_base, U32_BOUND); + test_param_out_of_bounds(asm_op, U32_BOUND); // should fail when b is a valid parameter but a is out of bounds - test_execution_failure( - format!("{}.{}", asm_op_base, 1).as_str(), - &[U32_BOUND], - "FailedAssertion", - ); + let asm_op = format!("{}.{}", asm_op, 1); + let test = build_op_test!(&asm_op, &[U32_BOUND]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); } #[test] @@ -82,21 +98,30 @@ fn u32neq() { let asm_op = "u32neq"; // --- simple cases --------------------------------------------------------------------------- - test_op_execution(asm_op, &[1, 1], &[0]); - test_op_execution(asm_op, &[0, 1], &[1]); + let test = build_op_test!(asm_op, &[1, 1]); + test.expect_stack(&[0]); + + let test = build_op_test!(asm_op, &[0, 1]); + test.expect_stack(&[1]); // --- random u32: equality ------------------------------------------------------------------- let a = rand_value::() as u32; - test_op_execution(asm_op, &[a as u64, a as u64], &[0]); + + let test = build_op_test!(asm_op, &[a as u64, a as u64]); + test.expect_stack(&[0]); // --- random u32: probable inequality -------------------------------------------------------- let b = rand_value::() as u32; let expected = if a != b { 1 } else { 0 }; - test_op_execution(asm_op, &[a as u64, b as u64], &[expected]); + + let test = build_op_test!(asm_op, &[a as u64, b as u64]); + test.expect_stack(&[expected]); // --- test that the rest of the stack isn't affected ----------------------------------------- let c = rand_value::(); - test_op_execution(asm_op, &[c, a as u64, b as u64], &[expected, c]); + + let test = build_op_test!(asm_op, &[c, a as u64, b as u64]); + test.expect_stack(&[expected, c]); } #[test] @@ -112,36 +137,43 @@ fn u32neq_b() { let build_asm_op = |param: u32| format!("u32neq.{}", param); // --- simple cases --------------------------------------------------------------------------- - test_op_execution(build_asm_op(1).as_str(), &[1], &[0]); - test_op_execution(build_asm_op(0).as_str(), &[1], &[1]); + let test = build_op_test!(build_asm_op(1).as_str(), &[1]); + test.expect_stack(&[0]); + + let test = build_op_test!(build_asm_op(0).as_str(), &[1]); + test.expect_stack(&[1]); // --- random u32: equality ------------------------------------------------------------------- let a = rand_value::() as u32; - test_op_execution(build_asm_op(a).as_str(), &[a as u64], &[0]); + + let test = build_op_test!(build_asm_op(a).as_str(), &[a as u64]); + test.expect_stack(&[0]); // --- random u32: probable inequality -------------------------------------------------------- let b = rand_value::() as u32; let expected = if a != b { 1 } else { 0 }; - test_op_execution(build_asm_op(b).as_str(), &[a as u64], &[expected]); + + let test = build_op_test!(build_asm_op(b).as_str(), &[a as u64]); + test.expect_stack(&[expected]); // --- test that the rest of the stack isn't affected ----------------------------------------- let c = rand_value::(); - test_op_execution(build_asm_op(b).as_str(), &[c, a as u64], &[expected, c]); + + let test = build_op_test!(build_asm_op(b).as_str(), &[c, a as u64]); + test.expect_stack(&[expected, c]); } #[test] fn u32neq_b_fail() { - let asm_op_base = "u32neq"; + let asm_op = "u32neq"; // should fail when b is out of bounds and provided as a parameter - test_param_out_of_bounds(asm_op_base, U32_BOUND); + test_param_out_of_bounds(asm_op, U32_BOUND); // should fail when b is a valid parameter but a is out of bounds - test_execution_failure( - format!("{}.{}", asm_op_base, 1).as_str(), - &[U32_BOUND], - "FailedAssertion", - ); + let asm_op = format!("{}.{}", asm_op, 1); + let test = build_op_test!(&asm_op, &[U32_BOUND]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); } #[test] @@ -318,9 +350,13 @@ proptest! { // should test for equality let expected = if a == b { 1 } else { 0 }; // b provided via the stack - test_op_execution_proptest(asm_op, &values, &[expected])?; + let test = build_op_test!(asm_op, &values); + test.prop_expect_stack(&[expected])?; + // b provided as a parameter - test_op_execution_proptest(format!("{}.{}", asm_op, b).as_str(), &[a as u64], &[expected])?; + let asm_op = format!("{}.{}", asm_op, b); + let test = build_op_test!(&asm_op, &[a as u64]); + test.prop_expect_stack(&[expected])?; } #[test] @@ -331,13 +367,18 @@ proptest! { // should test for inequality let expected = if a != b { 1 } else { 0 }; // b provided via the stack - test_op_execution_proptest(asm_op, &values, &[expected])?; + let test = build_op_test!(asm_op, &values); + test.prop_expect_stack(&[expected])?; + // b provided as a parameter - test_op_execution_proptest(format!("{}.{}", asm_op, b).as_str(), &[a as u64], &[expected])?; + let asm_op = format!("{}.{}", asm_op, b); + let test = build_op_test!(&asm_op, &[a as u64]); + test.prop_expect_stack(&[expected])?; } #[test] fn u32lt_proptest(a in any::(), b in any::()) { + let asm_op = "u32lt"; let expected = match a.cmp(&b) { Ordering::Less => 1, Ordering::Equal => 0, @@ -345,12 +386,17 @@ proptest! { }; // safe and unsafe should produce the same result for valid values - test_op_execution_proptest("u32lt", &[a as u64, b as u64], &[expected])?; - test_op_execution_proptest("u32lt.unsafe", &[a as u64, b as u64], &[expected])?; + let test = build_op_test!(asm_op, &[a as u64, b as u64]); + test.prop_expect_stack(&[expected])?; + + let asm_op = format!("{}.unsafe", asm_op); + let test = build_op_test!(&asm_op, &[a as u64, b as u64]); + test.prop_expect_stack(&[expected])?; } #[test] fn u32lte_proptest(a in any::(), b in any::()) { + let asm_op = "u32lte"; let expected = match a.cmp(&b) { Ordering::Less => 1, Ordering::Equal => 1, @@ -358,12 +404,17 @@ proptest! { }; // safe and unsafe should produce the same result for valid values - test_op_execution_proptest("u32lte", &[a as u64, b as u64], &[expected])?; - test_op_execution_proptest("u32lte.unsafe", &[a as u64, b as u64], &[expected])?; + let test = build_op_test!(asm_op, &[a as u64, b as u64]); + test.prop_expect_stack(&[expected])?; + + let asm_op = format!("{}.unsafe", asm_op); + let test = build_op_test!(&asm_op, &[a as u64, b as u64]); + test.prop_expect_stack(&[expected])?; } #[test] fn u32gt_proptest(a in any::(), b in any::()) { + let asm_op = "u32gt"; let expected = match a.cmp(&b) { Ordering::Less => 0, Ordering::Equal => 0, @@ -371,12 +422,17 @@ proptest! { }; // safe and unsafe should produce the same result for valid values - test_op_execution_proptest("u32gt", &[a as u64, b as u64], &[expected])?; - test_op_execution_proptest("u32gt.unsafe", &[a as u64, b as u64], &[expected])?; + let test = build_op_test!(asm_op, &[a as u64, b as u64]); + test.prop_expect_stack(&[expected])?; + + let asm_op = format!("{}.unsafe", asm_op); + let test = build_op_test!(&asm_op, &[a as u64, b as u64]); + test.prop_expect_stack(&[expected])?; } #[test] fn u32gte_proptest(a in any::(), b in any::()) { + let asm_op = "u32gte"; let expected = match a.cmp(&b) { Ordering::Less => 0, Ordering::Equal => 1, @@ -384,26 +440,40 @@ proptest! { }; // safe and unsafe should produce the same result for valid values - test_op_execution_proptest("u32gte", &[a as u64, b as u64], &[expected])?; - test_op_execution_proptest("u32gte.unsafe", &[a as u64, b as u64], &[expected])?; + let test = build_op_test!(asm_op, &[a as u64, b as u64]); + test.prop_expect_stack(&[expected])?; + + let asm_op = format!("{}.unsafe", asm_op); + let test = build_op_test!(&asm_op, &[a as u64, b as u64]); + test.prop_expect_stack(&[expected])?; } #[test] fn u32min_proptest(a in any::(), b in any::()) { + let asm_op = "u32min"; let expected = if a < b { a } else { b }; // safe and unsafe should produce the same result for valid values - test_op_execution_proptest("u32min", &[a as u64, b as u64], &[expected as u64])?; - test_op_execution_proptest("u32min.unsafe", &[a as u64, b as u64], &[expected as u64])?; + let test = build_op_test!(asm_op, &[a as u64, b as u64]); + test.prop_expect_stack(&[expected as u64])?; + + let asm_op = format!("{}.unsafe", asm_op); + let test = build_op_test!(&asm_op, &[a as u64, b as u64]); + test.prop_expect_stack(&[expected as u64])?; } #[test] fn u32max_proptest(a in any::(), b in any::()) { + let asm_op = "u32max"; let expected = if a > b { a } else { b }; // safe and unsafe should produce the same result for valid values - test_op_execution_proptest("u32max", &[a as u64, b as u64], &[expected as u64])?; - test_op_execution_proptest("u32max.unsafe", &[a as u64, b as u64], &[expected as u64])?; + let test = build_op_test!(asm_op, &[a as u64, b as u64]); + test.prop_expect_stack(&[expected as u64])?; + + let asm_op = format!("{}.unsafe", asm_op); + let test = build_op_test!(&asm_op, &[a as u64, b as u64]); + test.prop_expect_stack(&[expected as u64])?; } } @@ -415,11 +485,16 @@ proptest! { fn test_comparison_op(asm_op: &str, expected_lt: u64, expected_eq: u64, expected_gt: u64) { // --- simple cases --------------------------------------------------------------------------- // a < b should put the expected value on the stack for the less-than case - test_op_execution(asm_op, &[0, 1], &[expected_lt]); + let test = build_op_test!(asm_op, &[0, 1]); + test.expect_stack(&[expected_lt]); + // a = b should put the expected value on the stack for the equal-to case - test_op_execution(asm_op, &[0, 0], &[expected_eq]); + let test = build_op_test!(asm_op, &[0, 0]); + test.expect_stack(&[expected_eq]); + // a > b should put the expected value on the stack for the greater-than case - test_op_execution(asm_op, &[1, 0], &[expected_gt]); + let test = build_op_test!(asm_op, &[1, 0]); + test.expect_stack(&[expected_gt]); // --- random u32 values ---------------------------------------------------------------------- let a = rand_value::() as u32; @@ -429,11 +504,15 @@ fn test_comparison_op(asm_op: &str, expected_lt: u64, expected_eq: u64, expected Ordering::Equal => expected_eq, Ordering::Greater => expected_gt, }; - test_op_execution(asm_op, &[a as u64, b as u64], &[expected]); + + let test = build_op_test!(asm_op, &[a as u64, b as u64]); + test.expect_stack(&[expected]); // --- test that the rest of the stack isn't affected ----------------------------------------- let c = rand_value::(); - test_op_execution(asm_op, &[c, a as u64, b as u64], &[expected, c]); + + let test = build_op_test!(asm_op, &[c, a as u64, b as u64]); + test.expect_stack(&[expected, c]); } /// Tests a u32min assembly operation (u32min or u32min.unsafe) against a number of cases to ensure @@ -441,11 +520,16 @@ fn test_comparison_op(asm_op: &str, expected_lt: u64, expected_eq: u64, expected fn test_min(asm_op: &str) { // --- simple cases --------------------------------------------------------------------------- // a < b should put a on the stack - test_op_execution(asm_op, &[0, 1], &[0]); + let test = build_op_test!(asm_op, &[0, 1]); + test.expect_stack(&[0]); + // a = b should put b on the stack - test_op_execution(asm_op, &[0, 0], &[0]); + let test = build_op_test!(asm_op, &[0, 0]); + test.expect_stack(&[0]); + // a > b should put b on the stack - test_op_execution(asm_op, &[1, 0], &[0]); + let test = build_op_test!(asm_op, &[1, 0]); + test.expect_stack(&[0]); // --- random u32 values ---------------------------------------------------------------------- let a = rand_value::() as u32; @@ -455,11 +539,15 @@ fn test_min(asm_op: &str) { Ordering::Equal => b, Ordering::Greater => b, }; - test_op_execution(asm_op, &[a as u64, b as u64], &[expected as u64]); + + let test = build_op_test!(asm_op, &[a as u64, b as u64]); + test.expect_stack(&[expected as u64]); // --- test that the rest of the stack isn't affected ----------------------------------------- let c = rand_value::(); - test_op_execution(asm_op, &[c, a as u64, b as u64], &[expected as u64, c]); + + let test = build_op_test!(asm_op, &[c, a as u64, b as u64]); + test.expect_stack(&[expected as u64, c]); } /// Tests a u32max assembly operation (u32max or u32max.unsafe) against a number of cases to ensure @@ -467,11 +555,16 @@ fn test_min(asm_op: &str) { fn test_max(asm_op: &str) { // --- simple cases --------------------------------------------------------------------------- // a < b should put b on the stack - test_op_execution(asm_op, &[0, 1], &[1]); + let test = build_op_test!(asm_op, &[0, 1]); + test.expect_stack(&[1]); + // a = b should put b on the stack - test_op_execution(asm_op, &[0, 0], &[0]); + let test = build_op_test!(asm_op, &[0, 0]); + test.expect_stack(&[0]); + // a > b should put a on the stack - test_op_execution(asm_op, &[1, 0], &[1]); + let test = build_op_test!(asm_op, &[1, 0]); + test.expect_stack(&[1]); // --- random u32 values ---------------------------------------------------------------------- let a = rand_value::() as u32; @@ -481,9 +574,13 @@ fn test_max(asm_op: &str) { Ordering::Equal => b, Ordering::Greater => a, }; - test_op_execution(asm_op, &[a as u64, b as u64], &[expected as u64]); + + let test = build_op_test!(asm_op, &[a as u64, b as u64]); + test.expect_stack(&[expected as u64]); // --- test that the rest of the stack isn't affected ----------------------------------------- let c = rand_value::(); - test_op_execution(asm_op, &[c, a as u64, b as u64], &[expected as u64, c]); + + let test = build_op_test!(asm_op, &[c, a as u64, b as u64]); + test.expect_stack(&[expected as u64, c]); } diff --git a/processor/src/tests/u32_ops/conversion_ops.rs b/processor/src/tests/u32_ops/conversion_ops.rs index 1fdeed8fe6..d1e7f09ea6 100644 --- a/processor/src/tests/u32_ops/conversion_ops.rs +++ b/processor/src/tests/u32_ops/conversion_ops.rs @@ -1,6 +1,6 @@ use super::{ - rand_word, test_execution_failure, test_inputs_out_of_bounds, test_op_execution, - test_op_execution_proptest, Felt, StarkField, U32_BOUND, WORD_LEN, + build_op_test, prop_randw, test_inputs_out_of_bounds, Felt, StarkField, TestError, U32_BOUND, + WORD_LEN, }; use proptest::prelude::*; use rand_utils::rand_value; @@ -19,13 +19,16 @@ fn u32test() { let larger = equal + 1; // --- a < 2^32 ------------------------------------------------------------------------------- - test_op_execution(asm_op, &[smaller], &[1, smaller]); + let test = build_op_test!(asm_op, &[smaller]); + test.expect_stack(&[1, smaller]); // --- a = 2^32 ------------------------------------------------------------------------------- - test_op_execution(asm_op, &[equal], &[0, equal]); + let test = build_op_test!(asm_op, &[equal]); + test.expect_stack(&[0, equal]); // --- a > 2^32 ------------------------------------------------------------------------------- - test_op_execution(asm_op, &[larger], &[0, larger]); + let test = build_op_test!(asm_op, &[larger]); + test.expect_stack(&[0, larger]); } #[test] @@ -35,32 +38,44 @@ fn u32testw() { // --- all elements in range ------------------------------------------------------------------ let values = [1, 1, 1, 1]; let expected = [1, 1, 1, 1, 1]; - test_op_execution(asm_op, &values, &expected); + + let test = build_op_test!(asm_op, &values); + test.expect_stack(&expected); // --- 1st element >= 2^32 -------------------------------------------------------------------- let values = [U32_BOUND, 0, 0, 0]; let expected = [0, 0, 0, 0, U32_BOUND]; - test_op_execution(asm_op, &values, &expected); + + let test = build_op_test!(asm_op, &values); + test.expect_stack(&expected); // --- 2nd element >= 2^32 -------------------------------------------------------------------- let values = [0, U32_BOUND, 0, 0]; let expected = [0, 0, 0, U32_BOUND, 0]; - test_op_execution(asm_op, &values, &expected); + + let test = build_op_test!(asm_op, &values); + test.expect_stack(&expected); // --- 3rd element >= 2^32 -------------------------------------------------------------------- let values = [0, 0, U32_BOUND, 0]; let expected = [0, 0, U32_BOUND, 0, 0]; - test_op_execution(asm_op, &values, &expected); + + let test = build_op_test!(asm_op, &values); + test.expect_stack(&expected); // --- 4th element >= 2^32 -------------------------------------------------------------------- let values = [0, 0, 0, U32_BOUND]; let expected = [0, U32_BOUND, 0, 0, 0]; - test_op_execution(asm_op, &values, &expected); + + let test = build_op_test!(asm_op, &values); + test.expect_stack(&expected); // --- all elements out of range -------------------------------------------------------------- let values = [U32_BOUND, U32_BOUND, U32_BOUND, U32_BOUND]; let expected = [0, U32_BOUND, U32_BOUND, U32_BOUND, U32_BOUND]; - test_op_execution(asm_op, &values, &expected); + + let test = build_op_test!(asm_op, &values); + test.expect_stack(&expected); } #[test] @@ -68,7 +83,8 @@ fn u32assert() { // assertion passes and leaves the stack unchanged if a < 2^32 let asm_op = "u32assert"; let value = 1_u64; - test_op_execution(asm_op, &[value], &[value]); + let test = build_op_test!(asm_op, &[value]); + test.expect_stack(&[value]); } #[test] @@ -82,17 +98,21 @@ fn u32assert_fail() { let larger = equal + 1; // --- test when a = 2^32 --------------------------------------------------------------------- - test_execution_failure(asm_op, &[equal], err); + let test = build_op_test!(asm_op, &[equal]); + test.expect_error(TestError::ExecutionError(err)); // --- test when a > 2^32 --------------------------------------------------------------------- - test_execution_failure(asm_op, &[larger], err); + let test = build_op_test!(asm_op, &[larger]); + test.expect_error(TestError::ExecutionError(err)); } #[test] fn u32assertw() { // assertion passes and leaves the stack unchanged if each element of the word < 2^32 let asm_op = "u32assertw"; - test_op_execution(asm_op, &[2, 3, 4, 5], &[5, 4, 3, 2]); + + let test = build_op_test!(asm_op, &[2, 3, 4, 5]); + test.expect_stack(&[5, 4, 3, 2]); } #[test] @@ -105,7 +125,8 @@ fn u32assertw_fail() { test_inputs_out_of_bounds(asm_op, WORD_LEN); // --- all elements out of range -------------------------------------------------------------- - test_execution_failure(asm_op, &[U32_BOUND; WORD_LEN], err); + let test = build_op_test!(asm_op, &[U32_BOUND; WORD_LEN]); + test.expect_error(TestError::ExecutionError(err)); } #[test] @@ -113,15 +134,19 @@ fn u32cast() { let asm_op = "u32cast"; // --- a < 2^32 ------------------------------------------------------------------------------- - test_op_execution(asm_op, &[1], &[1]); + let test = build_op_test!(asm_op, &[1]); + test.expect_stack(&[1]); // --- a > 2^32 ------------------------------------------------------------------------------- - test_op_execution(asm_op, &[U32_BOUND], &[0]); + let test = build_op_test!(asm_op, &[U32_BOUND]); + test.expect_stack(&[0]); // --- rest of stack isn't affected ----------------------------------------------------------- let a = rand_value(); let b = rand_value(); - test_op_execution(asm_op, &[a, b], &[b % U32_BOUND, a]); + + let test = build_op_test!(asm_op, &[a, b]); + test.expect_stack(&[b % U32_BOUND, a]); } #[test] @@ -129,20 +154,25 @@ fn u32split() { let asm_op = "u32split"; // --- low bits set, no high bits set --------------------------------------------------------- - test_op_execution(asm_op, &[1], &[0, 1]); + let test = build_op_test!(asm_op, &[1]); + test.expect_stack(&[0, 1]); // --- high bits set, no low bits set --------------------------------------------------------- - test_op_execution(asm_op, &[U32_BOUND], &[1, 0]); + let test = build_op_test!(asm_op, &[U32_BOUND]); + test.expect_stack(&[1, 0]); // --- high bits and low bits set ------------------------------------------------------------- - test_op_execution(asm_op, &[U32_BOUND + 1], &[1, 1]); + let test = build_op_test!(asm_op, &[U32_BOUND + 1]); + test.expect_stack(&[1, 1]); // --- rest of stack isn't affected ----------------------------------------------------------- let a = rand_value(); let b = rand_value(); let expected_hi = b >> 32; let expected_lo = b % U32_BOUND; - test_op_execution(asm_op, &[a, b], &[expected_hi, expected_lo, a]); + + let test = build_op_test!(asm_op, &[a, b]); + test.expect_stack(&[expected_hi, expected_lo, a]); } // U32 OPERATIONS TESTS - RANDOMIZED - CONVERSIONS AND TESTS @@ -150,14 +180,19 @@ fn u32split() { proptest! { #[test] fn u32test_proptest(value in any::()) { + let asm_op = "u32test"; + // check to see if the value of the element will be a valid u32 let expected_result = if value % Felt::MODULUS < U32_BOUND { 1 } else { 0 }; - test_op_execution_proptest("u32test", &[value], &[expected_result, value])?; + let test = build_op_test!(asm_op, &[value]); + test.prop_expect_stack(&[expected_result, value])?; } #[test] - fn u32testw_proptest(word in rand_word::()) { + fn u32testw_proptest(word in prop_randw::()) { + let asm_op="u32testw"; + // should leave a 1 on the stack since all values in the word are valid u32 values let values: Vec = word.iter().map(|a| *a as u64).collect(); let mut expected = values.clone(); @@ -166,45 +201,56 @@ proptest! { // reverse the values to put the expected array in stack order expected.reverse(); - test_op_execution_proptest("u32testw", &values, &expected)?; + let test = build_op_test!(asm_op, &values); + test.prop_expect_stack(&expected)?; } #[test] fn u32assert_proptest(value in any::()) { - // assertion passes and leaves the stack unchanged if a < 2^32 let asm_op = "u32assert"; - test_op_execution_proptest(asm_op, &[value as u64], &[value as u64])?; + + // assertion passes and leaves the stack unchanged if a < 2^32 + let test = build_op_test!(asm_op, &[value as u64]); + test.prop_expect_stack(&[value as u64])?; } #[test] - fn u32assertw_proptest(word in rand_word::()) { - // should pass and leave the stack unchanged if a < 2^32 for all values in the word + fn u32assertw_proptest(word in prop_randw::()) { let asm_op = "u32assertw"; + + // should pass and leave the stack unchanged if a < 2^32 for all values in the word let values: Vec = word.iter().map(|a| *a as u64).collect(); let mut expected = values.clone(); // reverse the values to put the expected array in stack order expected.reverse(); - test_op_execution_proptest(asm_op, &values, &expected)?; - } + let test = build_op_test!(asm_op, &values); + test.prop_expect_stack(&expected)?; +} #[test] fn u32cast_proptest(value in any::()) { + let asm_op = "u32cast"; + // expected result will be mod 2^32 applied to a field element // so the field modulus should be applied first let expected_result = value % Felt::MODULUS % U32_BOUND; - test_op_execution_proptest("u32cast", &[value], &[expected_result])?; + let test = build_op_test!(asm_op, &[value]); + test.prop_expect_stack(&[expected_result])?; } #[test] fn u32split_proptest(value in any::()) { + let asm_op = "u32split"; + // expected result will be mod 2^32 applied to a field element // so the field modulus must be applied first let felt_value = value % Felt::MODULUS; let expected_b = felt_value >> 32; let expected_c = felt_value as u32 as u64; - test_op_execution_proptest("u32split", &[value, value], &[expected_b, expected_c, value])?; + let test = build_op_test!(asm_op, &[value, value]); + test.prop_expect_stack(&[expected_b, expected_c, value])?; } } diff --git a/processor/src/tests/u32_ops/mod.rs b/processor/src/tests/u32_ops/mod.rs index 014ce9f2d3..ea66efe4bc 100644 --- a/processor/src/tests/u32_ops/mod.rs +++ b/processor/src/tests/u32_ops/mod.rs @@ -1,7 +1,6 @@ use super::{ - super::StarkField, build_inputs, compile, execute, rand_word, test_compilation_failure, - test_execution_failure, test_op_execution, test_op_execution_proptest, - test_param_out_of_bounds, Felt, + super::{build_op_test, StarkField}, + prop_randw, Felt, TestError, }; mod arithmetic_ops; @@ -20,7 +19,8 @@ const WORD_LEN: usize = 4; /// This helper function tests a provided u32 assembly operation, which takes a single input, to /// ensure that it fails when the input is >= 2^32. fn test_input_out_of_bounds(asm_op: &str) { - test_execution_failure(asm_op, &[U32_BOUND], "FailedAssertion"); + let test = build_op_test!(asm_op, &[U32_BOUND]); + test.expect_error(TestError::ExecutionError("FailedAssertion")); } /// This helper function tests a provided u32 assembly operation, which takes multiple inputs, to @@ -32,21 +32,31 @@ fn test_inputs_out_of_bounds(asm_op: &str, input_count: usize) { let mut i_inputs = inputs.clone(); // should fail when the value of the input at index i is out of bounds i_inputs[i] = U32_BOUND; - test_execution_failure(asm_op, &i_inputs, "FailedAssertion"); + + let test = build_op_test!(asm_op, &i_inputs); + test.expect_error(TestError::ExecutionError("FailedAssertion")); } } +/// This helper function tests a provided assembly operation which takes a single parameter +/// to ensure that it fails when that parameter is over the maximum allowed value (out of bounds). +fn test_param_out_of_bounds(asm_op_base: &str, gt_max_value: u64) { + let asm_op = format!("{}.{}", asm_op_base, gt_max_value); + let test = build_op_test!(&asm_op); + test.expect_error(TestError::AssemblyError("parameter")); +} + /// This helper function tests that when the given u32 assembly instruction is executed on /// out-of-bounds inputs it does not fail. Each input is tested independently. fn test_unsafe_execution(asm_op: &str, input_count: usize) { - let script = compile(format!("begin {} end", asm_op).as_str()); let values = vec![1_u64; input_count]; for i in 0..input_count { let mut i_values = values.clone(); // should execute successfully when the value of the input at index i is out of bounds i_values[i] = U32_BOUND; - let inputs = build_inputs(&i_values); - assert!(execute(&script, &inputs).is_ok()); + + let test = build_op_test!(asm_op, &i_values); + assert!(test.execute().is_ok()); } } From 96fb3be227b153ca595b5e1ea5adff141edd86c3 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Tue, 8 Mar 2022 20:00:03 -0800 Subject: [PATCH 06/26] feat: add u256 multiplication to standard library --- processor/Cargo.toml | 3 +- processor/src/tests/stdlib/math/mod.rs | 4 + processor/src/tests/stdlib/math/u256_mod.rs | 45 +++ .../src/tests/stdlib/{ => math}/u64_mod.rs | 0 processor/src/tests/stdlib/mod.rs | 2 +- stdlib/asm/math/u256.masm | 339 ++++++++++++++++++ stdlib/src/asm.rs | 339 ++++++++++++++++++ 7 files changed, 730 insertions(+), 2 deletions(-) create mode 100644 processor/src/tests/stdlib/math/mod.rs create mode 100644 processor/src/tests/stdlib/math/u256_mod.rs rename processor/src/tests/stdlib/{ => math}/u64_mod.rs (100%) diff --git a/processor/Cargo.toml b/processor/Cargo.toml index 9dd861d76f..4d56e34ce2 100644 --- a/processor/Cargo.toml +++ b/processor/Cargo.toml @@ -27,7 +27,8 @@ winter-utils = { package = "winter-utils", version = "0.3", default-features = f [dev-dependencies] assembly = { package = "miden-assembly", path = "../assembly", version = "0.2", default-features = false } +blake3 = "1.3.1" logtest = { version = "2.0.0", default-features = false } +num-bigint = "0.4" proptest = "1.0.0" rand-utils = { package = "winter-rand-utils", version = "0.3" } -blake3 = "1.3.1" diff --git a/processor/src/tests/stdlib/math/mod.rs b/processor/src/tests/stdlib/math/mod.rs new file mode 100644 index 0000000000..be892eaa93 --- /dev/null +++ b/processor/src/tests/stdlib/math/mod.rs @@ -0,0 +1,4 @@ +use super::build_test; + +mod u256_mod; +mod u64_mod; diff --git a/processor/src/tests/stdlib/math/u256_mod.rs b/processor/src/tests/stdlib/math/u256_mod.rs new file mode 100644 index 0000000000..42dae4e41f --- /dev/null +++ b/processor/src/tests/stdlib/math/u256_mod.rs @@ -0,0 +1,45 @@ +use super::build_test; +use num_bigint::BigUint; +use rand_utils::rand_vector; + +// MULTIPLICATION +// ================================================================================================ + +#[test] +fn mul_unsafe() { + let a = rand_u256(); + let b = rand_u256(); + + let source = " + use.std::math::u256 + begin + exec.u256::mul_unsafe + end"; + + let operands = a + .to_u32_digits() + .iter() + .chain(b.to_u32_digits().iter()) + .map(|&v| v as u64) + .collect::>(); + let result = (a * b) + .to_u32_digits() + .iter() + .map(|&v| v as u64) + .take(8) + .rev() + .collect::>(); + + build_test!(source, &operands).expect_stack(&result); +} + +// HELPER FUNCTIONS +// ================================================================================================ + +fn rand_u256() -> BigUint { + let limbs = rand_vector::(8) + .iter() + .map(|&v| v as u32) + .collect::>(); + BigUint::new(limbs) +} diff --git a/processor/src/tests/stdlib/u64_mod.rs b/processor/src/tests/stdlib/math/u64_mod.rs similarity index 100% rename from processor/src/tests/stdlib/u64_mod.rs rename to processor/src/tests/stdlib/math/u64_mod.rs diff --git a/processor/src/tests/stdlib/mod.rs b/processor/src/tests/stdlib/mod.rs index b011e3569e..a4c5909e2c 100644 --- a/processor/src/tests/stdlib/mod.rs +++ b/processor/src/tests/stdlib/mod.rs @@ -1,4 +1,4 @@ use super::super::build_test; mod crypto; -mod u64_mod; +mod math; diff --git a/stdlib/asm/math/u256.masm b/stdlib/asm/math/u256.masm index dc38f32f4e..9512ce83e8 100644 --- a/stdlib/asm/math/u256.masm +++ b/stdlib/asm/math/u256.masm @@ -197,4 +197,343 @@ export.eq_unsafe dropw dropw and +end + +# ===== MULTIPLICATION ========================================================================== # +proc.mulstep + movdn.2 + u32madd.unsafe + movdn.2 + u32add.unsafe + movup.2 + add +end + +proc.mulstep4 + movup.12 + dup.1 + movup.10 + push.0 # start k at 0 # + exec.mulstep + swap + movdn.9 + dup.1 + movup.9 + movup.13 + swap.3 + exec.mulstep + swap + movdn.8 + dup.1 + movup.8 + movup.12 + swap.3 + exec.mulstep + swap + movdn.7 + dup.1 + movup.7 + movup.11 + swap.3 + exec.mulstep + swap + movdn.6 +end + +# Performs addition of two unsigned 256 bit integers discarding the overflow. # +# The input values are assumed to be represented using 32 bit limbs, but this is not checked. # +# Stack transition looks as follows: # +# [b7, b6, b5, b4, b3, b2, b1, b0, a7, a6, a5, a4, a3, a2, a1, a0, ...] -> [c7, c6, c5, c4, c3, c2, c1, c0, ...] # +# where c = (a * b) % 2^256, and a0, b0, and c0 are least significant 32-bit limbs of a, b, and c respectively. # +export.mul_unsafe.6 + # Memory storing setup # + popw.local.0 + # b[5-8] at 0 # + storew.local.1 + # b[0-4] at 1 # + push.0 dropw + # b[0] at top of stack, followed by a[0-7] # + movdn.8 + storew.local.2 + # a[0-4] at 2 # + swapw + storew.local.3 + # a[5-8] at 3 # + padw + storew.local.4 + storew.local.5 + # p at 4 and 5 # + + # b[0] # + dropw + swapw + pushw.local.4 + movdnw.2 + movup.12 + + exec.mulstep4 + + movdn.9 + movdn.9 + swapw + popw.local.4 + pushw.local.5 + swapw + movup.9 + movup.9 + + dup.1 + movup.6 + movup.10 + swap.3 + exec.mulstep + swap + movdn.5 + dup.1 + movup.5 + movup.9 + swap.3 + exec.mulstep + swap + movdn.4 + dup.1 + movup.4 + movup.8 + swap.3 + exec.mulstep + swap + movdn.3 + swap + movup.2 + movup.6 + swap.3 + exec.mulstep + + drop + popw.local.5 + + # b[1] # + pushw.local.4 + pushw.local.5 + movup.7 + dropw + pushw.local.3 pushw.local.2 # load the xs # + pushw.local.1 + movup.2 + movdn.3 + push.0 dropw # only need b[1] # + + exec.mulstep4 + + movdn.9 + movdn.9 + swapw + movdn.3 + pushw.local.4 + push.0 dropw # only need p[0] # + movdn.3 + # save p[0-3] to memory, not needed any more # + popw.local.4 + + pushw.local.5 + movup.3 + drop + swapw + movup.9 + movup.9 + + dup.1 + movup.6 + movup.9 + swap.3 + exec.mulstep + swap + movdn.7 + dup.1 + movup.5 + movup.7 + swap.3 + exec.mulstep + swap + movdn.5 + swap + movup.3 + movup.4 + swap.3 + exec.mulstep + + drop + swap + drop + popw.local.5 + + # b[2] # + pushw.local.4 + pushw.local.5 + movup.7 + movup.7 + dropw + pushw.local.3 pushw.local.2 # load the xs # + pushw.local.1 + swap + movdn.3 + push.0 dropw # only need b[1] # + + exec.mulstep4 + + movdn.9 + movdn.9 + swapw + movdn.3 + movdn.3 + pushw.local.4 + drop drop + movdn.3 + movdn.3 + popw.local.4 + + pushw.local.5 + movup.3 + movup.3 + drop + drop + swapw + movup.9 + movup.9 + + dup.1 + movup.6 + movup.8 + swap.3 + exec.mulstep + swap + movdn.6 + dup.1 + movup.5 + movup.6 + swap.3 + exec.mulstep + swap + swap drop + movdn.3 + drop drop drop + popw.local.5 + + # b[3] # + pushw.local.4 + pushw.local.5 + + movup.7 movup.7 movup.7 + dropw + pushw.local.3 pushw.local.2 + + pushw.local.1 + movdn.3 + push.0 dropw + + exec.mulstep4 + + movdn.9 + movdn.9 + + swapw + movup.3 + pushw.local.4 + drop + movup.3 + + popw.local.4 + pushw.local.5 + movdn.3 + push.0 dropw + swapw + movup.9 + movup.9 + + swap + movup.5 + movup.6 + swap.3 + exec.mulstep + + drop + movdn.3 + push.0 dropw + + # b[4] # + pushw.local.3 pushw.local.2 # load the xs # + # OPTIM: don't need a[4-7] #, but can't use mulstep4 if we don't load # + + pushw.local.0 + push.0 dropw # b[4] # + + exec.mulstep4 + dropw drop drop # OPTIM: don't need a[4-7] #, but can't use mulstep4 if we don't load # + + # b[5] # + pushw.local.3 + pushw.local.0 + movup.2 movdn.3 + push.0 dropw + movup.7 + dup.1 + movup.6 + push.0 + exec.mulstep + swap + movdn.7 + movup.4 + dup.2 + movup.7 + swap.3 + exec.mulstep + swap + movdn.5 + swap + movup.3 + movup.4 + swap.3 + exec.mulstep + drop + swap + drop + + # b[6] # + pushw.local.3 + pushw.local.0 + swap + movdn.3 + push.0 dropw + movup.6 + dup.1 + movup.6 + push.0 + exec.mulstep + swap + movdn.6 + swap + movup.4 + movup.5 + swap.3 + exec.mulstep + drop + movdn.2 + drop drop + + # b[7] # + pushw.local.3 + pushw.local.0 + + movdn.3 push.0 dropw + movup.4 + movup.5 + movdn.2 + push.0 + exec.mulstep + drop + movdn.3 + drop drop drop + + pushw.local.4 + swapw end \ No newline at end of file diff --git a/stdlib/src/asm.rs b/stdlib/src/asm.rs index 9633bede92..ebddb3d5be 100644 --- a/stdlib/src/asm.rs +++ b/stdlib/src/asm.rs @@ -1169,6 +1169,345 @@ export.eq_unsafe dropw dropw and +end + +# ===== MULTIPLICATION ========================================================================== # +proc.mulstep + movdn.2 + u32madd.unsafe + movdn.2 + u32add.unsafe + movup.2 + add +end + +proc.mulstep4 + movup.12 + dup.1 + movup.10 + push.0 # start k at 0 # + exec.mulstep + swap + movdn.9 + dup.1 + movup.9 + movup.13 + swap.3 + exec.mulstep + swap + movdn.8 + dup.1 + movup.8 + movup.12 + swap.3 + exec.mulstep + swap + movdn.7 + dup.1 + movup.7 + movup.11 + swap.3 + exec.mulstep + swap + movdn.6 +end + +# Performs addition of two unsigned 256 bit integers discarding the overflow. # +# The input values are assumed to be represented using 32 bit limbs, but this is not checked. # +# Stack transition looks as follows: # +# [b7, b6, b5, b4, b3, b2, b1, b0, a7, a6, a5, a4, a3, a2, a1, a0, ...] -> [c7, c6, c5, c4, c3, c2, c1, c0, ...] # +# where c = (a * b) % 2^256, and a0, b0, and c0 are least significant 32-bit limbs of a, b, and c respectively. # +export.mul_unsafe.6 + # Memory storing setup # + popw.local.0 + # b[5-8] at 0 # + storew.local.1 + # b[0-4] at 1 # + push.0 dropw + # b[0] at top of stack, followed by a[0-7] # + movdn.8 + storew.local.2 + # a[0-4] at 2 # + swapw + storew.local.3 + # a[5-8] at 3 # + padw + storew.local.4 + storew.local.5 + # p at 4 and 5 # + + # b[0] # + dropw + swapw + pushw.local.4 + movdnw.2 + movup.12 + + exec.mulstep4 + + movdn.9 + movdn.9 + swapw + popw.local.4 + pushw.local.5 + swapw + movup.9 + movup.9 + + dup.1 + movup.6 + movup.10 + swap.3 + exec.mulstep + swap + movdn.5 + dup.1 + movup.5 + movup.9 + swap.3 + exec.mulstep + swap + movdn.4 + dup.1 + movup.4 + movup.8 + swap.3 + exec.mulstep + swap + movdn.3 + swap + movup.2 + movup.6 + swap.3 + exec.mulstep + + drop + popw.local.5 + + # b[1] # + pushw.local.4 + pushw.local.5 + movup.7 + dropw + pushw.local.3 pushw.local.2 # load the xs # + pushw.local.1 + movup.2 + movdn.3 + push.0 dropw # only need b[1] # + + exec.mulstep4 + + movdn.9 + movdn.9 + swapw + movdn.3 + pushw.local.4 + push.0 dropw # only need p[0] # + movdn.3 + # save p[0-3] to memory, not needed any more # + popw.local.4 + + pushw.local.5 + movup.3 + drop + swapw + movup.9 + movup.9 + + dup.1 + movup.6 + movup.9 + swap.3 + exec.mulstep + swap + movdn.7 + dup.1 + movup.5 + movup.7 + swap.3 + exec.mulstep + swap + movdn.5 + swap + movup.3 + movup.4 + swap.3 + exec.mulstep + + drop + swap + drop + popw.local.5 + + # b[2] # + pushw.local.4 + pushw.local.5 + movup.7 + movup.7 + dropw + pushw.local.3 pushw.local.2 # load the xs # + pushw.local.1 + swap + movdn.3 + push.0 dropw # only need b[1] # + + exec.mulstep4 + + movdn.9 + movdn.9 + swapw + movdn.3 + movdn.3 + pushw.local.4 + drop drop + movdn.3 + movdn.3 + popw.local.4 + + pushw.local.5 + movup.3 + movup.3 + drop + drop + swapw + movup.9 + movup.9 + + dup.1 + movup.6 + movup.8 + swap.3 + exec.mulstep + swap + movdn.6 + dup.1 + movup.5 + movup.6 + swap.3 + exec.mulstep + swap + swap drop + movdn.3 + drop drop drop + popw.local.5 + + # b[3] # + pushw.local.4 + pushw.local.5 + + movup.7 movup.7 movup.7 + dropw + pushw.local.3 pushw.local.2 + + pushw.local.1 + movdn.3 + push.0 dropw + + exec.mulstep4 + + movdn.9 + movdn.9 + + swapw + movup.3 + pushw.local.4 + drop + movup.3 + + popw.local.4 + pushw.local.5 + movdn.3 + push.0 dropw + swapw + movup.9 + movup.9 + + swap + movup.5 + movup.6 + swap.3 + exec.mulstep + + drop + movdn.3 + push.0 dropw + + # b[4] # + pushw.local.3 pushw.local.2 # load the xs # + # OPTIM: don't need a[4-7] #, but can't use mulstep4 if we don't load # + + pushw.local.0 + push.0 dropw # b[4] # + + exec.mulstep4 + dropw drop drop # OPTIM: don't need a[4-7] #, but can't use mulstep4 if we don't load # + + # b[5] # + pushw.local.3 + pushw.local.0 + movup.2 movdn.3 + push.0 dropw + movup.7 + dup.1 + movup.6 + push.0 + exec.mulstep + swap + movdn.7 + movup.4 + dup.2 + movup.7 + swap.3 + exec.mulstep + swap + movdn.5 + swap + movup.3 + movup.4 + swap.3 + exec.mulstep + drop + swap + drop + + # b[6] # + pushw.local.3 + pushw.local.0 + swap + movdn.3 + push.0 dropw + movup.6 + dup.1 + movup.6 + push.0 + exec.mulstep + swap + movdn.6 + swap + movup.4 + movup.5 + swap.3 + exec.mulstep + drop + movdn.2 + drop drop + + # b[7] # + pushw.local.3 + pushw.local.0 + + movdn.3 push.0 dropw + movup.4 + movup.5 + movdn.2 + push.0 + exec.mulstep + drop + movdn.3 + drop drop drop + + pushw.local.4 + swapw end"), // ----- std::math::u64 --------------------------------------------------------------------------- ("std::math::u64", "# Performs addition of two unsigned 64 bit integers discarding the overflow. # From 05afe0855bd91cbd10a160f0ef136c177c928177 Mon Sep 17 00:00:00 2001 From: grjte Date: Thu, 10 Mar 2022 17:59:44 +0000 Subject: [PATCH 07/26] test: remove tests reading from uninitialized local memory Since local memory is not guaranteed to be zeros, we no longer want to include this in the tests. --- processor/src/tests/io_ops/local_ops.rs | 36 ++++++++++++------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/processor/src/tests/io_ops/local_ops.rs b/processor/src/tests/io_ops/local_ops.rs index b15a08c2fb..c09f60dca5 100644 --- a/processor/src/tests/io_ops/local_ops.rs +++ b/processor/src/tests/io_ops/local_ops.rs @@ -5,7 +5,6 @@ use super::{build_test, TestError}; #[test] fn push_local() { - // --- read from uninitialized memory --------------------------------------------------------- let source = " proc.foo.1 push.local.0 @@ -14,17 +13,17 @@ fn push_local() { exec.foo end"; - let test = build_test!(source); - test.expect_stack(&[0]); + // --- 1 value is pushed & the rest of the stack is unchanged --------------------------------- + let inputs = [1, 2, 3, 4]; + // In general, there is no guarantee that reading from uninitialized memory will result in ZEROs + // but in this case since no other operations are executed, we do know it will push a ZERO. + let final_stack = [0, 4, 3, 2, 1]; - // --- the rest of the stack is unchanged ----------------------------------------------------- - let test = build_test!(source, &[1, 2, 3, 4]); - test.expect_stack(&[0, 4, 3, 2, 1]); + build_test!(source, &inputs).expect_stack(&final_stack); } #[test] fn pushw_local() { - // --- read from uninitialized memory --------------------------------------------------------- let source = " proc.foo.1 pushw.local.0 @@ -33,12 +32,13 @@ fn pushw_local() { exec.foo end"; - let test = build_test!(source); - test.expect_stack(&[0, 0, 0, 0]); + // --- 4 values are pushed & the rest of the stack is unchanged ------------------------------- + let inputs = [1, 2, 3, 4]; + // In general, there is no guarantee that reading from uninitialized memory will result in ZEROs + // but in this case since no other operations are executed, we do know it will push ZEROs. + let final_stack = [0, 0, 0, 0, 4, 3, 2, 1]; - // --- the rest of the stack is unchanged ----------------------------------------------------- - let test = build_test!(source, &[1, 2, 3, 4]); - test.expect_stack(&[0, 0, 0, 0, 4, 3, 2, 1]); + build_test!(source, &inputs).expect_stack(&final_stack); } // REMOVING VALUES FROM THE STACK (POP) @@ -145,7 +145,6 @@ fn popw_local_invalid() { #[test] fn loadw_local() { - // --- read from uninitialized memory --------------------------------------------------------- let source = " proc.foo.1 loadw.local.0 @@ -154,12 +153,13 @@ fn loadw_local() { exec.foo end"; - let test = build_test!(source, &[5, 6, 7, 8]); - test.expect_stack(&[0, 0, 0, 0]); + // --- the top 4 values are overwritten & the rest of the stack is unchanged ------------------ + let inputs = [1, 2, 3, 4, 5, 6, 7, 8]; + // In general, there is no guarantee that reading from uninitialized memory will result in ZEROs + // but in this case since no other operations are executed, we do know it will load ZEROs. + let final_stack = [0, 0, 0, 0, 4, 3, 2, 1]; - // --- the rest of the stack is unchanged ----------------------------------------------------- - let test = build_test!(source, &[1, 2, 3, 4, 5, 6, 7, 8]); - test.expect_stack(&[0, 0, 0, 0, 4, 3, 2, 1]); + build_test!(source, &inputs).expect_stack(&final_stack); } // SAVING STACK VALUES WITHOUT REMOVING THEM (STORE) From e386b72a8c78f20ec20ae05aec6a3b5d8124ead7 Mon Sep 17 00:00:00 2001 From: grjte Date: Thu, 10 Mar 2022 21:23:56 +0000 Subject: [PATCH 08/26] feat: enforce a minimum stack depth of 16 and remove StackUnderflow --- core/src/inputs/mod.rs | 6 +- core/src/lib.rs | 5 +- processor/src/errors.rs | 1 - processor/src/lib.rs | 14 ++--- processor/src/operations/crypto_ops.rs | 6 -- processor/src/operations/decorators/mod.rs | 2 - processor/src/operations/field_ops.rs | 22 ------- processor/src/operations/io_ops.rs | 6 -- processor/src/operations/stack_ops.rs | 21 +------ processor/src/operations/sys_ops.rs | 5 -- processor/src/operations/u32_ops.rs | 20 ------ processor/src/stack/mod.rs | 70 ++++++++------------- processor/src/tests/mod.rs | 8 +-- processor/src/tests/stdlib/crypto/blake3.rs | 14 ++--- processor/src/trace.rs | 10 +-- 15 files changed, 59 insertions(+), 151 deletions(-) diff --git a/core/src/inputs/mod.rs b/core/src/inputs/mod.rs index 250b19f23b..22991b14d0 100644 --- a/core/src/inputs/mod.rs +++ b/core/src/inputs/mod.rs @@ -2,7 +2,7 @@ use super::{ errors::{AdviceSetError, InputError}, hasher, utils::IntoBytes, - Felt, FieldElement, Word, STACK_TOP_SIZE, + Felt, FieldElement, Word, MIN_STACK_DEPTH, }; use core::convert::TryInto; use winter_utils::collections::{BTreeMap, Vec}; @@ -49,9 +49,9 @@ impl ProgramInputs { advice_tape: &[u64], advice_sets: Vec, ) -> Result { - if stack_init.len() > STACK_TOP_SIZE { + if stack_init.len() > MIN_STACK_DEPTH { return Err(InputError::TooManyStackValues( - STACK_TOP_SIZE, + MIN_STACK_DEPTH, stack_init.len(), )); } diff --git a/core/src/lib.rs b/core/src/lib.rs index 7f8655ddb9..78f3ff08f7 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -19,5 +19,6 @@ pub type Word = [Felt; 4]; // CONSTANTS // ================================================================================================ -/// Number of stack registers which can be accesses by the VM directly. -pub const STACK_TOP_SIZE: usize = 16; +/// The minimum stack depth enforced by the VM. This is also the number of stack registers which can +/// be accessed by the VM directly. +pub const MIN_STACK_DEPTH: usize = 16; diff --git a/processor/src/errors.rs b/processor/src/errors.rs index 10f8e17e11..96f537789d 100644 --- a/processor/src/errors.rs +++ b/processor/src/errors.rs @@ -8,7 +8,6 @@ pub enum ExecutionError { UnsupportedCodeBlock(CodeBlock), UnexecutableCodeBlock(CodeBlock), NotBinaryValue(Felt), - StackUnderflow(&'static str, usize), DivideByZero(usize), FailedAssertion(usize), EmptyAdviceTape(usize), diff --git a/processor/src/lib.rs b/processor/src/lib.rs index be77fa30b0..ac0e5a5e6f 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -5,7 +5,7 @@ use vm_core::{ Script, }, AdviceInjector, DebugOptions, Felt, FieldElement, Operation, ProgramInputs, StarkField, Word, - STACK_TOP_SIZE, + MIN_STACK_DEPTH, }; mod operations; @@ -50,7 +50,7 @@ const AUXILIARY_TABLE_WIDTH: usize = 18; // TYPE ALIASES // ================================================================================================ -type StackTrace = [Vec; STACK_TOP_SIZE]; +type StackTrace = [Vec; MIN_STACK_DEPTH]; type AuxiliaryTableTrace = [Vec; AUXILIARY_TABLE_WIDTH]; // EXECUTOR @@ -131,7 +131,7 @@ impl Process { fn execute_split_block(&mut self, block: &Split) -> Result<(), ExecutionError> { // start SPLIT block; this also removes the top stack item to determine which branch of // the block should be executed. - let condition = self.stack.peek()?; + let condition = self.stack.peek(); self.decoder.start_split(block, condition); self.execute_op(Operation::Drop)?; @@ -157,7 +157,7 @@ impl Process { fn execute_loop_block(&mut self, block: &Loop) -> Result<(), ExecutionError> { // start LOOP block; this requires examining the top of the stack to determine whether // the loop's body should be executed. - let condition = self.stack.peek()?; + let condition = self.stack.peek(); self.decoder.start_loop(block, condition); // if the top of the stack is ONE, execute the loop body; otherwise skip the loop body; @@ -172,7 +172,7 @@ impl Process { // keep executing the loop body until the condition on the top of the stack is no // longer ONE; each iteration of the loop is preceded by executing REPEAT operation // which drops the condition from the stack - while self.stack.peek()? == Felt::ONE { + while self.stack.peek() == Felt::ONE { self.execute_op(Operation::Drop)?; self.decoder.repeat(block); @@ -186,12 +186,12 @@ impl Process { // execute END operation; this can be done only if the top of the stack is ZERO, in which // case the top of the stack is dropped - if self.stack.peek()? == Felt::ZERO { + if self.stack.peek() == Felt::ZERO { self.execute_op(Operation::Drop)?; } else if condition == Felt::ONE { unreachable!("top of the stack should not be ONE"); } else { - return Err(ExecutionError::NotBinaryValue(self.stack.peek()?)); + return Err(ExecutionError::NotBinaryValue(self.stack.peek())); } self.decoder.end_loop(block); diff --git a/processor/src/operations/crypto_ops.rs b/processor/src/operations/crypto_ops.rs index 72b8cef853..ed8809b859 100644 --- a/processor/src/operations/crypto_ops.rs +++ b/processor/src/operations/crypto_ops.rs @@ -17,8 +17,6 @@ impl Process { /// # Errors /// Returns an error if the stack contains fewer than 12 elements. pub(super) fn op_rpperm(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(12, "RPPERM")?; - let input_state = [ self.stack.get(11), self.stack.get(10), @@ -69,8 +67,6 @@ impl Process { /// identified by the specified root. /// - Path to the node at the specified depth and index is not known to the advice provider. pub(super) fn op_mpverify(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(10, "MPVERIFY")?; - // read depth, index, node value, and root value from the stack let depth = self.stack.get(0); let index = self.stack.get(1); @@ -147,8 +143,6 @@ impl Process { /// identified by the specified root. /// - Path to the node at the specified depth and index is not known to the advice provider. pub(super) fn op_mrupdate(&mut self, copy: bool) -> Result<(), ExecutionError> { - self.stack.check_depth(14, "MRUPDATE")?; - // read depth, index, old and new node values, and tree root value from the stack let depth = self.stack.get(0); let index = self.stack.get(1); diff --git a/processor/src/operations/decorators/mod.rs b/processor/src/operations/decorators/mod.rs index b70e375bcf..0e662e844c 100644 --- a/processor/src/operations/decorators/mod.rs +++ b/processor/src/operations/decorators/mod.rs @@ -125,8 +125,6 @@ impl Process { /// identified by the specified root. /// - Value of the node at the specified depth and index is not known to the advice provider. fn inject_merkle_node(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(6, "INJMKNODE")?; - // read node depth, node index, and tree root from the stack let depth = self.stack.get(0); let index = self.stack.get(1); diff --git a/processor/src/operations/field_ops.rs b/processor/src/operations/field_ops.rs index 06d7d8ba92..c88b1b1539 100644 --- a/processor/src/operations/field_ops.rs +++ b/processor/src/operations/field_ops.rs @@ -12,8 +12,6 @@ impl Process { /// # Errors /// Returns an error if the stack contains fewer than two elements. pub(super) fn op_add(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(2, "ADD")?; - let b = self.stack.get(0); let a = self.stack.get(1); self.stack.set(0, a + b); @@ -27,8 +25,6 @@ impl Process { /// # Errors /// Returns an error if the stack is empty. pub(super) fn op_neg(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(1, "NEG")?; - let a = self.stack.get(0); self.stack.set(0, -a); self.stack.copy_state(1); @@ -41,8 +37,6 @@ impl Process { /// # Errors /// Returns an error if the stack contains fewer than two elements. pub(super) fn op_mul(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(2, "MUL")?; - let b = self.stack.get(0); let a = self.stack.get(1); self.stack.set(0, a * b); @@ -58,8 +52,6 @@ impl Process { /// * The stack is empty. /// * The value on the top of the stack is ZERO. pub(super) fn op_inv(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(1, "INV")?; - let a = self.stack.get(0); if a == Felt::ZERO { return Err(ExecutionError::DivideByZero(self.system.clk())); @@ -75,8 +67,6 @@ impl Process { /// # Errors /// Returns an error if the stack is empty. pub(super) fn op_incr(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(1, "INCR")?; - let a = self.stack.get(0); self.stack.set(0, a + Felt::ONE); self.stack.copy_state(1); @@ -94,8 +84,6 @@ impl Process { /// * The stack contains fewer than two elements. /// * Either of the two elements on the top of the stack is not a binary value. pub(super) fn op_and(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(2, "AND")?; - let b = assert_binary(self.stack.get(0))?; let a = assert_binary(self.stack.get(1))?; if a == Felt::ONE && b == Felt::ONE { @@ -115,8 +103,6 @@ impl Process { /// * The stack contains fewer than two elements. /// * Either of the two elements on the top of the stack is not a binary value. pub(super) fn op_or(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(2, "OR")?; - let b = assert_binary(self.stack.get(0))?; let a = assert_binary(self.stack.get(1))?; if a == Felt::ONE || b == Felt::ONE { @@ -136,8 +122,6 @@ impl Process { /// * The stack is empty. /// * The value on the top of the stack is not a binary value. pub(super) fn op_not(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(1, "NOT")?; - let a = assert_binary(self.stack.get(0))?; self.stack.set(0, Felt::ONE - a); self.stack.copy_state(1); @@ -153,8 +137,6 @@ impl Process { /// # Errors /// Returns an error if the stack contains fewer than two elements. pub(super) fn op_eq(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(2, "EQ")?; - let b = self.stack.get(0); let a = self.stack.get(1); if a == b { @@ -172,8 +154,6 @@ impl Process { /// # Errors /// Returns an error if the stack is empty. pub(super) fn op_eqz(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(1, "EQZ")?; - let a = self.stack.get(0); if a == Felt::ZERO { self.stack.set(0, Felt::ONE); @@ -190,8 +170,6 @@ impl Process { /// # Errors /// Returns an error if the stack contains fewer than 8 elements. pub(super) fn op_eqw(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(8, "EQW")?; - let b3 = self.stack.get(0); let b2 = self.stack.get(1); let b1 = self.stack.get(2); diff --git a/processor/src/operations/io_ops.rs b/processor/src/operations/io_ops.rs index 201465eccb..c11843fc8d 100644 --- a/processor/src/operations/io_ops.rs +++ b/processor/src/operations/io_ops.rs @@ -33,8 +33,6 @@ impl Process { /// # Errors /// Returns an error if the stack contains fewer than five elements. pub(super) fn op_loadw(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(5, "LOADW")?; - // get the address from the stack and read the word from memory let addr = self.stack.get(0); let word = self.memory.read(addr); @@ -60,8 +58,6 @@ impl Process { /// # Errors /// Returns an error if the stack contains fewer than five elements. pub(super) fn op_storew(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(5, "STOREW")?; - // get the address from the stack and build the word to be saved from the stack values let addr = self.stack.get(0); let word = [ @@ -105,8 +101,6 @@ impl Process { /// * The stack contains fewer than four elements. /// * The advice tape contains fewer than four elements. pub(super) fn op_readw(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(4, "READW")?; - let a = self.advice.read_tape()?; let b = self.advice.read_tape()?; let c = self.advice.read_tape()?; diff --git a/processor/src/operations/stack_ops.rs b/processor/src/operations/stack_ops.rs index b53a56427a..c667b4e15b 100644 --- a/processor/src/operations/stack_ops.rs +++ b/processor/src/operations/stack_ops.rs @@ -1,4 +1,4 @@ -use super::{super::STACK_TOP_SIZE, ExecutionError, Felt, FieldElement, Process, StarkField}; +use super::{super::MIN_STACK_DEPTH, ExecutionError, Felt, FieldElement, Process, StarkField}; impl Process { // STACK MANIPULATION @@ -15,7 +15,6 @@ impl Process { /// # Errors /// Returns an error if the stack is empty. pub(super) fn op_drop(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(1, "DROP")?; self.stack.shift_left(1); Ok(()) } @@ -25,7 +24,6 @@ impl Process { /// # Errors /// Returns an error if the stack contains fewer than n + 1 values. pub(super) fn op_dup(&mut self, n: usize) -> Result<(), ExecutionError> { - self.stack.check_depth(n + 1, "DUP")?; let value = self.stack.get(n); self.stack.set(0, value); self.stack.shift_right(0); @@ -37,7 +35,6 @@ impl Process { /// # Errors /// Returns an error if the stack contains fewer than two elements. pub(super) fn op_swap(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(2, "SWAP")?; let a = self.stack.get(0); let b = self.stack.get(1); self.stack.set(0, b); @@ -51,8 +48,6 @@ impl Process { /// # Errors /// Returns an error if the stack contains fewer than 8 elements. pub(super) fn op_swapw(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(8, "SWAPW")?; - let a0 = self.stack.get(0); let a1 = self.stack.get(1); let a2 = self.stack.get(2); @@ -80,8 +75,6 @@ impl Process { /// # Errors /// Returns an error if the stack contains fewer than 12 elements. pub(super) fn op_swapw2(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(12, "SWAPW2")?; - let a0 = self.stack.get(0); let a1 = self.stack.get(1); let a2 = self.stack.get(2); @@ -117,8 +110,6 @@ impl Process { /// # Errors /// Returns an error if the stack contains fewer than 16 elements. pub(super) fn op_swapw3(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(16, "SWAPW3")?; - let a0 = self.stack.get(0); let a1 = self.stack.get(1); let a2 = self.stack.get(2); @@ -163,8 +154,6 @@ impl Process { /// # Errors /// Returns an error if the stack contains fewer than n + 1 values. pub(super) fn op_movup(&mut self, n: usize) -> Result<(), ExecutionError> { - self.stack.check_depth(n + 1, "MOVUP")?; - // move the nth value to the top of the stack let value = self.stack.get(n); self.stack.set(0, value); @@ -176,7 +165,7 @@ impl Process { } // all other items on the stack remain in place - if (n + 1) < STACK_TOP_SIZE { + if (n + 1) < MIN_STACK_DEPTH { self.stack.copy_state(n + 1); } Ok(()) @@ -189,8 +178,6 @@ impl Process { /// # Errors /// Returns an error if the stack contains fewer than n + 1 values. pub(super) fn op_movdn(&mut self, n: usize) -> Result<(), ExecutionError> { - self.stack.check_depth(n + 1, "MOVDN")?; - // move the value at the top of the stack to the nth position let value = self.stack.get(0); self.stack.set(n, value); @@ -202,7 +189,7 @@ impl Process { } // all other items on the stack remain in place - if (n + 1) < STACK_TOP_SIZE { + if (n + 1) < MIN_STACK_DEPTH { self.stack.copy_state(n + 1); } Ok(()) @@ -219,7 +206,6 @@ impl Process { /// - The stack contains fewer than 3 elements. /// - The top element of the stack is neither 0 nor 1. pub(super) fn op_cswap(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(3, "CSWAP")?; let c = self.stack.get(0); let b = self.stack.get(1); let a = self.stack.get(2); @@ -248,7 +234,6 @@ impl Process { /// - The stack contains fewer than 9 elements. /// - The top element of the stack is neither 0 nor 1. pub(super) fn op_cswapw(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(9, "CSWAPW")?; let c = self.stack.get(0); let b0 = self.stack.get(1); let b1 = self.stack.get(2); diff --git a/processor/src/operations/sys_ops.rs b/processor/src/operations/sys_ops.rs index 32b0e88162..5c44246231 100644 --- a/processor/src/operations/sys_ops.rs +++ b/processor/src/operations/sys_ops.rs @@ -12,7 +12,6 @@ impl Process { /// # Errors /// Returns an error if the popped value is not ONE. pub(super) fn op_assert(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(1, "ASSERT")?; if self.stack.get(0) != Felt::ONE { return Err(ExecutionError::FailedAssertion(self.system.clk())); } @@ -29,8 +28,6 @@ impl Process { /// # Errors /// Returns an error if the stack is empty. pub(super) fn op_fmpadd(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(1, "FMPADD")?; - let offset = self.stack.get(0); let fmp = self.system.fmp(); @@ -47,8 +44,6 @@ impl Process { /// * The stack is empty. /// * New value of `fmp` register is greater than or equal to 2^32. pub(super) fn op_fmpupdate(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(1, "FMPUPDATE")?; - let offset = self.stack.get(0); let fmp = self.system.fmp(); diff --git a/processor/src/operations/u32_ops.rs b/processor/src/operations/u32_ops.rs index 8922a10778..753c077512 100644 --- a/processor/src/operations/u32_ops.rs +++ b/processor/src/operations/u32_ops.rs @@ -10,8 +10,6 @@ impl Process { /// # Errors /// Returns an error if the stack is empty. pub(super) fn op_u32split(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(1, "U32SPLIT")?; - let a = self.stack.get(0); let (lo, hi) = split_element(a); @@ -31,8 +29,6 @@ impl Process { /// # Errors /// Returns an error if the stack contains fewer than two elements. pub(super) fn op_u32add(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(2, "U32ADD")?; - let b = self.stack.get(0); let a = self.stack.get(1); let result = a + b; @@ -52,8 +48,6 @@ impl Process { /// * The stack contains fewer than three elements. /// * The third element from the top fo the stack is not a binary value. pub(super) fn op_u32addc(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(3, "U32ADDC")?; - let b = self.stack.get(0).as_int(); let a = self.stack.get(1).as_int(); let c = assert_binary(self.stack.get(2))?.as_int(); @@ -73,8 +67,6 @@ impl Process { /// # Errors /// Returns an error if the stack contains fewer than two elements. pub(super) fn op_u32sub(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(2, "U32SUB")?; - let b = self.stack.get(0).as_int(); let a = self.stack.get(1).as_int(); let result = a.wrapping_sub(b); @@ -91,8 +83,6 @@ impl Process { /// # Errors /// Returns an error if the stack contains fewer than two elements. pub(super) fn op_u32mul(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(2, "U32MUL")?; - let b = self.stack.get(0).as_int(); let a = self.stack.get(1).as_int(); let result = Felt::new(a * b); @@ -111,8 +101,6 @@ impl Process { /// # Errors /// Returns an error if the stack contains fewer than three elements. pub(super) fn op_u32madd(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(3, "U32MADD")?; - let b = self.stack.get(0).as_int(); let a = self.stack.get(1).as_int(); let c = self.stack.get(2).as_int(); @@ -133,8 +121,6 @@ impl Process { /// * The stack contains fewer than two elements. /// * The divisor is ZERO. pub(super) fn op_u32div(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(2, "U32DIV")?; - let b = self.stack.get(0).as_int(); let a = self.stack.get(1).as_int(); @@ -160,8 +146,6 @@ impl Process { /// # Errors /// Returns an error if the stack contains fewer than two elements. pub(super) fn op_u32and(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(2, "U32AND")?; - let b = self.stack.get(0); let a = self.stack.get(1); let result = self.bitwise.u32and(a, b)?; @@ -177,8 +161,6 @@ impl Process { /// # Errors /// Returns an error if the stack contains fewer than two elements. pub(super) fn op_u32or(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(2, "U32OR")?; - let b = self.stack.get(0); let a = self.stack.get(1); let result = self.bitwise.u32or(a, b)?; @@ -194,8 +176,6 @@ impl Process { /// # Errors /// Returns an error if the stack contains fewer than two elements. pub(super) fn op_u32xor(&mut self) -> Result<(), ExecutionError> { - self.stack.check_depth(2, "U32XOR")?; - let b = self.stack.get(0); let a = self.stack.get(1); let result = self.bitwise.u32xor(a, b)?; diff --git a/processor/src/stack/mod.rs b/processor/src/stack/mod.rs index 1f9a44c431..9cc4bdc1fe 100644 --- a/processor/src/stack/mod.rs +++ b/processor/src/stack/mod.rs @@ -1,4 +1,4 @@ -use super::{ExecutionError, Felt, FieldElement, ProgramInputs, StackTrace, STACK_TOP_SIZE}; +use super::{Felt, FieldElement, ProgramInputs, StackTrace, MIN_STACK_DEPTH}; use core::cmp; // STACK @@ -18,8 +18,8 @@ impl Stack { /// TODO: add comments pub fn new(inputs: &ProgramInputs, init_trace_length: usize) -> Self { let init_values = inputs.stack_init(); - let mut trace: Vec> = Vec::with_capacity(STACK_TOP_SIZE); - for i in 0..STACK_TOP_SIZE { + let mut trace: Vec> = Vec::with_capacity(MIN_STACK_DEPTH); + for i in 0..MIN_STACK_DEPTH { let mut column = vec![Felt::ZERO; init_trace_length]; if i < init_values.len() { column[0] = init_values[i]; @@ -31,7 +31,7 @@ impl Stack { step: 0, trace: trace.try_into().expect("failed to convert vector to array"), overflow: Vec::new(), - depth: init_values.len(), + depth: MIN_STACK_DEPTH, } } @@ -55,15 +55,8 @@ impl Stack { } /// Returns a copy of the item currently at the top of the stack. - /// - /// # Errors - /// Returns an error if the stack is empty. - pub fn peek(&self) -> Result { - if self.depth == 0 { - return Err(ExecutionError::StackUnderflow("peek", self.step)); - } - - Ok(self.trace[0][self.step]) + pub fn peek(&self) -> Felt { + self.trace[0][self.step] } /// Return states from the stack, which includes both the trace state and overflow table. @@ -72,11 +65,11 @@ impl Stack { let n = n.unwrap_or(usize::MAX); let num_items = cmp::min(n, self.depth()); - let num_top_items = cmp::min(STACK_TOP_SIZE, num_items); + let num_top_items = cmp::min(MIN_STACK_DEPTH, num_items); let mut result = self.trace_state()[..num_top_items].to_vec(); - if num_items > STACK_TOP_SIZE { - let num_overflow_items = num_items - STACK_TOP_SIZE; + if num_items > MIN_STACK_DEPTH { + let num_overflow_items = num_items - MIN_STACK_DEPTH; result.extend_from_slice(&self.overflow[..num_overflow_items]); } @@ -88,8 +81,8 @@ impl Stack { /// Trace state is always 16 elements long and contains the top 16 values of the stack. When /// the stack depth is less than 16, the un-used slots contain ZEROs. #[allow(dead_code)] - pub fn trace_state(&self) -> [Felt; STACK_TOP_SIZE] { - let mut result = [Felt::ZERO; STACK_TOP_SIZE]; + pub fn trace_state(&self) -> [Felt; MIN_STACK_DEPTH] { + let mut result = [Felt::ZERO; MIN_STACK_DEPTH]; for (result, column) in result.iter_mut().zip(self.trace.iter()) { *result = column[self.step]; } @@ -120,11 +113,11 @@ impl Stack { /// same position at the next clock cycle. pub fn copy_state(&mut self, start_pos: usize) { debug_assert!( - start_pos < STACK_TOP_SIZE, + start_pos < MIN_STACK_DEPTH, "start cannot exceed stack top size" ); debug_assert!(start_pos <= self.depth, "stack underflow"); - let end_pos = cmp::min(self.depth, STACK_TOP_SIZE); + let end_pos = cmp::min(self.depth, MIN_STACK_DEPTH); for i in start_pos..end_pos { self.trace[i][self.step + 1] = self.trace[i][self.step]; } @@ -137,11 +130,11 @@ impl Stack { /// "in-memory" portion of the stack. /// /// # Panics - /// Panics if the stack is empty. + /// Panics if the resulting stack depth would be less than the minimum stack depth. pub fn shift_left(&mut self, start_pos: usize) { debug_assert!(start_pos > 0, "start position must be greater than 0"); debug_assert!( - start_pos < STACK_TOP_SIZE, + start_pos < MIN_STACK_DEPTH, "start position cannot exceed stack top size" ); debug_assert!( @@ -149,23 +142,26 @@ impl Stack { "start position cannot exceed current depth" ); + const MAX_TOP_IDX: usize = MIN_STACK_DEPTH - 1; match self.depth { - 0 => unreachable!("stack underflow"), - 1..=16 => { - for i in start_pos..self.depth { + 0..=MAX_TOP_IDX => unreachable!("stack underflow"), + MIN_STACK_DEPTH => { + for i in start_pos..=MAX_TOP_IDX { self.trace[i - 1][self.step + 1] = self.trace[i][self.step]; } + // Shift in a ZERO to prevent depth shrinking below the minimum stack depth + self.trace[MAX_TOP_IDX][self.step + 1] = Felt::ZERO; } _ => { - for i in start_pos..STACK_TOP_SIZE { + for i in start_pos..=MAX_TOP_IDX { self.trace[i - 1][self.step + 1] = self.trace[i][self.step]; } let from_overflow = self.overflow.pop().expect("overflow stack is empty"); - self.trace[STACK_TOP_SIZE - 1][self.step + 1] = from_overflow; + self.trace[MAX_TOP_IDX][self.step + 1] = from_overflow; + + self.depth -= 1; } } - - self.depth -= 1; } /// Copies stack values starting a the specified position at the current clock cycle to @@ -175,7 +171,7 @@ impl Stack { /// stack. pub fn shift_right(&mut self, start_pos: usize) { debug_assert!( - start_pos < STACK_TOP_SIZE, + start_pos < MIN_STACK_DEPTH, "start position cannot exceed stack top size" ); debug_assert!( @@ -183,7 +179,7 @@ impl Stack { "start position cannot exceed current depth" ); - const MAX_TOP_IDX: usize = STACK_TOP_SIZE - 1; + const MAX_TOP_IDX: usize = MIN_STACK_DEPTH - 1; match self.depth { 0 => {} // if the stack is empty, do nothing 1..=MAX_TOP_IDX => { @@ -222,16 +218,4 @@ impl Stack { } } } - - /// Returns an error if the current stack depth is smaller than the specified required depth. - /// - /// The returned error includes the name of the operation (passed in as `op`) which triggered - /// the check. - pub fn check_depth(&self, req_depth: usize, op: &'static str) -> Result<(), ExecutionError> { - if self.depth < req_depth { - Err(ExecutionError::StackUnderflow(op, self.step)) - } else { - Ok(()) - } - } } diff --git a/processor/src/tests/mod.rs b/processor/src/tests/mod.rs index 16d9ff15d8..480dc41901 100644 --- a/processor/src/tests/mod.rs +++ b/processor/src/tests/mod.rs @@ -1,6 +1,6 @@ use super::{ execute, ExecutionError, ExecutionTrace, Felt, FieldElement, Process, ProgramInputs, Script, - Word, STACK_TOP_SIZE, + Word, MIN_STACK_DEPTH, }; use proptest::prelude::*; @@ -152,7 +152,7 @@ impl Test { } /// Returns the last state of the stack after executing a test. - fn get_last_stack_state(&self) -> [Felt; STACK_TOP_SIZE] { + fn get_last_stack_state(&self) -> [Felt; MIN_STACK_DEPTH] { let trace = self.execute().unwrap(); trace.last_stack_state() @@ -164,8 +164,8 @@ impl Test { /// Takes an array of u64 values and builds a stack, perserving their order and converting them to /// field elements. -fn convert_to_stack(values: &[u64]) -> [Felt; STACK_TOP_SIZE] { - let mut result = [Felt::ZERO; STACK_TOP_SIZE]; +fn convert_to_stack(values: &[u64]) -> [Felt; MIN_STACK_DEPTH] { + let mut result = [Felt::ZERO; MIN_STACK_DEPTH]; for (&value, result) in values.iter().zip(result.iter_mut()) { *result = Felt::new(value); } diff --git a/processor/src/tests/stdlib/crypto/blake3.rs b/processor/src/tests/stdlib/crypto/blake3.rs index 79b3f95e4e..c799895893 100644 --- a/processor/src/tests/stdlib/crypto/blake3.rs +++ b/processor/src/tests/stdlib/crypto/blake3.rs @@ -1,4 +1,4 @@ -use crate::{execute, Felt, FieldElement, ProgramInputs, Script, STACK_TOP_SIZE}; +use crate::{execute, Felt, FieldElement, ProgramInputs, Script, MIN_STACK_DEPTH}; use vm_core::utils::IntoBytes; #[test] @@ -22,10 +22,10 @@ fn blake3_2_to_1_hash() { i_digest[32..].copy_from_slice(&i_digest_1); // allocate space on stack so that bytes can be converted to blake3 words - let mut i_words = [0u64; STACK_TOP_SIZE]; + let mut i_words = [0u64; MIN_STACK_DEPTH]; // convert each of four consecutive little endian bytes (of input) to blake3 words - for i in 0..STACK_TOP_SIZE { + for i in 0..MIN_STACK_DEPTH { i_words[i] = from_le_bytes_to_words(&i_digest[i * 4..(i + 1) * 4]) as u64; } i_words.reverse(); @@ -35,10 +35,10 @@ fn blake3_2_to_1_hash() { // prepare digest in desired blake3 word form so that assertion writing becomes easier let digest_bytes = digest.as_bytes(); - let mut digest_words = [0u64; STACK_TOP_SIZE >> 1]; + let mut digest_words = [0u64; MIN_STACK_DEPTH >> 1]; // convert each of four consecutive little endian bytes (of digest) to blake3 words - for i in 0..(STACK_TOP_SIZE >> 1) { + for i in 0..(MIN_STACK_DEPTH >> 1) { digest_words[i] = from_le_bytes_to_words(&digest_bytes[i * 4..(i + 1) * 4]) as u64; } @@ -63,8 +63,8 @@ fn compile(source: &str) -> Script { /// Takes an array of u64 values and builds a stack, perserving their order and converting them to /// field elements. -fn convert_to_stack(values: &[u64]) -> [Felt; STACK_TOP_SIZE] { - let mut result = [Felt::ZERO; STACK_TOP_SIZE]; +fn convert_to_stack(values: &[u64]) -> [Felt; MIN_STACK_DEPTH] { + let mut result = [Felt::ZERO; MIN_STACK_DEPTH]; for (&value, result) in values.iter().zip(result.iter_mut()) { *result = Felt::new(value); } diff --git a/processor/src/trace.rs b/processor/src/trace.rs index 1e6613754d..08a1a5fb95 100644 --- a/processor/src/trace.rs +++ b/processor/src/trace.rs @@ -1,6 +1,6 @@ use super::{ AuxiliaryTableTrace, Bitwise, Felt, FieldElement, Hasher, Memory, Process, StackTrace, - AUXILIARY_TABLE_WIDTH, STACK_TOP_SIZE, + AUXILIARY_TABLE_WIDTH, MIN_STACK_DEPTH, }; use core::slice; use winterfell::Trace; @@ -71,15 +71,15 @@ impl ExecutionTrace { // -------------------------------------------------------------------------------------------- /// TODO: add docs - pub fn init_stack_state(&self) -> [Felt; STACK_TOP_SIZE] { - let mut result = [Felt::ZERO; STACK_TOP_SIZE]; + pub fn init_stack_state(&self) -> [Felt; MIN_STACK_DEPTH] { + let mut result = [Felt::ZERO; MIN_STACK_DEPTH]; self.read_row_into(0, &mut result); result } /// TODO: add docs - pub fn last_stack_state(&self) -> [Felt; STACK_TOP_SIZE] { - let mut result = [Felt::ZERO; STACK_TOP_SIZE]; + pub fn last_stack_state(&self) -> [Felt; MIN_STACK_DEPTH] { + let mut result = [Felt::ZERO; MIN_STACK_DEPTH]; self.read_row_into(self.length() - 1, &mut result); result } From 2951b0dbe2b56083aa5a9a5cec8ad70cc3aedf16 Mon Sep 17 00:00:00 2001 From: grjte Date: Thu, 10 Mar 2022 21:26:34 +0000 Subject: [PATCH 09/26] docs: min stack depth and remove StackUnderflow updates --- processor/src/operations/crypto_ops.rs | 5 --- processor/src/operations/decorators/mod.rs | 1 - processor/src/operations/field_ops.rs | 39 +++---------------- processor/src/operations/io_ops.rs | 10 +---- processor/src/operations/stack_ops.rs | 32 +--------------- processor/src/operations/sys_ops.rs | 7 +--- processor/src/operations/u32_ops.rs | 44 ++++------------------ processor/src/stack/mod.rs | 3 -- 8 files changed, 18 insertions(+), 123 deletions(-) diff --git a/processor/src/operations/crypto_ops.rs b/processor/src/operations/crypto_ops.rs index ed8809b859..65b6546f71 100644 --- a/processor/src/operations/crypto_ops.rs +++ b/processor/src/operations/crypto_ops.rs @@ -13,9 +13,6 @@ impl Process { /// word follows, with the number of elements to be hashed at the deepest position in stack[11]. /// For a Rescue Prime permutation of [A, B, C] where A is the capacity, the stack should be /// arranged (from the top) as [C, B, A, ...]. - /// - /// # Errors - /// Returns an error if the stack contains fewer than 12 elements. pub(super) fn op_rpperm(&mut self) -> Result<(), ExecutionError> { let input_state = [ self.stack.get(11), @@ -61,7 +58,6 @@ impl Process { /// /// # Errors /// Returns an error if: - /// - The stack contains fewer than 10 elements. /// - Merkle tree for the specified root cannot be found in the advice provider. /// - The specified depth is either zero or greater than the depth of the Merkle tree /// identified by the specified root. @@ -137,7 +133,6 @@ impl Process { /// /// # Errors /// Returns an error if: - /// - The stack contains fewer than 14 elements. /// - Merkle tree for the specified root cannot be found in the advice provider. /// - The specified depth is either zero or greater than the depth of the Merkle tree /// identified by the specified root. diff --git a/processor/src/operations/decorators/mod.rs b/processor/src/operations/decorators/mod.rs index 0e662e844c..312ff9ab38 100644 --- a/processor/src/operations/decorators/mod.rs +++ b/processor/src/operations/decorators/mod.rs @@ -119,7 +119,6 @@ impl Process { /// /// # Errors /// Returns an error if: - /// - The stack contains fewer than 6 elements. /// - Merkle tree for the specified root cannot be found in the advice provider. /// - The specified depth is either zero or greater than the depth of the Merkle tree /// identified by the specified root. diff --git a/processor/src/operations/field_ops.rs b/processor/src/operations/field_ops.rs index c88b1b1539..7d5a7c8a19 100644 --- a/processor/src/operations/field_ops.rs +++ b/processor/src/operations/field_ops.rs @@ -8,9 +8,6 @@ impl Process { // -------------------------------------------------------------------------------------------- /// Pops two elements off the stack, adds them together, and pushes the result back onto the /// stack. - /// - /// # Errors - /// Returns an error if the stack contains fewer than two elements. pub(super) fn op_add(&mut self) -> Result<(), ExecutionError> { let b = self.stack.get(0); let a = self.stack.get(1); @@ -21,9 +18,6 @@ impl Process { /// Pops an element off the stack, computes its additive inverse, and pushes the result back /// onto the stack. - /// - /// # Errors - /// Returns an error if the stack is empty. pub(super) fn op_neg(&mut self) -> Result<(), ExecutionError> { let a = self.stack.get(0); self.stack.set(0, -a); @@ -33,9 +27,6 @@ impl Process { /// Pops two elements off the stack, multiplies them, and pushes the result back onto the /// stack. - /// - /// # Errors - /// Returns an error if the stack contains fewer than two elements. pub(super) fn op_mul(&mut self) -> Result<(), ExecutionError> { let b = self.stack.get(0); let a = self.stack.get(1); @@ -48,9 +39,7 @@ impl Process { /// back onto the stack. /// /// # Errors - /// Returns an error if: - /// * The stack is empty. - /// * The value on the top of the stack is ZERO. + /// Returns an error if the value on the top of the stack is ZERO. pub(super) fn op_inv(&mut self) -> Result<(), ExecutionError> { let a = self.stack.get(0); if a == Felt::ZERO { @@ -63,9 +52,6 @@ impl Process { } /// Pops an element off the stack, adds ONE to it, and pushes the result back onto the stack. - /// - /// # Errors - /// Returns an error if the stack is empty. pub(super) fn op_incr(&mut self) -> Result<(), ExecutionError> { let a = self.stack.get(0); self.stack.set(0, a + Felt::ONE); @@ -80,9 +66,8 @@ impl Process { /// onto the stack. /// /// # Errors - /// Returns an error if: - /// * The stack contains fewer than two elements. - /// * Either of the two elements on the top of the stack is not a binary value. + /// Returns an error if either of the two elements on the top of the stack is not a binary + /// value. pub(super) fn op_and(&mut self) -> Result<(), ExecutionError> { let b = assert_binary(self.stack.get(0))?; let a = assert_binary(self.stack.get(1))?; @@ -99,9 +84,8 @@ impl Process { /// onto the stack. /// /// # Errors - /// Returns an error if: - /// * The stack contains fewer than two elements. - /// * Either of the two elements on the top of the stack is not a binary value. + /// Returns an error if either of the two elements on the top of the stack is not a binary + /// value. pub(super) fn op_or(&mut self) -> Result<(), ExecutionError> { let b = assert_binary(self.stack.get(0))?; let a = assert_binary(self.stack.get(1))?; @@ -118,9 +102,7 @@ impl Process { /// the stack. /// /// # Errors - /// Returns an error if: - /// * The stack is empty. - /// * The value on the top of the stack is not a binary value. + /// Returns an error if the value on the top of the stack is not a binary value. pub(super) fn op_not(&mut self) -> Result<(), ExecutionError> { let a = assert_binary(self.stack.get(0))?; self.stack.set(0, Felt::ONE - a); @@ -133,9 +115,6 @@ impl Process { /// Pops two elements off the stack and compares them. If the elements are equal, pushes ONE /// onto the stack, otherwise pushes ZERO onto the stack. - /// - /// # Errors - /// Returns an error if the stack contains fewer than two elements. pub(super) fn op_eq(&mut self) -> Result<(), ExecutionError> { let b = self.stack.get(0); let a = self.stack.get(1); @@ -150,9 +129,6 @@ impl Process { /// Pops an element off the stack and compares it to ZERO. If the element is ZERO, pushes ONE /// onto the stack, otherwise pushes ZERO onto the stack. - /// - /// # Errors - /// Returns an error if the stack is empty. pub(super) fn op_eqz(&mut self) -> Result<(), ExecutionError> { let a = self.stack.get(0); if a == Felt::ZERO { @@ -166,9 +142,6 @@ impl Process { /// Compares the first word (four elements) with the second word on the stack, if the words are /// equal, pushes ONE onto the stack, otherwise pushes ZERO onto the stack. - /// - /// # Errors - /// Returns an error if the stack contains fewer than 8 elements. pub(super) fn op_eqw(&mut self) -> Result<(), ExecutionError> { let b3 = self.stack.get(0); let b2 = self.stack.get(1); diff --git a/processor/src/operations/io_ops.rs b/processor/src/operations/io_ops.rs index c11843fc8d..10cc6d4a8e 100644 --- a/processor/src/operations/io_ops.rs +++ b/processor/src/operations/io_ops.rs @@ -29,9 +29,6 @@ impl Process { /// - The top four elements of the stack are overwritten with values retried from memory. /// /// Thus, the net result of the operation is that the stack is shifted left by one item. - /// - /// # Errors - /// Returns an error if the stack contains fewer than five elements. pub(super) fn op_loadw(&mut self) -> Result<(), ExecutionError> { // get the address from the stack and read the word from memory let addr = self.stack.get(0); @@ -54,9 +51,6 @@ impl Process { /// removed from the stack. /// /// Thus, the net result of the operation is that the stack is shifted left by one item. - /// - /// # Errors - /// Returns an error if the stack contains fewer than five elements. pub(super) fn op_storew(&mut self) -> Result<(), ExecutionError> { // get the address from the stack and build the word to be saved from the stack values let addr = self.stack.get(0); @@ -97,9 +91,7 @@ impl Process { /// elements with it. /// /// # Errors - /// Returns an error if: - /// * The stack contains fewer than four elements. - /// * The advice tape contains fewer than four elements. + /// Returns an error if the advice tape contains fewer than four elements. pub(super) fn op_readw(&mut self) -> Result<(), ExecutionError> { let a = self.advice.read_tape()?; let b = self.advice.read_tape()?; diff --git a/processor/src/operations/stack_ops.rs b/processor/src/operations/stack_ops.rs index c667b4e15b..597aca717c 100644 --- a/processor/src/operations/stack_ops.rs +++ b/processor/src/operations/stack_ops.rs @@ -11,18 +11,12 @@ impl Process { } /// Removes the top element off the stack. - /// - /// # Errors - /// Returns an error if the stack is empty. pub(super) fn op_drop(&mut self) -> Result<(), ExecutionError> { self.stack.shift_left(1); Ok(()) } /// Pushes the copy the n-th item onto the stack. n is 0-based. - /// - /// # Errors - /// Returns an error if the stack contains fewer than n + 1 values. pub(super) fn op_dup(&mut self, n: usize) -> Result<(), ExecutionError> { let value = self.stack.get(n); self.stack.set(0, value); @@ -31,9 +25,6 @@ impl Process { } /// Swaps stack elements 0 and 1. - /// - /// # Errors - /// Returns an error if the stack contains fewer than two elements. pub(super) fn op_swap(&mut self) -> Result<(), ExecutionError> { let a = self.stack.get(0); let b = self.stack.get(1); @@ -44,9 +35,6 @@ impl Process { } /// Swaps stack elements 0, 1, 2, and 3 with elements 4, 5, 6, and 7. - /// - /// # Errors - /// Returns an error if the stack contains fewer than 8 elements. pub(super) fn op_swapw(&mut self) -> Result<(), ExecutionError> { let a0 = self.stack.get(0); let a1 = self.stack.get(1); @@ -71,9 +59,6 @@ impl Process { } /// Swaps stack elements 0, 1, 2, and 3 with elements 8, 9, 10, and 11. - /// - /// # Errors - /// Returns an error if the stack contains fewer than 12 elements. pub(super) fn op_swapw2(&mut self) -> Result<(), ExecutionError> { let a0 = self.stack.get(0); let a1 = self.stack.get(1); @@ -106,9 +91,6 @@ impl Process { } /// Swaps stack elements 0, 1, 2, and 3, with elements 12, 13, 14, and 15. - /// - /// # Errors - /// Returns an error if the stack contains fewer than 16 elements. pub(super) fn op_swapw3(&mut self) -> Result<(), ExecutionError> { let a0 = self.stack.get(0); let a1 = self.stack.get(1); @@ -150,9 +132,6 @@ impl Process { /// Moves n-th element to the top of the stack. n is 0-based. /// /// Elements between 0 and n are shifted right by one slot. - /// - /// # Errors - /// Returns an error if the stack contains fewer than n + 1 values. pub(super) fn op_movup(&mut self, n: usize) -> Result<(), ExecutionError> { // move the nth value to the top of the stack let value = self.stack.get(n); @@ -174,9 +153,6 @@ impl Process { /// Moves element 0 to the n-th position on the stack. n is 0-based. /// /// Elements between 0 and n are shifted left by one slot. - /// - /// # Errors - /// Returns an error if the stack contains fewer than n + 1 values. pub(super) fn op_movdn(&mut self, n: usize) -> Result<(), ExecutionError> { // move the value at the top of the stack to the nth position let value = self.stack.get(0); @@ -202,9 +178,7 @@ impl Process { /// stack. If the popped element is 0, the stack remains unchanged. /// /// # Errors - /// Returns an error if: - /// - The stack contains fewer than 3 elements. - /// - The top element of the stack is neither 0 nor 1. + /// Returns an error if the top element of the stack is neither 0 nor 1. pub(super) fn op_cswap(&mut self) -> Result<(), ExecutionError> { let c = self.stack.get(0); let b = self.stack.get(1); @@ -230,9 +204,7 @@ impl Process { /// elements 4, 5, 6, and 7. If the popped element is 0, the stack remains unchanged. /// /// # Errors - /// Returns an error if: - /// - The stack contains fewer than 9 elements. - /// - The top element of the stack is neither 0 nor 1. + /// Returns an error if the top element of the stack is neither 0 nor 1. pub(super) fn op_cswapw(&mut self) -> Result<(), ExecutionError> { let c = self.stack.get(0); let b0 = self.stack.get(1); diff --git a/processor/src/operations/sys_ops.rs b/processor/src/operations/sys_ops.rs index 5c44246231..2230291c60 100644 --- a/processor/src/operations/sys_ops.rs +++ b/processor/src/operations/sys_ops.rs @@ -24,9 +24,6 @@ impl Process { /// Pops an element off the stack, adds the current value of the `fmp` register to it, and /// pushes the result back onto the stack. - /// - /// # Errors - /// Returns an error if the stack is empty. pub(super) fn op_fmpadd(&mut self) -> Result<(), ExecutionError> { let offset = self.stack.get(0); let fmp = self.system.fmp(); @@ -40,9 +37,7 @@ impl Process { /// Pops an element off the stack and adds it to the current value of `fmp` register. /// /// # Errors - /// Returns an error if: - /// * The stack is empty. - /// * New value of `fmp` register is greater than or equal to 2^32. + /// Returns an error if the new value of `fmp` register is greater than or equal to 2^32. pub(super) fn op_fmpupdate(&mut self) -> Result<(), ExecutionError> { let offset = self.stack.get(0); let fmp = self.system.fmp(); diff --git a/processor/src/operations/u32_ops.rs b/processor/src/operations/u32_ops.rs index 753c077512..9c310c6d08 100644 --- a/processor/src/operations/u32_ops.rs +++ b/processor/src/operations/u32_ops.rs @@ -6,9 +6,6 @@ impl Process { /// Pops the top element off the stack, splits it into low and high 32-bit values, and pushes /// these values back onto the stack. - /// - /// # Errors - /// Returns an error if the stack is empty. pub(super) fn op_u32split(&mut self) -> Result<(), ExecutionError> { let a = self.stack.get(0); let (lo, hi) = split_element(a); @@ -25,9 +22,6 @@ impl Process { /// Pops two elements off the stack, adds them, splits the result into low and high 32-bit /// values, and pushes these values back onto the stack. - /// - /// # Errors - /// Returns an error if the stack contains fewer than two elements. pub(super) fn op_u32add(&mut self) -> Result<(), ExecutionError> { let b = self.stack.get(0); let a = self.stack.get(1); @@ -44,9 +38,7 @@ impl Process { /// values, and pushes these values back onto the stack. /// /// # Errors - /// Returns an error if: - /// * The stack contains fewer than three elements. - /// * The third element from the top fo the stack is not a binary value. + /// Returns an error if the third element from the top fo the stack is not a binary value. pub(super) fn op_u32addc(&mut self) -> Result<(), ExecutionError> { let b = self.stack.get(0).as_int(); let a = self.stack.get(1).as_int(); @@ -63,9 +55,6 @@ impl Process { /// Pops two elements off the stack, subtracts the top element from the second element, and /// pushes the result as well as a flag indicating whether there was underflow back onto the /// stack. - /// - /// # Errors - /// Returns an error if the stack contains fewer than two elements. pub(super) fn op_u32sub(&mut self) -> Result<(), ExecutionError> { let b = self.stack.get(0).as_int(); let a = self.stack.get(1).as_int(); @@ -79,9 +68,6 @@ impl Process { /// Pops two elements off the stack, multiplies them, splits the result into low and high /// 32-bit values, and pushes these values back onto the stack. - /// - /// # Errors - /// Returns an error if the stack contains fewer than two elements. pub(super) fn op_u32mul(&mut self) -> Result<(), ExecutionError> { let b = self.stack.get(0).as_int(); let a = self.stack.get(1).as_int(); @@ -97,9 +83,6 @@ impl Process { /// Pops three elements off the stack, multiplies the first two and adds the third element to /// the result, splits the result into low and high 32-bit values, and pushes these values /// back onto the stack. - /// - /// # Errors - /// Returns an error if the stack contains fewer than three elements. pub(super) fn op_u32madd(&mut self) -> Result<(), ExecutionError> { let b = self.stack.get(0).as_int(); let a = self.stack.get(1).as_int(); @@ -117,9 +100,7 @@ impl Process { /// the quotient and the remainder back onto the stack. /// /// # Errors - /// Returns an error if: - /// * The stack contains fewer than two elements. - /// * The divisor is ZERO. + /// Returns an error if the divisor is ZERO. pub(super) fn op_u32div(&mut self) -> Result<(), ExecutionError> { let b = self.stack.get(0).as_int(); let a = self.stack.get(1).as_int(); @@ -140,11 +121,8 @@ impl Process { // BITWISE OPERATIONS // -------------------------------------------------------------------------------------------- - /// Pops two elements off the stack, computes their bitwise AND, splits the result into low and - /// high 32-bit values, and pushes these values back onto the stack. - /// - /// # Errors - /// Returns an error if the stack contains fewer than two elements. + /// Pops two elements off the stack, computes their bitwise AND, and pushes the result back + /// onto the stack. pub(super) fn op_u32and(&mut self) -> Result<(), ExecutionError> { let b = self.stack.get(0); let a = self.stack.get(1); @@ -155,11 +133,8 @@ impl Process { Ok(()) } - /// Pops two elements off the stack, computes their bitwise OR, splits the result into low and - /// high 32-bit values, and pushes these values back onto the stack. - /// - /// # Errors - /// Returns an error if the stack contains fewer than two elements. + /// Pops two elements off the stack, computes their bitwise OR, and pushes the result back onto + /// the stack. pub(super) fn op_u32or(&mut self) -> Result<(), ExecutionError> { let b = self.stack.get(0); let a = self.stack.get(1); @@ -170,11 +145,8 @@ impl Process { Ok(()) } - /// Pops two elements off the stack, computes their bitwise XOR, splits the result into low and - /// high 32-bit values, and pushes these values back onto the stack. - /// - /// # Errors - /// Returns an error if the stack contains fewer than two elements. + /// Pops two elements off the stack, computes their bitwise XOR, and pushes the result back onto + /// the stack. pub(super) fn op_u32xor(&mut self) -> Result<(), ExecutionError> { let b = self.stack.get(0); let a = self.stack.get(1); diff --git a/processor/src/stack/mod.rs b/processor/src/stack/mod.rs index 9cc4bdc1fe..46d9d93ce9 100644 --- a/processor/src/stack/mod.rs +++ b/processor/src/stack/mod.rs @@ -128,9 +128,6 @@ impl Stack { /// /// If the stack depth is greater than 16, an item is moved from the overflow stack to the /// "in-memory" portion of the stack. - /// - /// # Panics - /// Panics if the resulting stack depth would be less than the minimum stack depth. pub fn shift_left(&mut self, start_pos: usize) { debug_assert!(start_pos > 0, "start position must be greater than 0"); debug_assert!( From 6f081adc4bc728dfb62cfc68e24f32a165c79e45 Mon Sep 17 00:00:00 2001 From: grjte Date: Thu, 10 Mar 2022 11:15:28 +0000 Subject: [PATCH 10/26] test: update processor unit tests for min stack depth changes --- .../src/operations/decorators/debug_tests.rs | 9 +- processor/src/operations/field_ops.rs | 80 +++++++++++++++-- processor/src/operations/io_ops.rs | 38 +++++--- processor/src/operations/stack_ops.rs | 89 ++++++++----------- processor/src/operations/sys_ops.rs | 15 ++++ processor/src/operations/u32_ops.rs | 20 +++++ 6 files changed, 177 insertions(+), 74 deletions(-) diff --git a/processor/src/operations/decorators/debug_tests.rs b/processor/src/operations/decorators/debug_tests.rs index 1a3ad8e4ea..ee3e9f7671 100644 --- a/processor/src/operations/decorators/debug_tests.rs +++ b/processor/src/operations/decorators/debug_tests.rs @@ -1,7 +1,7 @@ use crate::{Felt, Operation, Process}; use core::cmp; use logtest::Logger; -use vm_core::{DebugOptions, ProgramInputs}; +use vm_core::{DebugOptions, ProgramInputs, MIN_STACK_DEPTH}; #[test] fn test_debug() { @@ -121,7 +121,7 @@ fn test_print_mem(logger: &mut Logger) { fn assert_stack(size: usize, depth: usize, expected_stack: &[u64], logger: &mut Logger) { assert_eq!(logger.len(), 2); - let top_size = cmp::min(size, 16); + let top_size = cmp::min(size, MIN_STACK_DEPTH); // build the expected debug string from the expected stack let mut expected = expected_stack @@ -159,7 +159,10 @@ fn test_print_stack(logger: &mut Logger) { // values are pushed onto the stack when ProgramInputs are created, so we expect them to be on // the stack in reverse order from the original input order stack_inputs.reverse(); - assert_stack(4, 4, &stack_inputs, logger); + // expect the stack to be padded with 0s if the number of inputs is less than min depth + let depth = cmp::max(stack_inputs.len(), MIN_STACK_DEPTH); + stack_inputs.resize(depth, 0); + assert_stack(depth, depth, &stack_inputs, logger); stack_inputs = (1..=16).collect::>(); inputs = ProgramInputs::new(&stack_inputs, &[], vec![]).unwrap(); diff --git a/processor/src/operations/field_ops.rs b/processor/src/operations/field_ops.rs index 7d5a7c8a19..9c9e597cb1 100644 --- a/processor/src/operations/field_ops.rs +++ b/processor/src/operations/field_ops.rs @@ -173,6 +173,7 @@ mod tests { Process, }; use rand_utils::rand_value; + use vm_core::MIN_STACK_DEPTH; // ARITHMETIC OPERATIONS // -------------------------------------------------------------------------------------------- @@ -187,9 +188,13 @@ mod tests { process.execute_op(Operation::Add).unwrap(); let expected = build_expected(&[a + b, c]); - assert_eq!(2, process.stack.depth()); + assert_eq!(MIN_STACK_DEPTH + 2, process.stack.depth()); assert_eq!(4, process.stack.current_step()); assert_eq!(expected, process.stack.trace_state()); + + // calling add with a stack of minimum depth is ok + let mut process = Process::new_dummy(); + assert!(process.execute_op(Operation::Add).is_ok()); } #[test] @@ -203,7 +208,7 @@ mod tests { let expected = build_expected(&[-a, b, c]); assert_eq!(expected, process.stack.trace_state()); - assert_eq!(3, process.stack.depth()); + assert_eq!(MIN_STACK_DEPTH + 3, process.stack.depth()); assert_eq!(4, process.stack.current_step()); } @@ -217,9 +222,13 @@ mod tests { process.execute_op(Operation::Mul).unwrap(); let expected = build_expected(&[a * b, c]); - assert_eq!(2, process.stack.depth()); + assert_eq!(MIN_STACK_DEPTH + 2, process.stack.depth()); assert_eq!(4, process.stack.current_step()); assert_eq!(expected, process.stack.trace_state()); + + // calling mul with a stack of minimum depth is ok + let mut process = Process::new_dummy(); + assert!(process.execute_op(Operation::Mul).is_ok()); } #[test] @@ -233,7 +242,7 @@ mod tests { process.execute_op(Operation::Inv).unwrap(); let expected = build_expected(&[a.inv(), b, c]); - assert_eq!(3, process.stack.depth()); + assert_eq!(MIN_STACK_DEPTH + 3, process.stack.depth()); assert_eq!(4, process.stack.current_step()); assert_eq!(expected, process.stack.trace_state()); } @@ -253,7 +262,7 @@ mod tests { process.execute_op(Operation::Incr).unwrap(); let expected = build_expected(&[a + Felt::ONE, b, c]); - assert_eq!(3, process.stack.depth()); + assert_eq!(MIN_STACK_DEPTH + 3, process.stack.depth()); assert_eq!(4, process.stack.current_step()); assert_eq!(expected, process.stack.trace_state()); } @@ -287,7 +296,7 @@ mod tests { let expected = build_expected(&[Felt::ZERO, Felt::new(2)]); assert_eq!(expected, process.stack.trace_state()); - // --- test 1 AND 0 --------------------------------------------------- + // --- test 1 AND 1 --------------------------------------------------- let mut process = Process::new_dummy(); init_stack_with(&mut process, &[2, 1, 1]); @@ -300,10 +309,63 @@ mod tests { init_stack_with(&mut process, &[2, 1, 2]); assert!(process.execute_op(Operation::And).is_err()); - // --- second operand is not binary ------------------------------------ + // --- second operand is not binary ----------------------------------- let mut process = Process::new_dummy(); init_stack_with(&mut process, &[2, 2, 1]); assert!(process.execute_op(Operation::And).is_err()); + + // --- calling AND with a stack of minimum depth is ok ---------------- + let mut process = Process::new_dummy(); + assert!(process.execute_op(Operation::And).is_ok()); + } + + #[test] + fn op_or() { + // --- test 0 OR 0 --------------------------------------------------- + let mut process = Process::new_dummy(); + init_stack_with(&mut process, &[2, 0, 0]); + + process.execute_op(Operation::Or).unwrap(); + let expected = build_expected(&[Felt::ZERO, Felt::new(2)]); + assert_eq!(expected, process.stack.trace_state()); + + // --- test 1 OR 0 --------------------------------------------------- + let mut process = Process::new_dummy(); + init_stack_with(&mut process, &[2, 0, 1]); + + process.execute_op(Operation::Or).unwrap(); + let expected = build_expected(&[Felt::ONE, Felt::new(2)]); + assert_eq!(expected, process.stack.trace_state()); + + // --- test 0 OR 1 --------------------------------------------------- + let mut process = Process::new_dummy(); + init_stack_with(&mut process, &[2, 1, 0]); + + process.execute_op(Operation::Or).unwrap(); + let expected = build_expected(&[Felt::ONE, Felt::new(2)]); + assert_eq!(expected, process.stack.trace_state()); + + // --- test 1 OR 0 --------------------------------------------------- + let mut process = Process::new_dummy(); + init_stack_with(&mut process, &[2, 1, 1]); + + process.execute_op(Operation::Or).unwrap(); + let expected = build_expected(&[Felt::ONE, Felt::new(2)]); + assert_eq!(expected, process.stack.trace_state()); + + // --- first operand is not binary ------------------------------------ + let mut process = Process::new_dummy(); + init_stack_with(&mut process, &[2, 1, 2]); + assert!(process.execute_op(Operation::Or).is_err()); + + // --- second operand is not binary ----------------------------------- + let mut process = Process::new_dummy(); + init_stack_with(&mut process, &[2, 2, 1]); + assert!(process.execute_op(Operation::Or).is_err()); + + // --- calling OR with a stack of minimum depth is a ok ---------------- + let mut process = Process::new_dummy(); + assert!(process.execute_op(Operation::Or).is_ok()); } #[test] @@ -350,6 +412,10 @@ mod tests { process.execute_op(Operation::Eq).unwrap(); let expected = build_expected(&[Felt::ZERO, Felt::new(3)]); assert_eq!(expected, process.stack.trace_state()); + + // --- calling EQ with a stack of minimum depth is a ok --------------- + let mut process = Process::new_dummy(); + assert!(process.execute_op(Operation::Eq).is_ok()); } #[test] diff --git a/processor/src/operations/io_ops.rs b/processor/src/operations/io_ops.rs index 10cc6d4a8e..5c27a0f6b7 100644 --- a/processor/src/operations/io_ops.rs +++ b/processor/src/operations/io_ops.rs @@ -129,11 +129,12 @@ mod tests { super::{FieldElement, Operation}, Felt, Process, }; + use vm_core::MIN_STACK_DEPTH; #[test] fn op_push() { let mut process = Process::new_dummy(); - assert_eq!(0, process.stack.depth()); + assert_eq!(MIN_STACK_DEPTH, process.stack.depth()); assert_eq!(0, process.stack.current_step()); assert_eq!([Felt::ZERO; 16], process.stack.trace_state()); @@ -143,7 +144,7 @@ mod tests { let mut expected = [Felt::ZERO; 16]; expected[0] = Felt::ONE; - assert_eq!(1, process.stack.depth()); + assert_eq!(MIN_STACK_DEPTH + 1, process.stack.depth()); assert_eq!(1, process.stack.current_step()); assert_eq!(expected, process.stack.trace_state()); @@ -154,7 +155,7 @@ mod tests { expected[0] = Felt::new(3); expected[1] = Felt::ONE; - assert_eq!(2, process.stack.depth()); + assert_eq!(MIN_STACK_DEPTH + 2, process.stack.depth()); assert_eq!(2, process.stack.current_step()); assert_eq!(expected, process.stack.trace_state()); } @@ -191,6 +192,10 @@ mod tests { assert_eq!(2, process.memory.size()); assert_eq!(word1, process.memory.get_value(0).unwrap()); assert_eq!(word2, process.memory.get_value(3).unwrap()); + + // --- calling STOREW with a stack of minimum depth is ok ---------------- + let mut process = Process::new_dummy(); + assert!(process.execute_op(Operation::StoreW).is_ok()); } #[test] @@ -217,6 +222,10 @@ mod tests { // check memory state assert_eq!(1, process.memory.size()); assert_eq!(word, process.memory.get_value(1).unwrap()); + + // --- calling STOREW with a stack of minimum depth is ok ---------------- + let mut process = Process::new_dummy(); + assert!(process.execute_op(Operation::LoadW).is_ok()); } // ADVICE INPUT TESTS @@ -251,12 +260,14 @@ mod tests { // reading again should result in an error because advice tape is empty assert!(process.execute_op(Operation::ReadW).is_err()); - // should return an error if the stack has fewer than 4 values + // should not return an error if the stack has fewer than 4 values let mut process = Process::new_dummy_with_advice_tape(&[3, 4, 5, 6]); process.execute_op(Operation::Push(Felt::ONE)).unwrap(); process.execute_op(Operation::Pad).unwrap(); process.execute_op(Operation::Pad).unwrap(); - assert!(process.execute_op(Operation::ReadW).is_err()); + assert!(process.execute_op(Operation::ReadW).is_ok()); + let expected = build_expected_stack(&[6, 5, 4, 3]); + assert_eq!(expected, process.stack.trace_state()); } // ENVIRONMENT INPUT TESTS @@ -267,22 +278,27 @@ mod tests { // stack is empty let mut process = Process::new_dummy(); process.execute_op(Operation::SDepth).unwrap(); - let expected = build_expected_stack(&[0]); + let expected = build_expected_stack(&[MIN_STACK_DEPTH as u64]); assert_eq!(expected, process.stack.trace_state()); - assert_eq!(1, process.stack.depth()); + assert_eq!(MIN_STACK_DEPTH + 1, process.stack.depth()); // stack has one item process.execute_op(Operation::SDepth).unwrap(); - let expected = build_expected_stack(&[1, 0]); + let expected = build_expected_stack(&[MIN_STACK_DEPTH as u64 + 1, MIN_STACK_DEPTH as u64]); assert_eq!(expected, process.stack.trace_state()); - assert_eq!(2, process.stack.depth()); + assert_eq!(MIN_STACK_DEPTH + 2, process.stack.depth()); // stack has 3 items process.execute_op(Operation::Pad).unwrap(); process.execute_op(Operation::SDepth).unwrap(); - let expected = build_expected_stack(&[3, 0, 1, 0]); + let expected = build_expected_stack(&[ + MIN_STACK_DEPTH as u64 + 3, + 0, + MIN_STACK_DEPTH as u64 + 1, + MIN_STACK_DEPTH as u64, + ]); assert_eq!(expected, process.stack.trace_state()); - assert_eq!(4, process.stack.depth()); + assert_eq!(MIN_STACK_DEPTH + 4, process.stack.depth()); } // HELPER METHODS diff --git a/processor/src/operations/stack_ops.rs b/processor/src/operations/stack_ops.rs index 597aca717c..57897890bd 100644 --- a/processor/src/operations/stack_ops.rs +++ b/processor/src/operations/stack_ops.rs @@ -254,7 +254,7 @@ mod tests { use super::{ super::{FieldElement, Operation, Process}, - Felt, + Felt, MIN_STACK_DEPTH, }; #[test] @@ -270,7 +270,7 @@ mod tests { process.execute_op(Operation::Pad).unwrap(); let expected = build_expected(&[0, 1]); - assert_eq!(2, process.stack.depth()); + assert_eq!(MIN_STACK_DEPTH + 2, process.stack.depth()); assert_eq!(2, process.stack.current_step()); assert_eq!(expected, process.stack.trace_state()); @@ -278,7 +278,7 @@ mod tests { process.execute_op(Operation::Pad).unwrap(); let expected = build_expected(&[0, 0, 1]); - assert_eq!(3, process.stack.depth()); + assert_eq!(MIN_STACK_DEPTH + 3, process.stack.depth()); assert_eq!(3, process.stack.current_step()); assert_eq!(expected, process.stack.trace_state()); } @@ -293,25 +293,22 @@ mod tests { process.execute_op(Operation::Drop).unwrap(); let expected = build_expected(&[1]); assert_eq!(expected, process.stack.trace_state()); - assert_eq!(1, process.stack.depth()); + assert_eq!(MIN_STACK_DEPTH + 1, process.stack.depth()); // drop the next value process.execute_op(Operation::Drop).unwrap(); let expected = build_expected(&[]); assert_eq!(expected, process.stack.trace_state()); - assert_eq!(0, process.stack.depth()); + assert_eq!(MIN_STACK_DEPTH, process.stack.depth()); - // the stack is empty, drop should result in an error - assert!(process.execute_op(Operation::Drop).is_err()); + // calling drop with a minimum stack depth should be ok + assert!(process.execute_op(Operation::Drop).is_ok()); } #[test] fn op_dup() { let mut process = Process::new_dummy(); - // calling DUP on an empty stack should be an error - assert!(process.execute_op(Operation::Dup0).is_err()); - // push one item onto the stack process.execute_op(Operation::Push(Felt::ONE)).unwrap(); let expected = build_expected(&[1]); @@ -322,8 +319,10 @@ mod tests { let expected = build_expected(&[1, 1]); assert_eq!(expected, process.stack.trace_state()); - // duplicating non-existent item should be an error - assert!(process.execute_op(Operation::Dup2).is_err()); + // duplicating non-existent item from the min stack range should be ok + assert!(process.execute_op(Operation::Dup2).is_ok()); + // drop it again before continuing the tests and stack comparison + process.execute_op(Operation::Drop).unwrap(); // put 15 more items onto the stack let mut expected = [Felt::ONE; 16]; @@ -350,7 +349,7 @@ mod tests { process.execute_op(Operation::Drop).unwrap(); process.execute_op(Operation::Drop).unwrap(); - assert_eq!(15, process.stack.depth()); + assert_eq!(MIN_STACK_DEPTH + 15, process.stack.depth()); assert_eq!(&expected[2..], &process.stack.trace_state()[..14]); assert_eq!(Felt::ONE, process.stack.trace_state()[14]); @@ -367,10 +366,9 @@ mod tests { let expected = build_expected(&[2, 3, 1]); assert_eq!(expected, process.stack.trace_state()); - // swapping fewer than 2 items should be an error - process.execute_op(Operation::Drop).unwrap(); - process.execute_op(Operation::Drop).unwrap(); - assert!(process.execute_op(Operation::Swap).is_err()); + // swapping with a minimum stack should be ok + let mut process = Process::new_dummy(); + assert!(process.execute_op(Operation::Swap).is_ok()); } #[test] @@ -383,10 +381,9 @@ mod tests { let expected = build_expected(&[5, 4, 3, 2, 9, 8, 7, 6, 1]); assert_eq!(expected, process.stack.trace_state()); - // swapping fewer than 8 items should be an error - process.execute_op(Operation::Drop).unwrap(); - process.execute_op(Operation::Drop).unwrap(); - assert!(process.execute_op(Operation::SwapW).is_err()); + // swapping with a minimum stack should be ok + let mut process = Process::new_dummy(); + assert!(process.execute_op(Operation::SwapW).is_ok()); } #[test] @@ -399,10 +396,9 @@ mod tests { let expected = build_expected(&[5, 4, 3, 2, 9, 8, 7, 6, 13, 12, 11, 10, 1]); assert_eq!(expected, process.stack.trace_state()); - // swapping fewer than 12 items should be an error - process.execute_op(Operation::Drop).unwrap(); - process.execute_op(Operation::Drop).unwrap(); - assert!(process.execute_op(Operation::SwapW2).is_err()); + // swapping with a minimum stack should be ok + let mut process = Process::new_dummy(); + assert!(process.execute_op(Operation::SwapW2).is_ok()); } #[test] @@ -423,9 +419,9 @@ mod tests { let expected = build_expected(&[4, 3, 2, 13, 12, 11, 10, 9, 8, 7, 6, 17, 16, 15, 14, 1]); assert_eq!(expected, process.stack.trace_state()); - // swapping fewer than 12 items should be an error - process.execute_op(Operation::Drop).unwrap(); - assert!(process.execute_op(Operation::SwapW3).is_err()); + // swapping with a minimum stack should be ok + let mut process = Process::new_dummy(); + assert!(process.execute_op(Operation::SwapW3).is_ok()); } #[test] @@ -457,13 +453,9 @@ mod tests { let expected = build_expected(&[16, 8, 4, 3, 1, 2, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15]); assert_eq!(expected, process.stack.trace_state()); - // error when not enough items on the stack - process.execute_op(Operation::Drop).unwrap(); - assert!(process.execute_op(Operation::MovUp15).is_err()); - - process.execute_op(Operation::Drop).unwrap(); - process.execute_op(Operation::Drop).unwrap(); - assert!(process.execute_op(Operation::MovUp13).is_err()); + // executing movup with a minimum stack depth should be ok + let mut process = Process::new_dummy(); + assert!(process.execute_op(Operation::MovUp2).is_ok()); } #[test] @@ -495,13 +487,9 @@ mod tests { let expected = build_expected(&[4, 2, 5, 6, 7, 8, 3, 9, 10, 11, 12, 13, 14, 15, 16, 1]); assert_eq!(expected, process.stack.trace_state()); - // error when not enough items on the stack - process.execute_op(Operation::Drop).unwrap(); - assert!(process.execute_op(Operation::MovDn15).is_err()); - - process.execute_op(Operation::Drop).unwrap(); - process.execute_op(Operation::Drop).unwrap(); - assert!(process.execute_op(Operation::MovDn13).is_err()); + // executing movdn with a minimum stack depth should be ok + let mut process = Process::new_dummy(); + assert!(process.execute_op(Operation::MovDn2).is_ok()); } #[test] @@ -523,11 +511,9 @@ mod tests { // error: top of the stack is not binary assert!(process.execute_op(Operation::CSwap).is_err()); - // error: not enough values on the stack - process.execute_op(Operation::Drop).unwrap(); - process.execute_op(Operation::Drop).unwrap(); - process.execute_op(Operation::Pad).unwrap(); - assert!(process.execute_op(Operation::CSwap).is_err()); + // executing conditional swap with a minimum stack depth should be ok + let mut process = Process::new_dummy(); + assert!(process.execute_op(Operation::CSwap).is_ok()); } #[test] @@ -549,12 +535,9 @@ mod tests { // error: top of the stack is not binary assert!(process.execute_op(Operation::CSwapW).is_err()); - // error: not enough values on the stack - process.execute_op(Operation::Drop).unwrap(); - process.execute_op(Operation::Drop).unwrap(); - process.execute_op(Operation::Drop).unwrap(); - process.execute_op(Operation::Pad).unwrap(); - assert!(process.execute_op(Operation::CSwapW).is_err()); + // executing conditional swap with a minimum stack depth should be ok + let mut process = Process::new_dummy(); + assert!(process.execute_op(Operation::CSwapW).is_ok()); } // HELPER FUNCTIONS diff --git a/processor/src/operations/sys_ops.rs b/processor/src/operations/sys_ops.rs index 2230291c60..1026f155b7 100644 --- a/processor/src/operations/sys_ops.rs +++ b/processor/src/operations/sys_ops.rs @@ -64,6 +64,17 @@ mod tests { Felt, FieldElement, Process, FMP_MAX, FMP_MIN, }; + #[test] + fn op_assert() { + // calling assert with a minimum stack should be an ok, as long as the top value is ONE + let mut process = Process::new_dummy(); + process.execute_op(Operation::Push(Felt::ONE)).unwrap(); + process.execute_op(Operation::Swap).unwrap(); + process.execute_op(Operation::Drop).unwrap(); + + assert!(process.execute_op(Operation::Assert).is_ok()); + } + #[test] fn op_fmpupdate() { let mut process = Process::new_dummy(); @@ -112,6 +123,10 @@ mod tests { let expected = build_expected(&[2]); assert_eq!(expected, process.stack.trace_state()); + + // calling fmpupdate with a minimum stack should be ok + let mut process = Process::new_dummy(); + assert!(process.execute_op(Operation::FmpUpdate).is_ok()); } #[test] diff --git a/processor/src/operations/u32_ops.rs b/processor/src/operations/u32_ops.rs index 9c310c6d08..fb0dd50807 100644 --- a/processor/src/operations/u32_ops.rs +++ b/processor/src/operations/u32_ops.rs @@ -268,6 +268,10 @@ mod tests { let mut process = Process::new_dummy(); init_stack_with(&mut process, &[c, b, a]); assert!(process.execute_op(Operation::U32addc).is_err()); + + // --- test with minimum stack depth ---------------------------------- + let mut process = Process::new_dummy(); + assert!(process.execute_op(Operation::U32addc).is_ok()); } #[test] @@ -318,6 +322,10 @@ mod tests { process.execute_op(Operation::U32madd).unwrap(); let expected = build_expected(&[hi, lo, d]); assert_eq!(expected, process.stack.trace_state()); + + // --- test with minimum stack depth ---------------------------------- + let mut process = Process::new_dummy(); + assert!(process.execute_op(Operation::U32madd).is_ok()); } #[test] @@ -343,6 +351,10 @@ mod tests { process.execute_op(Operation::U32and).unwrap(); let expected = build_expected(&[a & b, c, d]); assert_eq!(expected, process.stack.trace_state()); + + // --- test with minimum stack depth ---------------------------------- + let mut process = Process::new_dummy(); + assert!(process.execute_op(Operation::U32and).is_ok()); } #[test] @@ -353,6 +365,10 @@ mod tests { process.execute_op(Operation::U32or).unwrap(); let expected = build_expected(&[a | b, c, d]); assert_eq!(expected, process.stack.trace_state()); + + // --- test with minimum stack depth ---------------------------------- + let mut process = Process::new_dummy(); + assert!(process.execute_op(Operation::U32or).is_ok()); } #[test] @@ -363,6 +379,10 @@ mod tests { process.execute_op(Operation::U32xor).unwrap(); let expected = build_expected(&[a ^ b, c, d]); assert_eq!(expected, process.stack.trace_state()); + + // --- test with minimum stack depth ---------------------------------- + let mut process = Process::new_dummy(); + assert!(process.execute_op(Operation::U32xor).is_ok()); } // HELPER FUNCTIONS From 8121d04fdc1c1aa1b28510fb971d6cef7b73c980 Mon Sep 17 00:00:00 2001 From: grjte Date: Thu, 10 Mar 2022 12:01:54 +0000 Subject: [PATCH 11/26] test: fix integration tests for min stack depth --- processor/src/tests/io_ops/env_ops.rs | 6 ++-- processor/src/tests/io_ops/local_ops.rs | 47 +------------------------ processor/src/tests/io_ops/mem_ops.rs | 20 +---------- processor/src/tests/io_ops/mod.rs | 2 +- 4 files changed, 6 insertions(+), 69 deletions(-) diff --git a/processor/src/tests/io_ops/env_ops.rs b/processor/src/tests/io_ops/env_ops.rs index bfb9a56cd5..ee3d25f90f 100644 --- a/processor/src/tests/io_ops/env_ops.rs +++ b/processor/src/tests/io_ops/env_ops.rs @@ -1,4 +1,4 @@ -use super::{build_op_test, build_test}; +use super::{build_op_test, build_test, MIN_STACK_DEPTH}; use crate::system::FMP_MIN; // PUSHING VALUES ONTO THE STACK (PUSH) @@ -10,11 +10,11 @@ fn push_env_sdepth() { // --- empty stack ---------------------------------------------------------------------------- let test = build_op_test!(test_op); - test.expect_stack(&[0]); + test.expect_stack(&[MIN_STACK_DEPTH as u64]); // --- multi-element stack -------------------------------------------------------------------- let test = build_op_test!(test_op, &[2, 4, 6, 8, 10]); - test.expect_stack(&[5, 10, 8, 6, 4, 2]); + test.expect_stack(&[MIN_STACK_DEPTH as u64, 10, 8, 6, 4, 2]); // --- overflowed stack ----------------------------------------------------------------------- // push 2 values to increase the lenth of the stack beyond 16 diff --git a/processor/src/tests/io_ops/local_ops.rs b/processor/src/tests/io_ops/local_ops.rs index c09f60dca5..385fbb9e7c 100644 --- a/processor/src/tests/io_ops/local_ops.rs +++ b/processor/src/tests/io_ops/local_ops.rs @@ -1,4 +1,4 @@ -use super::{build_test, TestError}; +use super::build_test; // PUSHING VALUES ONTO THE STACK (PUSH) // ================================================================================================ @@ -77,21 +77,6 @@ fn pop_local() { test.expect_stack_and_memory(&[1], mem_addr, &[3, 0, 0, 0]); } -#[test] -fn pop_local_invalid() { - let source = " - proc.foo.1 - pop.local.0 - end - begin - exec.foo - end"; - - // --- pop fails when stack is empty ---------------------------------------------------------- - let test = build_test!(source); - test.expect_error(TestError::ExecutionError("StackUnderflow")); -} - #[test] fn popw_local() { // --- test write to local memory ------------------------------------------------------------- @@ -125,21 +110,6 @@ fn popw_local() { test.expect_stack_and_memory(&[], mem_addr, &[5, 6, 7, 8]); } -#[test] -fn popw_local_invalid() { - let source = " - proc.foo.1 - popw.local.0 - end - begin - exec.foo - end"; - - // --- pop fails when stack is empty ---------------------------------------------------------- - let test = build_test!(source, &[1, 2]); - test.expect_error(TestError::ExecutionError("StackUnderflow")); -} - // OVERWRITING VALUES ON THE STACK (LOAD) // ================================================================================================ @@ -200,21 +170,6 @@ fn storew_local() { test.expect_stack_and_memory(&[4, 3, 2, 1], mem_addr, &[5, 6, 7, 8]); } -#[test] -fn storew_local_invalid() { - let source = " - proc.foo.1 - storew.local.0 - end - begin - exec.foo - end"; - - // --- pop fails when stack is empty ---------------------------------------------------------- - let test = build_test!(source, &[1, 2]); - test.expect_error(TestError::ExecutionError("StackUnderflow")); -} - // NESTED PROCEDURES & PAIRED OPERATIONS (push/pop, pushw/popw, loadw/storew) // ================================================================================================ diff --git a/processor/src/tests/io_ops/mem_ops.rs b/processor/src/tests/io_ops/mem_ops.rs index 260c7c1241..012d1183df 100644 --- a/processor/src/tests/io_ops/mem_ops.rs +++ b/processor/src/tests/io_ops/mem_ops.rs @@ -1,4 +1,4 @@ -use super::{build_op_test, build_test, TestError}; +use super::{build_op_test, build_test}; // PUSHING VALUES ONTO THE STACK (PUSH) // ================================================================================================ @@ -60,15 +60,6 @@ fn pop_mem() { test.expect_stack_and_memory(&[3, 2, 1], addr, &[4, 0, 0, 0]); } -#[test] -fn pop_mem_invalid() { - let asm_op = "pop.mem.0"; - - // --- pop fails when stack is empty ---------------------------------------------------------- - let test = build_op_test!(asm_op); - test.expect_error(TestError::ExecutionError("StackUnderflow")); -} - #[test] fn popw_mem() { let asm_op = "popw.mem"; @@ -88,15 +79,6 @@ fn popw_mem() { test.expect_stack_and_memory(&[0], addr, &[1, 2, 3, 4]); } -#[test] -fn popw_mem_invalid() { - let asm_op = "popw.mem.0"; - - // --- popw fails when the stack doesn't contain a full word ---------------------------------------------------------- - let test = build_op_test!(asm_op, &[1, 2]); - test.expect_error(TestError::ExecutionError("StackUnderflow")); -} - // OVERWRITING VALUES ON THE STACK (LOAD) // ================================================================================================ diff --git a/processor/src/tests/io_ops/mod.rs b/processor/src/tests/io_ops/mod.rs index 5828fb6d77..4adf499c3f 100644 --- a/processor/src/tests/io_ops/mod.rs +++ b/processor/src/tests/io_ops/mod.rs @@ -1,5 +1,5 @@ use super::{ - super::{build_op_test, build_test}, + super::{build_op_test, build_test, MIN_STACK_DEPTH}, TestError, }; From 796bf0d73da13f12613a01d72a6896ba434589ce Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Thu, 10 Mar 2022 15:31:58 -0800 Subject: [PATCH 12/26] feat: implement u64 comparisons --- processor/src/tests/stdlib/math/u64_mod.rs | 137 +++++++++++++++++++++ stdlib/asm/math/u64.masm | 60 +++++++++ stdlib/src/asm.rs | 56 +++++++++ 3 files changed, 253 insertions(+) diff --git a/processor/src/tests/stdlib/math/u64_mod.rs b/processor/src/tests/stdlib/math/u64_mod.rs index 2d1651d858..f016097371 100644 --- a/processor/src/tests/stdlib/math/u64_mod.rs +++ b/processor/src/tests/stdlib/math/u64_mod.rs @@ -1,4 +1,5 @@ use super::build_test; +use proptest::prelude::*; use rand_utils::rand_value; #[test] @@ -41,6 +42,104 @@ fn mul_unsafe() { test.expect_stack(&[c1, c0]); } +// COMPARISONS +// ------------------------------------------------------------------------------------------------ + +#[test] +fn lt_unsafe() { + // test a few manual cases; randomized tests are done using proptest + let source = " + use.std::math::u64 + begin + exec.u64::lt_unsafe + end"; + + // a = 0, b = 0 + build_test!(source, &[0, 0, 0, 0]).expect_stack(&[0]); + + // a = 0, b = 1 + build_test!(source, &[0, 0, 1, 0]).expect_stack(&[1]); + + // a = 1, b = 0 + build_test!(source, &[1, 0, 0, 0]).expect_stack(&[0]); +} + +#[test] +fn lte_unsafe() { + let source = " + use.std::math::u64 + begin + exec.u64::lte_unsafe + end"; + + // a = 0, b = 0 + build_test!(source, &[0, 0, 0, 0]).expect_stack(&[1]); + + // a = 0, b = 1 + build_test!(source, &[0, 0, 1, 0]).expect_stack(&[1]); + + // a = 1, b = 0 + build_test!(source, &[1, 0, 0, 0]).expect_stack(&[0]); + + // randomized test + let a: u64 = rand_value(); + let b: u64 = rand_value(); + let c = (a <= b) as u64; + + let (a1, a0) = split_u64(a); + let (b1, b0) = split_u64(b); + build_test!(source, &[a0, a1, b0, b1]).expect_stack(&[c]); +} + +#[test] +fn gt_unsafe() { + // test a few manual cases; randomized tests are done using proptest + let source = " + use.std::math::u64 + begin + exec.u64::gt_unsafe + end"; + + // a = 0, b = 0 + build_test!(source, &[0, 0, 0, 0]).expect_stack(&[0]); + + // a = 0, b = 1 + build_test!(source, &[0, 0, 1, 0]).expect_stack(&[0]); + + // a = 1, b = 0 + build_test!(source, &[1, 0, 0, 0]).expect_stack(&[1]); +} + +#[test] +fn gte_unsafe() { + let source = " + use.std::math::u64 + begin + exec.u64::gte_unsafe + end"; + + // a = 0, b = 0 + build_test!(source, &[0, 0, 0, 0]).expect_stack(&[1]); + + // a = 0, b = 1 + build_test!(source, &[0, 0, 1, 0]).expect_stack(&[0]); + + // a = 1, b = 0 + build_test!(source, &[1, 0, 0, 0]).expect_stack(&[1]); + + // randomized test + let a: u64 = rand_value(); + let b: u64 = rand_value(); + let c = (a >= b) as u64; + + let (a1, a0) = split_u64(a); + let (b1, b0) = split_u64(b); + build_test!(source, &[a0, a1, b0, b1]).expect_stack(&[c]); +} + +// DIVISION +// ------------------------------------------------------------------------------------------------ + #[test] fn div_unsafe() { let a: u64 = rand_value(); @@ -67,6 +166,44 @@ fn div_unsafe() { test.expect_stack(&[d1, d0]); } +// RANDOMIZED TESTS +// ================================================================================================ + +proptest! { + #[test] + fn lt_unsafe_proptest(a in any::(), b in any::()) { + + let (a1, a0) = split_u64(a); + let (b1, b0) = split_u64(b); + let c = (a < b) as u64; + + let source = " + use.std::math::u64 + begin + exec.u64::lt_unsafe + end"; + + build_test!(source, &[a0, a1, b0, b1]).prop_expect_stack(&[c])?; + } + + #[test] + fn gt_unsafe_proptest(a in any::(), b in any::()) { + + let (a1, a0) = split_u64(a); + let (b1, b0) = split_u64(b); + let c = (a > b) as u64; + + let source = " + use.std::math::u64 + begin + exec.u64::gt_unsafe + end"; + + build_test!(source, &[a0, a1, b0, b1]).prop_expect_stack(&[c])?; + } + +} + // HELPER FUNCTIONS // ================================================================================================ diff --git a/stdlib/asm/math/u64.masm b/stdlib/asm/math/u64.masm index 6ff375f04e..47f205d83b 100644 --- a/stdlib/asm/math/u64.masm +++ b/stdlib/asm/math/u64.masm @@ -1,3 +1,5 @@ +# ===== ADDITION ================================================================================ # + # Performs addition of two unsigned 64 bit integers discarding the overflow. # # The input values are assumed to be represented using 32 bit limbs, but this is not checked. # # Stack transition looks as follows: # @@ -12,6 +14,8 @@ export.add_unsafe drop end +# ===== MULTIPLICATION ========================================================================== # + # Performs multiplication of two unsigned 64 bit integers discarding the overflow. # # The input values are assumed to be represented using 32 bit limbs, but this is not checked. # # Stack transition looks as follows: # @@ -30,6 +34,62 @@ export.mul_unsafe drop end +# ===== COMPARISONS ============================================================================= # + +# Performs less-than comparison of two unsigned 64 bit integers. # +# The input values are assumed to be represented using 32 bit limbs, but this is not checked. # +# Stack transition looks as follows: # +# [b_hi, b_lo, a_hi, a_lo, ...] -> c, ...], where c = 1 when a < b, and 0 otherwise. # +export.lt_unsafe + movup.3 + movup.2 + u32sub.unsafe + movdn.3 + drop + u32sub.unsafe + swap + eq.0 + movup.2 + and + or +end + +# Performs greater-than comparison of two unsigned 64 bit integers. # +# The input values are assumed to be represented using 32 bit limbs, but this is not checked. # +# Stack transition looks as follows: # +# [b_hi, b_lo, a_hi, a_lo, ...] -> c, ...], where c = 1 when a > b, and 0 otherwise. # +export.gt_unsafe + movup.2 + u32sub.unsafe + movup.2 + movup.3 + u32sub.unsafe + swap + drop + movup.2 + eq.0 + and + or +end + +# Performs less-than-or-equal comparison of two unsigned 64 bit integers. # +# The input values are assumed to be represented using 32 bit limbs, but this is not checked. # +# Stack transition looks as follows: # +# [b_hi, b_lo, a_hi, a_lo, ...] -> c, ...], where c = 1 when a <= b, and 0 otherwise. # +export.lte_unsafe + exec.gt_unsafe + not +end + +# Performs greater-than-or-equal comparison of two unsigned 64 bit integers. # +# The input values are assumed to be represented using 32 bit limbs, but this is not checked. # +# Stack transition looks as follows: # +# [b_hi, b_lo, a_hi, a_lo, ...] -> c, ...], where c = 1 when a >= b, and 0 otherwise. # +export.gte_unsafe + exec.lt_unsafe + not +end + # ===== DIVISION ================================================================================ # # Performs division of two unsigned 64 bit integers discarding the remainder. # diff --git a/stdlib/src/asm.rs b/stdlib/src/asm.rs index ebddb3d5be..8c44007491 100644 --- a/stdlib/src/asm.rs +++ b/stdlib/src/asm.rs @@ -1542,6 +1542,62 @@ export.mul_unsafe drop end +# ===== COMPARISONS ============================================================================= # + +# Performs less-than comparison of two unsigned 64 bit integers. # +# The input values are assumed to be represented using 32 bit limbs, but this is not checked. # +# Stack transition looks as follows: # +# [b_hi, b_lo, a_hi, a_lo, ...] -> c, ...], where c = 1 when a < b, and 0 otherwise. # +export.lt_unsafe + movup.3 + movup.2 + u32sub.unsafe + movdn.3 + drop + u32sub.unsafe + swap + eq.0 + movup.2 + and + or +end + +# Performs greater-than comparison of two unsigned 64 bit integers. # +# The input values are assumed to be represented using 32 bit limbs, but this is not checked. # +# Stack transition looks as follows: # +# [b_hi, b_lo, a_hi, a_lo, ...] -> c, ...], where c = 1 when a > b, and 0 otherwise. # +export.gt_unsafe + movup.2 + u32sub.unsafe + movup.2 + movup.3 + u32sub.unsafe + swap + drop + movup.2 + eq.0 + and + or +end + +# Performs less-than-or-equal comparison of two unsigned 64 bit integers. # +# The input values are assumed to be represented using 32 bit limbs, but this is not checked. # +# Stack transition looks as follows: # +# [b_hi, b_lo, a_hi, a_lo, ...] -> c, ...], where c = 1 when a <= b, and 0 otherwise. # +export.lte_unsafe + exec.gt_unsafe + not +end + +# Performs greater-than-or-equal comparison of two unsigned 64 bit integers. # +# The input values are assumed to be represented using 32 bit limbs, but this is not checked. # +# Stack transition looks as follows: # +# [b_hi, b_lo, a_hi, a_lo, ...] -> c, ...], where c = 1 when a >= b, and 0 otherwise. # +export.gte_unsafe + exec.lt_unsafe + not +end + # ===== DIVISION ================================================================================ # # Performs division of two unsigned 64 bit integers discarding the remainder. # From 3978c5e3ff0a36b2748b773534398240ac062ff4 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Thu, 10 Mar 2022 17:20:47 -0800 Subject: [PATCH 13/26] fix: bugfix in u64 division --- processor/src/tests/stdlib/math/u64_mod.rs | 17 +++++++++ stdlib/asm/math/u64.masm | 36 +++++++++++-------- stdlib/src/asm.rs | 42 ++++++++++++++-------- 3 files changed, 66 insertions(+), 29 deletions(-) diff --git a/processor/src/tests/stdlib/math/u64_mod.rs b/processor/src/tests/stdlib/math/u64_mod.rs index f016097371..aae5f10b87 100644 --- a/processor/src/tests/stdlib/math/u64_mod.rs +++ b/processor/src/tests/stdlib/math/u64_mod.rs @@ -202,6 +202,23 @@ proptest! { build_test!(source, &[a0, a1, b0, b1]).prop_expect_stack(&[c])?; } + #[test] + fn div_unsafe_proptest(a in any::(), b in any::()) { + + let c = a / b; + + let (a1, a0) = split_u64(a); + let (b1, b0) = split_u64(b); + let (c1, c0) = split_u64(c); + + let source = " + use.std::math::u64 + begin + exec.u64::div_unsafe + end"; + + build_test!(source, &[a0, a1, b0, b1]).prop_expect_stack(&[c1, c0])?; + } } // HELPER FUNCTIONS diff --git a/stdlib/asm/math/u64.masm b/stdlib/asm/math/u64.masm index 47f205d83b..9610ac9620 100644 --- a/stdlib/asm/math/u64.masm +++ b/stdlib/asm/math/u64.masm @@ -99,34 +99,42 @@ end export.div_unsafe adv.u64div # inject the quotient and the remainder into the advice tape # - push.adv.1 # read the values from the advice tape and make sure they are u32's # - u32assert # TODO: this can be optimized once we have u32assert2 instruction # - push.adv.1 - u32assert - push.adv.1 - u32assert - push.adv.1 + push.adv.1 # read the quotient from the advice tape and make sure it consists of # + u32assert # 32-bit limbs # + push.adv.1 # TODO: this can be optimized once we have u32assert2 instruction # u32assert - dup.5 # multiply quotient by the divisor; this also consumes the divisor # - dup.4 + dup.3 # multiply quotient by the divisor and make sure the resulting value # + dup.2 # fits into 2 32-bit limbs # u32mul.unsafe - dup.6 - dup.6 + dup.4 + dup.4 u32madd eq.0 assert - movup.7 dup.5 + dup.3 u32madd eq.0 assert - movup.6 - dup.5 + dup.4 + dup.3 mul eq.0 assert + push.adv.1 # read the remainder from the advice tape and make sure it consists of # + u32assert # 32-bit limbs # + push.adv.1 + u32assert + + movup.7 # make sure the divisor is greater than the remainder. this also consumes # + movup.7 # the divisor # + dup.3 + dup.3 + exec.gt_unsafe + assert + swap # add remainder to the previous result; this also consumes the remainder # movup.3 u32add.unsafe diff --git a/stdlib/src/asm.rs b/stdlib/src/asm.rs index 8c44007491..f8ea5c3f4c 100644 --- a/stdlib/src/asm.rs +++ b/stdlib/src/asm.rs @@ -1510,7 +1510,9 @@ export.mul_unsafe.6 swapw end"), // ----- std::math::u64 --------------------------------------------------------------------------- -("std::math::u64", "# Performs addition of two unsigned 64 bit integers discarding the overflow. # +("std::math::u64", "# ===== ADDITION ================================================================================ # + +# Performs addition of two unsigned 64 bit integers discarding the overflow. # # The input values are assumed to be represented using 32 bit limbs, but this is not checked. # # Stack transition looks as follows: # # [b_hi, b_lo, a_hi, a_lo, ...] -> [c_hi, c_lo, ...], where c = (a + b) % 2^64 # @@ -1524,6 +1526,8 @@ export.add_unsafe drop end +# ===== MULTIPLICATION ========================================================================== # + # Performs multiplication of two unsigned 64 bit integers discarding the overflow. # # The input values are assumed to be represented using 32 bit limbs, but this is not checked. # # Stack transition looks as follows: # @@ -1607,34 +1611,42 @@ end export.div_unsafe adv.u64div # inject the quotient and the remainder into the advice tape # - push.adv.1 # read the values from the advice tape and make sure they are u32's # - u32assert # TODO: this can be optimized once we have u32assert2 instruction # - push.adv.1 - u32assert - push.adv.1 - u32assert - push.adv.1 + push.adv.1 # read the quotient from the advice tape and make sure it consists of # + u32assert # 32-bit limbs # + push.adv.1 # TODO: this can be optimized once we have u32assert2 instruction # u32assert - dup.5 # multiply quotient by the divisor; this also consumes the divisor # - dup.4 + dup.3 # multiply quotient by the divisor and make sure the resulting value # + dup.2 # fits into 2 32-bit limbs # u32mul.unsafe - dup.6 - dup.6 + dup.4 + dup.4 u32madd eq.0 assert - movup.7 dup.5 + dup.3 u32madd eq.0 assert - movup.6 - dup.5 + dup.4 + dup.3 mul eq.0 assert + push.adv.1 # read the remainder from the advice tape and make sure it consists of # + u32assert # 32-bit limbs # + push.adv.1 + u32assert + + movup.7 # make sure the divisor is greater than the remainder. this also consumes # + movup.7 # the divisor # + dup.3 + dup.3 + exec.gt_unsafe + assert + swap # add remainder to the previous result; this also consumes the remainder # movup.3 u32add.unsafe From 2d8906be758c4b768a6b7bdaf8b164f3f183eb5b Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Thu, 10 Mar 2022 17:32:22 -0800 Subject: [PATCH 14/26] fix: removed unneeded safety checks from u64 ops --- processor/src/tests/stdlib/math/u64_mod.rs | 2 +- stdlib/asm/math/u64.masm | 20 ++++++++++---------- stdlib/src/asm.rs | 20 ++++++++++---------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/processor/src/tests/stdlib/math/u64_mod.rs b/processor/src/tests/stdlib/math/u64_mod.rs index aae5f10b87..f7cbb5af41 100644 --- a/processor/src/tests/stdlib/math/u64_mod.rs +++ b/processor/src/tests/stdlib/math/u64_mod.rs @@ -224,7 +224,7 @@ proptest! { // HELPER FUNCTIONS // ================================================================================================ -/// Split the provided u64 value into 32 hight and low bits. +/// Split the provided u64 value into 32 high and low bits. fn split_u64(value: u64) -> (u64, u64) { (value >> 32, value as u32 as u64) } diff --git a/stdlib/asm/math/u64.masm b/stdlib/asm/math/u64.masm index 9610ac9620..03aa2ed2f9 100644 --- a/stdlib/asm/math/u64.masm +++ b/stdlib/asm/math/u64.masm @@ -10,7 +10,7 @@ export.add_unsafe u32add.unsafe movup.3 movup.3 - u32addc + u32addc.unsafe drop end @@ -26,11 +26,11 @@ export.mul_unsafe u32mul.unsafe movup.4 movup.4 - u32madd + u32madd.unsafe drop movup.3 movup.3 - u32madd + u32madd.unsafe drop end @@ -39,7 +39,7 @@ end # Performs less-than comparison of two unsigned 64 bit integers. # # The input values are assumed to be represented using 32 bit limbs, but this is not checked. # # Stack transition looks as follows: # -# [b_hi, b_lo, a_hi, a_lo, ...] -> c, ...], where c = 1 when a < b, and 0 otherwise. # +# [b_hi, b_lo, a_hi, a_lo, ...] -> [c, ...], where c = 1 when a < b, and 0 otherwise. # export.lt_unsafe movup.3 movup.2 @@ -57,7 +57,7 @@ end # Performs greater-than comparison of two unsigned 64 bit integers. # # The input values are assumed to be represented using 32 bit limbs, but this is not checked. # # Stack transition looks as follows: # -# [b_hi, b_lo, a_hi, a_lo, ...] -> c, ...], where c = 1 when a > b, and 0 otherwise. # +# [b_hi, b_lo, a_hi, a_lo, ...] -> [c, ...], where c = 1 when a > b, and 0 otherwise. # export.gt_unsafe movup.2 u32sub.unsafe @@ -75,7 +75,7 @@ end # Performs less-than-or-equal comparison of two unsigned 64 bit integers. # # The input values are assumed to be represented using 32 bit limbs, but this is not checked. # # Stack transition looks as follows: # -# [b_hi, b_lo, a_hi, a_lo, ...] -> c, ...], where c = 1 when a <= b, and 0 otherwise. # +# [b_hi, b_lo, a_hi, a_lo, ...] -> [c, ...], where c = 1 when a <= b, and 0 otherwise. # export.lte_unsafe exec.gt_unsafe not @@ -84,7 +84,7 @@ end # Performs greater-than-or-equal comparison of two unsigned 64 bit integers. # # The input values are assumed to be represented using 32 bit limbs, but this is not checked. # # Stack transition looks as follows: # -# [b_hi, b_lo, a_hi, a_lo, ...] -> c, ...], where c = 1 when a >= b, and 0 otherwise. # +# [b_hi, b_lo, a_hi, a_lo, ...] -> [c, ...], where c = 1 when a >= b, and 0 otherwise. # export.gte_unsafe exec.lt_unsafe not @@ -109,12 +109,12 @@ export.div_unsafe u32mul.unsafe dup.4 dup.4 - u32madd + u32madd.unsafe eq.0 assert dup.5 dup.3 - u32madd + u32madd.unsafe eq.0 assert dup.4 @@ -140,7 +140,7 @@ export.div_unsafe u32add.unsafe movup.3 movup.3 - u32addc + u32addc.unsafe eq.0 assert diff --git a/stdlib/src/asm.rs b/stdlib/src/asm.rs index f8ea5c3f4c..92ca4ab79c 100644 --- a/stdlib/src/asm.rs +++ b/stdlib/src/asm.rs @@ -1522,7 +1522,7 @@ export.add_unsafe u32add.unsafe movup.3 movup.3 - u32addc + u32addc.unsafe drop end @@ -1538,11 +1538,11 @@ export.mul_unsafe u32mul.unsafe movup.4 movup.4 - u32madd + u32madd.unsafe drop movup.3 movup.3 - u32madd + u32madd.unsafe drop end @@ -1551,7 +1551,7 @@ end # Performs less-than comparison of two unsigned 64 bit integers. # # The input values are assumed to be represented using 32 bit limbs, but this is not checked. # # Stack transition looks as follows: # -# [b_hi, b_lo, a_hi, a_lo, ...] -> c, ...], where c = 1 when a < b, and 0 otherwise. # +# [b_hi, b_lo, a_hi, a_lo, ...] -> [c, ...], where c = 1 when a < b, and 0 otherwise. # export.lt_unsafe movup.3 movup.2 @@ -1569,7 +1569,7 @@ end # Performs greater-than comparison of two unsigned 64 bit integers. # # The input values are assumed to be represented using 32 bit limbs, but this is not checked. # # Stack transition looks as follows: # -# [b_hi, b_lo, a_hi, a_lo, ...] -> c, ...], where c = 1 when a > b, and 0 otherwise. # +# [b_hi, b_lo, a_hi, a_lo, ...] -> [c, ...], where c = 1 when a > b, and 0 otherwise. # export.gt_unsafe movup.2 u32sub.unsafe @@ -1587,7 +1587,7 @@ end # Performs less-than-or-equal comparison of two unsigned 64 bit integers. # # The input values are assumed to be represented using 32 bit limbs, but this is not checked. # # Stack transition looks as follows: # -# [b_hi, b_lo, a_hi, a_lo, ...] -> c, ...], where c = 1 when a <= b, and 0 otherwise. # +# [b_hi, b_lo, a_hi, a_lo, ...] -> [c, ...], where c = 1 when a <= b, and 0 otherwise. # export.lte_unsafe exec.gt_unsafe not @@ -1596,7 +1596,7 @@ end # Performs greater-than-or-equal comparison of two unsigned 64 bit integers. # # The input values are assumed to be represented using 32 bit limbs, but this is not checked. # # Stack transition looks as follows: # -# [b_hi, b_lo, a_hi, a_lo, ...] -> c, ...], where c = 1 when a >= b, and 0 otherwise. # +# [b_hi, b_lo, a_hi, a_lo, ...] -> [c, ...], where c = 1 when a >= b, and 0 otherwise. # export.gte_unsafe exec.lt_unsafe not @@ -1621,12 +1621,12 @@ export.div_unsafe u32mul.unsafe dup.4 dup.4 - u32madd + u32madd.unsafe eq.0 assert dup.5 dup.3 - u32madd + u32madd.unsafe eq.0 assert dup.4 @@ -1652,7 +1652,7 @@ export.div_unsafe u32add.unsafe movup.3 movup.3 - u32addc + u32addc.unsafe eq.0 assert From 5f3919c08221f5c559e8f287c0f3944b1fe7e252 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Wed, 9 Mar 2022 22:27:51 -0800 Subject: [PATCH 15/26] feat: re-enable air, verifier, and miden crates --- Cargo.toml | 6 +- air/Cargo.toml | 7 +- air/src/lib.rs | 243 ++++++------------------------------- core/src/inputs/mod.rs | 3 +- core/src/program/mod.rs | 6 +- miden/Cargo.toml | 14 ++- miden/src/lib.rs | 134 ++++++-------------- processor/src/errors.rs | 2 + processor/src/lib.rs | 3 +- processor/src/tests/mod.rs | 5 +- processor/src/trace.rs | 12 +- verifier/Cargo.toml | 12 +- verifier/src/lib.rs | 61 ++++++++-- 13 files changed, 171 insertions(+), 337 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 575e0efcf1..f789cc75c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,13 @@ [workspace] members = [ - #"air", + "air", "assembly", "core", #"examples", - #"miden", + "miden", "processor", "stdlib", - #"verifier" + "verifier" ] [profile.release] diff --git a/air/Cargo.toml b/air/Cargo.toml index be1bcdbf78..6935a97eda 100644 --- a/air/Cargo.toml +++ b/air/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden-air" -version = "0.1.0" +version = "0.2.0" description = "Algebraic intermediate representation of Miden VM processor" authors = ["miden contributors"] readme = "README.md" @@ -8,7 +8,8 @@ license = "MIT" repository = "https://github.com/maticnetwork/miden" categories = ["cryptography", "no-std"] keywords = ["air", "arithmetization", "crypto", "miden"] -edition = "2018" +edition = "2021" +rust-version = "1.57" [lib] bench = false @@ -19,6 +20,6 @@ default = ["std"] std = ["vm-core/std", "winter-air/std", "winter-utils/std"] [dependencies] -vm-core = { package = "miden-core", path = "../core", version = "0.1", default-features = false } +vm-core = { package = "miden-core", path = "../core", version = "0.2", default-features = false } winter-air = { package = "winter-air", version = "0.3", default-features = false } winter-utils = { package = "winter-utils", version = "0.3", default-features = false } diff --git a/air/src/lib.rs b/air/src/lib.rs index 1e518d8eab..67dc065bfa 100644 --- a/air/src/lib.rs +++ b/air/src/lib.rs @@ -1,208 +1,52 @@ -use core::convert::{TryFrom, TryInto}; -use vm_core::{ - hasher, op_sponge, opcodes, BASE_CYCLE_LENGTH, CF_OP_BITS_RANGE, HD_OP_BITS_RANGE, - LD_OP_BITS_RANGE, MIN_CONTEXT_DEPTH, MIN_LOOP_DEPTH, NUM_CF_OPS, NUM_HD_OPS, NUM_LD_OPS, - OP_COUNTER_IDX, OP_SPONGE_RANGE, -}; +use vm_core::{hasher::Digest, MIN_STACK_DEPTH}; use winter_air::{ Air, AirContext, Assertion, EvaluationFrame, ProofOptions as WinterProofOptions, TraceInfo, - TransitionConstraintDegree, }; -use winter_utils::{group_slice_elements, ByteWriter, Serializable}; +use winter_utils::{ByteWriter, Serializable}; -mod decoder; mod options; -mod stack; -mod transition; -mod utils; +//mod utils; // EXPORTS // ================================================================================================ pub use options::ProofOptions; -pub use transition::VmTransition; -pub use vm_core::{ - utils::ToElements, BaseElement, FieldElement, StarkField, TraceState, MAX_OUTPUTS, - MIN_TRACE_LENGTH, -}; +pub use vm_core::{utils::ToElements, Felt, FieldElement, StarkField}; pub use winter_air::{FieldExtension, HashFunction}; // PROCESSOR AIR // ================================================================================================ pub struct ProcessorAir { - context: AirContext, - op_count: usize, - inputs: Vec, - outputs: Vec, - program_hash: [BaseElement; op_sponge::DIGEST_SIZE], - ctx_depth: usize, - loop_depth: usize, - stack_depth: usize, - decoder_constraint_count: usize, + context: AirContext, } impl Air for ProcessorAir { - type BaseField = BaseElement; + type BaseField = Felt; type PublicInputs = PublicInputs; - fn new(trace_info: TraceInfo, pub_inputs: PublicInputs, options: WinterProofOptions) -> Self { - let meta = TraceMetadata::from_trace_info(&trace_info); - - let mut tcd = decoder::get_transition_constraint_degrees(meta.ctx_depth, meta.loop_depth); - let decoder_constraint_count = tcd.len(); - tcd.append(&mut stack::get_transition_constraint_degrees( - meta.stack_depth, - )); - - Self { - context: AirContext::new(trace_info, tcd, options), - op_count: meta.op_count, - inputs: pub_inputs.inputs, - outputs: pub_inputs.outputs, - program_hash: pub_inputs.program_hash, - ctx_depth: meta.ctx_depth, - loop_depth: meta.loop_depth, - stack_depth: meta.stack_depth, - decoder_constraint_count, - } + fn new( + _trace_info: TraceInfo, + _pub_inputs: PublicInputs, + _options: WinterProofOptions, + ) -> Self { + unimplemented!() } - fn get_periodic_column_values(&self) -> Vec> { - let mut result = Vec::new(); - for mask in decoder::MASKS.iter() { - result.push(mask.to_elements()); - } - - for ark in hasher::ARK.iter() { - result.push(ark.to_vec()); - } - - result + fn get_assertions(&self) -> Vec> { + unimplemented!() } - #[allow(clippy::vec_init_then_push)] - fn get_assertions(&self) -> Vec> { - let mut result = Vec::new(); - - // --- set assertions for the first step -------------------------------------------------- - - // make sure op_counter is set to zero - result.push(Assertion::single(OP_COUNTER_IDX, 0, BaseElement::ZERO)); - - // make sure instruction sponge registers are set to zeros - for i in OP_SPONGE_RANGE { - result.push(Assertion::single(i, 0, BaseElement::ZERO)); - } - - // make sure cf_bits are set to HACC (000) - for i in CF_OP_BITS_RANGE { - result.push(Assertion::single(i, 0, BaseElement::ZERO)); - } - - // make sure low-degree op_bits are set to BEGIN (0000) - for i in LD_OP_BITS_RANGE { - result.push(Assertion::single(i, 0, BaseElement::ZERO)); - } - - // make sure high-degree op_bits are set to BEGIN (00) - for i in HD_OP_BITS_RANGE { - result.push(Assertion::single(i, 0, BaseElement::ZERO)); - } - - // make sure all context stack registers are zeros - let ctx_stack_start = HD_OP_BITS_RANGE.end; - let ctx_stack_end = ctx_stack_start + self.ctx_depth; - for i in ctx_stack_start..ctx_stack_end { - result.push(Assertion::single(i, 0, BaseElement::ZERO)); - } - - // make sure all loop stack registers are 0s - let loop_stack_start = ctx_stack_end; - let loop_stack_end = loop_stack_start + self.loop_depth; - for i in loop_stack_start..loop_stack_end { - result.push(Assertion::single(i, 0, BaseElement::ZERO)); - } - - // make sure user stack registers are set to inputs - let user_stack_start = loop_stack_end; - for (i, &input_value) in self.inputs.iter().enumerate() { - result.push(Assertion::single(user_stack_start + i, 0, input_value)); - } - - // --- set assertions for the last step --------------------------------------------------- - let last_step = self.trace_length() - 1; - - // make sure op_counter register is set to the claimed value of operations - result.push(Assertion::single( - OP_COUNTER_IDX, - last_step, - BaseElement::new(self.op_count as u128), - )); - - // make sure operation sponge contains program hash - let program_hash_start = OP_SPONGE_RANGE.start; - for (i, &value) in self.program_hash.iter().enumerate() { - result.push(Assertion::single(program_hash_start + i, last_step, value)); - } - - // make sure control flow op_bits are set VOID (111) - for i in CF_OP_BITS_RANGE { - result.push(Assertion::single(i, last_step, BaseElement::ONE)); - } - - // make sure low-degree op_bits are set to NOOP (11111) - for i in LD_OP_BITS_RANGE { - result.push(Assertion::single(i, last_step, BaseElement::ONE)); - } - - // make sure high-degree op_bits are set to NOOP (11) - for i in HD_OP_BITS_RANGE { - result.push(Assertion::single(i, last_step, BaseElement::ONE)); - } - - // make sure all context stack registers are zeros - for i in ctx_stack_start..ctx_stack_end { - result.push(Assertion::single(i, last_step, BaseElement::ZERO)); - } - - // make sure all loop stack registers are 0s - for i in loop_stack_start..loop_stack_end { - result.push(Assertion::single(i, last_step, BaseElement::ZERO)); - } - - // make sure user stack registers are set to outputs - for (i, &output_value) in self.outputs.iter().enumerate() { - result.push(Assertion::single( - user_stack_start + i, - last_step, - output_value, - )); - } - - result - } - - fn evaluate_transition>( + fn evaluate_transition>( &self, - frame: &EvaluationFrame, - periodic_values: &[E], - result: &mut [E], + _frame: &EvaluationFrame, + _periodic_values: &[E], + _result: &mut [E], ) { - let mut transition = VmTransition::new(self.ctx_depth, self.loop_depth, self.stack_depth); - transition.update(frame); - - let (masks, ark) = periodic_values.split_at(decoder::MASKS.len()); - - decoder::enforce_constraints(&transition, masks, ark, result); - stack::enforce_constraints( - &transition, - ark, - &mut result[self.decoder_constraint_count..], - ); + unimplemented!() } - fn context(&self) -> &AirContext { + fn context(&self) -> &AirContext { &self.context } } @@ -211,38 +55,30 @@ impl Air for ProcessorAir { // ================================================================================================ pub struct PublicInputs { - program_hash: [BaseElement; op_sponge::DIGEST_SIZE], - inputs: Vec, - outputs: Vec, + program_hash: Digest, + init_stack_state: [Felt; MIN_STACK_DEPTH], + last_stack_state: [Felt; MIN_STACK_DEPTH], } impl PublicInputs { - pub fn new(program_hash: [u8; 32], inputs: &[u128], outputs: &[u128]) -> Self { - let program_hash: &[[u8; 16]] = group_slice_elements(&program_hash); - let program_hash = [ - BaseElement::try_from(program_hash[0]).unwrap(), - BaseElement::try_from(program_hash[1]).unwrap(), - ]; - + pub fn new( + program_hash: Digest, + init_stack_state: [Felt; MIN_STACK_DEPTH], + last_stack_state: [Felt; MIN_STACK_DEPTH], + ) -> Self { Self { program_hash, - inputs: inputs - .iter() - .map(|&v| BaseElement::try_from(v).unwrap()) - .collect(), - outputs: outputs - .iter() - .map(|&v| BaseElement::try_from(v).unwrap()) - .collect(), + init_stack_state, + last_stack_state, } } } impl Serializable for PublicInputs { fn write_into(&self, target: &mut W) { - target.write(&self.program_hash[..]); - target.write(&self.inputs); - target.write(&self.outputs); + target.write(self.program_hash.as_elements()); + target.write(self.init_stack_state.as_slice()); + target.write(self.last_stack_state.as_slice()); } } @@ -257,16 +93,7 @@ pub struct TraceMetadata { } impl TraceMetadata { - pub fn from_trace_info(trace_info: &TraceInfo) -> Self { - let op_count = u64::from_le_bytes(trace_info.meta()[..8].try_into().unwrap()) as usize; - let ctx_depth = trace_info.meta()[8] as usize; - let loop_depth = trace_info.meta()[9] as usize; - let decoder_width = TraceState::::compute_decoder_width(ctx_depth, loop_depth); - TraceMetadata { - op_count, - ctx_depth, - loop_depth, - stack_depth: trace_info.width() - decoder_width, - } + pub fn from_trace_info(_trace_info: &TraceInfo) -> Self { + unimplemented!() } } diff --git a/core/src/inputs/mod.rs b/core/src/inputs/mod.rs index 22991b14d0..156a815578 100644 --- a/core/src/inputs/mod.rs +++ b/core/src/inputs/mod.rs @@ -40,7 +40,8 @@ impl ProgramInputs { /// Returns [ProgramInputs] instantiated with the specified initial stack values, advice tape /// values, and advice sets. /// - /// # Returns an error if: + /// # Errors + /// Returns an error if: /// - The number initial stack values is greater than 16. /// - Any of the initial stack values or the advice tape values are not valid field elements. /// - Any of the advice sets have the same root. diff --git a/core/src/program/mod.rs b/core/src/program/mod.rs index 64a0895815..3d3de28cf0 100644 --- a/core/src/program/mod.rs +++ b/core/src/program/mod.rs @@ -22,7 +22,7 @@ pub use library::Library; #[derive(Clone, Debug)] pub struct Script { root: CodeBlock, - hash: [u8; 32], + hash: Digest, } impl Script { @@ -30,7 +30,7 @@ impl Script { // -------------------------------------------------------------------------------------------- /// Constructs a new program from the specified code block. pub fn new(root: CodeBlock) -> Self { - let hash = hasher::merge(&[root.hash(), Digest::default()]).into(); + let hash = hasher::merge(&[root.hash(), Digest::default()]); Self { root, hash } } @@ -43,7 +43,7 @@ impl Script { } /// Returns a hash of this script. - pub fn hash(&self) -> &[u8; 32] { + pub fn hash(&self) -> &Digest { &self.hash } } diff --git a/miden/Cargo.toml b/miden/Cargo.toml index 49983a9499..113aeb5807 100644 --- a/miden/Cargo.toml +++ b/miden/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden" -version = "0.1.0" +version = "0.2.0" description="Miden virtual machine" authors = ["miden contributors"] readme="README.md" @@ -8,7 +8,8 @@ license = "MIT" repository = "https://github.com/maticnetwork/miden" categories = ["cryptography", "emulators", "no-std"] keywords = ["miden", "stark", "virtual-machine", "zkp"] -edition = "2018" +edition = "2021" +rust-version = "1.57" [lib] name = "miden" @@ -22,10 +23,11 @@ default = ["std"] std = ["air/std", "assembly/std", "hex/std", "processor/std", "prover/std", "verifier/std"] [dependencies] -air = { package = "miden-air", path = "../air", version = "0.1", default-features = false } -assembly = { package = "miden-assembly", path = "../assembly", version = "0.1", default-features = false } -processor = { package = "miden-processor", path = "../processor", version = "0.1", default-features = false } +air = { package = "miden-air", path = "../air", version = "0.2", default-features = false } +assembly = { package = "miden-assembly", path = "../assembly", version = "0.2", default-features = false } +processor = { package = "miden-processor", path = "../processor", version = "0.2", default-features = false } prover = { package = "winter-prover", version = "0.3", default-features = false } hex = { version = "0.4", optional = true } log = { version = "0.4", default-features = false } -verifier = { package = "miden-verifier", path = "../verifier", version = "0.1", default-features = false } +verifier = { package = "miden-verifier", path = "../verifier", version = "0.2", default-features = false } +vm-core = { package = "miden-core", path = "../core", version = "0.2", default-features = false } diff --git a/miden/src/lib.rs b/miden/src/lib.rs index 09ba3f0391..ad5e48d958 100644 --- a/miden/src/lib.rs +++ b/miden/src/lib.rs @@ -1,84 +1,72 @@ -use air::{ProcessorAir, PublicInputs, TraceMetadata, TraceState, MAX_OUTPUTS, MIN_TRACE_LENGTH}; -use core::convert::TryInto; +use air::{ProcessorAir, PublicInputs}; +use processor::{ExecutionError, ExecutionTrace}; +use prover::{Prover, Trace}; +use vm_core::{Felt, StarkField, MIN_STACK_DEPTH}; + #[cfg(feature = "std")] use log::debug; -use prover::{Prover, ProverError, Serializable, Trace, TraceTable}; #[cfg(feature = "std")] use std::time::Instant; -#[cfg(test)] -mod tests; +//#[cfg(test)] +//mod tests; // EXPORTS // ================================================================================================ pub use air::{FieldExtension, HashFunction, ProofOptions}; pub use assembly; -pub use processor::{BaseElement, FieldElement, Program, ProgramInputs, StarkField}; pub use prover::StarkProof; -pub use verifier::{verify, VerifierError}; +pub use verifier::{verify, VerificationError}; +pub use vm_core::{program::Script, ProgramInputs}; // EXECUTOR // ================================================================================================ -/// Executes the specified `program` and returns the result together with a STARK-based proof of execution. +/// Executes the specified `program` and returns the result together with a STARK-based proof of +/// the program's execution. +/// +/// * `inputs` specifies the initial state of the stack as well as non-deterministic (secret) +/// inputs for the VM. +/// * `num_outputs` specifies the number of elements from the top of the stack to be returned. +/// * `options` defines parameters for STARK proof generation. /// -/// * `inputs` specifies the initial stack state and provides secret input tapes; -/// * `num_outputs` specifies the number of elements from the top of the stack to be returned; +/// # Errors +/// Returns an error if program execution or STARK proof generation fails for any reason. pub fn execute( - program: &Program, + program: &Script, inputs: &ProgramInputs, num_outputs: usize, options: &ProofOptions, -) -> Result<(Vec, StarkProof), ProverError> { +) -> Result<(Vec, StarkProof), ExecutionError> { assert!( - num_outputs <= MAX_OUTPUTS, + num_outputs <= MIN_STACK_DEPTH, "cannot produce more than {} outputs, but requested {}", - MAX_OUTPUTS, + MIN_STACK_DEPTH, num_outputs ); // execute the program to create an execution trace #[cfg(feature = "std")] let now = Instant::now(); - let trace = processor::execute(program, inputs); + let trace = processor::execute(program, inputs)?; #[cfg(feature = "std")] debug!( - "Generated execution trace of {} registers and {} steps in {} ms", + "Generated execution trace of {} columns and {} steps in {} ms", trace.width(), trace.length(), now.elapsed().as_millis() ); - // copy the user stack state the the last step to return as output - let last_state = get_last_state(&trace); - let outputs = last_state.user_stack()[..num_outputs] + // copy the stack state at the last step to return as output + let outputs = trace.last_stack_state()[..num_outputs] .iter() .map(|&v| v.as_int()) .collect::>(); - // make sure number of executed operations was sufficient - assert!( - last_state.op_counter().as_int() as usize >= MIN_TRACE_LENGTH, - "a program must consist of at least {} operation, but only {} were executed", - MIN_TRACE_LENGTH, - last_state.op_counter() - ); - - // make sure program hash generated by the VM matches the hash of the program - let program_hash: [u8; 32] = last_state.program_hash().to_bytes().try_into().unwrap(); - #[cfg(feature = "std")] - assert!( - *program.hash() == program_hash, - "expected program hash {} does not match trace hash {}", - hex::encode(program.hash()), - hex::encode(program_hash) - ); - // generate STARK proof - let prover = ExecutionProver::new(inputs.clone(), num_outputs, options.clone()); - let proof = prover.prove(trace)?; - //let proof = prover::prove::(trace, pub_inputs, options.deref().clone())?; + let prover = ExecutionProver::new(options.clone()); + let proof = prover.prove(trace).map_err(ExecutionError::ProverError)?; Ok((outputs, proof)) } @@ -87,75 +75,29 @@ pub fn execute( // ================================================================================================ struct ExecutionProver { - inputs: ProgramInputs, - num_outputs: usize, options: ProofOptions, } impl ExecutionProver { - pub fn new(inputs: ProgramInputs, num_outputs: usize, options: ProofOptions) -> Self { - Self { - inputs, - num_outputs, - options, - } + pub fn new(options: ProofOptions) -> Self { + Self { options } } } impl Prover for ExecutionProver { - type BaseField = BaseElement; + type BaseField = Felt; type Air = ProcessorAir; - type Trace = TraceTable; + type Trace = ExecutionTrace; fn options(&self) -> &prover::ProofOptions { &self.options } - fn get_pub_inputs(&self, trace: &Self::Trace) -> PublicInputs { - // copy the user stack state the the last step to return as output - let last_state = get_last_state(trace); - let outputs = last_state.user_stack()[..self.num_outputs] - .iter() - .map(|&v| v.as_int()) - .collect::>(); - - let inputs = self - .inputs - .public_inputs() - .iter() - .map(|&v| v.as_int()) - .collect::>(); - - let program_hash: [u8; 32] = last_state.program_hash().to_bytes().try_into().unwrap(); - - PublicInputs::new(program_hash, &inputs, &outputs) - } -} - -// HELPER FUNCTIONS -// ================================================================================================ - -fn get_last_state(trace: &TraceTable) -> TraceState { - let last_step = trace.length() - 1; - let meta = TraceMetadata::from_trace_info(&trace.get_info()); - - let mut last_row = vec![BaseElement::ZERO; trace.width()]; - trace.read_row_into(last_step, &mut last_row); - - TraceState::from_slice(meta.ctx_depth, meta.loop_depth, meta.stack_depth, &last_row) -} - -/// Prints out an execution trace. -#[allow(unused)] -fn print_trace(trace: &TraceTable, _multiples_of: usize) { - let trace_width = trace.width(); - let meta = TraceMetadata::from_trace_info(&trace.get_info()); - - let mut state = vec![BaseElement::ZERO; trace_width]; - for i in 0..trace.length() { - trace.read_row_into(i, &mut state); - let state = - TraceState::from_slice(meta.ctx_depth, meta.loop_depth, meta.stack_depth, &state); - println!("{:?}", state); + fn get_pub_inputs(&self, trace: &ExecutionTrace) -> PublicInputs { + PublicInputs::new( + trace.program_hash(), + trace.init_stack_state(), + trace.last_stack_state(), + ) } } diff --git a/processor/src/errors.rs b/processor/src/errors.rs index 96f537789d..37c939cd02 100644 --- a/processor/src/errors.rs +++ b/processor/src/errors.rs @@ -1,4 +1,5 @@ use super::{AdviceSetError, CodeBlock, Felt}; +use winterfell::ProverError; // EXECUTION ERROR // ================================================================================================ @@ -16,4 +17,5 @@ pub enum ExecutionError { AdviceSetUpdateFailed(AdviceSetError), InvalidFmpValue(Felt, Felt), NotU32Value(Felt), + ProverError(ProverError), } diff --git a/processor/src/lib.rs b/processor/src/lib.rs index ac0e5a5e6f..af66d30806 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -1,5 +1,6 @@ use vm_core::{ errors::AdviceSetError, + hasher::Digest, program::{ blocks::{CodeBlock, Join, Loop, OpBatch, Span, Split}, Script, @@ -61,7 +62,7 @@ type AuxiliaryTableTrace = [Vec; AUXILIARY_TABLE_WIDTH]; pub fn execute(script: &Script, inputs: &ProgramInputs) -> Result { let mut process = Process::new(inputs.clone()); process.execute_code_block(script.root())?; - Ok(ExecutionTrace::new(process)) + Ok(ExecutionTrace::new(process, *script.hash())) } // PROCESS diff --git a/processor/src/tests/mod.rs b/processor/src/tests/mod.rs index 480dc41901..1b35e1277f 100644 --- a/processor/src/tests/mod.rs +++ b/processor/src/tests/mod.rs @@ -105,7 +105,8 @@ impl Test { let mut process = Process::new(self.inputs.clone()); // execute the test - process.execute_code_block(self.compile().root()).unwrap(); + let script = self.compile(); + process.execute_code_block(script.root()).unwrap(); // validate the memory state let mem_state = process.memory.get_value(mem_addr).unwrap(); @@ -113,7 +114,7 @@ impl Test { assert_eq!(expected_mem, mem_state); // validate the stack state - let stack_state = ExecutionTrace::new(process).last_stack_state(); + let stack_state = ExecutionTrace::new(process, *script.hash()).last_stack_state(); let expected_stack = convert_to_stack(final_stack); assert_eq!(expected_stack, stack_state); } diff --git a/processor/src/trace.rs b/processor/src/trace.rs index 08a1a5fb95..d67b9396c6 100644 --- a/processor/src/trace.rs +++ b/processor/src/trace.rs @@ -1,5 +1,5 @@ use super::{ - AuxiliaryTableTrace, Bitwise, Felt, FieldElement, Hasher, Memory, Process, StackTrace, + AuxiliaryTableTrace, Bitwise, Digest, Felt, FieldElement, Hasher, Memory, Process, StackTrace, AUXILIARY_TABLE_WIDTH, MIN_STACK_DEPTH, }; use core::slice; @@ -15,13 +15,15 @@ pub struct ExecutionTrace { stack: StackTrace, #[allow(dead_code)] aux_table: AuxiliaryTableTrace, + // TODO: program hash should be retrieved from decoder trace, but for now we store it explicitly + program_hash: Digest, } impl ExecutionTrace { // CONSTRUCTOR // -------------------------------------------------------------------------------------------- /// Builds an execution trace for the provided process. - pub(super) fn new(process: Process) -> Self { + pub(super) fn new(process: Process, program_hash: Digest) -> Self { let Process { system, decoder: _, @@ -64,12 +66,18 @@ impl ExecutionTrace { meta: Vec::new(), stack: stack_trace, aux_table: aux_table_trace, + program_hash, } } // PUBLIC ACCESSORS // -------------------------------------------------------------------------------------------- + /// TODO: add docs + pub fn program_hash(&self) -> Digest { + self.program_hash + } + /// TODO: add docs pub fn init_stack_state(&self) -> [Felt; MIN_STACK_DEPTH] { let mut result = [Felt::ZERO; MIN_STACK_DEPTH]; diff --git a/verifier/Cargo.toml b/verifier/Cargo.toml index 1c5b066809..d18a899a13 100644 --- a/verifier/Cargo.toml +++ b/verifier/Cargo.toml @@ -1,14 +1,15 @@ [package] name = "miden-verifier" -version = "0.1.0" +version = "0.2.0" description="Miden VM execution verifier" authors = ["miden contributors"] readme="README.md" -edition = "2018" +license = "MIT" repository = "https://github.com/maticnetwork/miden" categories = ["cryptography", "no-std"] keywords = ["miden", "stark", "verifier", "zkp"] -license = "MIT" +edition = "2021" +rust-version = "1.57" [lib] bench = false @@ -19,6 +20,7 @@ default = ["std"] std = ["air/std", "assembly/std", "winterfell/std"] [dependencies] -air = { package = "miden-air", path = "../air", version = "0.1", default-features = false } -assembly = { package = "miden-assembly", path = "../assembly", version = "0.1", default-features = false } +air = { package = "miden-air", path = "../air", version = "0.2", default-features = false } +assembly = { package = "miden-assembly", path = "../assembly", version = "0.2", default-features = false } +vm-core = { package = "miden-core", path = "../core", version = "0.2", default-features = false } winterfell = { package = "winter-verifier", version = "0.3", default-features = false } diff --git a/verifier/src/lib.rs b/verifier/src/lib.rs index c03aec69e3..3741006e98 100644 --- a/verifier/src/lib.rs +++ b/verifier/src/lib.rs @@ -1,10 +1,13 @@ use air::{ProcessorAir, PublicInputs}; +use vm_core::{Felt, FieldElement, MIN_STACK_DEPTH}; +use winterfell::VerifierError; // EXPORTS // ================================================================================================ pub use assembly; -pub use winterfell::{StarkProof, VerifierError}; +pub use vm_core::hasher::Digest; +pub use winterfell::StarkProof; // VERIFIER // ================================================================================================ @@ -17,11 +20,55 @@ pub use winterfell::{StarkProof, VerifierError}; /// # Errors /// Returns an error if the provided proof does not prove a correct execution of the program. pub fn verify( - program_hash: [u8; 32], - public_inputs: &[u128], - outputs: &[u128], + program_hash: Digest, + public_inputs: &[u64], + outputs: &[u64], proof: StarkProof, -) -> Result<(), VerifierError> { - let pub_inputs = PublicInputs::new(program_hash, public_inputs, outputs); - winterfell::verify::(proof, pub_inputs) +) -> Result<(), VerificationError> { + // build initial stack state from public inputs + if public_inputs.len() > MIN_STACK_DEPTH { + return Err(VerificationError::TooManyInputValues( + MIN_STACK_DEPTH, + public_inputs.len(), + )); + } + + let mut init_stack_state = [Felt::ZERO; MIN_STACK_DEPTH]; + for (element, &value) in init_stack_state.iter_mut().zip(public_inputs.iter().rev()) { + *element = value + .try_into() + .map_err(|_| VerificationError::InputNotFieldElement(value))?; + } + + // build final stack state from outputs + if outputs.len() > MIN_STACK_DEPTH { + return Err(VerificationError::TooManyOutputValues( + MIN_STACK_DEPTH, + outputs.len(), + )); + } + + let mut last_stack_state = [Felt::ZERO; MIN_STACK_DEPTH]; + for (element, &value) in last_stack_state.iter_mut().zip(outputs.iter().rev()) { + *element = value + .try_into() + .map_err(|_| VerificationError::OutputNotFieldElement(value))?; + } + + // build public inputs and try to verify the proof + let pub_inputs = PublicInputs::new(program_hash, init_stack_state, last_stack_state); + winterfell::verify::(proof, pub_inputs).map_err(VerificationError::VerifierError) +} + +// ERRORS +// ================================================================================================ + +/// TODO: add docs, implement Display +#[derive(Debug, PartialEq)] +pub enum VerificationError { + VerifierError(VerifierError), + InputNotFieldElement(u64), + TooManyInputValues(usize, usize), + OutputNotFieldElement(u64), + TooManyOutputValues(usize, usize), } From 7a941bd278f665f7515c3c68e4dd745fd7ca92f7 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Sat, 12 Mar 2022 19:50:19 -0800 Subject: [PATCH 16/26] feat: implement basic AIR structure --- air/src/decoder/flow_ops.rs | 674 ------------------------- air/src/decoder/mod.rs | 116 ----- air/src/decoder/op_bits.rs | 267 ---------- air/src/decoder/op_sponge.rs | 44 -- air/src/decoder/tests.rs | 1 - air/src/lib.rs | 79 +-- air/src/options.rs | 1 + air/src/stack/arithmetic.rs | 132 ----- air/src/stack/comparison.rs | 157 ------ air/src/stack/conditional.rs | 100 ---- air/src/stack/hash.rs | 41 -- air/src/stack/input.rs | 29 -- air/src/stack/manipulation.rs | 139 ----- air/src/stack/mod.rs | 303 ----------- air/src/transition.rs | 280 ---------- air/src/utils.rs | 167 ------ core/src/lib.rs | 38 +- core/src/utils/mod.rs | 12 + processor/src/lib.rs | 11 +- processor/src/system/mod.rs | 7 +- processor/src/tests/aux_table_trace.rs | 10 +- processor/src/trace.rs | 80 ++- 22 files changed, 165 insertions(+), 2523 deletions(-) delete mode 100644 air/src/decoder/flow_ops.rs delete mode 100644 air/src/decoder/mod.rs delete mode 100644 air/src/decoder/op_bits.rs delete mode 100644 air/src/decoder/op_sponge.rs delete mode 100644 air/src/decoder/tests.rs delete mode 100644 air/src/stack/arithmetic.rs delete mode 100644 air/src/stack/comparison.rs delete mode 100644 air/src/stack/conditional.rs delete mode 100644 air/src/stack/hash.rs delete mode 100644 air/src/stack/input.rs delete mode 100644 air/src/stack/manipulation.rs delete mode 100644 air/src/stack/mod.rs delete mode 100644 air/src/transition.rs delete mode 100644 air/src/utils.rs diff --git a/air/src/decoder/flow_ops.rs b/air/src/decoder/flow_ops.rs deleted file mode 100644 index 521f751b50..0000000000 --- a/air/src/decoder/flow_ops.rs +++ /dev/null @@ -1,674 +0,0 @@ -use super::{ - are_equal, enforce_left_shift, enforce_right_shift, enforce_stack_copy, is_zero, BaseElement, - EvaluationResult, FieldElement, TraceState, OP_SPONGE_WIDTH, -}; - -// CONSTRAINT EVALUATORS -// ================================================================================================ - -pub fn enforce_begin(result: &mut [E], current: &TraceState, next: &TraceState, op_flag: E) -where - E: FieldElement, -{ - // make sure sponge state has been cleared - let new_sponge = next.op_sponge(); - result.agg_constraint(0, op_flag, is_zero(new_sponge[0])); - result.agg_constraint(1, op_flag, is_zero(new_sponge[1])); - result.agg_constraint(2, op_flag, is_zero(new_sponge[2])); - result.agg_constraint(3, op_flag, is_zero(new_sponge[3])); - - // make sure hash of parent block was pushed onto the context stack - let parent_hash = current.op_sponge()[0]; - let ctx_stack_start = OP_SPONGE_WIDTH + 1; // 1 is for loop image constraint - let ctx_stack_end = ctx_stack_start + current.ctx_stack().len(); - let ctx_result = &mut result[ctx_stack_start..ctx_stack_end]; - ctx_result.agg_constraint(0, op_flag, are_equal(parent_hash, next.ctx_stack()[0])); - enforce_right_shift( - ctx_result, - current.ctx_stack(), - next.ctx_stack(), - 1, - op_flag, - ); - - // make sure loop stack didn't change - let loop_result = &mut result[ctx_stack_end..ctx_stack_end + current.loop_stack().len()]; - enforce_stack_copy( - loop_result, - current.loop_stack(), - next.loop_stack(), - 0, - op_flag, - ); -} - -pub fn enforce_tend(result: &mut [E], current: &TraceState, next: &TraceState, op_flag: E) -where - E: FieldElement, -{ - let parent_hash = current.ctx_stack()[0]; - let block_hash = current.op_sponge()[0]; - - let new_sponge = next.op_sponge(); - result.agg_constraint(0, op_flag, are_equal(parent_hash, new_sponge[0])); - result.agg_constraint(1, op_flag, are_equal(block_hash, new_sponge[1])); - // no constraint on the 3rd element of the sponge - result.agg_constraint(3, op_flag, is_zero(new_sponge[3])); - - // make parent hash was popped from context stack - let ctx_stack_start = OP_SPONGE_WIDTH + 1; // 1 is for loop image constraint - let ctx_stack_end = ctx_stack_start + current.ctx_stack().len(); - let ctx_result = &mut result[ctx_stack_start..ctx_stack_end]; - enforce_left_shift( - ctx_result, - current.ctx_stack(), - next.ctx_stack(), - 1, - 1, - op_flag, - ); - - // make sure loop stack didn't change - let loop_result = &mut result[ctx_stack_end..ctx_stack_end + current.loop_stack().len()]; - enforce_stack_copy( - loop_result, - current.loop_stack(), - next.loop_stack(), - 0, - op_flag, - ); -} - -pub fn enforce_fend(result: &mut [E], current: &TraceState, next: &TraceState, op_flag: E) -where - E: FieldElement, -{ - let parent_hash = current.ctx_stack()[0]; - let block_hash = current.op_sponge()[0]; - - let new_sponge = next.op_sponge(); - result.agg_constraint(0, op_flag, are_equal(parent_hash, new_sponge[0])); - // no constraint on the 2nd element of the sponge - result.agg_constraint(2, op_flag, are_equal(block_hash, new_sponge[2])); - result.agg_constraint(3, op_flag, is_zero(new_sponge[3])); - - // make sure parent hash was popped from context stack - let ctx_stack_start = OP_SPONGE_WIDTH + 1; // 1 is for loop image constraint - let ctx_stack_end = ctx_stack_start + current.ctx_stack().len(); - let ctx_result = &mut result[ctx_stack_start..ctx_stack_end]; - enforce_left_shift( - ctx_result, - current.ctx_stack(), - next.ctx_stack(), - 1, - 1, - op_flag, - ); - - // make sure loop stack didn't change - let loop_result = &mut result[ctx_stack_end..ctx_stack_end + current.loop_stack().len()]; - enforce_stack_copy( - loop_result, - current.loop_stack(), - next.loop_stack(), - 0, - op_flag, - ); -} - -pub fn enforce_loop(result: &mut [E], current: &TraceState, next: &TraceState, op_flag: E) -where - E: FieldElement, -{ - // make sure sponge state has been cleared - let new_sponge = next.op_sponge(); - result.agg_constraint(0, op_flag, is_zero(new_sponge[0])); - result.agg_constraint(1, op_flag, is_zero(new_sponge[1])); - result.agg_constraint(2, op_flag, is_zero(new_sponge[2])); - result.agg_constraint(3, op_flag, is_zero(new_sponge[3])); - - // make sure hash of parent block was pushed onto the context stack - let parent_hash = current.op_sponge()[0]; - let ctx_stack_start = OP_SPONGE_WIDTH + 1; // 1 is for loop image constraint - let ctx_stack_end = ctx_stack_start + current.ctx_stack().len(); - let ctx_result = &mut result[ctx_stack_start..ctx_stack_end]; - ctx_result.agg_constraint(0, op_flag, are_equal(parent_hash, next.ctx_stack()[0])); - enforce_right_shift( - ctx_result, - current.ctx_stack(), - next.ctx_stack(), - 1, - op_flag, - ); - - // make sure loop stack was shifted by 1 item to the right, but don't enforce constraints - // on the first item of the stack (which will contain loop image) - let loop_result = &mut result[ctx_stack_end..ctx_stack_end + current.loop_stack().len()]; - enforce_right_shift( - loop_result, - current.loop_stack(), - next.loop_stack(), - 1, - op_flag, - ); -} - -pub fn enforce_wrap(result: &mut [E], current: &TraceState, next: &TraceState, op_flag: E) -where - E: FieldElement, -{ - // make sure sponge state has been cleared - let new_sponge = next.op_sponge(); - result.agg_constraint(0, op_flag, is_zero(new_sponge[0])); - result.agg_constraint(1, op_flag, is_zero(new_sponge[1])); - result.agg_constraint(2, op_flag, is_zero(new_sponge[2])); - result.agg_constraint(3, op_flag, is_zero(new_sponge[3])); - - // make sure item at the top of loop stack is equal to loop image - let loop_image = current.op_sponge()[0]; - result.agg_constraint( - OP_SPONGE_WIDTH, - op_flag, - are_equal(loop_image, current.loop_stack()[0]), - ); - - // make sure context stack didn't change - let ctx_stack_start = OP_SPONGE_WIDTH + 1; // 1 is for loop image constraint - let ctx_stack_end = ctx_stack_start + current.ctx_stack().len(); - let ctx_result = &mut result[ctx_stack_start..ctx_stack_end]; - enforce_stack_copy( - ctx_result, - current.ctx_stack(), - next.ctx_stack(), - 0, - op_flag, - ); - - // make sure loop stack didn't change - let loop_result = &mut result[ctx_stack_end..ctx_stack_end + current.loop_stack().len()]; - enforce_stack_copy( - loop_result, - current.loop_stack(), - next.loop_stack(), - 0, - op_flag, - ); -} - -pub fn enforce_break(result: &mut [E], current: &TraceState, next: &TraceState, op_flag: E) -where - E: FieldElement, -{ - // make sure sponge state didn't change - let old_sponge = current.op_sponge(); - let new_sponge = next.op_sponge(); - for i in 0..OP_SPONGE_WIDTH { - result.agg_constraint(i, op_flag, are_equal(old_sponge[i], new_sponge[i])); - } - - // make sure item at the top of loop stack is equal to loop image - let loop_image = old_sponge[0]; - result.agg_constraint( - OP_SPONGE_WIDTH, - op_flag, - are_equal(loop_image, current.loop_stack()[0]), - ); - - // make sure context stack didn't change - let ctx_stack_start = OP_SPONGE_WIDTH + 1; // 1 is for loop image constraint - let ctx_stack_end = ctx_stack_start + current.ctx_stack().len(); - let ctx_result = &mut result[ctx_stack_start..ctx_stack_end]; - enforce_stack_copy( - ctx_result, - current.ctx_stack(), - next.ctx_stack(), - 0, - op_flag, - ); - - // make loop image was popped from loop stack - let loop_result = &mut result[ctx_stack_end..ctx_stack_end + current.loop_stack().len()]; - enforce_left_shift( - loop_result, - current.loop_stack(), - next.loop_stack(), - 1, - 1, - op_flag, - ); -} - -pub fn enforce_void(result: &mut [E], current: &TraceState, next: &TraceState, op_flag: E) -where - E: FieldElement, -{ - // make sure sponge state didn't change - let old_sponge = current.op_sponge(); - let new_sponge = next.op_sponge(); - for i in 0..OP_SPONGE_WIDTH { - result.agg_constraint(i, op_flag, are_equal(old_sponge[i], new_sponge[i])); - } - - // make sure context stack didn't change - let ctx_stack_start = OP_SPONGE_WIDTH + 1; // 1 is for loop image constraint - let ctx_stack_end = ctx_stack_start + current.ctx_stack().len(); - let ctx_result = &mut result[ctx_stack_start..ctx_stack_end]; - enforce_stack_copy( - ctx_result, - current.ctx_stack(), - next.ctx_stack(), - 0, - op_flag, - ); - - // make sure loop stack didn't change - let loop_result = &mut result[ctx_stack_end..ctx_stack_end + current.loop_stack().len()]; - enforce_stack_copy( - loop_result, - current.loop_stack(), - next.loop_stack(), - 0, - op_flag, - ); -} - -// TESTS -// ================================================================================================ - -#[cfg(test)] -mod tests { - - use super::{are_equal, TraceState}; - use crate::ToElements; - use vm_core::{ - opcodes::{FlowOps, UserOps}, - BaseElement, FieldElement, StarkField, - }; - - #[test] - fn op_begin() { - // correct transition, context depth = 1 - let state1 = new_state(15, FlowOps::Begin, &[3, 5, 7, 9], &[0], &[]); - let state2 = new_state(16, FlowOps::Void, &[0, 0, 0, 0], &[3], &[]); - - let mut evaluations = vec![BaseElement::ZERO; 7]; - super::enforce_begin(&mut evaluations, &state1, &state2, BaseElement::ONE); - assert_eq!([0, 0, 0, 0, 0, 0, 0].to_elements(), evaluations); - - // correct transition, context depth = 2 - let state1 = new_state(15, FlowOps::Begin, &[3, 5, 7, 9], &[2, 0], &[]); - let state2 = new_state(16, FlowOps::Void, &[0, 0, 0, 0], &[3, 2], &[]); - - let mut evaluations = vec![BaseElement::ZERO; 8]; - super::enforce_begin(&mut evaluations, &state1, &state2, BaseElement::ONE); - assert_eq!([0, 0, 0, 0, 0, 0, 0, 0].to_elements(), evaluations); - - // incorrect transition, context depth = 1 - let state1 = new_state(15, FlowOps::Begin, &[3, 5, 7, 9], &[0], &[]); - let state2 = new_state(16, FlowOps::Void, &[1, 2, 3, 4], &[5], &[]); - - let mut evaluations = vec![BaseElement::ZERO; 7]; - super::enforce_begin(&mut evaluations, &state1, &state2, BaseElement::ONE); - assert_eq!( - [ - 1, - 2, - 3, - 4, - 0, - are_equal(BaseElement::new(3), BaseElement::new(5)).as_int(), - 0 - ] - .to_elements(), - evaluations - ); - - // incorrect transition, context depth = 2 - let state1 = new_state(15, FlowOps::Begin, &[3, 5, 7, 9], &[2, 0], &[]); - let state2 = new_state(16, FlowOps::Void, &[1, 2, 3, 4], &[5, 6], &[]); - - let mut evaluations = vec![BaseElement::ZERO; 8]; - super::enforce_begin(&mut evaluations, &state1, &state2, BaseElement::ONE); - assert_eq!( - [ - 1, - 2, - 3, - 4, - 0, - are_equal(BaseElement::new(3), BaseElement::new(5)).as_int(), - are_equal(BaseElement::new(2), BaseElement::new(6)).as_int(), - 0 - ] - .to_elements(), - evaluations - ); - } - - #[test] - fn op_tend() { - // correct transition, context depth = 1 - let state1 = new_state(15, FlowOps::Tend, &[3, 5, 7, 9], &[8], &[]); - let state2 = new_state(16, FlowOps::Void, &[8, 3, 4, 0], &[0], &[]); - - let mut evaluations = vec![BaseElement::ZERO; 7]; - super::enforce_tend(&mut evaluations, &state1, &state2, BaseElement::ONE); - assert_eq!([0, 0, 0, 0, 0, 0, 0].to_elements(), evaluations); - - // correct transition, context depth = 2 - let state1 = new_state(15, FlowOps::Tend, &[3, 5, 7, 9], &[8, 2], &[]); - let state2 = new_state(16, FlowOps::Void, &[8, 3, 4, 0], &[2, 0], &[]); - - let mut evaluations = vec![BaseElement::ZERO; 8]; - super::enforce_tend(&mut evaluations, &state1, &state2, BaseElement::ONE); - assert_eq!([0, 0, 0, 0, 0, 0, 0, 0].to_elements(), evaluations); - - // incorrect transition, context depth = 1 - let state1 = new_state(15, FlowOps::Tend, &[3, 5, 7, 9], &[8], &[]); - let state2 = new_state(16, FlowOps::Void, &[1, 2, 3, 4], &[8], &[]); - - let mut evaluations = vec![BaseElement::ZERO; 7]; - super::enforce_tend(&mut evaluations, &state1, &state2, BaseElement::ONE); - assert_eq!([7, 1, 0, 4, 0, 8, 0].to_elements(), evaluations); - - // incorrect transition, context depth = 2 - let state1 = new_state(15, FlowOps::Tend, &[3, 5, 7, 9], &[4, 6], &[]); - let state2 = new_state(16, FlowOps::Void, &[1, 2, 3, 4], &[5, 6], &[]); - - let mut evaluations = vec![BaseElement::ZERO; 8]; - super::enforce_tend(&mut evaluations, &state1, &state2, BaseElement::ONE); - assert_eq!([3, 1, 0, 4, 0, 1, 6, 0].to_elements(), evaluations); - } - - #[test] - fn op_fend() { - // correct transition, context depth = 1 - let state1 = new_state(15, FlowOps::Fend, &[3, 5, 7, 9], &[8], &[]); - let state2 = new_state(16, FlowOps::Void, &[8, 4, 3, 0], &[0], &[]); - - let mut evaluations = vec![BaseElement::ZERO; 7]; - super::enforce_fend(&mut evaluations, &state1, &state2, BaseElement::ONE); - assert_eq!([0, 0, 0, 0, 0, 0, 0].to_elements(), evaluations); - - // correct transition, context depth = 2 - let state1 = new_state(15, FlowOps::Fend, &[3, 5, 7, 9], &[8, 2], &[]); - let state2 = new_state(16, FlowOps::Void, &[8, 6, 3, 0], &[2, 0], &[]); - - let mut evaluations = vec![BaseElement::ZERO; 8]; - super::enforce_fend(&mut evaluations, &state1, &state2, BaseElement::ONE); - assert_eq!([0, 0, 0, 0, 0, 0, 0, 0].to_elements(), evaluations); - - // incorrect transition, context depth = 1 - let state1 = new_state(15, FlowOps::Fend, &[3, 5, 7, 9], &[8], &[]); - let state2 = new_state(16, FlowOps::Void, &[1, 3, 2, 4], &[8], &[]); - - let mut evaluations = vec![BaseElement::ZERO; 7]; - super::enforce_fend(&mut evaluations, &state1, &state2, BaseElement::ONE); - assert_eq!([7, 0, 1, 4, 0, 8, 0].to_elements(), evaluations); - - // incorrect transition, context depth = 2 - let state1 = new_state(15, FlowOps::Fend, &[3, 5, 7, 9], &[4, 6], &[]); - let state2 = new_state(16, FlowOps::Void, &[1, 6, 2, 4], &[5, 6], &[]); - - let mut evaluations = vec![BaseElement::ZERO; 8]; - super::enforce_fend(&mut evaluations, &state1, &state2, BaseElement::ONE); - assert_eq!([3, 0, 1, 4, 0, 1, 6, 0].to_elements(), evaluations); - } - - #[test] - fn op_loop() { - // correct transition, context depth = 1, loop depth = 1 - let state1 = new_state(15, FlowOps::Loop, &[3, 5, 7, 9], &[0], &[0]); - let state2 = new_state(16, FlowOps::Void, &[0, 0, 0, 0], &[3], &[11]); - - let mut evaluations = vec![BaseElement::ZERO; 7]; - super::enforce_loop(&mut evaluations, &state1, &state2, BaseElement::ONE); - assert_eq!([0, 0, 0, 0, 0, 0, 0].to_elements(), evaluations); - - // incorrect transition (state not cleared), context depth = 1, loop depth = 1 - let state1 = new_state(15, FlowOps::Loop, &[3, 5, 7, 9], &[0], &[0]); - let state2 = new_state(16, FlowOps::Void, &[1, 2, 3, 4], &[3], &[11]); - - let mut evaluations = vec![BaseElement::ZERO; 7]; - super::enforce_loop(&mut evaluations, &state1, &state2, BaseElement::ONE); - assert_eq!([1, 2, 3, 4, 0, 0, 0].to_elements(), evaluations); - - // incorrect transition (context not copied), context depth = 1, loop depth = 1 - let state1 = new_state(15, FlowOps::Loop, &[3, 5, 7, 9], &[0], &[0]); - let state2 = new_state(16, FlowOps::Void, &[0, 0, 0, 0], &[0], &[11]); - - let mut evaluations = vec![BaseElement::ZERO; 7]; - super::enforce_loop(&mut evaluations, &state1, &state2, BaseElement::ONE); - assert_eq!([0, 0, 0, 0, 0, 3, 0].to_elements(), evaluations); - - // correct transition, context depth = 2, loop depth = 2 - let state1 = new_state(15, FlowOps::Loop, &[3, 5, 7, 9], &[6, 0], &[11, 0]); - let state2 = new_state(16, FlowOps::Void, &[0, 0, 0, 0], &[3, 6], &[13, 11]); - - let mut evaluations = vec![BaseElement::ZERO; 9]; - super::enforce_loop(&mut evaluations, &state1, &state2, BaseElement::ONE); - assert_eq!([0, 0, 0, 0, 0, 0, 0, 0, 0].to_elements(), evaluations); - - // incorrect transition (loop stack not shifted), context depth = 2, loop depth = 2 - let state1 = new_state(15, FlowOps::Loop, &[3, 5, 7, 9], &[6, 0], &[11, 0]); - let state2 = new_state(16, FlowOps::Void, &[0, 0, 0, 0], &[3, 6], &[11, 0]); - - let mut evaluations = vec![BaseElement::ZERO; 9]; - super::enforce_loop(&mut evaluations, &state1, &state2, BaseElement::ONE); - assert_eq!([0, 0, 0, 0, 0, 0, 0, 0, 11].to_elements(), evaluations); - } - - #[test] - fn op_wrap() { - // correct transition, context depth = 1, loop depth = 1 - let state1 = new_state(15, FlowOps::Wrap, &[3, 5, 7, 9], &[11], &[3]); - let state2 = new_state(16, FlowOps::Void, &[0, 0, 0, 0], &[11], &[3]); - - let mut evaluations = vec![BaseElement::ZERO; 7]; - super::enforce_wrap(&mut evaluations, &state1, &state2, BaseElement::ONE); - assert_eq!([0, 0, 0, 0, 0, 0, 0].to_elements(), evaluations); - - // incorrect transition (loop image mismatch), context depth = 1, loop depth = 1 - let state1 = new_state(15, FlowOps::Wrap, &[3, 5, 7, 9], &[11], &[5]); - let state2 = new_state(16, FlowOps::Void, &[0, 0, 0, 0], &[11], &[5]); - - let mut evaluations = vec![BaseElement::ZERO; 7]; - super::enforce_wrap(&mut evaluations, &state1, &state2, BaseElement::ONE); - assert_eq!( - [ - 0, - 0, - 0, - 0, - are_equal(BaseElement::new(3), BaseElement::new(5)).as_int(), - 0, - 0 - ] - .to_elements(), - evaluations - ); - - // incorrect transition (loop stack changed), context depth = 1, loop depth = 1 - let state1 = new_state(15, FlowOps::Wrap, &[3, 5, 7, 9], &[11], &[3]); - let state2 = new_state(16, FlowOps::Void, &[0, 0, 0, 0], &[11], &[4]); - - let mut evaluations = vec![BaseElement::ZERO; 7]; - super::enforce_wrap(&mut evaluations, &state1, &state2, BaseElement::ONE); - assert_eq!( - [ - 0, - 0, - 0, - 0, - 0, - 0, - are_equal(BaseElement::new(3), BaseElement::new(4)).as_int() - ] - .to_elements(), - evaluations - ); - - // incorrect transition (context stack changed), context depth = 1, loop depth = 1 - let state1 = new_state(15, FlowOps::Wrap, &[3, 5, 7, 9], &[11], &[3]); - let state2 = new_state(16, FlowOps::Void, &[0, 0, 0, 0], &[10], &[3]); - - let mut evaluations = vec![BaseElement::ZERO; 7]; - super::enforce_wrap(&mut evaluations, &state1, &state2, BaseElement::ONE); - assert_eq!( - [ - 0, - 0, - 0, - 0, - 0, - are_equal(BaseElement::new(11), BaseElement::new(10)).as_int(), - 0 - ] - .to_elements(), - evaluations - ); - - // incorrect transition (sponge not reset), context depth = 1, loop depth = 1 - let state1 = new_state(15, FlowOps::Wrap, &[3, 5, 7, 9], &[11], &[3]); - let state2 = new_state(16, FlowOps::Void, &[1, 2, 3, 4], &[11], &[3]); - - let mut evaluations = vec![BaseElement::ZERO; 7]; - super::enforce_wrap(&mut evaluations, &state1, &state2, BaseElement::ONE); - assert_eq!([1, 2, 3, 4, 0, 0, 0].to_elements(), evaluations); - } - - #[test] - fn op_break() { - // correct transition, context depth = 1, loop depth = 1 - let state1 = new_state(15, FlowOps::Break, &[3, 5, 7, 9], &[11], &[3]); - let state2 = new_state(16, FlowOps::Void, &[3, 5, 7, 9], &[11], &[0]); - - let mut evaluations = vec![BaseElement::ZERO; 7]; - super::enforce_break(&mut evaluations, &state1, &state2, BaseElement::ONE); - assert_eq!([0, 0, 0, 0, 0, 0, 0].to_elements(), evaluations); - - // incorrect transition (loop image mismatch), context depth = 1, loop depth = 1 - let state1 = new_state(15, FlowOps::Wrap, &[3, 5, 7, 9], &[11], &[5]); - let state2 = new_state(16, FlowOps::Void, &[3, 5, 7, 9], &[11], &[0]); - - let mut evaluations = vec![BaseElement::ZERO; 7]; - super::enforce_break(&mut evaluations, &state1, &state2, BaseElement::ONE); - assert_eq!( - [ - 0, - 0, - 0, - 0, - are_equal(BaseElement::new(3), BaseElement::new(5)).as_int(), - 0, - 0 - ] - .to_elements(), - evaluations - ); - - // incorrect transition (loop stack not popped), context depth = 1, loop depth = 1 - let state1 = new_state(15, FlowOps::Wrap, &[3, 5, 7, 9], &[11], &[3]); - let state2 = new_state(16, FlowOps::Void, &[3, 5, 7, 9], &[11], &[3]); - - let mut evaluations = vec![BaseElement::ZERO; 7]; - super::enforce_break(&mut evaluations, &state1, &state2, BaseElement::ONE); - assert_eq!( - [ - 0, - 0, - 0, - 0, - 0, - 0, - are_equal(BaseElement::new(3), BaseElement::ZERO).as_int() - ] - .to_elements(), - evaluations - ); - - // incorrect transition (context stack changed), context depth = 1, loop depth = 1 - let state1 = new_state(15, FlowOps::Wrap, &[3, 5, 7, 9], &[11], &[3]); - let state2 = new_state(16, FlowOps::Void, &[3, 5, 7, 9], &[10], &[0]); - - let mut evaluations = vec![BaseElement::ZERO; 7]; - super::enforce_break(&mut evaluations, &state1, &state2, BaseElement::ONE); - assert_eq!( - [ - 0, - 0, - 0, - 0, - 0, - are_equal(BaseElement::new(11), BaseElement::new(10)).as_int(), - 0 - ] - .to_elements(), - evaluations - ); - - // incorrect transition (sponge changed), context depth = 1, loop depth = 1 - let state1 = new_state(15, FlowOps::Wrap, &[3, 5, 7, 9], &[11], &[3]); - let state2 = new_state(16, FlowOps::Void, &[1, 3, 5, 7], &[11], &[0]); - - let mut evaluations = vec![BaseElement::ZERO; 7]; - super::enforce_break(&mut evaluations, &state1, &state2, BaseElement::ONE); - assert_eq!([2, 2, 2, 2, 0, 0, 0].to_elements(), evaluations); - } - - #[test] - fn op_void() { - // correct transition, context depth = 1 - let state1 = new_state(15, FlowOps::Void, &[3, 5, 7, 9], &[8], &[]); - let state2 = new_state(16, FlowOps::Void, &[3, 5, 7, 9], &[8], &[]); - - let mut evaluations = vec![BaseElement::ZERO; 7]; - super::enforce_void(&mut evaluations, &state1, &state2, BaseElement::ONE); - assert_eq!([0, 0, 0, 0, 0, 0, 0].to_elements(), evaluations); - - // correct transition, context depth = 2, loop depth = 1 - let state1 = new_state(15, FlowOps::Void, &[3, 5, 7, 9], &[8, 2], &[11]); - let state2 = new_state(16, FlowOps::Void, &[3, 5, 7, 9], &[8, 2], &[11]); - - let mut evaluations = vec![BaseElement::ZERO; 8]; - super::enforce_void(&mut evaluations, &state1, &state2, BaseElement::ONE); - assert_eq!([0, 0, 0, 0, 0, 0, 0, 0].to_elements(), evaluations); - - // incorrect transition, context depth = 1, loop depth = 1 - let state1 = new_state(15, FlowOps::Void, &[3, 5, 7, 9], &[8], &[11]); - let state2 = new_state(16, FlowOps::Void, &[2, 4, 6, 8], &[7], &[10]); - - let mut evaluations = vec![BaseElement::ZERO; 7]; - super::enforce_void(&mut evaluations, &state1, &state2, BaseElement::ONE); - assert_eq!([1, 1, 1, 1, 0, 1, 1].to_elements(), evaluations); - } - - // HELPER FUNCTIONS - // -------------------------------------------------------------------------------------------- - fn new_state( - step: usize, - flow_op: FlowOps, - sponge: &[u128; 4], - ctx_stack: &[u128], - loop_stack: &[u128], - ) -> TraceState { - let ctx_depth = ctx_stack.len(); - let loop_depth = loop_stack.len(); - - let mut state = vec![step as u128, sponge[0], sponge[1], sponge[2], sponge[3]]; - - for i in 0..3 { - state.push(((flow_op as u128) >> i) & 1); - } - - for i in 0..7 { - state.push(((UserOps::Noop as u128) >> i) & 1); - } - - state.extend_from_slice(ctx_stack); - state.extend_from_slice(loop_stack); - state.push(101); // single value for user stack - - TraceState::from_slice(ctx_depth, loop_depth, 1, &state.to_elements()) - } -} diff --git a/air/src/decoder/mod.rs b/air/src/decoder/mod.rs deleted file mode 100644 index 58484a33a4..0000000000 --- a/air/src/decoder/mod.rs +++ /dev/null @@ -1,116 +0,0 @@ -use crate::{ - op_sponge::STATE_WIDTH as OP_SPONGE_WIDTH, - opcodes::{FlowOps, UserOps}, - utils::{ - are_equal, binary_not, enforce_left_shift, enforce_right_shift, enforce_stack_copy, - is_binary, is_zero, EvaluationResult, - }, - BaseElement, FieldElement, TraceState, TransitionConstraintDegree, VmTransition, - BASE_CYCLE_LENGTH, MIN_CONTEXT_DEPTH, MIN_LOOP_DEPTH, -}; -use core::cmp; - -mod op_bits; -use op_bits::enforce_op_bits; - -mod op_sponge; -use op_sponge::enforce_hacc; - -mod flow_ops; -use flow_ops::{ - enforce_begin, enforce_break, enforce_fend, enforce_loop, enforce_tend, enforce_void, - enforce_wrap, -}; - -#[cfg(test)] -mod tests; - -// CONSTANTS -// ================================================================================================ -const NUM_OP_CONSTRAINTS: usize = 15; -const OP_CONSTRAINT_DEGREES: [usize; NUM_OP_CONSTRAINTS] = [ - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // all op bits are binary - 3, // op_counter should be incremented for HACC operations - 8, // ld_ops and hd_ops cannot be all 0s - 8, // when cf_ops are not all 0s, ld_ops and hd_ops must be all 1s - 6, // VOID can be followed only by VOID - 4, // operations happen on allowed step multiples -]; - -const NUM_SPONGE_CONSTRAINTS: usize = 4; -const SPONGE_CONSTRAINT_DEGREES: [usize; NUM_SPONGE_CONSTRAINTS] = [ - 6, 7, 6, 6, // sponge transition constraints -]; - -const STACK_CONSTRAINT_DEGREE: usize = 4; - -const CYCLE_MASK_IDX: usize = 0; -const PREFIX_MASK_IDX: usize = 1; -const PUSH_MASK_IDX: usize = 2; - -// CONSTRAINT DEGREES -// ================================================================================================ - -pub fn get_transition_constraint_degrees( - ctx_depth: usize, - loop_depth: usize, -) -> Vec { - let mut result = Vec::new(); - - for °ree in OP_CONSTRAINT_DEGREES.iter().take(NUM_OP_CONSTRAINTS - 1) { - result.push(TransitionConstraintDegree::new(degree)); - } - // the last op bit constraint is actually not degree 4, but degree 4 multiplied with - // 8-step cycle periodic column - result.push(TransitionConstraintDegree::with_cycles(3, vec![8])); - - for °ree in SPONGE_CONSTRAINT_DEGREES.iter() { - result.push(TransitionConstraintDegree::new(degree)); - } - - result.resize( - result.len() - + cmp::max(ctx_depth, MIN_CONTEXT_DEPTH) - + cmp::max(loop_depth, MIN_LOOP_DEPTH), - TransitionConstraintDegree::new(STACK_CONSTRAINT_DEGREE), - ); - - result -} - -// CONSTRAINT EVALUATOR -// ================================================================================================ - -pub fn enforce_constraints>( - transition: &VmTransition, - masks: &[E], - ark: &[E], - result: &mut [E], -) { - // evaluate constraints for decoding op codes - enforce_op_bits(&mut result[..NUM_OP_CONSTRAINTS], transition, masks); - - // evaluate constraints for flow control operations - let result = &mut result[NUM_OP_CONSTRAINTS..]; - let op_flags = transition.cf_op_flags(); - - let current = transition.current(); - let next = transition.next(); - - enforce_hacc(result, transition, ark, op_flags[FlowOps::Hacc as usize]); - enforce_begin(result, current, next, op_flags[FlowOps::Begin as usize]); - enforce_tend(result, current, next, op_flags[FlowOps::Tend as usize]); - enforce_fend(result, current, next, op_flags[FlowOps::Fend as usize]); - enforce_loop(result, current, next, op_flags[FlowOps::Loop as usize]); - enforce_wrap(result, current, next, op_flags[FlowOps::Wrap as usize]); - enforce_break(result, current, next, op_flags[FlowOps::Break as usize]); - enforce_void(result, current, next, op_flags[FlowOps::Void as usize]); -} - -// CYCLE MASKS -// ================================================================================================ -pub const MASKS: [[u128; BASE_CYCLE_LENGTH]; 3] = [ - [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], // multiples of 16 - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], // one less than multiple of 16 - [0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1], // multiples of 8 -]; diff --git a/air/src/decoder/op_bits.rs b/air/src/decoder/op_bits.rs deleted file mode 100644 index b1c2bbca37..0000000000 --- a/air/src/decoder/op_bits.rs +++ /dev/null @@ -1,267 +0,0 @@ -use super::{ - are_equal, binary_not, is_binary, BaseElement, EvaluationResult, FieldElement, FlowOps, - UserOps, VmTransition, CYCLE_MASK_IDX, PREFIX_MASK_IDX, PUSH_MASK_IDX, -}; - -// CONSTRAINT EVALUATOR -// ================================================================================================ - -pub fn enforce_op_bits(result: &mut [E], transition: &VmTransition, masks: &[E]) -where - E: FieldElement, -{ - let mut i = 0; - - let current = transition.current(); - let next = transition.next(); - - // make sure all op bits are binary and compute their product/sum - let mut cf_bit_sum = E::ZERO; - for &op_bit in current.cf_op_bits() { - result[i] = is_binary(op_bit); - cf_bit_sum += op_bit; - i += 1; - } - - let mut ld_bit_prod = E::ONE; - for &op_bit in current.ld_op_bits() { - result[i] = is_binary(op_bit); - ld_bit_prod *= op_bit; - i += 1; - } - - let mut hd_bit_prod = E::ONE; - for &op_bit in current.hd_op_bits() { - result[i] = is_binary(op_bit); - hd_bit_prod *= op_bit; - i += 1; - } - - // when cf_ops = hacc, operation counter should be incremented by 1; - // otherwise, operation counter should remain the same - let op_counter = current.op_counter(); - let is_hacc = transition.cf_op_flags()[FlowOps::Hacc.op_index()]; - let hacc_transition = (op_counter + E::ONE) * is_hacc; - let rest_transition = op_counter * binary_not(is_hacc); - result[i] = are_equal(hacc_transition + rest_transition, next.op_counter()); - i += 1; - - // ld_ops and hd_ops can be all 0s at the first step, but cannot be all 0s - // at any other step - result[i] = op_counter * binary_not(ld_bit_prod) * binary_not(hd_bit_prod); - i += 1; - - // when cf_ops are not all 0s, ld_ops and hd_ops must be all 1s - result[i] = cf_bit_sum * binary_not(ld_bit_prod * hd_bit_prod); - i += 1; - - let cf_op_flags = transition.cf_op_flags(); - - // VOID can be followed only by VOID - let current_void_flag = cf_op_flags[FlowOps::Void.op_index()]; - let next_void_flag = next.get_void_op_flag(); - result[i] = current_void_flag * binary_not(next_void_flag); - i += 1; - - let hd_op_flags = transition.hd_op_flags(); - - // BEGIN, LOOP, BREAK, and WRAP are allowed only on one less than multiple of 16 - let prefix_mask = masks[PREFIX_MASK_IDX]; - result.agg_constraint(i, cf_op_flags[FlowOps::Begin.op_index()], prefix_mask); - result.agg_constraint(i, cf_op_flags[FlowOps::Loop.op_index()], prefix_mask); - result.agg_constraint(i, cf_op_flags[FlowOps::Wrap.op_index()], prefix_mask); - result.agg_constraint(i, cf_op_flags[FlowOps::Break.op_index()], prefix_mask); - - // TEND and FEND is allowed only on multiples of 16 - let base_cycle_mask = masks[CYCLE_MASK_IDX]; - result.agg_constraint(i, cf_op_flags[FlowOps::Tend.op_index()], base_cycle_mask); - result.agg_constraint(i, cf_op_flags[FlowOps::Fend.op_index()], base_cycle_mask); - - // PUSH is allowed only on multiples of 8 - let push_cycle_mask = masks[PUSH_MASK_IDX]; - result.agg_constraint(i, hd_op_flags[UserOps::Push.hd_index()], push_cycle_mask); -} - -// TESTS -// ================================================================================================ - -#[cfg(test)] -mod tests { - - use super::{ - super::NUM_OP_CONSTRAINTS, BaseElement, FieldElement, FlowOps, UserOps, VmTransition, - }; - use crate::{ToElements, TraceState}; - use vm_core::StarkField; - - #[test] - fn op_bits_are_binary() { - let success_result = vec![BaseElement::ZERO; NUM_OP_CONSTRAINTS]; - - // all bits are 1s: success - let state = new_state(FlowOps::Void as u8, UserOps::Noop as u8, 1); - assert_eq!(success_result, evaluate_state(&state, [0, 0, 0], false)); - - // control flow bits are not binary - for i in 0..3 { - let mut op_bits = [1; 3]; - op_bits[i] = 3; - let mut expected_evaluations = vec![BaseElement::ZERO; 10]; - expected_evaluations[i] = BaseElement::new(3 * 3 - 3); - - let state = new_state_from_bits(op_bits, [1, 1, 1, 1, 1, 1, 1]); - assert_eq!( - expected_evaluations, - &evaluate_state(&state, [0, 0, 0], false)[..10] - ); - } - - // user bits are not binary - for i in 0..7 { - let mut op_bits = [1, 1, 1, 1, 1, 1, 1]; - op_bits[i] = 3; - let mut expected_evaluations = vec![BaseElement::ZERO; 10]; - expected_evaluations[i + 3] = BaseElement::new(3 * 3 - 3); - - let state = new_state_from_bits([0, 0, 0], op_bits); - assert_eq!( - expected_evaluations, - &evaluate_state(&state, [0, 0, 0], false)[..10] - ); - } - } - - #[test] - fn invalid_op_combinations() { - let success_result = vec![BaseElement::ZERO; NUM_OP_CONSTRAINTS]; - - // user op bits cannot be all 0s - for cf_op in 0..8 { - let state = new_state(cf_op, 0, 1); - assert_ne!(success_result, evaluate_state(&state, [0, 0, 0], false)); - } - - // when cf_ops are not all 0s, user_ops must be all 1s - for cf_op in 1..8 { - for user_op in 0..127 { - let state = new_state(cf_op as u8, user_op as u8, 1); - assert_ne!(success_result, evaluate_state(&state, [0, 0, 0], false)); - } - - let state = new_state(cf_op as u8, UserOps::Noop as u8, 1); - assert_eq!(success_result, evaluate_state(&state, [0, 0, 0], false)); - } - } - - #[test] - fn invalid_op_alignment() { - let success_result = vec![BaseElement::ZERO; NUM_OP_CONSTRAINTS]; - - // TEND and FEND are allowed only on multiples of 16 - let state = new_state(FlowOps::Tend as u8, UserOps::Noop as u8, 1); - assert_eq!(success_result, evaluate_state(&state, [0, 0, 0], false)); - assert_ne!(success_result, evaluate_state(&state, [1, 0, 0], false)); - - let state = new_state(FlowOps::Fend as u8, UserOps::Noop as u8, 1); - assert_eq!(success_result, evaluate_state(&state, [0, 0, 0], false)); - assert_ne!(success_result, evaluate_state(&state, [1, 0, 0], false)); - - // BEGIN, LOOP, WRAP, and BREAK are allowed only on one less than multiples of 16 - let state = new_state(FlowOps::Begin as u8, UserOps::Noop as u8, 1); - assert_eq!(success_result, evaluate_state(&state, [0, 0, 0], false)); - assert_ne!(success_result, evaluate_state(&state, [0, 1, 0], false)); - - let state = new_state(FlowOps::Loop as u8, UserOps::Noop as u8, 1); - assert_eq!(success_result, evaluate_state(&state, [0, 0, 0], false)); - assert_ne!(success_result, evaluate_state(&state, [0, 1, 0], false)); - - let state = new_state(FlowOps::Wrap as u8, UserOps::Noop as u8, 1); - assert_eq!(success_result, evaluate_state(&state, [0, 0, 0], false)); - assert_ne!(success_result, evaluate_state(&state, [0, 1, 0], false)); - - let state = new_state(FlowOps::Break as u8, UserOps::Noop as u8, 1); - assert_eq!(success_result, evaluate_state(&state, [0, 0, 0], false)); - assert_ne!(success_result, evaluate_state(&state, [0, 1, 0], false)); - - // PUSH is allowed only on multiples of 8 - let state = new_state(FlowOps::Hacc as u8, UserOps::Push as u8, 1); - assert_eq!(success_result, evaluate_state(&state, [0, 0, 0], true)); - assert_ne!(success_result, evaluate_state(&state, [0, 0, 1], true)); - } - - #[test] - fn invalid_op_sequence() { - let success_result = vec![BaseElement::ZERO; NUM_OP_CONSTRAINTS]; - - // void can follow non-void - let state1 = new_state(FlowOps::Hacc as u8, UserOps::Add as u8, 1); - let state2 = new_state(FlowOps::Void as u8, UserOps::Noop as u8, 2); - let transition = VmTransition::from_states(state1, state2); - let mut evaluations = vec![BaseElement::ZERO; NUM_OP_CONSTRAINTS]; - super::enforce_op_bits(&mut evaluations, &transition, &[0, 0, 0].to_elements()); - assert_eq!(success_result, evaluations); - - // void can follow void - let state1 = new_state(FlowOps::Void as u8, UserOps::Noop as u8, 1); - let state2 = new_state(FlowOps::Void as u8, UserOps::Noop as u8, 1); - let transition = VmTransition::from_states(state1, state2); - let mut evaluations = vec![BaseElement::ZERO; NUM_OP_CONSTRAINTS]; - super::enforce_op_bits(&mut evaluations, &transition, &[0, 0, 0].to_elements()); - assert_eq!(success_result, evaluations); - - // non-void cannot follow void - let state1 = new_state(FlowOps::Void as u8, UserOps::Noop as u8, 1); - let state2 = new_state(FlowOps::Hacc as u8, UserOps::Add as u8, 1); - let transition = VmTransition::from_states(state1, state2); - let mut evaluations = vec![BaseElement::ZERO; NUM_OP_CONSTRAINTS]; - super::enforce_op_bits(&mut evaluations, &transition, &[0, 0, 0].to_elements()); - assert_ne!(success_result, evaluations); - } - - // HELPER FUNCTIONS - // -------------------------------------------------------------------------------------------- - fn new_state(flow_op: u8, user_op: u8, op_counter: u128) -> TraceState { - let mut state = TraceState::new(1, 0, 1); - - let mut op_bits = [BaseElement::ZERO; 10]; - for i in 0..3 { - op_bits[i] = BaseElement::new(((flow_op as u128) >> i) & 1); - } - - for i in 0..7 { - op_bits[i + 3] = BaseElement::new(((user_op as u128) >> i) & 1); - } - - state.set_op_bits(op_bits); - state.set_op_counter(BaseElement::new(op_counter)); - state - } - - fn new_state_from_bits(cf_bits: [u128; 3], u_bits: [u128; 7]) -> TraceState { - let mut state = TraceState::new(1, 0, 1); - let cf_bits = cf_bits.to_elements(); - let u_bits = u_bits.to_elements(); - state.set_op_bits([ - cf_bits[0], cf_bits[1], cf_bits[2], u_bits[0], u_bits[1], u_bits[2], u_bits[3], - u_bits[4], u_bits[5], u_bits[6], - ]); - state - } - - fn evaluate_state( - state: &TraceState, - masks: [u128; 3], - inc_counter: bool, - ) -> Vec { - let op_counter = if inc_counter { - state.op_counter().as_int() + 1 - } else { - state.op_counter().as_int() - }; - let next_state = new_state(FlowOps::Void as u8, UserOps::Noop as u8, op_counter); - let transition = VmTransition::from_states(state.clone(), next_state); - let mut evaluations = vec![BaseElement::ZERO; NUM_OP_CONSTRAINTS]; - super::enforce_op_bits(&mut evaluations, &transition, &masks.to_elements()); - evaluations - } -} diff --git a/air/src/decoder/op_sponge.rs b/air/src/decoder/op_sponge.rs deleted file mode 100644 index 64e9a4e46d..0000000000 --- a/air/src/decoder/op_sponge.rs +++ /dev/null @@ -1,44 +0,0 @@ -use super::{are_equal, BaseElement, EvaluationResult, FieldElement, UserOps, VmTransition}; -use vm_core::op_sponge::{apply_inv_mds, apply_mds, apply_sbox, STATE_WIDTH}; - -// CONSTRAINT EVALUATOR -// ================================================================================================ - -pub fn enforce_hacc>( - result: &mut [E], - transition: &VmTransition, - ark: &[E], - op_flag: E, -) { - // determine current op_value - let stack_top = transition.next().user_stack()[0]; - let push_flag = transition.hd_op_flags()[UserOps::Push.hd_index()]; - let op_value = stack_top * push_flag; - - // evaluate the first half of Rescue round - let mut old_sponge = [E::ZERO; STATE_WIDTH]; - old_sponge.copy_from_slice(transition.current().op_sponge()); - for i in 0..STATE_WIDTH { - old_sponge[i] += ark[i]; - } - apply_sbox(&mut old_sponge); - apply_mds(&mut old_sponge); - - // op_code injection - old_sponge[0] += transition.current().op_code(); - old_sponge[1] += op_value; - - // evaluate inverse of the second half of Rescue round - let mut new_sponge = [E::ZERO; STATE_WIDTH]; - new_sponge.copy_from_slice(transition.next().op_sponge()); - apply_inv_mds(&mut new_sponge); - apply_sbox(&mut new_sponge); - for i in 0..STATE_WIDTH { - new_sponge[i] -= ark[STATE_WIDTH + i]; - } - - // add the constraints to the result - for i in 0..STATE_WIDTH { - result.agg_constraint(i, op_flag, are_equal(old_sponge[i], new_sponge[i])); - } -} diff --git a/air/src/decoder/tests.rs b/air/src/decoder/tests.rs deleted file mode 100644 index b272463eca..0000000000 --- a/air/src/decoder/tests.rs +++ /dev/null @@ -1 +0,0 @@ -// TODO: add diff --git a/air/src/lib.rs b/air/src/lib.rs index 67dc065bfa..1907661c3a 100644 --- a/air/src/lib.rs +++ b/air/src/lib.rs @@ -1,11 +1,11 @@ -use vm_core::{hasher::Digest, MIN_STACK_DEPTH}; +use vm_core::{hasher::Digest, StackTopState, CLK_COL_IDX, FMP_COL_IDX, STACK_TRACE_OFFSET}; use winter_air::{ Air, AirContext, Assertion, EvaluationFrame, ProofOptions as WinterProofOptions, TraceInfo, + TransitionConstraintDegree, }; use winter_utils::{ByteWriter, Serializable}; mod options; -//mod utils; // EXPORTS // ================================================================================================ @@ -17,33 +17,64 @@ pub use winter_air::{FieldExtension, HashFunction}; // PROCESSOR AIR // ================================================================================================ +/// TODO: add docs pub struct ProcessorAir { context: AirContext, + init_stack_state: StackTopState, + last_stack_state: StackTopState, } impl Air for ProcessorAir { type BaseField = Felt; type PublicInputs = PublicInputs; - fn new( - _trace_info: TraceInfo, - _pub_inputs: PublicInputs, - _options: WinterProofOptions, - ) -> Self { - unimplemented!() + fn new(trace_info: TraceInfo, pub_inputs: PublicInputs, options: WinterProofOptions) -> Self { + let degrees = vec![ + TransitionConstraintDegree::new(1), // clk' = clk + 1 + ]; + + Self { + context: AirContext::new(trace_info, degrees, options), + init_stack_state: pub_inputs.init_stack_state, + last_stack_state: pub_inputs.last_stack_state, + } } + #[allow(clippy::vec_init_then_push)] fn get_assertions(&self) -> Vec> { - unimplemented!() + let mut result = Vec::new(); + + // first value of clk is 0 + result.push(Assertion::single(CLK_COL_IDX, 0, Felt::ZERO)); + + // first value of fmp is 2^30 + result.push(Assertion::single(FMP_COL_IDX, 0, Felt::new(2u64.pow(30)))); + + // stack column at the first step should be set to init stack values + for (i, &value) in self.init_stack_state.iter().enumerate() { + result.push(Assertion::single(STACK_TRACE_OFFSET + i, 0, value)); + } + + // stack column at the last step should be set to last stack values + let last_step = self.trace_length() - 1; + for (i, &value) in self.last_stack_state.iter().enumerate() { + result.push(Assertion::single(STACK_TRACE_OFFSET + i, last_step, value)); + } + + result } fn evaluate_transition>( &self, - _frame: &EvaluationFrame, + frame: &EvaluationFrame, _periodic_values: &[E], - _result: &mut [E], + result: &mut [E], ) { - unimplemented!() + let current = frame.current(); + let next = frame.next(); + + // clk' = clk + 1 + result[0] = next[CLK_COL_IDX] - (current[CLK_COL_IDX] + E::ONE) } fn context(&self) -> &AirContext { @@ -56,15 +87,15 @@ impl Air for ProcessorAir { pub struct PublicInputs { program_hash: Digest, - init_stack_state: [Felt; MIN_STACK_DEPTH], - last_stack_state: [Felt; MIN_STACK_DEPTH], + init_stack_state: StackTopState, + last_stack_state: StackTopState, } impl PublicInputs { pub fn new( program_hash: Digest, - init_stack_state: [Felt; MIN_STACK_DEPTH], - last_stack_state: [Felt; MIN_STACK_DEPTH], + init_stack_state: StackTopState, + last_stack_state: StackTopState, ) -> Self { Self { program_hash, @@ -81,19 +112,3 @@ impl Serializable for PublicInputs { target.write(self.last_stack_state.as_slice()); } } - -// TRACE METADATA -// ================================================================================================ - -pub struct TraceMetadata { - pub op_count: usize, - pub ctx_depth: usize, - pub loop_depth: usize, - pub stack_depth: usize, -} - -impl TraceMetadata { - pub fn from_trace_info(_trace_info: &TraceInfo) -> Self { - unimplemented!() - } -} diff --git a/air/src/options.rs b/air/src/options.rs index fa559f4557..c5a04b1516 100644 --- a/air/src/options.rs +++ b/air/src/options.rs @@ -1,6 +1,7 @@ use core::ops::Deref; use winter_air::{FieldExtension, HashFunction, ProofOptions as WinterProofOptions}; +/// TODO: add docs #[derive(Clone)] pub struct ProofOptions(WinterProofOptions); diff --git a/air/src/stack/arithmetic.rs b/air/src/stack/arithmetic.rs deleted file mode 100644 index 174b081abe..0000000000 --- a/air/src/stack/arithmetic.rs +++ /dev/null @@ -1,132 +0,0 @@ -use super::{ - are_equal, binary_not, enforce_left_shift, enforce_stack_copy, is_binary, EvaluationResult, - FieldElement, -}; - -// ARITHMETIC OPERATION -// ================================================================================================ - -/// Enforces constraints for ADD operation. The constraints are based on the first 2 elements of -/// the stack; the rest of the stack is shifted left by 1 element. -pub fn enforce_add(result: &mut [E], old_stack: &[E], new_stack: &[E], op_flag: E) -where - E: FieldElement, -{ - let x = old_stack[0]; - let y = old_stack[1]; - let op_result = x + y; - result.agg_constraint(0, op_flag, are_equal(new_stack[0], op_result)); - - // ensure that the rest of the stack is shifted by 1 item to the left - enforce_left_shift(result, old_stack, new_stack, 2, 1, op_flag); -} - -/// Enforces constraints for MUL operation. The constraints are based on the first 2 elements of -/// the stack; the rest of the stack is shifted left by 1 element. -pub fn enforce_mul(result: &mut [E], old_stack: &[E], new_stack: &[E], op_flag: E) -where - E: FieldElement, -{ - let x = old_stack[0]; - let y = old_stack[1]; - let op_result = x * y; - result.agg_constraint(0, op_flag, are_equal(new_stack[0], op_result)); - - // ensure that the rest of the stack is shifted by 1 item to the left - enforce_left_shift(result, old_stack, new_stack, 2, 1, op_flag); -} - -/// Enforces constraints for INV operation. The constraints are based on the first element of -/// the stack; the rest of the stack is unaffected. -pub fn enforce_inv(result: &mut [E], old_stack: &[E], new_stack: &[E], op_flag: E) -where - E: FieldElement, -{ - // Constraints for INV operation is defined as: x * inv(x) = 1; this also means - // that if x = 0, the constraint will not be satisfied - let x = old_stack[0]; - let inv_x = new_stack[0]; - result.agg_constraint(0, op_flag, are_equal(E::ONE, inv_x * x)); - - // ensure nothing changed beyond the first item of the stack - enforce_stack_copy(result, old_stack, new_stack, 1, op_flag); -} - -/// Enforces constraints for NEG operation. The constraints are based on the first element of -/// the stack; the rest of the stack is unaffected. -pub fn enforce_neg(result: &mut [E], old_stack: &[E], new_stack: &[E], op_flag: E) -where - E: FieldElement, -{ - // Constraint for NEG operation is defined as: x + neg(x) = 0 - let x = old_stack[0]; - let neg_x = new_stack[0]; - result.agg_constraint(0, op_flag, neg_x + x); - - // ensure nothing changed beyond the first item of the stack - enforce_stack_copy(result, old_stack, new_stack, 1, op_flag); -} - -// BOOLEAN OPERATION -// ================================================================================================ - -/// Enforces constraints for NOT operation. The constraints are based on the first element of -/// the stack, but also evaluates an auxiliary constraint which guarantees that the first -/// element of the stack is binary. -pub fn enforce_not(result: &mut [E], aux: &mut [E], old_stack: &[E], new_stack: &[E], op_flag: E) -where - E: FieldElement, -{ - // NOT operation is defined simply as: 1 - x; this means 0 becomes 1, and 1 becomes 0 - let x = old_stack[0]; - let op_result = binary_not(x); - result.agg_constraint(0, op_flag, are_equal(new_stack[0], op_result)); - - // ensure nothing changed beyond the first item of the stack - enforce_stack_copy(result, old_stack, new_stack, 1, op_flag); - - // we also need to make sure that the operand is binary (i.e. 0 or 1) - aux.agg_constraint(0, op_flag, is_binary(x)); -} - -/// Enforces constraints for AND operation. The constraints are based on the first two elements -/// of the stack, but also evaluates auxiliary constraints which guarantee that both elements -/// are binary. -pub fn enforce_and(result: &mut [E], aux: &mut [E], old_stack: &[E], new_stack: &[E], op_flag: E) -where - E: FieldElement, -{ - // AND operation is the same as: x * y - let x = old_stack[0]; - let y = old_stack[1]; - let op_result = x * y; - result.agg_constraint(0, op_flag, are_equal(new_stack[0], op_result)); - - // ensure that the rest of the stack is shifted by 1 item to the left - enforce_left_shift(result, old_stack, new_stack, 2, 1, op_flag); - - // ensure that both operands are binary values - aux.agg_constraint(0, op_flag, is_binary(x)); - aux.agg_constraint(1, op_flag, is_binary(y)); -} - -/// Enforces constraints for OR operation. The constraints are based on the first two elements -/// of the stack, but also evaluates auxiliary constraints which guarantee that both elements -/// are binary. -pub fn enforce_or(result: &mut [E], aux: &mut [E], old_stack: &[E], new_stack: &[E], op_flag: E) -where - E: FieldElement, -{ - // OR operation is the same as: 1 - (1 - x) * (1 - y) - let x = old_stack[0]; - let y = old_stack[1]; - let op_result = binary_not(binary_not(x) * binary_not(y)); - result.agg_constraint(0, op_flag, are_equal(new_stack[0], op_result)); - - // ensure that the rest of the stack is shifted by 1 item to the left - enforce_left_shift(result, old_stack, new_stack, 2, 1, op_flag); - - // ensure that both operands are binary values - aux.agg_constraint(0, op_flag, is_binary(x)); - aux.agg_constraint(1, op_flag, is_binary(y)); -} diff --git a/air/src/stack/comparison.rs b/air/src/stack/comparison.rs deleted file mode 100644 index e29e17ce91..0000000000 --- a/air/src/stack/comparison.rs +++ /dev/null @@ -1,157 +0,0 @@ -use super::{ - are_equal, binary_not, enforce_left_shift, enforce_stack_copy, is_binary, is_zero, - EvaluationResult, FieldElement, -}; - -// CONSTANTS -// ================================================================================================ - -const POW2_IDX: usize = 0; -const X_BIT_IDX: usize = 1; -const Y_BIT_IDX: usize = 2; -const NOT_SET_IDX: usize = 3; -const GT_IDX: usize = 4; -const LT_IDX: usize = 5; -const Y_ACC_IDX: usize = 6; -const X_ACC_IDX: usize = 7; - -// ASSERTIONS -// ================================================================================================ - -/// Enforces constraints for ASSERT operation. The constraints are similar to DROP operation, but -/// have an auxiliary constraint which enforces that 1 - x = 0, where x is the top of the stack. -pub fn enforce_assert( - result: &mut [E], - aux: &mut [E], - old_stack: &[E], - new_stack: &[E], - op_flag: E, -) where - E: FieldElement, -{ - enforce_left_shift(result, old_stack, new_stack, 1, 1, op_flag); - aux.agg_constraint(0, op_flag, are_equal(E::ONE, old_stack[0])); -} - -/// Enforces constraints for ASSERTEQ operation. The stack is shifted by 2 registers the left and -/// an auxiliary constraint enforces that the first element of the stack is equal to the second. -pub fn enforce_asserteq( - result: &mut [E], - aux: &mut [E], - old_stack: &[E], - new_stack: &[E], - op_flag: E, -) where - E: FieldElement, -{ - enforce_left_shift(result, old_stack, new_stack, 2, 2, op_flag); - aux.agg_constraint(0, op_flag, are_equal(old_stack[0], old_stack[1])); -} - -// EQUALITY -// ================================================================================================ - -/// Evaluates constraints for EQ operation. These enforce that when x == y, top of the stack at -/// the next step is set to 1, otherwise top of the stack at the next step is set to 0. -pub fn enforce_eq(result: &mut [E], aux: &mut [E], old_stack: &[E], new_stack: &[E], op_flag: E) -where - E: FieldElement, -{ - // compute difference between top two values of the stack - let x = old_stack[1]; - let y = old_stack[2]; - let diff = x - y; - - // when x == y, the first stack register contains inverse of the difference - let inv_diff = old_stack[0]; - - // the operation is defined as 1 - diff * inv(diff) - let op_result = binary_not(diff * inv_diff); - result.agg_constraint(0, op_flag, are_equal(new_stack[0], op_result)); - - // stack items beyond 3nd item are shifted the the left by 2 - enforce_left_shift(result, old_stack, new_stack, 3, 2, op_flag); - - // we also need to make sure that result * diff = 0; this ensures that when diff != 0 - // the result must be set to 0 - aux.agg_constraint(0, op_flag, new_stack[0] * diff); -} - -// INEQUALITY -// ================================================================================================ - -/// Evaluates constraints for CMP operation. -pub fn enforce_cmp(result: &mut [E], old_stack: &[E], new_stack: &[E], op_flag: E) -where - E: FieldElement, -{ - let two = E::ONE + E::ONE; - - // layout of first 8 registers - // [pow, bit_a, bit_b, not_set, gt, lt, acc_b, acc_a] - - // x and y bits are binary - let x_bit = new_stack[X_BIT_IDX]; - let y_bit = new_stack[Y_BIT_IDX]; - result.agg_constraint(0, op_flag, is_binary(x_bit)); - result.agg_constraint(1, op_flag, is_binary(y_bit)); - - // comparison trackers were updated correctly - let not_set = new_stack[NOT_SET_IDX]; - let bit_gt = x_bit * binary_not(y_bit); - let bit_lt = y_bit * binary_not(x_bit); - - let gt = old_stack[GT_IDX] + bit_gt * not_set; - let lt = old_stack[LT_IDX] + bit_lt * not_set; - result.agg_constraint(2, op_flag, are_equal(new_stack[GT_IDX], gt)); - result.agg_constraint(3, op_flag, are_equal(new_stack[LT_IDX], lt)); - - // binary representation accumulators were updated correctly - let power_of_two = old_stack[POW2_IDX]; - let x_acc = old_stack[X_ACC_IDX] + x_bit * power_of_two; - let y_acc = old_stack[Y_ACC_IDX] + y_bit * power_of_two; - result.agg_constraint(4, op_flag, are_equal(new_stack[Y_ACC_IDX], y_acc)); - result.agg_constraint(5, op_flag, are_equal(new_stack[X_ACC_IDX], x_acc)); - - // when GT or LT register is set to 1, not_set flag is cleared - let not_set_check = binary_not(old_stack[LT_IDX]) * binary_not(old_stack[GT_IDX]); - result.agg_constraint(6, op_flag, are_equal(not_set, not_set_check)); - - // power of 2 register was updated correctly - let power_of_two_constraint = are_equal(new_stack[POW2_IDX] * two, power_of_two); - result.agg_constraint(7, op_flag, power_of_two_constraint); - - // registers beyond the 7th register were not affected - enforce_stack_copy(result, old_stack, new_stack, 8, op_flag); -} - -/// Evaluates constraints for BINACC operation. -pub fn enforce_binacc(result: &mut [E], old_stack: &[E], new_stack: &[E], op_flag: E) -where - E: FieldElement, -{ - let two = E::ONE + E::ONE; - - // layout of first 4 registers: - // [value bit, 0, power of two, accumulated value] - // value bit is located in the next state (not current state) - - // the bit was a binary value - let bit = new_stack[0]; - result.agg_constraint(0, op_flag, is_binary(bit)); - - // register after bit register was empty - result.agg_constraint(1, op_flag, is_zero(new_stack[1])); - - // power of 2 register was updated correctly - let power_of_two = old_stack[2]; - let power_of_two_constraint = are_equal(new_stack[2], power_of_two * two); - result.agg_constraint(2, op_flag, power_of_two_constraint); - - // binary representation accumulator was updated correctly - let acc = old_stack[3] + bit * power_of_two; - result.agg_constraint(3, op_flag, are_equal(new_stack[3], acc)); - - // registers beyond 2nd register remained the same - enforce_stack_copy(result, old_stack, new_stack, 4, op_flag); -} diff --git a/air/src/stack/conditional.rs b/air/src/stack/conditional.rs deleted file mode 100644 index 51342c9a50..0000000000 --- a/air/src/stack/conditional.rs +++ /dev/null @@ -1,100 +0,0 @@ -use super::{are_equal, binary_not, enforce_left_shift, is_binary, EvaluationResult, FieldElement}; - -// CONSTRAINT EVALUATORS -// ================================================================================================ - -/// Enforces constraints for CHOOSE operation. These constraints work with top 3 registers of the -/// stack and enforce that when condition = 1, x remains at the top of the stack; when -/// condition = 0, y remains at the top of the stack. Otherwise the operation fails. -pub fn enforce_choose( - result: &mut [E], - aux: &mut [E], - old_stack: &[E], - new_stack: &[E], - op_flag: E, -) where - E: FieldElement, -{ - let x = old_stack[0]; - let y = old_stack[1]; - let condition = old_stack[2]; - - // the constraint is: (condition * x) + ((1 - condition) * y) - let not_condition = binary_not(condition); - let op_result = condition * x + not_condition * y; - result.agg_constraint(0, op_flag, are_equal(new_stack[0], op_result)); - - // registers beyond the 3rd are shifted left by 2 slots - enforce_left_shift(result, old_stack, new_stack, 3, 2, op_flag); - - // make sure the condition was a binary value - aux.agg_constraint(0, op_flag, is_binary(condition)); -} - -/// Enforces constraints for CHOOSE2 operation. These constraints work with top 6 registers of -/// the stack and enforce that when condition = 1, (x0, x1) remain at the top of the stack; when -/// condition = 0, (y0, y1) remains at the top of the stack. Otherwise the operation fails. -pub fn enforce_choose2( - result: &mut [E], - aux: &mut [E], - old_stack: &[E], - new_stack: &[E], - op_flag: E, -) where - E: FieldElement, -{ - let x0 = old_stack[0]; - let x1 = old_stack[1]; - let y0 = old_stack[2]; - let y1 = old_stack[3]; - let condition = old_stack[4]; - - // the constraints are: (condition * x0) + ((1 - condition) * y0) - // and (condition * x1) + ((1 - condition) * y1) - let not_condition = binary_not(condition); - let op_result1 = condition * x0 + not_condition * y0; - let op_result2 = condition * x1 + not_condition * y1; - result.agg_constraint(0, op_flag, are_equal(new_stack[0], op_result1)); - result.agg_constraint(1, op_flag, are_equal(new_stack[1], op_result2)); - - // registers beyond the 6th are shifted left by 4 slots - enforce_left_shift(result, old_stack, new_stack, 6, 4, op_flag); - - // make sure the condition was a binary value - aux.agg_constraint(0, op_flag, is_binary(condition)); -} - -/// Enforces constraints for CSWAP2 operation. These constraints work with top 6 registers of the -/// stack and enforce that when condition = 1, (v2, v3) move to the top of the stack; when -/// condition = 0, top 4 elements of the stack remain unchanged. -pub fn enforce_cswap2( - result: &mut [E], - aux: &mut [E], - old_stack: &[E], - new_stack: &[E], - op_flag: E, -) where - E: FieldElement, -{ - let v0 = old_stack[0]; - let v1 = old_stack[1]; - let v2 = old_stack[2]; - let v3 = old_stack[3]; - let condition = old_stack[4]; - - let not_condition = binary_not(condition); - let op_result0 = condition * v2 + not_condition * v0; - let op_result1 = condition * v3 + not_condition * v1; - let op_result2 = condition * v0 + not_condition * v2; - let op_result3 = condition * v1 + not_condition * v3; - result.agg_constraint(0, op_flag, are_equal(new_stack[0], op_result0)); - result.agg_constraint(1, op_flag, are_equal(new_stack[1], op_result1)); - result.agg_constraint(2, op_flag, are_equal(new_stack[2], op_result2)); - result.agg_constraint(3, op_flag, are_equal(new_stack[3], op_result3)); - - // registers beyond the 6th are shifted left by 2 slots - enforce_left_shift(result, old_stack, new_stack, 6, 2, op_flag); - - // make sure the condition was a binary value - aux.agg_constraint(0, op_flag, is_binary(condition)); -} diff --git a/air/src/stack/hash.rs b/air/src/stack/hash.rs deleted file mode 100644 index 4b6f596828..0000000000 --- a/air/src/stack/hash.rs +++ /dev/null @@ -1,41 +0,0 @@ -use super::{ - are_equal, enforce_stack_copy, - hasher::{apply_inv_mds, apply_mds, apply_sbox, STATE_WIDTH}, - BaseElement, EvaluationResult, FieldElement, -}; - -/// Evaluates constraints for a single round of a modified Rescue hash function. Hash state is -/// assumed to be in the first 6 registers of user stack; the rest of the stack does not change. -pub fn enforce_rescr>( - result: &mut [E], - old_stack: &[E], - new_stack: &[E], - ark: &[E], - op_flag: E, -) { - // evaluate the first half of Rescue round - let mut old_state = [E::ZERO; STATE_WIDTH]; - old_state.copy_from_slice(&old_stack[..STATE_WIDTH]); - for i in 0..STATE_WIDTH { - old_state[i] += ark[i]; - } - apply_sbox(&mut old_state); - apply_mds(&mut old_state); - - // evaluate inverse of the second half of Rescue round - let mut new_state = [E::ZERO; STATE_WIDTH]; - new_state.copy_from_slice(&new_stack[..STATE_WIDTH]); - apply_inv_mds(&mut new_state); - apply_sbox(&mut new_state); - for i in 0..STATE_WIDTH { - new_state[i] -= ark[STATE_WIDTH + i]; - } - - // compar the results of both rounds - for i in 0..STATE_WIDTH { - result.agg_constraint(i, op_flag, are_equal(new_state[i], old_state[i])); - } - - // make sure the rest of the stack didn't change - enforce_stack_copy(result, old_stack, new_stack, STATE_WIDTH, op_flag); -} diff --git a/air/src/stack/input.rs b/air/src/stack/input.rs deleted file mode 100644 index 33000c4fea..0000000000 --- a/air/src/stack/input.rs +++ /dev/null @@ -1,29 +0,0 @@ -use super::{enforce_right_shift, FieldElement}; - -/// Enforces constraints for PUSH operation. The constraints on the first element of the stack -/// are enforced in the Decoder where the value pushed onto the stack is injected into sponge -/// state. This constraint enforces that the rest of the stack is shifted right by 1 element. -pub fn enforce_push(result: &mut [E], old_stack: &[E], new_stack: &[E], op_flag: E) -where - E: FieldElement, -{ - enforce_right_shift(result, old_stack, new_stack, 1, op_flag); -} - -/// Enforces constraints for READ operation. No constraints are placed on the first element of -/// the stack; the old stack is shifted right by 1 element. -pub fn enforce_read(result: &mut [E], old_stack: &[E], new_stack: &[E], op_flag: E) -where - E: FieldElement, -{ - enforce_right_shift(result, old_stack, new_stack, 1, op_flag); -} - -/// Enforces constraints for READ2 operation. No constraints are placed on the first two elements -/// of the stack; the old stack is shifted right by 2 element. -pub fn enforce_read2(result: &mut [E], old_stack: &[E], new_stack: &[E], op_flag: E) -where - E: FieldElement, -{ - enforce_right_shift(result, old_stack, new_stack, 2, op_flag); -} diff --git a/air/src/stack/manipulation.rs b/air/src/stack/manipulation.rs deleted file mode 100644 index 1d142dd742..0000000000 --- a/air/src/stack/manipulation.rs +++ /dev/null @@ -1,139 +0,0 @@ -use super::{ - are_equal, enforce_left_shift, enforce_right_shift, enforce_stack_copy, EvaluationResult, - FieldElement, -}; - -// STACK MANIPULATION OPERATIONS -// ================================================================================================ - -/// Enforces constraints for DUP operation. The constraints are based on the first element -/// of the stack; the old stack is shifted right by 1 element. -pub fn enforce_dup(result: &mut [E], old_stack: &[E], new_stack: &[E], op_flag: E) -where - E: FieldElement, -{ - result.agg_constraint(0, op_flag, are_equal(new_stack[0], old_stack[0])); - enforce_right_shift(result, old_stack, new_stack, 1, op_flag); -} - -/// Enforces constraints for DUP2 operation. The constraints are based on the first 2 element -/// of the stack; the old stack is shifted right by 2 element. -pub fn enforce_dup2(result: &mut [E], old_stack: &[E], new_stack: &[E], op_flag: E) -where - E: FieldElement, -{ - result.agg_constraint(0, op_flag, are_equal(new_stack[0], old_stack[0])); - result.agg_constraint(1, op_flag, are_equal(new_stack[1], old_stack[1])); - enforce_right_shift(result, old_stack, new_stack, 2, op_flag); -} - -/// Enforces constraints for DUP4 operation. The constraints are based on the first 4 element -/// of the stack; the old stack is shifted right by 4 element. -pub fn enforce_dup4(result: &mut [E], old_stack: &[E], new_stack: &[E], op_flag: E) -where - E: FieldElement, -{ - result.agg_constraint(0, op_flag, are_equal(new_stack[0], old_stack[0])); - result.agg_constraint(1, op_flag, are_equal(new_stack[1], old_stack[1])); - result.agg_constraint(2, op_flag, are_equal(new_stack[2], old_stack[2])); - result.agg_constraint(3, op_flag, are_equal(new_stack[3], old_stack[3])); - enforce_right_shift(result, old_stack, new_stack, 4, op_flag); -} - -/// Enforces constraints for PAD2 operation. The constraints are based on the first 2 element -/// of the stack; the old stack is shifted right by 2 element. -pub fn enforce_pad2(result: &mut [E], old_stack: &[E], new_stack: &[E], op_flag: E) -where - E: FieldElement, -{ - result.agg_constraint(0, op_flag, new_stack[0]); - result.agg_constraint(1, op_flag, new_stack[1]); - enforce_right_shift(result, old_stack, new_stack, 2, op_flag); -} - -// Enforces constraints for DROP operation. The stack is simply shifted left by 1 element. -pub fn enforce_drop(result: &mut [E], old_stack: &[E], new_stack: &[E], op_flag: E) -where - E: FieldElement, -{ - enforce_left_shift(result, old_stack, new_stack, 1, 1, op_flag); -} - -// Enforces constraints for DROP4 operation. The stack is simply shifted left by 4 element. -pub fn enforce_drop4(result: &mut [E], old_stack: &[E], new_stack: &[E], op_flag: E) -where - E: FieldElement, -{ - enforce_left_shift(result, old_stack, new_stack, 4, 4, op_flag); -} - -/// Enforces constraints for SWAP operation. The constraints are based on the first 2 element -/// of the stack; the rest of the stack is unaffected. -pub fn enforce_swap(result: &mut [E], old_stack: &[E], new_stack: &[E], op_flag: E) -where - E: FieldElement, -{ - result.agg_constraint(0, op_flag, are_equal(new_stack[0], old_stack[1])); - result.agg_constraint(0, op_flag, are_equal(new_stack[1], old_stack[0])); - enforce_stack_copy(result, old_stack, new_stack, 2, op_flag); -} - -/// Enforces constraints for SWAP2 operation. The constraints are based on the first 4 element -/// of the stack; the rest of the stack is unaffected. -pub fn enforce_swap2(result: &mut [E], old_stack: &[E], new_stack: &[E], op_flag: E) -where - E: FieldElement, -{ - result.agg_constraint(0, op_flag, are_equal(new_stack[0], old_stack[2])); - result.agg_constraint(1, op_flag, are_equal(new_stack[1], old_stack[3])); - result.agg_constraint(2, op_flag, are_equal(new_stack[2], old_stack[0])); - result.agg_constraint(3, op_flag, are_equal(new_stack[3], old_stack[1])); - enforce_stack_copy(result, old_stack, new_stack, 4, op_flag); -} - -/// Enforces constraints for SWAP4 operation. The constraints are based on the first 8 element -/// of the stack; the rest of the stack is unaffected. -pub fn enforce_swap4(result: &mut [E], old_stack: &[E], new_stack: &[E], op_flag: E) -where - E: FieldElement, -{ - result.agg_constraint(0, op_flag, are_equal(new_stack[0], old_stack[4])); - result.agg_constraint(1, op_flag, are_equal(new_stack[1], old_stack[5])); - result.agg_constraint(2, op_flag, are_equal(new_stack[2], old_stack[6])); - result.agg_constraint(3, op_flag, are_equal(new_stack[3], old_stack[7])); - result.agg_constraint(4, op_flag, are_equal(new_stack[4], old_stack[0])); - result.agg_constraint(5, op_flag, are_equal(new_stack[5], old_stack[1])); - result.agg_constraint(6, op_flag, are_equal(new_stack[6], old_stack[2])); - result.agg_constraint(7, op_flag, are_equal(new_stack[7], old_stack[3])); - enforce_stack_copy(result, old_stack, new_stack, 8, op_flag); -} - -/// Enforces constraints for ROLL4 operation. The constraints are based on the first 4 element -/// of the stack; the rest of the stack is unaffected. -pub fn enforce_roll4(result: &mut [E], old_stack: &[E], new_stack: &[E], op_flag: E) -where - E: FieldElement, -{ - result.agg_constraint(0, op_flag, are_equal(new_stack[0], old_stack[3])); - result.agg_constraint(1, op_flag, are_equal(new_stack[1], old_stack[0])); - result.agg_constraint(2, op_flag, are_equal(new_stack[2], old_stack[1])); - result.agg_constraint(3, op_flag, are_equal(new_stack[3], old_stack[2])); - enforce_stack_copy(result, old_stack, new_stack, 4, op_flag); -} - -/// Enforces constraints for ROLL8 operation. The constraints are based on the first 8 element -/// of the stack; the rest of the stack is unaffected. -pub fn enforce_roll8(result: &mut [E], old_stack: &[E], new_stack: &[E], op_flag: E) -where - E: FieldElement, -{ - result.agg_constraint(0, op_flag, are_equal(new_stack[0], old_stack[7])); - result.agg_constraint(1, op_flag, are_equal(new_stack[1], old_stack[0])); - result.agg_constraint(2, op_flag, are_equal(new_stack[2], old_stack[1])); - result.agg_constraint(3, op_flag, are_equal(new_stack[3], old_stack[2])); - result.agg_constraint(4, op_flag, are_equal(new_stack[4], old_stack[3])); - result.agg_constraint(5, op_flag, are_equal(new_stack[5], old_stack[4])); - result.agg_constraint(6, op_flag, are_equal(new_stack[6], old_stack[5])); - result.agg_constraint(7, op_flag, are_equal(new_stack[7], old_stack[6])); - enforce_stack_copy(result, old_stack, new_stack, 8, op_flag); -} diff --git a/air/src/stack/mod.rs b/air/src/stack/mod.rs deleted file mode 100644 index ed59be748f..0000000000 --- a/air/src/stack/mod.rs +++ /dev/null @@ -1,303 +0,0 @@ -use crate::{ - hasher, - opcodes::UserOps as OpCode, - utils::{ - are_equal, binary_not, enforce_left_shift, enforce_right_shift, enforce_stack_copy, - is_binary, is_zero, EvaluationResult, - }, - BaseElement, FieldElement, TransitionConstraintDegree, VmTransition, -}; - -mod input; -use input::{enforce_push, enforce_read, enforce_read2}; - -mod arithmetic; -use arithmetic::{ - enforce_add, enforce_and, enforce_inv, enforce_mul, enforce_neg, enforce_not, enforce_or, -}; - -mod manipulation; -use manipulation::{ - enforce_drop, enforce_drop4, enforce_dup, enforce_dup2, enforce_dup4, enforce_pad2, - enforce_roll4, enforce_roll8, enforce_swap, enforce_swap2, enforce_swap4, -}; - -mod comparison; -use comparison::{enforce_assert, enforce_asserteq, enforce_binacc, enforce_cmp, enforce_eq}; - -mod conditional; -use conditional::{enforce_choose, enforce_choose2, enforce_cswap2}; - -mod hash; -use hash::enforce_rescr; - -// CONSTANTS -// ================================================================================================ -pub const NUM_AUX_CONSTRAINTS: usize = 2; - -// CONSTRAINT DEGREES -// ================================================================================================ - -pub fn get_transition_constraint_degrees(stack_depth: usize) -> Vec { - // all stack transition constraints have degree 7 - let degree = TransitionConstraintDegree::new(7); - vec![degree; stack_depth + NUM_AUX_CONSTRAINTS] -} - -// HELPER FUNCTIONS -// ================================================================================================ -pub fn enforce_constraints>( - transition: &VmTransition, - ark: &[E], - result: &mut [E], -) { - // split constraint evaluation result into aux constraints and stack constraints - let (aux, result) = result.split_at_mut(NUM_AUX_CONSTRAINTS); - - // get user stack registers from current and next steps - let old_stack = transition.current().user_stack(); - let new_stack = transition.next().user_stack(); - - // initialize a vector to hold stack constraint evaluations; this is needed because - // constraint evaluator functions assume that the stack is at least 8 items deep; while - // it may actually be smaller than that - let mut evaluations = vec![E::ZERO; old_stack.len()]; - - // 1 ----- enforce constraints for low-degree operations -------------------------------------- - let ld_flags = transition.ld_op_flags(); - - // assertion operations - enforce_assert( - &mut evaluations, - aux, - old_stack, - new_stack, - ld_flags[OpCode::Assert.ld_index()], - ); - enforce_asserteq( - &mut evaluations, - aux, - old_stack, - new_stack, - ld_flags[OpCode::AssertEq.ld_index()], - ); - - // input operations - enforce_read( - &mut evaluations, - old_stack, - new_stack, - ld_flags[OpCode::Read.ld_index()], - ); - enforce_read2( - &mut evaluations, - old_stack, - new_stack, - ld_flags[OpCode::Read2.ld_index()], - ); - - // stack manipulation operations - enforce_dup( - &mut evaluations, - old_stack, - new_stack, - ld_flags[OpCode::Dup.ld_index()], - ); - enforce_dup2( - &mut evaluations, - old_stack, - new_stack, - ld_flags[OpCode::Dup2.ld_index()], - ); - enforce_dup4( - &mut evaluations, - old_stack, - new_stack, - ld_flags[OpCode::Dup4.ld_index()], - ); - enforce_pad2( - &mut evaluations, - old_stack, - new_stack, - ld_flags[OpCode::Pad2.ld_index()], - ); - - enforce_drop( - &mut evaluations, - old_stack, - new_stack, - ld_flags[OpCode::Drop.ld_index()], - ); - enforce_drop4( - &mut evaluations, - old_stack, - new_stack, - ld_flags[OpCode::Drop4.ld_index()], - ); - - enforce_swap( - &mut evaluations, - old_stack, - new_stack, - ld_flags[OpCode::Swap.ld_index()], - ); - enforce_swap2( - &mut evaluations, - old_stack, - new_stack, - ld_flags[OpCode::Swap2.ld_index()], - ); - enforce_swap4( - &mut evaluations, - old_stack, - new_stack, - ld_flags[OpCode::Swap4.ld_index()], - ); - - enforce_roll4( - &mut evaluations, - old_stack, - new_stack, - ld_flags[OpCode::Roll4.ld_index()], - ); - enforce_roll8( - &mut evaluations, - old_stack, - new_stack, - ld_flags[OpCode::Roll8.ld_index()], - ); - - // arithmetic and boolean operations - enforce_add( - &mut evaluations, - old_stack, - new_stack, - ld_flags[OpCode::Add.ld_index()], - ); - enforce_mul( - &mut evaluations, - old_stack, - new_stack, - ld_flags[OpCode::Mul.ld_index()], - ); - enforce_inv( - &mut evaluations, - old_stack, - new_stack, - ld_flags[OpCode::Inv.ld_index()], - ); - enforce_neg( - &mut evaluations, - old_stack, - new_stack, - ld_flags[OpCode::Neg.ld_index()], - ); - - enforce_not( - &mut evaluations, - aux, - old_stack, - new_stack, - ld_flags[OpCode::Not.ld_index()], - ); - enforce_and( - &mut evaluations, - aux, - old_stack, - new_stack, - ld_flags[OpCode::And.ld_index()], - ); - enforce_or( - &mut evaluations, - aux, - old_stack, - new_stack, - ld_flags[OpCode::Or.ld_index()], - ); - - // comparison operations - enforce_eq( - &mut evaluations, - aux, - old_stack, - new_stack, - ld_flags[OpCode::Eq.ld_index()], - ); - enforce_binacc( - &mut evaluations, - old_stack, - new_stack, - ld_flags[OpCode::BinAcc.ld_index()], - ); - - // conditional selection operations - enforce_choose( - &mut evaluations, - aux, - old_stack, - new_stack, - ld_flags[OpCode::Choose.ld_index()], - ); - enforce_choose2( - &mut evaluations, - aux, - old_stack, - new_stack, - ld_flags[OpCode::Choose2.ld_index()], - ); - enforce_cswap2( - &mut evaluations, - aux, - old_stack, - new_stack, - ld_flags[OpCode::CSwap2.ld_index()], - ); - - // 2 ----- enforce constraints for high-degree operations -------------------------------------- - let hd_flags = transition.hd_op_flags(); - - enforce_push( - &mut evaluations, - old_stack, - new_stack, - hd_flags[OpCode::Push.hd_index()], - ); - enforce_cmp( - &mut evaluations, - old_stack, - new_stack, - hd_flags[OpCode::Cmp.hd_index()], - ); - enforce_rescr( - &mut evaluations, - old_stack, - new_stack, - ark, - hd_flags[OpCode::RescR.hd_index()], - ); - - // 3 ----- enforce constraints for composite operations --------------------------------------- - - // BEGIN and NOOP have "composite" opcodes where all 7 opcode bits are set to either 1s or 0s; - // thus, the flags for these operations are computed separately by multiplying all opcodes; - // this results in flag degree of 7 for each operation, but since both operations enforce the - // same constraints (the stack doesn't change), higher degree terms cancel out, and we - // end up with overall constraint degree of (6 + 1 = 7) for both operations. - enforce_stack_copy( - &mut evaluations, - old_stack, - new_stack, - 0, - transition.begin_flag(), - ); - enforce_stack_copy( - &mut evaluations, - old_stack, - new_stack, - 0, - transition.noop_flag(), - ); - - // 4 ----- copy evaluations into the result --------------------------------------------------- - result.copy_from_slice(&evaluations[..result.len()]); -} diff --git a/air/src/transition.rs b/air/src/transition.rs deleted file mode 100644 index dd25729103..0000000000 --- a/air/src/transition.rs +++ /dev/null @@ -1,280 +0,0 @@ -use crate::{ - opcodes::UserOps as OpCode, utils::binary_not, BaseElement, FieldElement, TraceState, - NUM_CF_OPS, NUM_HD_OPS, NUM_LD_OPS, -}; -use winter_air::EvaluationFrame; - -// VM TRANSITION -// ================================================================================================ - -pub struct VmTransition> { - current: TraceState, - next: TraceState, - cf_op_flags: [E; NUM_CF_OPS], - ld_op_flags: [E; NUM_LD_OPS], - hd_op_flags: [E; NUM_HD_OPS], - begin_flag: E, - noop_flag: E, -} - -impl> VmTransition { - // CONSTRUCTOR - // -------------------------------------------------------------------------------------------- - - pub fn new(ctx_depth: usize, loop_depth: usize, stack_depth: usize) -> Self { - Self { - current: TraceState::new(ctx_depth, loop_depth, stack_depth), - next: TraceState::new(ctx_depth, loop_depth, stack_depth), - cf_op_flags: [E::ZERO; NUM_CF_OPS], - ld_op_flags: [E::ZERO; NUM_LD_OPS], - hd_op_flags: [E::ZERO; NUM_HD_OPS], - begin_flag: E::ZERO, - noop_flag: E::ZERO, - } - } - - #[cfg(test)] - pub fn from_states(current: TraceState, next: TraceState) -> Self { - let mut result = Self { - current, - next, - cf_op_flags: [E::ZERO; NUM_CF_OPS], - ld_op_flags: [E::ZERO; NUM_LD_OPS], - hd_op_flags: [E::ZERO; NUM_HD_OPS], - begin_flag: E::ZERO, - noop_flag: E::ZERO, - }; - result.set_op_flags(); - result - } - - // DATA MUTATORS - // -------------------------------------------------------------------------------------------- - - pub fn update(&mut self, frame: &EvaluationFrame) { - self.current.update(frame.current()); - self.next.update(frame.next()); - self.set_op_flags(); - } - - // STATE ACCESSORS - // -------------------------------------------------------------------------------------------- - - pub fn current(&self) -> &TraceState { - &self.current - } - - pub fn next(&self) -> &TraceState { - &self.next - } - - // OP FLAGS - // -------------------------------------------------------------------------------------------- - pub fn cf_op_flags(&self) -> [E; NUM_CF_OPS] { - self.cf_op_flags - } - - pub fn ld_op_flags(&self) -> [E; NUM_LD_OPS] { - self.ld_op_flags - } - - pub fn hd_op_flags(&self) -> [E; NUM_HD_OPS] { - self.hd_op_flags - } - - pub fn begin_flag(&self) -> E { - self.begin_flag - } - - pub fn noop_flag(&self) -> E { - self.noop_flag - } - - // HELPER METHODS - // -------------------------------------------------------------------------------------------- - - fn set_op_flags(&mut self) { - // set control flow flags - let not_0 = binary_not(self.current.cf_op_bits()[0]); - let not_1 = binary_not(self.current.cf_op_bits()[1]); - self.cf_op_flags[0] = not_0 * not_1; - self.cf_op_flags[1] = self.current.cf_op_bits()[0] * not_1; - self.cf_op_flags[2] = not_0 * self.current.cf_op_bits()[1]; - self.cf_op_flags[3] = self.current.cf_op_bits()[0] * self.current.cf_op_bits()[1]; - self.cf_op_flags.copy_within(0..4, 4); - - let not_2 = binary_not(self.current.cf_op_bits()[2]); - for i in 0..4 { - self.cf_op_flags[i] *= not_2; - } - for i in 4..8 { - self.cf_op_flags[i] *= self.current.cf_op_bits()[2]; - } - - // set low-degree operation flags - let not_0 = binary_not(self.current.ld_op_bits()[0]); - let not_1 = binary_not(self.current.ld_op_bits()[1]); - self.ld_op_flags[0] = not_0 * not_1; - self.ld_op_flags[1] = self.current.ld_op_bits()[0] * not_1; - self.ld_op_flags[2] = not_0 * self.current.cf_op_bits()[1]; - self.ld_op_flags[3] = self.current.ld_op_bits()[0] * self.current.ld_op_bits()[1]; - self.ld_op_flags.copy_within(0..4, 4); - - let not_2 = binary_not(self.current.ld_op_bits()[2]); - for i in 0..4 { - self.ld_op_flags[i] *= not_2; - } - for i in 4..8 { - self.ld_op_flags[i] *= self.current.ld_op_bits()[2]; - } - self.ld_op_flags.copy_within(0..8, 8); - - let not_3 = binary_not(self.current.ld_op_bits()[3]); - for i in 0..8 { - self.ld_op_flags[i] *= not_3; - } - for i in 8..16 { - self.ld_op_flags[i] *= self.current.ld_op_bits()[3]; - } - self.ld_op_flags.copy_within(0..16, 16); - - let not_4 = binary_not(self.current.ld_op_bits()[4]); - for i in 0..16 { - self.ld_op_flags[i] *= not_4; - } - for i in 16..32 { - self.ld_op_flags[i] *= self.current.ld_op_bits()[4]; - } - - // set high-degree operation flags - let not_0 = binary_not(self.current.hd_op_bits()[0]); - let not_1 = binary_not(self.current.hd_op_bits()[1]); - self.hd_op_flags[0] = not_0 * not_1; - self.hd_op_flags[1] = self.current.hd_op_bits()[0] * not_1; - self.hd_op_flags[2] = not_0 * self.current.hd_op_bits()[1]; - self.hd_op_flags[3] = self.current.hd_op_bits()[0] * self.current.hd_op_bits()[1]; - - // compute flag for BEGIN operation which is just 0000000; the below is equivalent - // to multiplying binary inverses of all op bits together. - self.begin_flag = - self.ld_op_flags[OpCode::Begin.ld_index()] * self.hd_op_flags[OpCode::Begin.hd_index()]; - - // compute flag for NOOP operation which is just 1111111; the below is equivalent to - // multiplying all op bits together. - self.noop_flag = - self.ld_op_flags[OpCode::Noop.ld_index()] * self.hd_op_flags[OpCode::Noop.hd_index()]; - - // we need to make special adjustments for PUSH and ASSERT op flags so that they - // don't coincide with BEGIN operation; we do this by multiplying each flag by a - // single op_bit from another op bank; this increases degree of each flag by 1 - debug_assert!(OpCode::Push.hd_index() == 0, "PUSH index is not 0!"); - self.hd_op_flags[0] *= self.current.ld_op_bits()[0]; - - debug_assert!(OpCode::Assert.ld_index() == 0, "ASSERT index is not 0!"); - self.ld_op_flags[0] *= self.current.hd_op_bits()[0]; - } -} - -// TESTS -// ================================================================================================ -#[cfg(test)] -mod tests { - use super::{EvaluationFrame, VmTransition}; - use vm_core::{utils::ToElements, BaseElement, FieldElement, StarkField}; - - #[test] - fn op_flags() { - // all zeros - let transition = vm_transition_from_current(&[ - 101, 1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 16, 17, - ]); - - assert_eq!( - [1, 0, 0, 0, 0, 0, 0, 0].to_elements(), - transition.cf_op_flags() - ); - assert_eq!( - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, - ] - .to_elements(), - transition.ld_op_flags() - ); - assert_eq!([0, 0, 0, 0].to_elements(), transition.hd_op_flags()); - assert_eq!(1, transition.begin_flag().as_int()); - assert_eq!(0, transition.noop_flag().as_int()); - - // all ones - let transition = vm_transition_from_current(&[ - 101, 1, 2, 3, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 15, 16, 17, - ]); - - assert_eq!( - [0, 0, 0, 0, 0, 0, 0, 1].to_elements(), - transition.cf_op_flags() - ); - assert_eq!( - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 1, - ] - .to_elements(), - transition.ld_op_flags() - ); - assert_eq!([0, 0, 0, 1].to_elements(), transition.hd_op_flags()); - assert_eq!(0, transition.begin_flag().as_int()); - assert_eq!(1, transition.noop_flag().as_int()); - - // mixed 1 - let transition = vm_transition_from_current(&[ - 101, 1, 2, 3, 4, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 15, 16, 17, - ]); - - assert_eq!( - [0, 1, 0, 0, 0, 0, 0, 0].to_elements(), - transition.cf_op_flags() - ); - assert_eq!( - [ - 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, - ] - .to_elements(), - transition.ld_op_flags() - ); - assert_eq!([0, 1, 0, 0].to_elements(), transition.hd_op_flags()); - assert_eq!(0, transition.begin_flag().as_int()); - assert_eq!(0, transition.noop_flag().as_int()); - - // mixed 2 - let transition = vm_transition_from_current(&[ - 101, 1, 2, 3, 4, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 15, 16, 17, - ]); - - assert_eq!( - [0, 0, 0, 1, 0, 0, 0, 0].to_elements(), - transition.cf_op_flags() - ); - assert_eq!( - [ - 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, - ] - .to_elements(), - transition.ld_op_flags() - ); - assert_eq!([0, 0, 1, 0].to_elements(), transition.hd_op_flags()); - } - - // HELPER FUNCTIONS - // -------------------------------------------------------------------------------------------- - - fn vm_transition_from_current(current_row: &[u128]) -> VmTransition { - let mut result = VmTransition::new(1, 0, 2); - let current = current_row.iter().map(|&v| BaseElement::new(v)).collect(); - let frame = EvaluationFrame::from_rows(current, vec![BaseElement::ZERO; current_row.len()]); - result.update(&frame); - result - } -} diff --git a/air/src/utils.rs b/air/src/utils.rs deleted file mode 100644 index ec959d7357..0000000000 --- a/air/src/utils.rs +++ /dev/null @@ -1,167 +0,0 @@ -use crate::FieldElement; - -// BASIC CONSTRAINTS OPERATORS -// ================================================================================================ - -#[inline(always)] -pub fn is_zero(v: E) -> E { - v -} - -#[inline(always)] -pub fn is_binary(v: E) -> E { - v.square() - v -} - -#[inline(always)] -pub fn binary_not(v: E) -> E { - E::ONE - v -} - -#[inline(always)] -pub fn are_equal(v1: E, v2: E) -> E { - v1 - v2 -} - -// COMMON STACK CONSTRAINTS -// ================================================================================================ - -/// Enforces that stack values starting from `from_slot` haven't changed. All constraints in the -/// `result` slice are filled in. -pub fn enforce_stack_copy( - result: &mut [E], - old_stack: &[E], - new_stack: &[E], - from_slot: usize, - op_flag: E, -) { - for i in from_slot..result.len() { - result.agg_constraint(i, op_flag, are_equal(old_stack[i], new_stack[i])); - } -} - -/// Enforces that values in the stack were shifted to the right by `num_slots`. Constraints in -/// the `result` slice are filled in starting from `num_slots` index. -pub fn enforce_right_shift( - result: &mut [E], - old_stack: &[E], - new_stack: &[E], - num_slots: usize, - op_flag: E, -) { - for i in num_slots..result.len() { - result.agg_constraint( - i, - op_flag, - are_equal(old_stack[i - num_slots], new_stack[i]), - ); - } -} - -/// Enforces that values in the stack were shifted to the left by `num_slots` starting from -/// `from_slots`. All constraints in the `result` slice are filled in. -pub fn enforce_left_shift( - result: &mut [E], - old_stack: &[E], - new_stack: &[E], - from_slot: usize, - num_slots: usize, - op_flag: E, -) { - // make sure values in the stack were shifted by `num_slots` to the left - let start_idx = from_slot - num_slots; - let remainder_idx = result.len() - num_slots; - for i in start_idx..remainder_idx { - result.agg_constraint( - i, - op_flag, - are_equal(old_stack[i + num_slots], new_stack[i]), - ); - } - - // also make sure that remaining slots were filled in with 0s - #[allow(clippy::needless_range_loop)] - for i in remainder_idx..result.len() { - result.agg_constraint(i, op_flag, is_zero(new_stack[i])); - } -} - -// TRAIT TO SIMPLIFY CONSTRAINT AGGREGATION -// ================================================================================================ - -pub trait EvaluationResult { - fn agg_constraint(&mut self, index: usize, flag: E, value: E); -} - -impl EvaluationResult for [E] { - fn agg_constraint(&mut self, index: usize, flag: E, value: E) { - self[index] += flag * value; - } -} - -impl EvaluationResult for Vec { - fn agg_constraint(&mut self, index: usize, flag: E, value: E) { - self[index] += flag * value; - } -} - -// TESTS -// ================================================================================================ - -#[cfg(test)] -mod tests { - use vm_core::{utils::ToElements, BaseElement, FieldElement}; - - #[test] - fn enforce_left_shift() { - let op_flag = BaseElement::ONE; - - // sift left by 1 starting from 1 - let mut result = vec![BaseElement::ZERO; 8]; - super::enforce_left_shift( - &mut result, - &[1, 2, 3, 4, 5, 6, 7, 8].to_elements(), - &[1, 2, 3, 4, 5, 6, 7, 8].to_elements(), - 1, - 1, - op_flag, - ); - assert_eq!([1, 1, 1, 1, 1, 1, 1, 8].to_elements(), result); - - // sift left by 2 starting from 2 - let mut result = vec![BaseElement::ZERO; 8]; - super::enforce_left_shift( - &mut result, - &[1, 2, 3, 4, 5, 6, 7, 8].to_elements(), - &[1, 2, 3, 4, 5, 6, 7, 8].to_elements(), - 2, - 2, - op_flag, - ); - assert_eq!([2, 2, 2, 2, 2, 2, 7, 8].to_elements(), result); - - // sift left by 1 starting from 2 - let mut result = vec![BaseElement::ZERO; 8]; - super::enforce_left_shift( - &mut result, - &[1, 2, 3, 4, 5, 6, 7, 8].to_elements(), - &[1, 2, 3, 4, 5, 6, 7, 8].to_elements(), - 2, - 1, - op_flag, - ); - assert_eq!([0, 1, 1, 1, 1, 1, 1, 8].to_elements(), result); - - // sift left by 4 starting from 6 - let mut result = vec![BaseElement::ZERO; 8]; - super::enforce_left_shift( - &mut result, - &[1, 2, 3, 4, 5, 6, 7, 8].to_elements(), - &[1, 2, 3, 4, 5, 6, 7, 8].to_elements(), - 6, - 4, - op_flag, - ); - assert_eq!([0, 0, 4, 4, 5, 6, 7, 8].to_elements(), result); - } -} diff --git a/core/src/lib.rs b/core/src/lib.rs index 78f3ff08f7..b4201e426f 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,6 +1,7 @@ +use core::ops::Range; + pub mod hasher; pub mod program; -pub mod utils; pub use math::{fields::f64::BaseElement as Felt, FieldElement, StarkField}; mod operations; @@ -9,6 +10,9 @@ pub use operations::{AdviceInjector, DebugOptions, Operation}; mod inputs; pub use inputs::{AdviceSet, ProgramInputs}; +pub mod utils; +use utils::range; + pub mod errors; // TYPE ALIASES @@ -16,9 +20,41 @@ pub mod errors; pub type Word = [Felt; 4]; +pub type StackTopState = [Felt; MIN_STACK_DEPTH]; + // CONSTANTS // ================================================================================================ /// The minimum stack depth enforced by the VM. This is also the number of stack registers which can /// be accessed by the VM directly. pub const MIN_STACK_DEPTH: usize = 16; + +// TRACE LAYOUT +// ------------------------------------------------------------------------------------------------ + +// system decoder stack range checks auxiliary table +// (2 columns) (22 columns) (19 columns) (4 columns) (18 columns) +// ├───────────────┴───────────────┴───────────────┴───────────────┴─────────────────┤ + +pub const SYS_TRACE_OFFSET: usize = 0; +pub const SYS_TRACE_WIDTH: usize = 2; +pub const SYS_TRACE_RANGE: Range = range(SYS_TRACE_OFFSET, SYS_TRACE_WIDTH); + +pub const CLK_COL_IDX: usize = SYS_TRACE_OFFSET; +pub const FMP_COL_IDX: usize = SYS_TRACE_OFFSET + 1; + +// TODO: decoder column trace + +// Stack trace +pub const STACK_TRACE_OFFSET: usize = SYS_TRACE_OFFSET + SYS_TRACE_WIDTH; +pub const STACK_TRACE_WIDTH: usize = MIN_STACK_DEPTH; // TODO: add helper columns +pub const STACK_TRACE_RANGE: Range = range(STACK_TRACE_OFFSET, STACK_TRACE_WIDTH); + +// TODO: range check trace + +// Auxiliary table trace +pub const AUX_TRACE_OFFSET: usize = STACK_TRACE_OFFSET + STACK_TRACE_WIDTH; +pub const AUX_TRACE_WIDTH: usize = 18; +pub const AUX_TRACE_RANGE: Range = range(AUX_TRACE_OFFSET, AUX_TRACE_WIDTH); + +pub const TRACE_WIDTH: usize = AUX_TRACE_OFFSET + AUX_TRACE_WIDTH; diff --git a/core/src/utils/mod.rs b/core/src/utils/mod.rs index 969b1a6392..fde8c89a6c 100644 --- a/core/src/utils/mod.rs +++ b/core/src/utils/mod.rs @@ -1,4 +1,5 @@ use super::{Felt, StarkField}; +use core::ops::Range; // TO ELEMENTS // ================================================================================================ @@ -52,3 +53,14 @@ impl PushMany for Vec { self.resize(new_len, value); } } + +// RANGE +// ================================================================================================ + +/// Returns a [Range] initialized with the specified `start` and with `end` set to `start` + `len`. +pub const fn range(start: usize, len: usize) -> Range { + Range { + start, + end: start + len, + } +} diff --git a/processor/src/lib.rs b/processor/src/lib.rs index af66d30806..82e72e0e4f 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -5,8 +5,8 @@ use vm_core::{ blocks::{CodeBlock, Join, Loop, OpBatch, Span, Split}, Script, }, - AdviceInjector, DebugOptions, Felt, FieldElement, Operation, ProgramInputs, StarkField, Word, - MIN_STACK_DEPTH, + AdviceInjector, DebugOptions, Felt, FieldElement, Operation, ProgramInputs, StackTopState, + StarkField, Word, AUX_TRACE_WIDTH, MIN_STACK_DEPTH, SYS_TRACE_WIDTH, }; mod operations; @@ -44,15 +44,12 @@ pub use errors::ExecutionError; #[cfg(test)] mod tests; -// CONSTANTS -// ================================================================================================ -const AUXILIARY_TABLE_WIDTH: usize = 18; - // TYPE ALIASES // ================================================================================================ +type SysTrace = [Vec; SYS_TRACE_WIDTH]; type StackTrace = [Vec; MIN_STACK_DEPTH]; -type AuxiliaryTableTrace = [Vec; AUXILIARY_TABLE_WIDTH]; +type AuxTableTrace = [Vec; AUX_TRACE_WIDTH]; // EXECUTOR // ================================================================================================ diff --git a/processor/src/system/mod.rs b/processor/src/system/mod.rs index bc13cf800b..b2b3f266d9 100644 --- a/processor/src/system/mod.rs +++ b/processor/src/system/mod.rs @@ -1,4 +1,4 @@ -use super::{Felt, FieldElement}; +use super::{Felt, FieldElement, SysTrace}; // CONSTANTS // ================================================================================================ @@ -56,6 +56,11 @@ impl System { self.clk_trace.len() } + /// Returns an execution trace of this system info container. + pub fn into_trace(self) -> SysTrace { + [self.clk_trace, self.fmp_trace] + } + // STATE MUTATORS // -------------------------------------------------------------------------------------------- diff --git a/processor/src/tests/aux_table_trace.rs b/processor/src/tests/aux_table_trace.rs index 9bd71536f5..2c0d75c1bd 100644 --- a/processor/src/tests/aux_table_trace.rs +++ b/processor/src/tests/aux_table_trace.rs @@ -3,7 +3,7 @@ use super::{ bitwise::BITWISE_OR, build_op_test, build_test, hasher::{LINEAR_HASH, RETURN_STATE}, - AuxiliaryTableTrace, FieldElement, + AuxTableTrace, FieldElement, }, Felt, }; @@ -117,7 +117,7 @@ fn stacked_aux_trace() { /// Validate the hasher trace output by the rpperm operation. The full hasher trace is tested in /// the Hasher module, so this just tests the AuxiliaryTableTrace selectors and the initial columns /// of the hasher trace. -fn validate_hasher_trace(aux_table: &AuxiliaryTableTrace, start: usize, end: usize) { +fn validate_hasher_trace(aux_table: &AuxTableTrace, start: usize, end: usize) { // The selectors should match the hasher selectors for row in start..end { // The selectors should match the selectors for the hasher segment @@ -152,7 +152,7 @@ fn validate_hasher_trace(aux_table: &AuxiliaryTableTrace, start: usize, end: usi /// Validate the bitwise trace output by the u32or operation. The full bitwise trace is tested in /// the Bitwise module, so this just tests the AuxiliaryTableTrace selectors, the initial columns /// of the bitwise trace, and the final columns after the bitwise trace. -fn validate_bitwise_trace(aux_table: &AuxiliaryTableTrace, start: usize, end: usize) { +fn validate_bitwise_trace(aux_table: &AuxTableTrace, start: usize, end: usize) { // The selectors should match the bitwise selectors for row in start..end { // The selectors should match the selectors for the bitwise segment @@ -172,7 +172,7 @@ fn validate_bitwise_trace(aux_table: &AuxiliaryTableTrace, start: usize, end: us /// Validate the bitwise trace output by the storew operation. The full memory trace is tested in /// the Memory module, so this just tests the AuxiliaryTableTrace selectors, the initial columns /// of the memory trace, and the final column after the memory trace. -fn validate_memory_trace(aux_table: &AuxiliaryTableTrace, start: usize, end: usize, addr: u64) { +fn validate_memory_trace(aux_table: &AuxTableTrace, start: usize, end: usize, addr: u64) { for row in start..end { // The selectors in the first row should match the memory selectors assert_eq!(Felt::ONE, aux_table[0][row]); @@ -189,7 +189,7 @@ fn validate_memory_trace(aux_table: &AuxiliaryTableTrace, start: usize, end: usi } /// Checks that the end of the auxiliary trace table is padded and has the correct selectors. -fn validate_padding(aux_table: &AuxiliaryTableTrace, start: usize, end: usize) { +fn validate_padding(aux_table: &AuxTableTrace, start: usize, end: usize) { for row in start..end { // selectors assert_eq!(Felt::ONE, aux_table[0][row]); diff --git a/processor/src/trace.rs b/processor/src/trace.rs index d67b9396c6..8888ab3f73 100644 --- a/processor/src/trace.rs +++ b/processor/src/trace.rs @@ -1,20 +1,24 @@ use super::{ - AuxiliaryTableTrace, Bitwise, Digest, Felt, FieldElement, Hasher, Memory, Process, StackTrace, - AUXILIARY_TABLE_WIDTH, MIN_STACK_DEPTH, + AuxTableTrace, Bitwise, Digest, Felt, FieldElement, Hasher, Memory, Process, StackTopState, + StackTrace, SysTrace, }; use core::slice; +use vm_core::{ + AUX_TRACE_OFFSET, AUX_TRACE_RANGE, AUX_TRACE_WIDTH, STACK_TRACE_OFFSET, STACK_TRACE_RANGE, + STACK_TRACE_WIDTH, SYS_TRACE_OFFSET, SYS_TRACE_RANGE, TRACE_WIDTH, +}; use winterfell::Trace; // VM EXECUTION TRACE // ================================================================================================ -/// TODO: for now this consists only of stack trace and auxiliary traces, but will need to -/// include decoder trace, etc. +/// TODO: for now this consists only of system register trace, stack trace, and auxiliary traces, +/// but will also need to include decoder trace and range checker trace. pub struct ExecutionTrace { meta: Vec, + system: SysTrace, stack: StackTrace, - #[allow(dead_code)] - aux_table: AuxiliaryTableTrace, + aux_table: AuxTableTrace, // TODO: program hash should be retrieved from decoder trace, but for now we store it explicitly program_hash: Digest, } @@ -46,7 +50,7 @@ impl ExecutionTrace { // note: it may be possible to optimize this by initializing with Felt::zeroed_vector, // depending on how the compiler reduces Felt(0) and whether initializing here + iterating // to update selector values is faster than using resize to initialize all values - let mut aux_table_trace: AuxiliaryTableTrace = (0..AUXILIARY_TABLE_WIDTH) + let mut aux_table_trace: AuxTableTrace = (0..AUX_TRACE_WIDTH) .map(|_| Vec::::with_capacity(trace_len)) .collect::>() .try_into() @@ -64,6 +68,7 @@ impl ExecutionTrace { Self { meta: Vec::new(), + system: system.into_trace(), stack: stack_trace, aux_table: aux_table_trace, program_hash, @@ -79,23 +84,28 @@ impl ExecutionTrace { } /// TODO: add docs - pub fn init_stack_state(&self) -> [Felt; MIN_STACK_DEPTH] { - let mut result = [Felt::ZERO; MIN_STACK_DEPTH]; - self.read_row_into(0, &mut result); + pub fn init_stack_state(&self) -> StackTopState { + let mut result = [Felt::ZERO; STACK_TRACE_WIDTH]; + for (result, column) in result.iter_mut().zip(self.stack.iter()) { + *result = column[0]; + } result } /// TODO: add docs - pub fn last_stack_state(&self) -> [Felt; MIN_STACK_DEPTH] { - let mut result = [Felt::ZERO; MIN_STACK_DEPTH]; - self.read_row_into(self.length() - 1, &mut result); + pub fn last_stack_state(&self) -> StackTopState { + let last_step = self.length() - 1; + let mut result = [Felt::ZERO; STACK_TRACE_WIDTH]; + for (result, column) in result.iter_mut().zip(self.stack.iter()) { + *result = column[last_step]; + } result } // ACCESSORS FOR TESTING // -------------------------------------------------------------------------------------------- #[cfg(test)] - pub fn aux_table(&self) -> &AuxiliaryTableTrace { + pub fn aux_table(&self) -> &AuxTableTrace { &self.aux_table } @@ -112,29 +122,45 @@ impl Trace for ExecutionTrace { type BaseField = Felt; fn width(&self) -> usize { - self.stack.len() + TRACE_WIDTH } fn length(&self) -> usize { - self.stack[0].len() + self.system[0].len() } - fn get(&self, col_idx: usize, row_idx: usize) -> Self::BaseField { - self.stack[col_idx][row_idx] + fn get(&self, col_idx: usize, row_idx: usize) -> Felt { + match col_idx { + i if SYS_TRACE_RANGE.contains(&i) => self.system[i - SYS_TRACE_OFFSET][row_idx], + i if STACK_TRACE_RANGE.contains(&i) => self.stack[i - STACK_TRACE_OFFSET][row_idx], + i if AUX_TRACE_RANGE.contains(&i) => self.aux_table[i - AUX_TRACE_OFFSET][row_idx], + _ => panic!("invalid column index"), + } } fn meta(&self) -> &[u8] { &self.meta } - fn read_row_into(&self, step: usize, target: &mut [Self::BaseField]) { - for (i, register) in self.stack.iter().enumerate() { - target[i] = register[step]; + fn read_row_into(&self, step: usize, target: &mut [Felt]) { + for (i, column) in self.system.iter().enumerate() { + target[i + SYS_TRACE_OFFSET] = column[step]; + } + + for (i, column) in self.stack.iter().enumerate() { + target[i + STACK_TRACE_OFFSET] = column[step]; + } + + for (i, column) in self.aux_table.iter().enumerate() { + target[i + AUX_TRACE_OFFSET] = column[step]; } } - fn into_columns(self) -> Vec> { - self.stack.into() + fn into_columns(self) -> Vec> { + let mut result: Vec> = self.system.into(); + self.stack.into_iter().for_each(|v| result.push(v)); + self.aux_table.into_iter().for_each(|v| result.push(v)); + result } } @@ -248,16 +274,16 @@ fn finalize_column(column: &mut Vec, step: usize, trace_len: usize) { /// - columns 3-17: unused columns padded with ZERO /// fn fill_aux_columns( - aux_table_trace: &mut AuxiliaryTableTrace, + aux_table_trace: &mut AuxTableTrace, trace_len: usize, hasher: Hasher, bitwise: Bitwise, memory: Memory, ) { // allocate fragments to be filled with the respective execution traces of each coprocessor - let mut hasher_fragment = TraceFragment::new(AUXILIARY_TABLE_WIDTH); - let mut bitwise_fragment = TraceFragment::new(AUXILIARY_TABLE_WIDTH); - let mut memory_fragment = TraceFragment::new(AUXILIARY_TABLE_WIDTH); + let mut hasher_fragment = TraceFragment::new(AUX_TRACE_WIDTH); + let mut bitwise_fragment = TraceFragment::new(AUX_TRACE_WIDTH); + let mut memory_fragment = TraceFragment::new(AUX_TRACE_WIDTH); // set the selectors and padding as required by each column and segment // and add the hasher, bitwise, and memory segments to their respective fragments From e12041c553e9b0f2a396717db0918a5ab936c242 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Sat, 12 Mar 2022 23:41:28 -0800 Subject: [PATCH 17/26] feat: enabled fibonacci example --- Cargo.toml | 2 +- air/src/lib.rs | 50 +++++--- air/src/options.rs | 4 +- core/src/inputs/mod.rs | 15 +++ examples/Cargo.toml | 9 +- examples/src/fibonacci.rs | 39 +++--- examples/src/lib.rs | 47 +------- examples/src/main.rs | 14 +-- miden/src/lib.rs | 42 ++++--- miden/src/tests.rs | 161 ------------------------- processor/src/errors.rs | 1 + processor/src/lib.rs | 3 +- processor/src/system/mod.rs | 11 +- processor/src/tests/aux_table_trace.rs | 6 +- processor/src/trace.rs | 35 +++++- verifier/src/lib.rs | 52 +++++--- 16 files changed, 179 insertions(+), 312 deletions(-) delete mode 100644 miden/src/tests.rs diff --git a/Cargo.toml b/Cargo.toml index f789cc75c7..ce6001c01c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = [ "air", "assembly", "core", - #"examples", + "examples", "miden", "processor", "stdlib", diff --git a/air/src/lib.rs b/air/src/lib.rs index 1907661c3a..d4ba490047 100644 --- a/air/src/lib.rs +++ b/air/src/lib.rs @@ -1,4 +1,4 @@ -use vm_core::{hasher::Digest, StackTopState, CLK_COL_IDX, FMP_COL_IDX, STACK_TRACE_OFFSET}; +use vm_core::{hasher::Digest, CLK_COL_IDX, FMP_COL_IDX, MIN_STACK_DEPTH, STACK_TRACE_OFFSET}; use winter_air::{ Air, AirContext, Assertion, EvaluationFrame, ProofOptions as WinterProofOptions, TraceInfo, TransitionConstraintDegree, @@ -20,8 +20,8 @@ pub use winter_air::{FieldExtension, HashFunction}; /// TODO: add docs pub struct ProcessorAir { context: AirContext, - init_stack_state: StackTopState, - last_stack_state: StackTopState, + stack_inputs: Vec, + stack_outputs: Vec, } impl Air for ProcessorAir { @@ -35,8 +35,8 @@ impl Air for ProcessorAir { Self { context: AirContext::new(trace_info, degrees, options), - init_stack_state: pub_inputs.init_stack_state, - last_stack_state: pub_inputs.last_stack_state, + stack_inputs: pub_inputs.stack_inputs, + stack_outputs: pub_inputs.stack_outputs, } } @@ -44,20 +44,24 @@ impl Air for ProcessorAir { fn get_assertions(&self) -> Vec> { let mut result = Vec::new(); + // --- set assertions for the first step -------------------------------------------------- + // first value of clk is 0 result.push(Assertion::single(CLK_COL_IDX, 0, Felt::ZERO)); // first value of fmp is 2^30 result.push(Assertion::single(FMP_COL_IDX, 0, Felt::new(2u64.pow(30)))); - // stack column at the first step should be set to init stack values - for (i, &value) in self.init_stack_state.iter().enumerate() { + // stack columns at the first step should be set to stack inputs + for (i, &value) in self.stack_inputs.iter().enumerate() { result.push(Assertion::single(STACK_TRACE_OFFSET + i, 0, value)); } - // stack column at the last step should be set to last stack values + // --- set assertions for the last step --------------------------------------------------- let last_step = self.trace_length() - 1; - for (i, &value) in self.last_stack_state.iter().enumerate() { + + // stack columns at the last step should be set to stack outputs + for (i, &value) in self.stack_outputs.iter().enumerate() { result.push(Assertion::single(STACK_TRACE_OFFSET + i, last_step, value)); } @@ -85,22 +89,28 @@ impl Air for ProcessorAir { // PUBLIC INPUTS // ================================================================================================ +#[derive(Debug)] pub struct PublicInputs { program_hash: Digest, - init_stack_state: StackTopState, - last_stack_state: StackTopState, + stack_inputs: Vec, + stack_outputs: Vec, } impl PublicInputs { - pub fn new( - program_hash: Digest, - init_stack_state: StackTopState, - last_stack_state: StackTopState, - ) -> Self { + pub fn new(program_hash: Digest, stack_inputs: Vec, stack_outputs: Vec) -> Self { + assert!( + stack_inputs.len() <= MIN_STACK_DEPTH, + "too many stack inputs" + ); + assert!( + stack_outputs.len() <= MIN_STACK_DEPTH, + "too many stack outputs" + ); + Self { program_hash, - init_stack_state, - last_stack_state, + stack_inputs, + stack_outputs, } } } @@ -108,7 +118,7 @@ impl PublicInputs { impl Serializable for PublicInputs { fn write_into(&self, target: &mut W) { target.write(self.program_hash.as_elements()); - target.write(self.init_stack_state.as_slice()); - target.write(self.last_stack_state.as_slice()); + target.write(self.stack_inputs.as_slice()); + target.write(self.stack_outputs.as_slice()); } } diff --git a/air/src/options.rs b/air/src/options.rs index c5a04b1516..c0c9c84c5d 100644 --- a/air/src/options.rs +++ b/air/src/options.rs @@ -32,7 +32,7 @@ impl ProofOptions { 8, 16, HashFunction::Blake3_192, - FieldExtension::None, + FieldExtension::Quadratic, 8, 256, )) @@ -44,7 +44,7 @@ impl ProofOptions { 16, 21, HashFunction::Blake3_256, - FieldExtension::Quadratic, + FieldExtension::Cubic, 8, 256, )) diff --git a/core/src/inputs/mod.rs b/core/src/inputs/mod.rs index 156a815578..2025825b15 100644 --- a/core/src/inputs/mod.rs +++ b/core/src/inputs/mod.rs @@ -91,6 +91,21 @@ impl ProgramInputs { }) } + /// Returns [ProgramInputs] initialized with stack inputs only. + /// + /// The provided inputs are pushed onto the stack one after the other. Thus, the first + /// element in the `stack_init` list will be the deepest in the stack. + /// + /// Advice tape and advice sets for the returned inputs are blank. + /// + /// # Errors + /// Returns an error if: + /// - The number initial stack values is greater than 16. + /// - Any of the initial stack values is not valid field elements. + pub fn from_stack_inputs(stack_init: &[u64]) -> Result { + Self::new(stack_init, &[], vec![]) + } + /// Returns [ProgramInputs] with no input values. pub fn none() -> Self { Self { diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 0dc7117aa7..3fd1e02f34 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "examples" -version = "0.1.0" +version = "0.2.0" description = "Examples of using Miden VM" authors = ["miden contributors"] readme = "README.md" license = "MIT" repository = "https://github.com/maticnetwork/miden" -edition = "2018" +edition = "2021" +rust-version = "1.57" [lib] bench = false @@ -27,7 +28,7 @@ std = ["hex/std", "miden/std", "rand-utils", "vm-core/std"] env_logger = { version = "0.9", default-features = false } hex = { version = "0.4", optional = true } log = { version = "0.4", default-features = false } -miden = { path = "../miden", version = "0.1", default-features = false } +miden = { path = "../miden", version = "0.2", default-features = false } structopt = { version = "0.3", default-features = false } -vm-core = { package = "miden-core", path = "../core", default-features = false } +vm-core = { package = "miden-core", path = "../core", version = "0.2", default-features = false } rand-utils = { package = "winter-rand-utils", version = "0.3", optional = true } diff --git a/examples/src/fibonacci.rs b/examples/src/fibonacci.rs index 999f5686ee..f85cc0bfea 100644 --- a/examples/src/fibonacci.rs +++ b/examples/src/fibonacci.rs @@ -1,6 +1,7 @@ use crate::Example; use log::debug; -use miden::{assembly, BaseElement, FieldElement, Program, ProgramInputs, StarkField}; +use miden::{Assembler, ProgramInputs, Script}; +use vm_core::{Felt, FieldElement, StarkField}; // EXAMPLE BUILDER // ================================================================================================ @@ -16,15 +17,15 @@ pub fn get_example(n: usize) -> Example { Example { program, - inputs: ProgramInputs::from_public(&[1, 0]), - pub_inputs: vec![1, 0], + inputs: ProgramInputs::from_stack_inputs(&[0, 1]).unwrap(), + pub_inputs: vec![0, 1], expected_result, num_outputs: 1, } } /// Generates a program to compute the `n`-th term of Fibonacci sequence -fn generate_fibonacci_program(n: usize) -> Program { +fn generate_fibonacci_program(n: usize) -> Script { // the program is a simple repetition of 4 stack operations: // the first operation moves the 2nd stack item to the top, // the second operation duplicates the top 2 stack items, @@ -32,30 +33,28 @@ fn generate_fibonacci_program(n: usize) -> Program { // the last operation pops top 2 stack items, adds them, and pushes // the result back onto the stack let program = format!( - " - begin - repeat.{} - swap dup.2 drop add - end - end", + "begin + repeat.{} + swap dup.1 add + end + end", n - 1 ); - assembly::compile(&program).unwrap() + let assembler = Assembler::new(); + assembler.compile_script(&program).unwrap() } /// Computes the `n`-th term of Fibonacci sequence -fn compute_fibonacci(n: usize) -> BaseElement { - let mut n1 = BaseElement::ZERO; - let mut n2 = BaseElement::ONE; +fn compute_fibonacci(n: usize) -> Felt { + let mut t0 = Felt::ZERO; + let mut t1 = Felt::ONE; - for _ in 0..(n - 1) { - let n3 = n1 + n2; - n1 = n2; - n2 = n3; + for _ in 0..n { + t1 = t0 + t1; + core::mem::swap(&mut t0, &mut t1); } - - n2 + t0 } // EXAMPLE TESTER diff --git a/examples/src/lib.rs b/examples/src/lib.rs index 0d7875ee6b..639d5c7b1e 100644 --- a/examples/src/lib.rs +++ b/examples/src/lib.rs @@ -1,24 +1,17 @@ -use miden::{Program, ProgramInputs, ProofOptions}; +use miden::{ProgramInputs, ProofOptions, Script}; use structopt::StructOpt; -pub mod collatz; -pub mod comparison; -pub mod conditional; pub mod fibonacci; -#[cfg(feature = "std")] -pub mod merkle; -#[cfg(feature = "std")] -pub mod range; // EXAMPLE // ================================================================================================ pub struct Example { - pub program: Program, + pub program: Script, pub inputs: ProgramInputs, - pub pub_inputs: Vec, + pub pub_inputs: Vec, pub num_outputs: usize, - pub expected_result: Vec, + pub expected_result: Vec, } // EXAMPLE OPTIONS @@ -54,38 +47,6 @@ pub enum ExampleType { #[structopt(short = "n", default_value = "1024")] sequence_length: usize, }, - /// Compute a Collatz sequence from the specified starting value - Collatz { - /// Starting value of the Collatz sequence - #[structopt(short = "n", default_value = "511")] - start_value: usize, - }, - /// If provided value is less than 9, multiplies it by 9; otherwise add 9 to it - Comparison { - /// Value to compare to 9 - #[structopt(short = "n", default_value = "11")] - value: usize, - }, - /// If provided value is 0, outputs 15; if provided value is 1, outputs 8 - Conditional { - /// Value to compare to 9 - #[structopt(short = "n", default_value = "1")] - value: usize, - }, - /// Computes a root of a randomly generated Merkle branch of the specified depth - #[cfg(feature = "std")] - Merkle { - /// Depth of the Merkle tree - #[structopt(short = "n", default_value = "20")] - tree_depth: usize, - }, - /// Determines how many of the randomly generated values are less than 2^63 - #[cfg(feature = "std")] - Range { - /// Number of randomly generated 64-bit values - #[structopt(short = "n", default_value = "100")] - num_values: usize, - }, } // TESTS diff --git a/examples/src/main.rs b/examples/src/main.rs index 8816de8939..96edb6b17f 100644 --- a/examples/src/main.rs +++ b/examples/src/main.rs @@ -21,13 +21,6 @@ fn main() { // instantiate and prepare the example let example = match options.example { ExampleType::Fib { sequence_length } => examples::fibonacci::get_example(sequence_length), - ExampleType::Collatz { start_value } => examples::collatz::get_example(start_value), - ExampleType::Comparison { value } => examples::comparison::get_example(value), - ExampleType::Conditional { value } => examples::conditional::get_example(value), - #[cfg(feature = "std")] - ExampleType::Merkle { tree_depth } => examples::merkle::get_example(tree_depth), - #[cfg(feature = "std")] - ExampleType::Range { num_values } => examples::range::get_example(num_values), }; let Example { @@ -45,10 +38,11 @@ fn main() { let now = Instant::now(); let (outputs, proof) = miden::execute(&program, &inputs, num_outputs, &proof_options).unwrap(); debug!("--------------------------------"); + #[cfg(feature = "std")] debug!( - "Executed program with hash {} in {} ms", - hex::encode(program.hash()), + "Executed program in {} ms", + //hex::encode(program.hash()), // TODO: include into message now.elapsed().as_millis() ); debug!("Program output: {:?}", outputs); @@ -72,6 +66,6 @@ fn main() { let now = Instant::now(); match miden::verify(*program.hash(), &pub_inputs, &outputs, proof) { Ok(_) => debug!("Execution verified in {} ms", now.elapsed().as_millis()), - Err(msg) => debug!("Failed to verify execution: {}", msg), + Err(err) => debug!("Failed to verify execution: {}", err), } } diff --git a/miden/src/lib.rs b/miden/src/lib.rs index ad5e48d958..5316b31a74 100644 --- a/miden/src/lib.rs +++ b/miden/src/lib.rs @@ -1,21 +1,20 @@ use air::{ProcessorAir, PublicInputs}; use processor::{ExecutionError, ExecutionTrace}; -use prover::{Prover, Trace}; +use prover::Prover; use vm_core::{Felt, StarkField, MIN_STACK_DEPTH}; #[cfg(feature = "std")] use log::debug; #[cfg(feature = "std")] +use prover::Trace; +#[cfg(feature = "std")] use std::time::Instant; -//#[cfg(test)] -//mod tests; - // EXPORTS // ================================================================================================ pub use air::{FieldExtension, HashFunction, ProofOptions}; -pub use assembly; +pub use assembly::{Assembler, AssemblyError}; pub use prover::StarkProof; pub use verifier::{verify, VerificationError}; pub use vm_core::{program::Script, ProgramInputs}; @@ -28,7 +27,8 @@ pub use vm_core::{program::Script, ProgramInputs}; /// /// * `inputs` specifies the initial state of the stack as well as non-deterministic (secret) /// inputs for the VM. -/// * `num_outputs` specifies the number of elements from the top of the stack to be returned. +/// * `num_stack_outputs` specifies the number of elements from the top of the stack to be +/// returned. /// * `options` defines parameters for STARK proof generation. /// /// # Errors @@ -36,15 +36,12 @@ pub use vm_core::{program::Script, ProgramInputs}; pub fn execute( program: &Script, inputs: &ProgramInputs, - num_outputs: usize, + num_stack_outputs: usize, options: &ProofOptions, ) -> Result<(Vec, StarkProof), ExecutionError> { - assert!( - num_outputs <= MIN_STACK_DEPTH, - "cannot produce more than {} outputs, but requested {}", - MIN_STACK_DEPTH, - num_outputs - ); + if num_stack_outputs > MIN_STACK_DEPTH { + return Err(ExecutionError::TooManyStackOutputs(num_stack_outputs)); + } // execute the program to create an execution trace #[cfg(feature = "std")] @@ -59,13 +56,14 @@ pub fn execute( ); // copy the stack state at the last step to return as output - let outputs = trace.last_stack_state()[..num_outputs] + let outputs = trace.last_stack_state()[..num_stack_outputs] .iter() .map(|&v| v.as_int()) .collect::>(); // generate STARK proof - let prover = ExecutionProver::new(options.clone()); + let num_stack_inputs = inputs.stack_init().len(); + let prover = ExecutionProver::new(options.clone(), num_stack_inputs, num_stack_outputs); let proof = prover.prove(trace).map_err(ExecutionError::ProverError)?; Ok((outputs, proof)) @@ -76,11 +74,17 @@ pub fn execute( struct ExecutionProver { options: ProofOptions, + num_stack_inputs: usize, + num_stack_outputs: usize, } impl ExecutionProver { - pub fn new(options: ProofOptions) -> Self { - Self { options } + pub fn new(options: ProofOptions, num_stack_inputs: usize, num_stack_outputs: usize) -> Self { + Self { + options, + num_stack_inputs, + num_stack_outputs, + } } } @@ -96,8 +100,8 @@ impl Prover for ExecutionProver { fn get_pub_inputs(&self, trace: &ExecutionTrace) -> PublicInputs { PublicInputs::new( trace.program_hash(), - trace.init_stack_state(), - trace.last_stack_state(), + trace.init_stack_state()[..self.num_stack_inputs].to_vec(), + trace.last_stack_state()[..self.num_stack_outputs].to_vec(), ) } } diff --git a/miden/src/tests.rs b/miden/src/tests.rs deleted file mode 100644 index 4e30729511..0000000000 --- a/miden/src/tests.rs +++ /dev/null @@ -1,161 +0,0 @@ -use crate::{ - assembly, BaseElement, FieldElement, ProgramInputs, Serializable, Trace, TraceMetadata, - TraceState, TraceTable, -}; -use air::ToElements; - -#[test] -fn execute_span() { - let program = assembly::compile("begin add push.5 mul push.7 end").unwrap(); - let inputs = ProgramInputs::from_public(&[1, 2]); - - let trace = processor::execute(&program, &inputs); - let trace_length = trace.length(); - let trace_width = trace.width(); - - assert_eq!(64, trace_length); - assert_eq!(17, trace_width); - let state = get_trace_state(&trace, trace_length - 1); - - assert_eq!(BaseElement::new(46), state.op_counter()); - assert_eq!(program.hash().to_vec(), state.program_hash().to_bytes()); - assert_eq!([1, 1, 1].to_elements(), state.cf_op_bits()); - assert_eq!([1, 1, 1, 1, 1].to_elements(), state.ld_op_bits()); - assert_eq!([1, 1].to_elements(), state.hd_op_bits()); - assert_eq!([0].to_elements(), state.ctx_stack()); - assert_eq!([7, 15, 0, 0, 0, 0, 0, 0].to_elements(), state.user_stack()); -} - -#[test] -fn execute_block() { - let program = assembly::compile("begin add block push.5 mul push.7 end end").unwrap(); - let inputs = ProgramInputs::from_public(&[1, 2]); - - let trace = processor::execute(&program, &inputs); - let trace_length = trace.length(); - let trace_width = trace.width(); - - assert_eq!(64, trace_length); - assert_eq!(18, trace_width); - let state = get_trace_state(&trace, trace_length - 1); - - assert_eq!(BaseElement::new(60), state.op_counter()); - assert_eq!(program.hash().to_vec(), state.program_hash().to_bytes()); - assert_eq!([1, 1, 1].to_elements(), state.cf_op_bits()); - assert_eq!([1, 1, 1, 1, 1].to_elements(), state.ld_op_bits()); - assert_eq!([1, 1].to_elements(), state.hd_op_bits()); - assert_eq!([0].to_elements(), state.ctx_stack()); - assert_eq!([0].to_elements(), state.loop_stack()); - assert_eq!([7, 15, 0, 0, 0, 0, 0, 0].to_elements(), state.user_stack()); -} - -#[test] -fn execute_if_else() { - let program = - assembly::compile("begin read if.true add push.3 else push.7 add push.8 end mul end") - .unwrap(); - - // execute true branch - let inputs = ProgramInputs::new(&[5, 3], &[1], &[]); - let trace = processor::execute(&program, &inputs); - let trace_length = trace.length(); - let trace_width = trace.width(); - - assert_eq!(128, trace_length); - assert_eq!(19, trace_width); - let state = get_trace_state(&trace, trace_length - 1); - - assert_eq!(BaseElement::new(76), state.op_counter()); - assert_eq!(program.hash().to_vec(), state.program_hash().to_bytes()); - assert_eq!([1, 1, 1].to_elements(), state.cf_op_bits()); - assert_eq!([1, 1, 1, 1, 1].to_elements(), state.ld_op_bits()); - assert_eq!([1, 1].to_elements(), state.hd_op_bits()); - assert_eq!([0].to_elements(), state.ctx_stack()); - assert_eq!([0].to_elements(), state.loop_stack()); - assert_eq!([24, 0, 0, 0, 0, 0, 0, 0].to_elements(), state.user_stack()); - - // execute false branch - let inputs = ProgramInputs::new(&[5, 3], &[0], &[]); - let trace = processor::execute(&program, &inputs); - let trace_length = trace.length(); - let trace_width = trace.width(); - - assert_eq!(128, trace_length); - assert_eq!(19, trace_width); - let state = get_trace_state(&trace, trace_length - 1); - - assert_eq!(BaseElement::new(92), state.op_counter()); - assert_eq!(program.hash().to_vec(), state.program_hash().to_bytes()); - assert_eq!([1, 1, 1].to_elements(), state.cf_op_bits()); - assert_eq!([1, 1, 1, 1, 1].to_elements(), state.ld_op_bits()); - assert_eq!([1, 1].to_elements(), state.hd_op_bits()); - assert_eq!([0].to_elements(), state.ctx_stack()); - assert_eq!([0].to_elements(), state.loop_stack()); - assert_eq!([96, 3, 0, 0, 0, 0, 0, 0].to_elements(), state.user_stack()); -} - -#[test] -fn execute_loop() { - let program = assembly::compile("begin mul read while.true dup mul read end end").unwrap(); - - // don't enter the loop - let inputs = ProgramInputs::new(&[5, 3], &[0], &[]); - let trace = processor::execute(&program, &inputs); - - assert_eq!(64, trace.length()); - assert_eq!(18, trace.width()); - let state = get_trace_state(&trace, trace.length() - 1); - - assert_eq!(BaseElement::new(60), state.op_counter()); - assert_eq!(program.hash().to_vec(), state.program_hash().to_bytes()); - assert_eq!([1, 1, 1].to_elements(), state.cf_op_bits()); - assert_eq!([1, 1, 1, 1, 1].to_elements(), state.ld_op_bits()); - assert_eq!([1, 1].to_elements(), state.hd_op_bits()); - assert_eq!([0].to_elements(), state.ctx_stack()); - assert_eq!([0].to_elements(), state.loop_stack()); - assert_eq!([15, 0, 0, 0, 0, 0, 0, 0].to_elements(), state.user_stack()); - - // execute one iteration - let inputs = ProgramInputs::new(&[5, 3], &[1, 0], &[]); - let trace = processor::execute(&program, &inputs); - - assert_eq!(128, trace.length()); - assert_eq!(19, trace.width()); - let state = get_trace_state(&trace, trace.length() - 1); - - assert_eq!(BaseElement::new(75), state.op_counter()); - assert_eq!(program.hash().to_vec(), state.program_hash().to_bytes()); - assert_eq!([1, 1, 1].to_elements(), state.cf_op_bits()); - assert_eq!([1, 1, 1, 1, 1].to_elements(), state.ld_op_bits()); - assert_eq!([1, 1].to_elements(), state.hd_op_bits()); - assert_eq!([0].to_elements(), state.ctx_stack()); - assert_eq!([0].to_elements(), state.loop_stack()); - assert_eq!([225, 0, 0, 0, 0, 0, 0, 0].to_elements(), state.user_stack()); - - // execute five iteration - let inputs = ProgramInputs::new(&[5, 3], &[1, 1, 1, 1, 1, 0], &[]); - let trace = processor::execute(&program, &inputs); - - assert_eq!(256, trace.length()); - assert_eq!(19, trace.width()); - let state = get_trace_state(&trace, trace.length() - 1); - - assert_eq!(BaseElement::new(135), state.op_counter()); - assert_eq!(program.hash().to_vec(), state.program_hash().to_bytes()); - assert_eq!([1, 1, 1].to_elements(), state.cf_op_bits()); - assert_eq!([1, 1, 1, 1, 1].to_elements(), state.ld_op_bits()); - assert_eq!([1, 1].to_elements(), state.hd_op_bits()); - assert_eq!([0].to_elements(), state.ctx_stack()); - assert_eq!([0].to_elements(), state.loop_stack()); - assert_eq!( - [43143988327398919500410556793212890625, 0, 0, 0, 0, 0, 0, 0].to_elements(), - state.user_stack() - ); -} - -fn get_trace_state(trace: &TraceTable, step: usize) -> TraceState { - let meta = TraceMetadata::from_trace_info(&trace.get_info()); - let mut row = vec![BaseElement::ZERO; trace.width()]; - trace.read_row_into(step, &mut row); - TraceState::from_slice(meta.ctx_depth, meta.loop_depth, meta.stack_depth, &row) -} diff --git a/processor/src/errors.rs b/processor/src/errors.rs index 37c939cd02..473bf1e038 100644 --- a/processor/src/errors.rs +++ b/processor/src/errors.rs @@ -18,4 +18,5 @@ pub enum ExecutionError { InvalidFmpValue(Felt, Felt), NotU32Value(Felt), ProverError(ProverError), + TooManyStackOutputs(usize), } diff --git a/processor/src/lib.rs b/processor/src/lib.rs index 82e72e0e4f..ef0198da9c 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -49,7 +49,7 @@ mod tests; type SysTrace = [Vec; SYS_TRACE_WIDTH]; type StackTrace = [Vec; MIN_STACK_DEPTH]; -type AuxTableTrace = [Vec; AUX_TRACE_WIDTH]; +type AuxTableTrace = [Vec; AUX_TRACE_WIDTH]; // TODO: potentially rename to AuxiliaryTrace // EXECUTOR // ================================================================================================ @@ -59,6 +59,7 @@ type AuxTableTrace = [Vec; AUX_TRACE_WIDTH]; pub fn execute(script: &Script, inputs: &ProgramInputs) -> Result { let mut process = Process::new(inputs.clone()); process.execute_code_block(script.root())?; + // TODO: make sure program hash from script and trace are the same Ok(ExecutionTrace::new(process, *script.hash())) } diff --git a/processor/src/system/mod.rs b/processor/src/system/mod.rs index b2b3f266d9..1768af523c 100644 --- a/processor/src/system/mod.rs +++ b/processor/src/system/mod.rs @@ -27,11 +27,16 @@ impl System { /// Returns a new [System] struct with execution traces instantiated with the specified length. /// Initializes the free memory pointer `fmp` used for local memory offsets to 2^30. pub fn new(init_trace_length: usize) -> Self { + // set the first value of the fmp trace to 2^30. + let fmp = Felt::new(FMP_MIN); + let mut fmp_trace = Felt::zeroed_vector(init_trace_length); + fmp_trace[0] = fmp; + Self { clk: 0, - clk_trace: vec![Felt::ZERO; init_trace_length], - fmp: Felt::new(FMP_MIN), - fmp_trace: vec![Felt::ZERO; init_trace_length], + clk_trace: Felt::zeroed_vector(init_trace_length), + fmp, + fmp_trace, } } diff --git a/processor/src/tests/aux_table_trace.rs b/processor/src/tests/aux_table_trace.rs index 2c0d75c1bd..74dd16cdf0 100644 --- a/processor/src/tests/aux_table_trace.rs +++ b/processor/src/tests/aux_table_trace.rs @@ -115,7 +115,7 @@ fn stacked_aux_trace() { // ================================================================================================ /// Validate the hasher trace output by the rpperm operation. The full hasher trace is tested in -/// the Hasher module, so this just tests the AuxiliaryTableTrace selectors and the initial columns +/// the Hasher module, so this just tests the AuxTableTrace selectors and the initial columns /// of the hasher trace. fn validate_hasher_trace(aux_table: &AuxTableTrace, start: usize, end: usize) { // The selectors should match the hasher selectors @@ -150,7 +150,7 @@ fn validate_hasher_trace(aux_table: &AuxTableTrace, start: usize, end: usize) { } /// Validate the bitwise trace output by the u32or operation. The full bitwise trace is tested in -/// the Bitwise module, so this just tests the AuxiliaryTableTrace selectors, the initial columns +/// the Bitwise module, so this just tests the AuxTableTrace selectors, the initial columns /// of the bitwise trace, and the final columns after the bitwise trace. fn validate_bitwise_trace(aux_table: &AuxTableTrace, start: usize, end: usize) { // The selectors should match the bitwise selectors @@ -170,7 +170,7 @@ fn validate_bitwise_trace(aux_table: &AuxTableTrace, start: usize, end: usize) { } /// Validate the bitwise trace output by the storew operation. The full memory trace is tested in -/// the Memory module, so this just tests the AuxiliaryTableTrace selectors, the initial columns +/// the Memory module, so this just tests the AuxTableTrace selectors, the initial columns /// of the memory trace, and the final column after the memory trace. fn validate_memory_trace(aux_table: &AuxTableTrace, start: usize, end: usize, addr: u64) { for row in start..end { diff --git a/processor/src/trace.rs b/processor/src/trace.rs index 8888ab3f73..e124f74d97 100644 --- a/processor/src/trace.rs +++ b/processor/src/trace.rs @@ -4,16 +4,16 @@ use super::{ }; use core::slice; use vm_core::{ - AUX_TRACE_OFFSET, AUX_TRACE_RANGE, AUX_TRACE_WIDTH, STACK_TRACE_OFFSET, STACK_TRACE_RANGE, - STACK_TRACE_WIDTH, SYS_TRACE_OFFSET, SYS_TRACE_RANGE, TRACE_WIDTH, + StarkField, AUX_TRACE_OFFSET, AUX_TRACE_RANGE, AUX_TRACE_WIDTH, STACK_TRACE_OFFSET, + STACK_TRACE_RANGE, STACK_TRACE_WIDTH, SYS_TRACE_OFFSET, SYS_TRACE_RANGE, TRACE_WIDTH, }; use winterfell::Trace; // VM EXECUTION TRACE // ================================================================================================ -/// TODO: for now this consists only of system register trace, stack trace, and auxiliary traces, -/// but will also need to include decoder trace and range checker trace. +/// TODO: for now this consists only of system register trace, stack trace, and auxiliary table +/// trace, but will also need to include decoder trace and range checker trace. pub struct ExecutionTrace { meta: Vec, system: SysTrace, @@ -66,9 +66,14 @@ impl ExecutionTrace { finalize_column(column, step, trace_len); } + // finalize system trace + let mut system_trace = system.into_trace(); + finalize_clk_column(&mut system_trace[0], step, trace_len); + finalize_column(&mut system_trace[1], step, trace_len); + Self { meta: Vec::new(), - system: system.into_trace(), + system: system_trace, stack: stack_trace, aux_table: aux_table_trace, program_hash, @@ -80,6 +85,7 @@ impl ExecutionTrace { /// TODO: add docs pub fn program_hash(&self) -> Digest { + // TODO: program hash should be read from the decoder trace self.program_hash } @@ -113,6 +119,15 @@ impl ExecutionTrace { pub fn stack(&self) -> &StackTrace { &self.stack } + + #[allow(dead_code)] + pub fn print(&self) { + let mut row = [Felt::ZERO; TRACE_WIDTH]; + for i in 0..self.length() { + self.read_row_into(i, &mut row); + println!("{:?}", row.iter().map(|v| v.as_int()).collect::>()); + } + } } // TRACE TRAIT IMPLEMENTATION @@ -241,6 +256,16 @@ fn finalize_column(column: &mut Vec, step: usize, trace_len: usize) { column.resize(trace_len, last_value); } +/// Completes the clk column by filling in all values after the specified step. The values +/// in the clk column are equal to the index of the row in the trace table. +fn finalize_clk_column(column: &mut Vec, step: usize, trace_len: usize) { + column.resize(trace_len, Felt::ZERO); + for (i, clk) in column.iter_mut().enumerate().take(trace_len).skip(step) { + // converting from u32 is OK here because max trace length is 2^32 + *clk = Felt::from(i as u32); + } +} + /// Fills the provided auxiliary table trace with the stacked execution traces of the Hasher, /// Bitwise, and Memory coprocessors, along with selector columns to identify each coprocessor /// trace and padding to fill the rest of the table. diff --git a/verifier/src/lib.rs b/verifier/src/lib.rs index 3741006e98..760c64d40c 100644 --- a/verifier/src/lib.rs +++ b/verifier/src/lib.rs @@ -1,5 +1,6 @@ use air::{ProcessorAir, PublicInputs}; -use vm_core::{Felt, FieldElement, MIN_STACK_DEPTH}; +use core::fmt; +use vm_core::MIN_STACK_DEPTH; use winterfell::VerifierError; // EXPORTS @@ -21,42 +22,46 @@ pub use winterfell::StarkProof; /// Returns an error if the provided proof does not prove a correct execution of the program. pub fn verify( program_hash: Digest, - public_inputs: &[u64], - outputs: &[u64], + stack_inputs: &[u64], + stack_outputs: &[u64], proof: StarkProof, ) -> Result<(), VerificationError> { - // build initial stack state from public inputs - if public_inputs.len() > MIN_STACK_DEPTH { + if stack_inputs.len() > MIN_STACK_DEPTH { return Err(VerificationError::TooManyInputValues( MIN_STACK_DEPTH, - public_inputs.len(), + stack_inputs.len(), )); } - let mut init_stack_state = [Felt::ZERO; MIN_STACK_DEPTH]; - for (element, &value) in init_stack_state.iter_mut().zip(public_inputs.iter().rev()) { - *element = value - .try_into() - .map_err(|_| VerificationError::InputNotFieldElement(value))?; + // convert stack inputs to field elements + let mut stack_input_felts = Vec::with_capacity(stack_inputs.len()); + for &input in stack_inputs.iter().rev() { + stack_input_felts.push( + input + .try_into() + .map_err(|_| VerificationError::InputNotFieldElement(input))?, + ); } - // build final stack state from outputs - if outputs.len() > MIN_STACK_DEPTH { + if stack_outputs.len() > MIN_STACK_DEPTH { return Err(VerificationError::TooManyOutputValues( MIN_STACK_DEPTH, - outputs.len(), + stack_outputs.len(), )); } - let mut last_stack_state = [Felt::ZERO; MIN_STACK_DEPTH]; - for (element, &value) in last_stack_state.iter_mut().zip(outputs.iter().rev()) { - *element = value - .try_into() - .map_err(|_| VerificationError::OutputNotFieldElement(value))?; + // convert stack outputs to field elements + let mut stack_output_felts = Vec::with_capacity(stack_outputs.len()); + for &output in stack_outputs.iter().rev() { + stack_output_felts.push( + output + .try_into() + .map_err(|_| VerificationError::OutputNotFieldElement(output))?, + ); } // build public inputs and try to verify the proof - let pub_inputs = PublicInputs::new(program_hash, init_stack_state, last_stack_state); + let pub_inputs = PublicInputs::new(program_hash, stack_input_felts, stack_output_felts); winterfell::verify::(proof, pub_inputs).map_err(VerificationError::VerifierError) } @@ -72,3 +77,10 @@ pub enum VerificationError { OutputNotFieldElement(u64), TooManyOutputValues(usize, usize), } + +impl fmt::Display for VerificationError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // TODO: implement friendly messages + write!(f, "{:?}", self) + } +} From 5f1647f712a7a0fe4a7d2844887bb37b3f47a295 Mon Sep 17 00:00:00 2001 From: hmuro andrej Date: Fri, 11 Mar 2022 15:17:31 +0300 Subject: [PATCH 18/26] Add u64 unsafe subtraction procedure to standard library --- processor/src/tests/stdlib/math/u64_mod.rs | 20 +++++ processor/src/tests/stdlib/u64_mod.rs | 98 ++++++++++++++++++++++ stdlib/asm/math/u64.masm | 17 ++++ stdlib/src/asm.rs | 17 ++++ 4 files changed, 152 insertions(+) create mode 100644 processor/src/tests/stdlib/u64_mod.rs diff --git a/processor/src/tests/stdlib/math/u64_mod.rs b/processor/src/tests/stdlib/math/u64_mod.rs index 2d1651d858..a13648e4ab 100644 --- a/processor/src/tests/stdlib/math/u64_mod.rs +++ b/processor/src/tests/stdlib/math/u64_mod.rs @@ -41,6 +41,26 @@ fn mul_unsafe() { test.expect_stack(&[c1, c0]); } +#[test] +fn sub_unsafe() { + let a: u64 = rand_value(); + let b: u64 = rand_value(); + let c = a.wrapping_sub(b); + + let source = " + use.std::math::u64 + begin + exec.u64::sub_unsafe + end"; + + let (a1, a0) = split_u64(a); + let (b1, b0) = split_u64(b); + let (c1, c0) = split_u64(c); + + let test = build_test!(source, &[a0, a1, b0, b1]); + test.expect_stack(&[c1, c0]); +} + #[test] fn div_unsafe() { let a: u64 = rand_value(); diff --git a/processor/src/tests/stdlib/u64_mod.rs b/processor/src/tests/stdlib/u64_mod.rs new file mode 100644 index 0000000000..55b6952cd3 --- /dev/null +++ b/processor/src/tests/stdlib/u64_mod.rs @@ -0,0 +1,98 @@ +use super::{compile, test_script_execution}; +use rand_utils::rand_value; + +#[test] +fn add_unsafe() { + let a: u64 = rand_value(); + let b: u64 = rand_value(); + let c = a.wrapping_add(b); + + let script = compile( + " + use.std::math::u64 + begin + exec.u64::add_unsafe + end", + ); + + let (a1, a0) = split_u64(a); + let (b1, b0) = split_u64(b); + let (c1, c0) = split_u64(c); + + test_script_execution(&script, &[a0, a1, b0, b1], &[c1, c0]); +} + +#[test] +fn mul_unsafe() { + let a: u64 = rand_value(); + let b: u64 = rand_value(); + let c = a.wrapping_mul(b); + + let script = compile( + " + use.std::math::u64 + begin + exec.u64::mul_unsafe + end", + ); + + let (a1, a0) = split_u64(a); + let (b1, b0) = split_u64(b); + let (c1, c0) = split_u64(c); + + test_script_execution(&script, &[a0, a1, b0, b1], &[c1, c0]); +} + +#[test] +fn sub_unsafe() { + let a: u64 = rand_value(); + let b: u64 = rand_value(); + let c = a.wrapping_sub(b); + + let script = compile( + " + use.std::math::u64 + begin + exec.u64::sub_unsafe + end", + ); + + let (a1, a0) = split_u64(a); + let (b1, b0) = split_u64(b); + let (c1, c0) = split_u64(c); + + test_script_execution(&script, &[a0, a1, b0, b1], &[c1, c0]); +} + +#[test] +fn div_unsafe() { + let a: u64 = rand_value(); + let b: u64 = rand_value(); + let c = a / b; + + let script = compile( + " + use.std::math::u64 + begin + exec.u64::div_unsafe + end", + ); + + let (a1, a0) = split_u64(a); + let (b1, b0) = split_u64(b); + let (c1, c0) = split_u64(c); + + test_script_execution(&script, &[a0, a1, b0, b1], &[c1, c0]); + + let d = a / b0; + let (d1, d0) = split_u64(d); + test_script_execution(&script, &[a0, a1, b0, 0], &[d1, d0]); +} + +// HELPER FUNCTIONS +// ================================================================================================ + +/// Split the provided u64 value into 32 hight and low bits. +fn split_u64(value: u64) -> (u64, u64) { + (value >> 32, value as u32 as u64) +} diff --git a/stdlib/asm/math/u64.masm b/stdlib/asm/math/u64.masm index 6ff375f04e..712b6c448e 100644 --- a/stdlib/asm/math/u64.masm +++ b/stdlib/asm/math/u64.masm @@ -30,6 +30,23 @@ export.mul_unsafe drop end +# Performs subtraction of two unsigned 64 bit integers discarding the overflow. # +# The input values are assumed to be represented using 32 bit limbs, but this is not checked. # +# Stack transition looks as follows: # +# [b_hi, b_lo, a_hi, a_lo, ...] -> [c_hi, c_lo, ...], where c = (a - b) % 2^64 # +export.sub_unsafe + movup.3 + movup.2 + u32sub.unsafe + movup.3 + movup.3 + u32sub.unsafe + drop + swap + u32sub.unsafe + drop +end + # ===== DIVISION ================================================================================ # # Performs division of two unsigned 64 bit integers discarding the remainder. # diff --git a/stdlib/src/asm.rs b/stdlib/src/asm.rs index ebddb3d5be..8d71cf39ec 100644 --- a/stdlib/src/asm.rs +++ b/stdlib/src/asm.rs @@ -1542,6 +1542,23 @@ export.mul_unsafe drop end +# Performs subtraction of two unsigned 64 bit integers discarding the overflow. # +# The input values are assumed to be represented using 32 bit limbs, but this is not checked. # +# Stack transition looks as follows: # +# [b_hi, b_lo, a_hi, a_lo, ...] -> [c_hi, c_lo, ...], where c = (a - b) % 2^64 # +export.sub_unsafe + movup.3 + movup.2 + u32sub.unsafe + movup.3 + movup.3 + u32sub.unsafe + drop + swap + u32sub.unsafe + drop +end + # ===== DIVISION ================================================================================ # # Performs division of two unsigned 64 bit integers discarding the remainder. # From 59fe1dbbcd6f765af65c86af9ba7e25afc7f2ef3 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Mon, 14 Mar 2022 21:45:14 -0700 Subject: [PATCH 19/26] refactor: minor test stdlib and test fixes --- processor/src/tests/stdlib/math/u64_mod.rs | 40 ++++----- processor/src/tests/stdlib/u64_mod.rs | 98 ---------------------- stdlib/asm/math/u64.masm | 36 ++++---- stdlib/src/asm.rs | 36 ++++---- 4 files changed, 58 insertions(+), 152 deletions(-) delete mode 100644 processor/src/tests/stdlib/u64_mod.rs diff --git a/processor/src/tests/stdlib/math/u64_mod.rs b/processor/src/tests/stdlib/math/u64_mod.rs index b60e5cd1fb..6b45fa99c6 100644 --- a/processor/src/tests/stdlib/math/u64_mod.rs +++ b/processor/src/tests/stdlib/math/u64_mod.rs @@ -22,6 +22,26 @@ fn add_unsafe() { test.expect_stack(&[c1, c0]); } +#[test] +fn sub_unsafe() { + let a: u64 = rand_value(); + let b: u64 = rand_value(); + let c = a.wrapping_sub(b); + + let source = " + use.std::math::u64 + begin + exec.u64::sub_unsafe + end"; + + let (a1, a0) = split_u64(a); + let (b1, b0) = split_u64(b); + let (c1, c0) = split_u64(c); + + let test = build_test!(source, &[a0, a1, b0, b1]); + test.expect_stack(&[c1, c0]); +} + #[test] fn mul_unsafe() { let a: u64 = rand_value(); @@ -140,26 +160,6 @@ fn gte_unsafe() { // DIVISION // ------------------------------------------------------------------------------------------------ -#[test] -fn sub_unsafe() { - let a: u64 = rand_value(); - let b: u64 = rand_value(); - let c = a.wrapping_sub(b); - - let source = " - use.std::math::u64 - begin - exec.u64::sub_unsafe - end"; - - let (a1, a0) = split_u64(a); - let (b1, b0) = split_u64(b); - let (c1, c0) = split_u64(c); - - let test = build_test!(source, &[a0, a1, b0, b1]); - test.expect_stack(&[c1, c0]); -} - #[test] fn div_unsafe() { let a: u64 = rand_value(); diff --git a/processor/src/tests/stdlib/u64_mod.rs b/processor/src/tests/stdlib/u64_mod.rs deleted file mode 100644 index 55b6952cd3..0000000000 --- a/processor/src/tests/stdlib/u64_mod.rs +++ /dev/null @@ -1,98 +0,0 @@ -use super::{compile, test_script_execution}; -use rand_utils::rand_value; - -#[test] -fn add_unsafe() { - let a: u64 = rand_value(); - let b: u64 = rand_value(); - let c = a.wrapping_add(b); - - let script = compile( - " - use.std::math::u64 - begin - exec.u64::add_unsafe - end", - ); - - let (a1, a0) = split_u64(a); - let (b1, b0) = split_u64(b); - let (c1, c0) = split_u64(c); - - test_script_execution(&script, &[a0, a1, b0, b1], &[c1, c0]); -} - -#[test] -fn mul_unsafe() { - let a: u64 = rand_value(); - let b: u64 = rand_value(); - let c = a.wrapping_mul(b); - - let script = compile( - " - use.std::math::u64 - begin - exec.u64::mul_unsafe - end", - ); - - let (a1, a0) = split_u64(a); - let (b1, b0) = split_u64(b); - let (c1, c0) = split_u64(c); - - test_script_execution(&script, &[a0, a1, b0, b1], &[c1, c0]); -} - -#[test] -fn sub_unsafe() { - let a: u64 = rand_value(); - let b: u64 = rand_value(); - let c = a.wrapping_sub(b); - - let script = compile( - " - use.std::math::u64 - begin - exec.u64::sub_unsafe - end", - ); - - let (a1, a0) = split_u64(a); - let (b1, b0) = split_u64(b); - let (c1, c0) = split_u64(c); - - test_script_execution(&script, &[a0, a1, b0, b1], &[c1, c0]); -} - -#[test] -fn div_unsafe() { - let a: u64 = rand_value(); - let b: u64 = rand_value(); - let c = a / b; - - let script = compile( - " - use.std::math::u64 - begin - exec.u64::div_unsafe - end", - ); - - let (a1, a0) = split_u64(a); - let (b1, b0) = split_u64(b); - let (c1, c0) = split_u64(c); - - test_script_execution(&script, &[a0, a1, b0, b1], &[c1, c0]); - - let d = a / b0; - let (d1, d0) = split_u64(d); - test_script_execution(&script, &[a0, a1, b0, 0], &[d1, d0]); -} - -// HELPER FUNCTIONS -// ================================================================================================ - -/// Split the provided u64 value into 32 hight and low bits. -fn split_u64(value: u64) -> (u64, u64) { - (value >> 32, value as u32 as u64) -} diff --git a/stdlib/asm/math/u64.masm b/stdlib/asm/math/u64.masm index 1c71f62b49..70599eeb18 100644 --- a/stdlib/asm/math/u64.masm +++ b/stdlib/asm/math/u64.masm @@ -14,6 +14,25 @@ export.add_unsafe drop end +# ===== SUBTRACTION ============================================================================= # + +# Performs subtraction of two unsigned 64 bit integers discarding the overflow. # +# The input values are assumed to be represented using 32 bit limbs, but this is not checked. # +# Stack transition looks as follows: # +# [b_hi, b_lo, a_hi, a_lo, ...] -> [c_hi, c_lo, ...], where c = (a - b) % 2^64 # +export.sub_unsafe + movup.3 + movup.2 + u32sub.unsafe + movup.3 + movup.3 + u32sub.unsafe + drop + swap + u32sub.unsafe + drop +end + # ===== MULTIPLICATION ========================================================================== # # Performs multiplication of two unsigned 64 bit integers discarding the overflow. # @@ -90,23 +109,6 @@ export.gte_unsafe not end -# Performs subtraction of two unsigned 64 bit integers discarding the overflow. # -# The input values are assumed to be represented using 32 bit limbs, but this is not checked. # -# Stack transition looks as follows: # -# [b_hi, b_lo, a_hi, a_lo, ...] -> [c_hi, c_lo, ...], where c = (a - b) % 2^64 # -export.sub_unsafe - movup.3 - movup.2 - u32sub.unsafe - movup.3 - movup.3 - u32sub.unsafe - drop - swap - u32sub.unsafe - drop -end - # ===== DIVISION ================================================================================ # # Performs division of two unsigned 64 bit integers discarding the remainder. # diff --git a/stdlib/src/asm.rs b/stdlib/src/asm.rs index 540695a169..42cda569c3 100644 --- a/stdlib/src/asm.rs +++ b/stdlib/src/asm.rs @@ -1526,6 +1526,25 @@ export.add_unsafe drop end +# ===== SUBTRACTION ============================================================================= # + +# Performs subtraction of two unsigned 64 bit integers discarding the overflow. # +# The input values are assumed to be represented using 32 bit limbs, but this is not checked. # +# Stack transition looks as follows: # +# [b_hi, b_lo, a_hi, a_lo, ...] -> [c_hi, c_lo, ...], where c = (a - b) % 2^64 # +export.sub_unsafe + movup.3 + movup.2 + u32sub.unsafe + movup.3 + movup.3 + u32sub.unsafe + drop + swap + u32sub.unsafe + drop +end + # ===== MULTIPLICATION ========================================================================== # # Performs multiplication of two unsigned 64 bit integers discarding the overflow. # @@ -1602,23 +1621,6 @@ export.gte_unsafe not end -# Performs subtraction of two unsigned 64 bit integers discarding the overflow. # -# The input values are assumed to be represented using 32 bit limbs, but this is not checked. # -# Stack transition looks as follows: # -# [b_hi, b_lo, a_hi, a_lo, ...] -> [c_hi, c_lo, ...], where c = (a - b) % 2^64 # -export.sub_unsafe - movup.3 - movup.2 - u32sub.unsafe - movup.3 - movup.3 - u32sub.unsafe - drop - swap - u32sub.unsafe - drop -end - # ===== DIVISION ================================================================================ # # Performs division of two unsigned 64 bit integers discarding the remainder. # From 5f5ba5950be6c17508759a40ba40bf99521faeca Mon Sep 17 00:00:00 2001 From: grjte Date: Sun, 13 Mar 2022 16:32:59 +0000 Subject: [PATCH 20/26] refactor: overflow vector to overflow table --- processor/src/stack/mod.rs | 97 +++++++++++++++++++++++++++++--------- 1 file changed, 75 insertions(+), 22 deletions(-) diff --git a/processor/src/stack/mod.rs b/processor/src/stack/mod.rs index 46d9d93ce9..381e978602 100644 --- a/processor/src/stack/mod.rs +++ b/processor/src/stack/mod.rs @@ -1,6 +1,24 @@ use super::{Felt, FieldElement, ProgramInputs, StackTrace, MIN_STACK_DEPTH}; use core::cmp; +// CONSTANTS +// ================================================================================================ + +/// Number of columns in the virtual overflow table. +const NUM_OVERFLOW_COLS: usize = 3; + +/// The address column of the overflow table. +const OVERFLOW_ADDR_IDX: usize = 0; + +/// The column of the overflow table that contains the overflowed values. +const OVERFLOW_VAL_IDX: usize = 1; + +/// The column of the overflow table that has the addresses of each of the previous overflow rows. +const OVERFLOW_PREV_ADDR_IDX: usize = 2; + +/// The last stack index accessible by the VM. +const MAX_TOP_IDX: usize = MIN_STACK_DEPTH - 1; + // STACK // ================================================================================================ @@ -8,7 +26,7 @@ use core::cmp; pub struct Stack { step: usize, trace: StackTrace, - overflow: Vec, + overflow: [Vec; NUM_OVERFLOW_COLS], depth: usize, } @@ -27,10 +45,12 @@ impl Stack { trace.push(column) } + let overflow = [Vec::new(), Vec::new(), Vec::new()]; + Self { step: 0, trace: trace.try_into().expect("failed to convert vector to array"), - overflow: Vec::new(), + overflow, depth: MIN_STACK_DEPTH, } } @@ -70,7 +90,7 @@ impl Stack { if num_items > MIN_STACK_DEPTH { let num_overflow_items = num_items - MIN_STACK_DEPTH; - result.extend_from_slice(&self.overflow[..num_overflow_items]); + result.extend_from_slice(&self.overflow[OVERFLOW_VAL_IDX][..num_overflow_items]); } result @@ -139,23 +159,24 @@ impl Stack { "start position cannot exceed current depth" ); - const MAX_TOP_IDX: usize = MIN_STACK_DEPTH - 1; match self.depth { 0..=MAX_TOP_IDX => unreachable!("stack underflow"), MIN_STACK_DEPTH => { for i in start_pos..=MAX_TOP_IDX { self.trace[i - 1][self.step + 1] = self.trace[i][self.step]; } - // Shift in a ZERO to prevent depth shrinking below the minimum stack depth + // Shift in a ZERO to prevent depth shrinking below the minimum stack depth. self.trace[MAX_TOP_IDX][self.step + 1] = Felt::ZERO; } _ => { for i in start_pos..=MAX_TOP_IDX { self.trace[i - 1][self.step + 1] = self.trace[i][self.step]; } - let from_overflow = self.overflow.pop().expect("overflow stack is empty"); - self.trace[MAX_TOP_IDX][self.step + 1] = from_overflow; + let from_overflow = self.pop_overflow(); + self.trace[MAX_TOP_IDX][self.step + 1] = from_overflow[OVERFLOW_VAL_IDX]; + + // Stack depth only decreases when it is greater than the minimum stack depth. self.depth -= 1; } } @@ -176,23 +197,16 @@ impl Stack { "start position cannot exceed current depth" ); - const MAX_TOP_IDX: usize = MIN_STACK_DEPTH - 1; - match self.depth { - 0 => {} // if the stack is empty, do nothing - 1..=MAX_TOP_IDX => { - for i in start_pos..self.depth { - self.trace[i + 1][self.step + 1] = self.trace[i][self.step]; - } - } - _ => { - for i in start_pos..MAX_TOP_IDX { - self.trace[i + 1][self.step + 1] = self.trace[i][self.step]; - } - let to_overflow = self.trace[MAX_TOP_IDX][self.step]; - self.overflow.push(to_overflow) - } + // Update the stack. + for i in start_pos..MAX_TOP_IDX { + self.trace[i + 1][self.step + 1] = self.trace[i][self.step]; } + // Update the overflow table. + let to_overflow = self.trace[MAX_TOP_IDX][self.step]; + self.push_overflow(to_overflow); + + // Stack depth always increases on right shift. self.depth += 1; } @@ -215,4 +229,43 @@ impl Stack { } } } + + /// Pushes a new row onto the overflow table that contains the value which has overflowed the + /// stack. + /// + /// Each row of the overflow table looks like ```[addr, value, prev_addr]```, where: + /// - `addr` is the address of the row, which is set to the current clock cycle. + /// - `value` is the overflowed value. + /// - `prev_addr` is the address of the previous row in the overflow table. + pub fn push_overflow(&mut self, value: Felt) { + let prev_addr = match self.overflow[OVERFLOW_ADDR_IDX].last() { + Some(addr) => *addr, + None => Felt::ZERO, + }; + // Set the address of this overflow row to the current clock cycle. + self.overflow[OVERFLOW_ADDR_IDX].push(Felt::new(self.step as u64)); + // Save the overflow value. + self.overflow[OVERFLOW_VAL_IDX].push(value); + // Save the address of the previous row. + self.overflow[OVERFLOW_PREV_ADDR_IDX].push(prev_addr) + } + + /// Pops the last row off the overflow table and returns it. + /// + /// # Errors + /// This function will panic if the overflow table is empty. + pub fn pop_overflow(&mut self) -> [Felt; NUM_OVERFLOW_COLS] { + let mut row = [Felt::ZERO; 3]; + row[OVERFLOW_ADDR_IDX] = self.overflow[OVERFLOW_ADDR_IDX] + .pop() + .expect("overflow stack is empty"); + row[OVERFLOW_VAL_IDX] = self.overflow[OVERFLOW_VAL_IDX] + .pop() + .expect("overflow stack is empty"); + row[OVERFLOW_PREV_ADDR_IDX] = self.overflow[OVERFLOW_PREV_ADDR_IDX] + .pop() + .expect("overflow stack is empty"); + + row + } } From db6fddb93c41bfdf3c5b5f771b13ff25110e4499 Mon Sep 17 00:00:00 2001 From: grjte Date: Tue, 15 Mar 2022 09:13:05 +0000 Subject: [PATCH 21/26] feat: add helper columns to stack trace --- core/src/lib.rs | 5 +- processor/src/lib.rs | 5 +- processor/src/stack/mod.rs | 118 +++++++++------------ processor/src/stack/trace.rs | 200 +++++++++++++++++++++++++++++++++++ processor/src/trace.rs | 12 +-- 5 files changed, 263 insertions(+), 77 deletions(-) create mode 100644 processor/src/stack/trace.rs diff --git a/core/src/lib.rs b/core/src/lib.rs index b4201e426f..f78fef8345 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -29,6 +29,9 @@ pub type StackTopState = [Felt; MIN_STACK_DEPTH]; /// be accessed by the VM directly. pub const MIN_STACK_DEPTH: usize = 16; +/// Number of bookkeeping and helper columns in the stack trace. +pub const NUM_STACK_HELPER_COLS: usize = 3; + // TRACE LAYOUT // ------------------------------------------------------------------------------------------------ @@ -47,7 +50,7 @@ pub const FMP_COL_IDX: usize = SYS_TRACE_OFFSET + 1; // Stack trace pub const STACK_TRACE_OFFSET: usize = SYS_TRACE_OFFSET + SYS_TRACE_WIDTH; -pub const STACK_TRACE_WIDTH: usize = MIN_STACK_DEPTH; // TODO: add helper columns +pub const STACK_TRACE_WIDTH: usize = MIN_STACK_DEPTH + NUM_STACK_HELPER_COLS; pub const STACK_TRACE_RANGE: Range = range(STACK_TRACE_OFFSET, STACK_TRACE_WIDTH); // TODO: range check trace diff --git a/processor/src/lib.rs b/processor/src/lib.rs index ef0198da9c..ef5dff6816 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -6,7 +6,8 @@ use vm_core::{ Script, }, AdviceInjector, DebugOptions, Felt, FieldElement, Operation, ProgramInputs, StackTopState, - StarkField, Word, AUX_TRACE_WIDTH, MIN_STACK_DEPTH, SYS_TRACE_WIDTH, + StarkField, Word, AUX_TRACE_WIDTH, MIN_STACK_DEPTH, NUM_STACK_HELPER_COLS, STACK_TRACE_WIDTH, + SYS_TRACE_WIDTH, }; mod operations; @@ -48,7 +49,7 @@ mod tests; // ================================================================================================ type SysTrace = [Vec; SYS_TRACE_WIDTH]; -type StackTrace = [Vec; MIN_STACK_DEPTH]; +type StackTrace = [Vec; STACK_TRACE_WIDTH]; type AuxTableTrace = [Vec; AUX_TRACE_WIDTH]; // TODO: potentially rename to AuxiliaryTrace // EXECUTOR diff --git a/processor/src/stack/mod.rs b/processor/src/stack/mod.rs index 381e978602..4ab4430cf1 100644 --- a/processor/src/stack/mod.rs +++ b/processor/src/stack/mod.rs @@ -1,6 +1,12 @@ -use super::{Felt, FieldElement, ProgramInputs, StackTrace, MIN_STACK_DEPTH}; +use super::{ + Felt, FieldElement, ProgramInputs, StackTopState, MIN_STACK_DEPTH, NUM_STACK_HELPER_COLS, + STACK_TRACE_WIDTH, +}; use core::cmp; +mod trace; +pub use trace::StackTrace; + // CONSTANTS // ================================================================================================ @@ -35,21 +41,13 @@ impl Stack { // -------------------------------------------------------------------------------------------- /// TODO: add comments pub fn new(inputs: &ProgramInputs, init_trace_length: usize) -> Self { - let init_values = inputs.stack_init(); - let mut trace: Vec> = Vec::with_capacity(MIN_STACK_DEPTH); - for i in 0..MIN_STACK_DEPTH { - let mut column = vec![Felt::ZERO; init_trace_length]; - if i < init_values.len() { - column[0] = init_values[i]; - } - trace.push(column) - } + let trace = StackTrace::new(inputs, init_trace_length); let overflow = [Vec::new(), Vec::new(), Vec::new()]; Self { step: 0, - trace: trace.try_into().expect("failed to convert vector to array"), + trace, overflow, depth: MIN_STACK_DEPTH, } @@ -71,12 +69,12 @@ impl Stack { /// Returns execution trace length for this stack. pub fn trace_len(&self) -> usize { - self.trace[0].len() + self.trace.trace_len() } /// Returns a copy of the item currently at the top of the stack. pub fn peek(&self) -> Felt { - self.trace[0][self.step] + self.trace.peek_at(self.step) } /// Return states from the stack, which includes both the trace state and overflow table. @@ -86,7 +84,7 @@ impl Stack { let num_items = cmp::min(n, self.depth()); let num_top_items = cmp::min(MIN_STACK_DEPTH, num_items); - let mut result = self.trace_state()[..num_top_items].to_vec(); + let mut result = self.trace.get_stack_values_at(self.step, num_top_items); if num_items > MIN_STACK_DEPTH { let num_overflow_items = num_items - MIN_STACK_DEPTH; @@ -96,22 +94,10 @@ impl Stack { result } - /// Returns trace state at the current step. - /// - /// Trace state is always 16 elements long and contains the top 16 values of the stack. When - /// the stack depth is less than 16, the un-used slots contain ZEROs. - #[allow(dead_code)] - pub fn trace_state(&self) -> [Felt; MIN_STACK_DEPTH] { - let mut result = [Felt::ZERO; MIN_STACK_DEPTH]; - for (result, column) in result.iter_mut().zip(self.trace.iter()) { - *result = column[self.step]; - } - result - } - - /// TODO: add docs - pub fn into_trace(self) -> StackTrace { - self.trace + /// Returns an execution trace of the stack and helper columns from the StackTrace as a single + /// array. + pub fn into_trace(self) -> [Vec; STACK_TRACE_WIDTH] { + self.trace.into_array() } // TRACE ACCESSORS AND MUTATORS @@ -120,13 +106,22 @@ impl Stack { /// Returns the value located at the specified position on the stack at the current clock cycle. pub fn get(&self, pos: usize) -> Felt { debug_assert!(pos < self.depth, "stack underflow"); - self.trace[pos][self.step] + self.trace.get_stack_value_at(self.step, pos) } /// Sets the value at the specified position on the stack at the next clock cycle. pub fn set(&mut self, pos: usize, value: Felt) { debug_assert!(pos == 0 || pos < self.depth, "stack underflow"); - self.trace[pos][self.step + 1] = value; + self.trace.set_stack_value_at(self.step + 1, pos, value); + } + + /// Returns trace state at the current step. + /// + /// Trace state is always 16 elements long and contains the top 16 values of the stack. When + /// the stack depth is less than 16, the un-used slots contain ZEROs. + #[allow(dead_code)] + pub fn trace_state(&self) -> StackTopState { + self.trace.get_stack_state_at(self.step) } /// Copies stack values starting at the specified position at the current clock cycle to the @@ -136,11 +131,7 @@ impl Stack { start_pos < MIN_STACK_DEPTH, "start cannot exceed stack top size" ); - debug_assert!(start_pos <= self.depth, "stack underflow"); - let end_pos = cmp::min(self.depth, MIN_STACK_DEPTH); - for i in start_pos..end_pos { - self.trace[i][self.step + 1] = self.trace[i][self.step]; - } + self.trace.copy_stack_state_at(self.step, start_pos); } /// Copies stack values starting at the specified position at the current clock cycle to @@ -154,27 +145,27 @@ impl Stack { start_pos < MIN_STACK_DEPTH, "start position cannot exceed stack top size" ); - debug_assert!( - start_pos <= self.depth, - "start position cannot exceed current depth" - ); match self.depth { 0..=MAX_TOP_IDX => unreachable!("stack underflow"), MIN_STACK_DEPTH => { - for i in start_pos..=MAX_TOP_IDX { - self.trace[i - 1][self.step + 1] = self.trace[i][self.step]; - } - // Shift in a ZERO to prevent depth shrinking below the minimum stack depth. - self.trace[MAX_TOP_IDX][self.step + 1] = Felt::ZERO; + // Shift in a ZERO, to prevent depth shrinking below the minimum stack depth. + self.trace + .stack_shift_left_at(self.step, start_pos, Felt::ZERO); + self.trace.copy_helpers_at(self.step); } _ => { - for i in start_pos..=MAX_TOP_IDX { - self.trace[i - 1][self.step + 1] = self.trace[i][self.step]; - } - + // Update the stack & overflow table. let from_overflow = self.pop_overflow(); - self.trace[MAX_TOP_IDX][self.step + 1] = from_overflow[OVERFLOW_VAL_IDX]; + self.trace.stack_shift_left_at( + self.step, + start_pos, + from_overflow[OVERFLOW_VAL_IDX], + ); + + // Update the bookkeeping & helper columns. + self.trace + .helpers_shift_left_at(self.step, from_overflow[OVERFLOW_PREV_ADDR_IDX]); // Stack depth only decreases when it is greater than the minimum stack depth. self.depth -= 1; @@ -185,27 +176,25 @@ impl Stack { /// Copies stack values starting a the specified position at the current clock cycle to /// position + 1 at the next clock cycle /// - /// If stack depth grows beyond 16 items, the additional item is pushed into the overflow - /// stack. + /// If stack depth grows beyond 16 items, the additional item is pushed into the overflow table. pub fn shift_right(&mut self, start_pos: usize) { debug_assert!( start_pos < MIN_STACK_DEPTH, "start position cannot exceed stack top size" ); - debug_assert!( - start_pos <= self.depth, - "start position cannot exceed current depth" - ); // Update the stack. - for i in start_pos..MAX_TOP_IDX { - self.trace[i + 1][self.step + 1] = self.trace[i][self.step]; - } + self.trace.stack_shift_right_at(self.step, start_pos); // Update the overflow table. - let to_overflow = self.trace[MAX_TOP_IDX][self.step]; + let to_overflow = self + .trace + .get_stack_value_at(self.step, MIN_STACK_DEPTH - 1); self.push_overflow(to_overflow); + // Update the bookkeeping & helper columns. + self.trace.helpers_shift_right_at(self.step); + // Stack depth always increases on right shift. self.depth += 1; } @@ -222,12 +211,7 @@ impl Stack { /// /// Trace length is doubled every time it needs to be increased. pub fn ensure_trace_capacity(&mut self) { - if self.step + 1 >= self.trace_len() { - let new_length = self.trace_len() * 2; - for register in self.trace.iter_mut() { - register.resize(new_length, Felt::ZERO); - } - } + self.trace.ensure_trace_capacity(self.step); } /// Pushes a new row onto the overflow table that contains the value which has overflowed the diff --git a/processor/src/stack/trace.rs b/processor/src/stack/trace.rs new file mode 100644 index 0000000000..f4fd5f6aa4 --- /dev/null +++ b/processor/src/stack/trace.rs @@ -0,0 +1,200 @@ +use vm_core::StarkField; + +use super::{ + Felt, FieldElement, ProgramInputs, StackTopState, MIN_STACK_DEPTH, NUM_STACK_HELPER_COLS, + STACK_TRACE_WIDTH, +}; + +// CONSTANTS +// ================================================================================================ + +// The largest stack index accessible by the VM. +const MAX_TOP_IDX: usize = MIN_STACK_DEPTH - 1; + +// STACK TRACE +// ================================================================================================ + +pub struct StackTrace { + stack: [Vec; MIN_STACK_DEPTH], + helpers: [Vec; NUM_STACK_HELPER_COLS], +} + +impl StackTrace { + // CONSTRUCTOR + // -------------------------------------------------------------------------------------------- + /// Returns a [StackTrace] instantiated with empty vectors for all columns. + pub fn new(inputs: &ProgramInputs, init_trace_length: usize) -> Self { + // Initialize the stack. + let init_values = inputs.stack_init(); + let mut stack: Vec> = Vec::with_capacity(MIN_STACK_DEPTH); + for i in 0..MIN_STACK_DEPTH { + let mut column = Felt::zeroed_vector(init_trace_length); + if i < init_values.len() { + column[0] = init_values[i]; + } + stack.push(column) + } + + // Initialize the bookkeeping & helper columns. + let mut b0 = Felt::zeroed_vector(init_trace_length); + b0[0] = Felt::new(MIN_STACK_DEPTH as u64); + let helpers: [Vec; 3] = [ + b0, + Felt::zeroed_vector(init_trace_length), + Felt::zeroed_vector(init_trace_length), + ]; + + StackTrace { + stack: stack + .try_into() + .expect("Failed to convert vector to an array"), + helpers, + } + } + + // PUBLIC ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Returns the length of the execution trace for this stack. + pub fn trace_len(&self) -> usize { + self.stack[0].len() + } + + pub fn into_array(self) -> [Vec; STACK_TRACE_WIDTH] { + let mut trace = Vec::with_capacity(STACK_TRACE_WIDTH); + trace.extend_from_slice(&self.stack); + trace.extend_from_slice(&self.helpers); + + trace + .try_into() + .expect("Failed to convert vector to an array") + } + + // STACK ACCESSORS AND MUTATORS + // -------------------------------------------------------------------------------------------- + + /// Returns a copy of the item at the top of the stack at the specified step + pub fn peek_at(&self, step: usize) -> Felt { + self.stack[0][step] + } + + /// Returns the value located at the specified position on the stack at the specified clock + /// cycle. + pub fn get_stack_value_at(&self, step: usize, pos: usize) -> Felt { + self.stack[pos][step] + } + + /// Sets the value at the specified position on the stack at the specified cycle. + pub fn set_stack_value_at(&mut self, step: usize, pos: usize, value: Felt) { + self.stack[pos][step] = value; + } + + /// Return the specified number of states from the top of the stack at the specified step. + pub fn get_stack_values_at(&self, step: usize, num_items: usize) -> Vec { + self.get_stack_state_at(step)[..num_items].to_vec() + } + + /// Returns the stack trace state at the specified step. + /// + /// Trace state is always 16 elements long and contains the top 16 values of the stack. + pub fn get_stack_state_at(&self, step: usize) -> StackTopState { + let mut result = [Felt::ZERO; MIN_STACK_DEPTH]; + for (result, column) in result.iter_mut().zip(self.stack.iter()) { + *result = column[step]; + } + result + } + + /// Copies the stack values starting at the specified position at the specified clock cycle to + /// the same position at the next clock cycle. + pub fn copy_stack_state_at(&mut self, step: usize, start_pos: usize) { + debug_assert!( + start_pos < MIN_STACK_DEPTH, + "start cannot exceed stack top size" + ); + for i in start_pos..MIN_STACK_DEPTH { + self.stack[i][step + 1] = self.stack[i][step]; + } + } + + /// Copies the stack values starting at the specified position at the specified clock cycle to + /// position - 1 at the next clock cycle. + /// + /// The final register is filled with the provided value in `last_value`. + pub fn stack_shift_left_at(&mut self, step: usize, start_pos: usize, last_value: Felt) { + for i in start_pos..=MAX_TOP_IDX { + self.stack[i - 1][step + 1] = self.stack[i][step]; + } + self.stack[MIN_STACK_DEPTH - 1][step + 1] = last_value; + } + + /// Copies stack values starting at the specified position at the specified clock cycle to + /// position + 1 at the next clock cycle. + pub fn stack_shift_right_at(&mut self, step: usize, start_pos: usize) { + for i in start_pos..MAX_TOP_IDX { + self.stack[i + 1][step + 1] = self.stack[i][step]; + } + } + + // BOOKKEEPING & HELPER COLUMN ACCESSORS AND MUTATORS + // -------------------------------------------------------------------------------------------- + + /// Returns the trace state of the stack helper columns at the specified step. + #[allow(dead_code)] + pub fn get_helpers_state_at(&self, step: usize) -> [Felt; NUM_STACK_HELPER_COLS] { + let mut result = [Felt::ZERO; NUM_STACK_HELPER_COLS]; + for (result, column) in result.iter_mut().zip(self.helpers.iter()) { + *result = column[step]; + } + result + } + + /// Copies the helper values at the specified clock cycle to the next clock cycle. + pub fn copy_helpers_at(&mut self, step: usize) { + for i in 0..NUM_STACK_HELPER_COLS { + self.helpers[i][step + 1] = self.helpers[i][step]; + } + } + + pub fn helpers_shift_right_at(&mut self, step: usize) { + // Increment b0 by one. + let b0 = self.helpers[0][step] + Felt::ONE; + self.helpers[0][step + 1] = b0; + // Set b1 to the curren tclock cycle. + self.helpers[1][step + 1] = Felt::new(step as u64); + // Update the helper column to 1 / (b0 - 16). + self.helpers[2][step + 1] = Felt::ONE / (b0 - Felt::new(MIN_STACK_DEPTH as u64)); + } + + pub fn helpers_shift_left_at(&mut self, step: usize, next_overflow_addr: Felt) { + // Decrement b0 by one. + let b0 = self.helpers[0][step] - Felt::ONE; + self.helpers[0][step + 1] = b0; + + // Set b1 to the overflow table address of the item at the top of the updated table. + self.helpers[1][step + 1] = next_overflow_addr; + + // Update the helper column to 1 / (b0 - 16) if depth > MIN_STACK_DEPTH or 0 otherwise. + let h0 = if b0.as_int() > MIN_STACK_DEPTH as u64 { + Felt::ONE / (b0 - Felt::new(MIN_STACK_DEPTH as u64)) + } else { + Felt::ZERO + }; + self.helpers[2][step + 1] = h0; + } + + // UTILITY METHODS + // -------------------------------------------------------------------------------------------- + + /// Makes sure there is enough memory allocated for the trace to accommodate a new row. + /// + /// Trace length is doubled every time it needs to be increased. + pub fn ensure_trace_capacity(&mut self, step: usize) { + if step + 1 >= self.trace_len() { + let new_length = self.trace_len() * 2; + for register in self.stack.iter_mut().chain(self.helpers.iter_mut()) { + register.resize(new_length, Felt::ZERO); + } + } + } +} diff --git a/processor/src/trace.rs b/processor/src/trace.rs index e124f74d97..c7d2502e3e 100644 --- a/processor/src/trace.rs +++ b/processor/src/trace.rs @@ -4,8 +4,8 @@ use super::{ }; use core::slice; use vm_core::{ - StarkField, AUX_TRACE_OFFSET, AUX_TRACE_RANGE, AUX_TRACE_WIDTH, STACK_TRACE_OFFSET, - STACK_TRACE_RANGE, STACK_TRACE_WIDTH, SYS_TRACE_OFFSET, SYS_TRACE_RANGE, TRACE_WIDTH, + StarkField, AUX_TRACE_OFFSET, AUX_TRACE_RANGE, AUX_TRACE_WIDTH, MIN_STACK_DEPTH, + STACK_TRACE_OFFSET, STACK_TRACE_RANGE, SYS_TRACE_OFFSET, SYS_TRACE_RANGE, TRACE_WIDTH, }; use winterfell::Trace; @@ -42,9 +42,7 @@ impl ExecutionTrace { let aux_trace_len = hasher.trace_len() + bitwise.trace_len() + memory.trace_len(); let mut trace_len = usize::max(stack.trace_len(), aux_trace_len); // pad the trace length to the next power of 2 - if !trace_len.is_power_of_two() { - trace_len = trace_len.next_power_of_two(); - } + trace_len = trace_len.next_power_of_two(); // allocate columns for the trace of the auxiliary table // note: it may be possible to optimize this by initializing with Felt::zeroed_vector, @@ -91,7 +89,7 @@ impl ExecutionTrace { /// TODO: add docs pub fn init_stack_state(&self) -> StackTopState { - let mut result = [Felt::ZERO; STACK_TRACE_WIDTH]; + let mut result = [Felt::ZERO; MIN_STACK_DEPTH]; for (result, column) in result.iter_mut().zip(self.stack.iter()) { *result = column[0]; } @@ -101,7 +99,7 @@ impl ExecutionTrace { /// TODO: add docs pub fn last_stack_state(&self) -> StackTopState { let last_step = self.length() - 1; - let mut result = [Felt::ZERO; STACK_TRACE_WIDTH]; + let mut result = [Felt::ZERO; MIN_STACK_DEPTH]; for (result, column) in result.iter_mut().zip(self.stack.iter()) { *result = column[last_step]; } From d7b18e26498c3f8ea44cd4ae5ede5b09306f117b Mon Sep 17 00:00:00 2001 From: grjte Date: Tue, 15 Mar 2022 09:58:03 +0000 Subject: [PATCH 22/26] docs: update the stack module documentation --- processor/src/stack/mod.rs | 23 +++++++++++++++++++++-- processor/src/stack/trace.rs | 25 +++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/processor/src/stack/mod.rs b/processor/src/stack/mod.rs index 4ab4430cf1..236b6daa77 100644 --- a/processor/src/stack/mod.rs +++ b/processor/src/stack/mod.rs @@ -28,7 +28,26 @@ const MAX_TOP_IDX: usize = MIN_STACK_DEPTH - 1; // STACK // ================================================================================================ -/// TODO: add comments +/// Stack for the VM. +/// +/// This component is responsible for managing the state of the VM's stack, as well +/// as building an execution trace for all stack transitions. +/// +/// ## Execution trace +/// The stack execution trace consists of 19 columns as illustrated below: +/// +/// s0 s1 s2 s13 s14 s15 b0 b1 h0 +/// ├────┴────┴────┴ ... ┴────┴─────┴─────┴────┴────┴────┤ +/// +/// The meaning of the above columns is as follows: +/// * - s0...s15 are the columns representing the top 16 slots of the stack. +/// * - Bookkeeping column b0 contains the number of items on the stack (i.e., the stack depth). +/// * - Bookkeeping column b1 contains an address of a row in the “overflow table” in which we’ll +/// store the data that doesn’t fit into the top 16 slots. When b1=0, it means that all stack data +/// fits into the top 16 slots of the stack. +/// * - Helper column h0 is used to ensure that stack depth does not drop below 16. Values in this +/// column are set by the prover non-deterministically to 1 / (b0−16) when b0 != 16, and to any +/// other value otherwise. pub struct Stack { step: usize, trace: StackTrace, @@ -39,7 +58,7 @@ pub struct Stack { impl Stack { // CONSTRUCTOR // -------------------------------------------------------------------------------------------- - /// TODO: add comments + /// Returns a [Stack] initialized with the specified program inputs. pub fn new(inputs: &ProgramInputs, init_trace_length: usize) -> Self { let trace = StackTrace::new(inputs, init_trace_length); diff --git a/processor/src/stack/trace.rs b/processor/src/stack/trace.rs index f4fd5f6aa4..4ecbdf5a73 100644 --- a/processor/src/stack/trace.rs +++ b/processor/src/stack/trace.rs @@ -14,6 +14,11 @@ const MAX_TOP_IDX: usize = MIN_STACK_DEPTH - 1; // STACK TRACE // ================================================================================================ +/// Execution trace of the stack component. +/// +/// The trace consists of 19 columns grouped logically as follows: +/// - 16 stack columns holding the top of the stack. +/// - 3 columns for bookkeeping and helper values that manage left and right shifts. pub struct StackTrace { stack: [Vec; MIN_STACK_DEPTH], helpers: [Vec; NUM_STACK_HELPER_COLS], @@ -156,6 +161,15 @@ impl StackTrace { } } + /// Updates the bookkeeping and helper columns to manage a right shift at the specified clock + /// cycle. + /// + /// This function assumes that the stack depth has been increased by one and a new row has been + /// added to the overflow table. It makes the following changes to the helper columns. + /// + /// b0: Increment the stack depth by one. + /// b1: Save the address of the new top row in overflow table, which is the current clock cycle. + /// h0: Set the value to 1 / (depth - 16). pub fn helpers_shift_right_at(&mut self, step: usize) { // Increment b0 by one. let b0 = self.helpers[0][step] + Felt::ONE; @@ -166,6 +180,17 @@ impl StackTrace { self.helpers[2][step + 1] = Felt::ONE / (b0 - Felt::new(MIN_STACK_DEPTH as u64)); } + /// Updates the bookkeeping and helper columns to manage a left shift at the specified clock + /// cycle. + /// + /// This function assumes that the stack depth has been decreased by one and a row has been + /// removed from the overflow table. It makes the following changes to the helper columns. + /// + /// b0: Decrement the stack depth by one. + /// b1: Update the address of the top row in the overflow table to the specified + /// `next_overflow_addr`. + /// h0: Set the value to 1 / (depth - 16) if the depth is still greater than the minimum stack + /// depth, or to zero otherwise. pub fn helpers_shift_left_at(&mut self, step: usize, next_overflow_addr: Felt) { // Decrement b0 by one. let b0 = self.helpers[0][step] - Felt::ONE; From d6c3a204c145867c113d9b6225279b5d7ff2352e Mon Sep 17 00:00:00 2001 From: grjte Date: Tue, 15 Mar 2022 15:06:58 +0000 Subject: [PATCH 23/26] test: add simple tests for stack and helper trace --- processor/src/stack/mod.rs | 3 + processor/src/stack/tests.rs | 175 +++++++++++++++++++++++++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 processor/src/stack/tests.rs diff --git a/processor/src/stack/mod.rs b/processor/src/stack/mod.rs index 236b6daa77..84f3ba7e92 100644 --- a/processor/src/stack/mod.rs +++ b/processor/src/stack/mod.rs @@ -7,6 +7,9 @@ use core::cmp; mod trace; pub use trace::StackTrace; +#[cfg(test)] +mod tests; + // CONSTANTS // ================================================================================================ diff --git a/processor/src/stack/tests.rs b/processor/src/stack/tests.rs new file mode 100644 index 0000000000..762335d210 --- /dev/null +++ b/processor/src/stack/tests.rs @@ -0,0 +1,175 @@ +use vm_core::{FieldElement, NUM_STACK_HELPER_COLS}; + +use super::{Felt, ProgramInputs, Stack, StackTopState, MIN_STACK_DEPTH}; + +#[test] +fn initialize() { + // initialize a new stack with some initial values + let mut stack_inputs = [1, 2, 3, 4]; + let inputs = ProgramInputs::new(&stack_inputs, &[], vec![]).unwrap(); + let stack = Stack::new(&inputs, 4); + + // Prepare the expected results. + stack_inputs.reverse(); + let expected_stack = build_stack(&[4, 3, 2, 1]); + let expected_helpers = [Felt::new(MIN_STACK_DEPTH as u64), Felt::ZERO, Felt::ZERO]; + + // Check the stack state. + assert_eq!(stack.trace_state(), expected_stack); + + // Check the helper columns. + assert_eq!( + stack.trace.get_helpers_state_at(stack.current_step()), + expected_helpers + ); +} + +#[test] +fn shift_left() { + let inputs = ProgramInputs::new(&[1, 2, 3, 4], &[], vec![]).unwrap(); + let mut stack = Stack::new(&inputs, 4); + + // ---- left shift an entire stack of minimum depth ------------------------------------------- + // Prepare the expected results. + let expected_stack = build_stack(&[3, 2, 1]); + let expected_helpers = build_helpers_left(0, 0); + + // Perform the left shift. + stack.shift_left(1); + stack.advance_clock(); + + // Check the stack state. + assert_eq!(stack.trace_state(), expected_stack); + + // Check the helper columns. + assert_eq!( + stack.trace.get_helpers_state_at(stack.current_step()), + expected_helpers + ); + + // ---- left shift an entire stack with multiple overflow items ------------------------------- + let mut stack = Stack::new(&inputs, 4); + // Shift right twice to add 2 items to the overflow table. + stack.shift_right(0); + let prev_overflow_addr = stack.current_step(); + stack.advance_clock(); + stack.shift_right(0); + stack.advance_clock(); + + // Prepare the expected results. + let expected_stack = build_stack(&[0, 4, 3, 2, 1]); + let expected_helpers = build_helpers_left(1, prev_overflow_addr); + + // Perform the left shift. + stack.shift_left(1); + stack.advance_clock(); + + // Check the stack state. + assert_eq!(stack.trace_state(), expected_stack); + + // Check the helper columns. + assert_eq!( + stack.trace.get_helpers_state_at(stack.current_step()), + expected_helpers + ); + + // ---- left shift an entire stack with one overflow item ------------------------------------- + // Prepare the expected results. + let expected_stack = build_stack(&[4, 3, 2, 1]); + let expected_helpers = build_helpers_left(0, 0); + + // Perform the left shift. + stack.ensure_trace_capacity(); + stack.shift_left(1); + stack.advance_clock(); + + // Check the stack state. + assert_eq!(stack.trace_state(), expected_stack); + + // Check the helper columns. + assert_eq!( + stack.trace.get_helpers_state_at(stack.current_step()), + expected_helpers + ); +} + +#[test] +fn shift_right() { + let inputs = ProgramInputs::new(&[1, 2, 3, 4], &[], vec![]).unwrap(); + let mut stack = Stack::new(&inputs, 4); + + // ---- right shift an entire stack of minimum depth ------------------------------------------ + let expected_stack = build_stack(&[0, 4, 3, 2, 1]); + let expected_helpers = build_helpers_right(1, stack.current_step()); + + stack.shift_right(0); + stack.advance_clock(); + + // Check the stack state. + assert_eq!(stack.trace_state(), expected_stack); + + // Check the helper columns. + assert_eq!( + stack.trace.get_helpers_state_at(stack.current_step()), + expected_helpers + ); + + // ---- right shift when the overflow table is non-empty -------------------------------------- + let expected_stack = build_stack(&[0, 0, 4, 3, 2, 1]); + let expected_helpers = build_helpers_right(2, stack.current_step()); + + stack.shift_right(0); + stack.advance_clock(); + + // Check the stack state. + assert_eq!(stack.trace_state(), expected_stack); + + // Check the helper columns. + assert_eq!( + stack.trace.get_helpers_state_at(stack.current_step()), + expected_helpers + ); +} + +// HELPERS +// ================================================================================================ + +/// Builds the trace row of stack helpers expected as the result of a right shift at clock cycle +/// `step` when there are `num_overflow` items in the overflow table. +fn build_helpers_right(num_overflow: usize, step: usize) -> [Felt; NUM_STACK_HELPER_COLS] { + let b0 = Felt::new((MIN_STACK_DEPTH + num_overflow) as u64); + let b1 = Felt::new(step as u64); + let h0 = Felt::ONE / (b0 - Felt::new(MIN_STACK_DEPTH as u64)); + + [b0, b1, h0] +} + +/// Builds the trace row of stack helpers expected as the result of a left shift when there are +/// `num_overflow` items in the overflow table and the top row in the table has address +/// `next_overflow_addr`. +fn build_helpers_left( + num_overflow: usize, + next_overflow_addr: usize, +) -> [Felt; NUM_STACK_HELPER_COLS] { + let depth = MIN_STACK_DEPTH + num_overflow; + let b0 = Felt::new(depth as u64); + let b1 = Felt::new(next_overflow_addr as u64); + let h0 = if depth > MIN_STACK_DEPTH { + Felt::ONE / (b0 - Felt::new(MIN_STACK_DEPTH as u64)) + } else { + Felt::ZERO + }; + + [b0, b1, h0] +} + +/// Builds a [StackTopState] that starts with the provided stack inputs and is padded with zeros +/// until the minimum stack depth. +fn build_stack(stack_inputs: &[u64]) -> StackTopState { + let mut expected_stack = [Felt::ZERO; MIN_STACK_DEPTH]; + for (idx, &input) in stack_inputs.iter().enumerate() { + expected_stack[idx] = Felt::new(input); + } + + expected_stack +} From d70fd163156d7c422da060fdbd9daa5b77547d12 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Tue, 15 Mar 2022 22:27:45 -0700 Subject: [PATCH 24/26] refactor: move stack overflow into a separate struct --- processor/src/stack/mod.rs | 140 +++++++++++--------------------- processor/src/stack/overflow.rs | 90 ++++++++++++++++++++ processor/src/stack/trace.rs | 16 ++-- 3 files changed, 143 insertions(+), 103 deletions(-) create mode 100644 processor/src/stack/overflow.rs diff --git a/processor/src/stack/mod.rs b/processor/src/stack/mod.rs index 84f3ba7e92..f6f7db948e 100644 --- a/processor/src/stack/mod.rs +++ b/processor/src/stack/mod.rs @@ -5,7 +5,10 @@ use super::{ use core::cmp; mod trace; -pub use trace::StackTrace; +use trace::StackTrace; + +mod overflow; +use overflow::OverflowTable; #[cfg(test)] mod tests; @@ -13,18 +16,6 @@ mod tests; // CONSTANTS // ================================================================================================ -/// Number of columns in the virtual overflow table. -const NUM_OVERFLOW_COLS: usize = 3; - -/// The address column of the overflow table. -const OVERFLOW_ADDR_IDX: usize = 0; - -/// The column of the overflow table that contains the overflowed values. -const OVERFLOW_VAL_IDX: usize = 1; - -/// The column of the overflow table that has the addresses of each of the previous overflow rows. -const OVERFLOW_PREV_ADDR_IDX: usize = 2; - /// The last stack index accessible by the VM. const MAX_TOP_IDX: usize = MIN_STACK_DEPTH - 1; @@ -33,8 +24,14 @@ const MAX_TOP_IDX: usize = MIN_STACK_DEPTH - 1; /// Stack for the VM. /// -/// This component is responsible for managing the state of the VM's stack, as well -/// as building an execution trace for all stack transitions. +/// This component is responsible for managing the state of the VM's stack, as well as building an +/// execution trace for all stack transitions. +/// +/// The state is separated into two parts: the top 16 slots of the stack (stored in the `trace` +/// member), and items which don't fit into the top 16 slots (stored in the `overflow` member). +/// +/// The depth of the stack can never drop below 16. If an item is removed from the stack when the +/// depth is 16, a ZERO element is inserted into the 16th slot. /// /// ## Execution trace /// The stack execution trace consists of 19 columns as illustrated below: @@ -43,18 +40,18 @@ const MAX_TOP_IDX: usize = MIN_STACK_DEPTH - 1; /// ├────┴────┴────┴ ... ┴────┴─────┴─────┴────┴────┴────┤ /// /// The meaning of the above columns is as follows: -/// * - s0...s15 are the columns representing the top 16 slots of the stack. -/// * - Bookkeeping column b0 contains the number of items on the stack (i.e., the stack depth). -/// * - Bookkeeping column b1 contains an address of a row in the “overflow table” in which we’ll -/// store the data that doesn’t fit into the top 16 slots. When b1=0, it means that all stack data -/// fits into the top 16 slots of the stack. -/// * - Helper column h0 is used to ensure that stack depth does not drop below 16. Values in this -/// column are set by the prover non-deterministically to 1 / (b0−16) when b0 != 16, and to any -/// other value otherwise. +/// - s0...s15 are the columns representing the top 16 slots of the stack. +/// - Bookkeeping column b0 contains the number of items on the stack (i.e., the stack depth). +/// - Bookkeeping column b1 contains an address of a row in the “overflow table” in which we’ll +/// store the data that doesn’t fit into the top 16 slots. When b1=0, it means that all stack +/// data fits into the top 16 slots of the stack. +/// - Helper column h0 is used to ensure that stack depth does not drop below 16. Values in this +/// column are set by the prover non-deterministically to 1 / (b0−16) when b0 != 16, and to any +/// other value otherwise. pub struct Stack { step: usize, trace: StackTrace, - overflow: [Vec; NUM_OVERFLOW_COLS], + overflow: OverflowTable, depth: usize, } @@ -63,14 +60,10 @@ impl Stack { // -------------------------------------------------------------------------------------------- /// Returns a [Stack] initialized with the specified program inputs. pub fn new(inputs: &ProgramInputs, init_trace_length: usize) -> Self { - let trace = StackTrace::new(inputs, init_trace_length); - - let overflow = [Vec::new(), Vec::new(), Vec::new()]; - Self { step: 0, - trace, - overflow, + trace: StackTrace::new(inputs, init_trace_length), + overflow: OverflowTable::new(true), depth: MIN_STACK_DEPTH, } } @@ -99,8 +92,9 @@ impl Stack { self.trace.peek_at(self.step) } - /// Return states from the stack, which includes both the trace state and overflow table. - /// If n is not passed in, this returns all states. + /// Return n items from the top of the stack, including the values from the overflow table. + /// + /// If n is None, this returns the entire stack. pub fn get_values(&self, n: Option) -> Vec { let n = n.unwrap_or(usize::MAX); let num_items = cmp::min(n, self.depth()); @@ -110,14 +104,13 @@ impl Stack { if num_items > MIN_STACK_DEPTH { let num_overflow_items = num_items - MIN_STACK_DEPTH; - result.extend_from_slice(&self.overflow[OVERFLOW_VAL_IDX][..num_overflow_items]); + self.overflow.append_into(&mut result, num_overflow_items); } result } - /// Returns an execution trace of the stack and helper columns from the StackTrace as a single - /// array. + /// Returns an execution trace of the top 16 stack slots and helper columns as a single array. pub fn into_trace(self) -> [Vec; STACK_TRACE_WIDTH] { self.trace.into_array() } @@ -127,25 +120,16 @@ impl Stack { /// Returns the value located at the specified position on the stack at the current clock cycle. pub fn get(&self, pos: usize) -> Felt { - debug_assert!(pos < self.depth, "stack underflow"); + debug_assert!(pos < MIN_STACK_DEPTH, "stack underflow"); self.trace.get_stack_value_at(self.step, pos) } /// Sets the value at the specified position on the stack at the next clock cycle. pub fn set(&mut self, pos: usize, value: Felt) { - debug_assert!(pos == 0 || pos < self.depth, "stack underflow"); + debug_assert!(pos < MIN_STACK_DEPTH, "stack underflow"); self.trace.set_stack_value_at(self.step + 1, pos, value); } - /// Returns trace state at the current step. - /// - /// Trace state is always 16 elements long and contains the top 16 values of the stack. When - /// the stack depth is less than 16, the un-used slots contain ZEROs. - #[allow(dead_code)] - pub fn trace_state(&self) -> StackTopState { - self.trace.get_stack_state_at(self.step) - } - /// Copies stack values starting at the specified position at the current clock cycle to the /// same position at the next clock cycle. pub fn copy_state(&mut self, start_pos: usize) { @@ -160,7 +144,8 @@ impl Stack { /// position - 1 at the next clock cycle. /// /// If the stack depth is greater than 16, an item is moved from the overflow stack to the - /// "in-memory" portion of the stack. + /// "in-memory" portion of the stack. If the stack depth is 16, the 16th element of the + /// stack is set to ZERO. pub fn shift_left(&mut self, start_pos: usize) { debug_assert!(start_pos > 0, "start position must be greater than 0"); debug_assert!( @@ -178,16 +163,12 @@ impl Stack { } _ => { // Update the stack & overflow table. - let from_overflow = self.pop_overflow(); - self.trace.stack_shift_left_at( - self.step, - start_pos, - from_overflow[OVERFLOW_VAL_IDX], - ); + let (from_overflow, prev_addr) = self.overflow.pop(self.step); + self.trace + .stack_shift_left_at(self.step, start_pos, from_overflow); // Update the bookkeeping & helper columns. - self.trace - .helpers_shift_left_at(self.step, from_overflow[OVERFLOW_PREV_ADDR_IDX]); + self.trace.helpers_shift_left_at(self.step, prev_addr); // Stack depth only decreases when it is greater than the minimum stack depth. self.depth -= 1; @@ -209,10 +190,8 @@ impl Stack { self.trace.stack_shift_right_at(self.step, start_pos); // Update the overflow table. - let to_overflow = self - .trace - .get_stack_value_at(self.step, MIN_STACK_DEPTH - 1); - self.push_overflow(to_overflow); + let to_overflow = self.trace.get_stack_value_at(self.step, MAX_TOP_IDX); + self.overflow.push(to_overflow, self.step); // Update the bookkeeping & helper columns. self.trace.helpers_shift_right_at(self.step); @@ -236,42 +215,15 @@ impl Stack { self.trace.ensure_trace_capacity(self.step); } - /// Pushes a new row onto the overflow table that contains the value which has overflowed the - /// stack. - /// - /// Each row of the overflow table looks like ```[addr, value, prev_addr]```, where: - /// - `addr` is the address of the row, which is set to the current clock cycle. - /// - `value` is the overflowed value. - /// - `prev_addr` is the address of the previous row in the overflow table. - pub fn push_overflow(&mut self, value: Felt) { - let prev_addr = match self.overflow[OVERFLOW_ADDR_IDX].last() { - Some(addr) => *addr, - None => Felt::ZERO, - }; - // Set the address of this overflow row to the current clock cycle. - self.overflow[OVERFLOW_ADDR_IDX].push(Felt::new(self.step as u64)); - // Save the overflow value. - self.overflow[OVERFLOW_VAL_IDX].push(value); - // Save the address of the previous row. - self.overflow[OVERFLOW_PREV_ADDR_IDX].push(prev_addr) - } + // TEST HELPERS + // -------------------------------------------------------------------------------------------- - /// Pops the last row off the overflow table and returns it. + /// Returns trace state at the current step. /// - /// # Errors - /// This function will panic if the overflow table is empty. - pub fn pop_overflow(&mut self) -> [Felt; NUM_OVERFLOW_COLS] { - let mut row = [Felt::ZERO; 3]; - row[OVERFLOW_ADDR_IDX] = self.overflow[OVERFLOW_ADDR_IDX] - .pop() - .expect("overflow stack is empty"); - row[OVERFLOW_VAL_IDX] = self.overflow[OVERFLOW_VAL_IDX] - .pop() - .expect("overflow stack is empty"); - row[OVERFLOW_PREV_ADDR_IDX] = self.overflow[OVERFLOW_PREV_ADDR_IDX] - .pop() - .expect("overflow stack is empty"); - - row + /// Trace state is always 16 elements long and contains the top 16 values of the stack. When + /// the stack depth is less than 16, the un-used slots contain ZEROs. + #[cfg(test)] + pub fn trace_state(&self) -> StackTopState { + self.trace.get_stack_state_at(self.step) } } diff --git a/processor/src/stack/overflow.rs b/processor/src/stack/overflow.rs new file mode 100644 index 0000000000..0b691aff87 --- /dev/null +++ b/processor/src/stack/overflow.rs @@ -0,0 +1,90 @@ +use super::{Felt, FieldElement}; +use winter_utils::collections::BTreeMap; + +// OVERFLOW TABLE +// ================================================================================================ + +/// Stores the values which beyond the top 16 elements of the stack. +/// +/// For each overflow item we also track the clock cycle at which it was inserted into the overflow +/// table. +/// +/// When `trace_enabled` is set to true, we also record all changes to the table so that we can +/// reconstruct the overflow table at any clock cycle. This can be used for debugging purposes. +pub struct OverflowTable { + rows: Vec, + trace: BTreeMap>, + trace_enabled: bool, +} + +impl OverflowTable { + /// Returns a new [OverflowTable]. The returned table is empty. + pub fn new(enable_trace: bool) -> Self { + Self { + rows: Vec::new(), + trace: BTreeMap::new(), + trace_enabled: enable_trace, + } + } + + /// Pushes the specified value into the overflow table. + pub fn push(&mut self, value: Felt, clk: usize) { + self.rows.push(OverflowRow::new(clk, value)); + if self.trace_enabled { + // insert a copy of the current table state into the trace + self.trace.insert(clk, self.get_values()); + } + } + + /// Removes the last value from the overflow table and returns it together with the clock + /// cycle of the next value in the table. + /// + /// If after the top value is removed the table is empty, the returned clock cycle is ZERO. + pub fn pop(&mut self, clk: usize) -> (Felt, Felt) { + let row = self.rows.pop().expect("overflow table is empty"); + + if self.trace_enabled { + // insert a copy of the current table state into the trace + self.trace.insert(clk, self.get_values()); + } + + // determine the clock cycle of the next row and return + if self.rows.is_empty() { + (row.val, Felt::ZERO) + } else { + let prev_row = self.rows.last().expect("no previous row"); + (row.val, prev_row.clk) + } + } + + /// Appends the top n values from the overflow table to the end of the provided vector. + pub fn append_into(&self, target: &mut Vec, n: usize) { + for row in self.rows.iter().rev().take(n) { + target.push(row.val); + } + } + + /// Returns a vector consisting of just the value portion of each table row. + fn get_values(&self) -> Vec { + self.rows.iter().map(|r| r.val).collect() + } +} + +// OVERFLOW ROW +// ================================================================================================ + +/// A single row in the stack overflow table. Each row stores the value of the stack item as well +/// as the clock cycle at which the stack item was pushed into the overflow table. +struct OverflowRow { + clk: Felt, + val: Felt, +} + +impl OverflowRow { + pub fn new(clk: usize, val: Felt) -> Self { + Self { + clk: Felt::new(clk as u64), + val, + } + } +} diff --git a/processor/src/stack/trace.rs b/processor/src/stack/trace.rs index 4ecbdf5a73..fcc4de386c 100644 --- a/processor/src/stack/trace.rs +++ b/processor/src/stack/trace.rs @@ -1,16 +1,10 @@ use vm_core::StarkField; use super::{ - Felt, FieldElement, ProgramInputs, StackTopState, MIN_STACK_DEPTH, NUM_STACK_HELPER_COLS, - STACK_TRACE_WIDTH, + Felt, FieldElement, ProgramInputs, StackTopState, MAX_TOP_IDX, MIN_STACK_DEPTH, + NUM_STACK_HELPER_COLS, STACK_TRACE_WIDTH, }; -// CONSTANTS -// ================================================================================================ - -// The largest stack index accessible by the VM. -const MAX_TOP_IDX: usize = MIN_STACK_DEPTH - 1; - // STACK TRACE // ================================================================================================ @@ -61,6 +55,7 @@ impl StackTrace { // -------------------------------------------------------------------------------------------- /// Returns the length of the execution trace for this stack. + #[inline(always)] pub fn trace_len(&self) -> usize { self.stack[0].len() } @@ -79,17 +74,20 @@ impl StackTrace { // -------------------------------------------------------------------------------------------- /// Returns a copy of the item at the top of the stack at the specified step + #[inline(always)] pub fn peek_at(&self, step: usize) -> Felt { self.stack[0][step] } /// Returns the value located at the specified position on the stack at the specified clock /// cycle. + #[inline(always)] pub fn get_stack_value_at(&self, step: usize, pos: usize) -> Felt { self.stack[pos][step] } /// Sets the value at the specified position on the stack at the specified cycle. + #[inline(always)] pub fn set_stack_value_at(&mut self, step: usize, pos: usize, value: Felt) { self.stack[pos][step] = value; } @@ -130,7 +128,7 @@ impl StackTrace { for i in start_pos..=MAX_TOP_IDX { self.stack[i - 1][step + 1] = self.stack[i][step]; } - self.stack[MIN_STACK_DEPTH - 1][step + 1] = last_value; + self.stack[MAX_TOP_IDX][step + 1] = last_value; } /// Copies stack values starting at the specified position at the specified clock cycle to From af8ba245f1810302d66db9c8ba30bf8cb442bc6a Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Tue, 15 Mar 2022 22:37:49 -0700 Subject: [PATCH 25/26] refactor: rename step to clk in stack modules --- processor/src/operations/field_ops.rs | 10 ++-- processor/src/operations/io_ops.rs | 6 +-- processor/src/operations/stack_ops.rs | 4 +- processor/src/stack/mod.rs | 48 ++++++++--------- processor/src/stack/tests.rs | 24 ++++----- processor/src/stack/trace.rs | 75 ++++++++++++++------------- 6 files changed, 84 insertions(+), 83 deletions(-) diff --git a/processor/src/operations/field_ops.rs b/processor/src/operations/field_ops.rs index 9c9e597cb1..c30b4d8e72 100644 --- a/processor/src/operations/field_ops.rs +++ b/processor/src/operations/field_ops.rs @@ -189,7 +189,7 @@ mod tests { let expected = build_expected(&[a + b, c]); assert_eq!(MIN_STACK_DEPTH + 2, process.stack.depth()); - assert_eq!(4, process.stack.current_step()); + assert_eq!(4, process.stack.current_clk()); assert_eq!(expected, process.stack.trace_state()); // calling add with a stack of minimum depth is ok @@ -209,7 +209,7 @@ mod tests { assert_eq!(expected, process.stack.trace_state()); assert_eq!(MIN_STACK_DEPTH + 3, process.stack.depth()); - assert_eq!(4, process.stack.current_step()); + assert_eq!(4, process.stack.current_clk()); } #[test] @@ -223,7 +223,7 @@ mod tests { let expected = build_expected(&[a * b, c]); assert_eq!(MIN_STACK_DEPTH + 2, process.stack.depth()); - assert_eq!(4, process.stack.current_step()); + assert_eq!(4, process.stack.current_clk()); assert_eq!(expected, process.stack.trace_state()); // calling mul with a stack of minimum depth is ok @@ -243,7 +243,7 @@ mod tests { let expected = build_expected(&[a.inv(), b, c]); assert_eq!(MIN_STACK_DEPTH + 3, process.stack.depth()); - assert_eq!(4, process.stack.current_step()); + assert_eq!(4, process.stack.current_clk()); assert_eq!(expected, process.stack.trace_state()); } @@ -263,7 +263,7 @@ mod tests { let expected = build_expected(&[a + Felt::ONE, b, c]); assert_eq!(MIN_STACK_DEPTH + 3, process.stack.depth()); - assert_eq!(4, process.stack.current_step()); + assert_eq!(4, process.stack.current_clk()); assert_eq!(expected, process.stack.trace_state()); } diff --git a/processor/src/operations/io_ops.rs b/processor/src/operations/io_ops.rs index 5c27a0f6b7..3c10ba05fb 100644 --- a/processor/src/operations/io_ops.rs +++ b/processor/src/operations/io_ops.rs @@ -135,7 +135,7 @@ mod tests { fn op_push() { let mut process = Process::new_dummy(); assert_eq!(MIN_STACK_DEPTH, process.stack.depth()); - assert_eq!(0, process.stack.current_step()); + assert_eq!(0, process.stack.current_clk()); assert_eq!([Felt::ZERO; 16], process.stack.trace_state()); // push one item onto the stack @@ -145,7 +145,7 @@ mod tests { expected[0] = Felt::ONE; assert_eq!(MIN_STACK_DEPTH + 1, process.stack.depth()); - assert_eq!(1, process.stack.current_step()); + assert_eq!(1, process.stack.current_clk()); assert_eq!(expected, process.stack.trace_state()); // push another item onto the stack @@ -156,7 +156,7 @@ mod tests { expected[1] = Felt::ONE; assert_eq!(MIN_STACK_DEPTH + 2, process.stack.depth()); - assert_eq!(2, process.stack.current_step()); + assert_eq!(2, process.stack.current_clk()); assert_eq!(expected, process.stack.trace_state()); } diff --git a/processor/src/operations/stack_ops.rs b/processor/src/operations/stack_ops.rs index 57897890bd..b883a9d7af 100644 --- a/processor/src/operations/stack_ops.rs +++ b/processor/src/operations/stack_ops.rs @@ -271,7 +271,7 @@ mod tests { let expected = build_expected(&[0, 1]); assert_eq!(MIN_STACK_DEPTH + 2, process.stack.depth()); - assert_eq!(2, process.stack.current_step()); + assert_eq!(2, process.stack.current_clk()); assert_eq!(expected, process.stack.trace_state()); // pad the stack again @@ -279,7 +279,7 @@ mod tests { let expected = build_expected(&[0, 0, 1]); assert_eq!(MIN_STACK_DEPTH + 3, process.stack.depth()); - assert_eq!(3, process.stack.current_step()); + assert_eq!(3, process.stack.current_clk()); assert_eq!(expected, process.stack.trace_state()); } diff --git a/processor/src/stack/mod.rs b/processor/src/stack/mod.rs index f6f7db948e..243be87d25 100644 --- a/processor/src/stack/mod.rs +++ b/processor/src/stack/mod.rs @@ -49,7 +49,7 @@ const MAX_TOP_IDX: usize = MIN_STACK_DEPTH - 1; /// column are set by the prover non-deterministically to 1 / (b0−16) when b0 != 16, and to any /// other value otherwise. pub struct Stack { - step: usize, + clk: usize, trace: StackTrace, overflow: OverflowTable, depth: usize, @@ -61,7 +61,7 @@ impl Stack { /// Returns a [Stack] initialized with the specified program inputs. pub fn new(inputs: &ProgramInputs, init_trace_length: usize) -> Self { Self { - step: 0, + clk: 0, trace: StackTrace::new(inputs, init_trace_length), overflow: OverflowTable::new(true), depth: MIN_STACK_DEPTH, @@ -71,15 +71,15 @@ impl Stack { // PUBLIC ACCESSORS // -------------------------------------------------------------------------------------------- - /// Returns depth of the stack at the current step. + /// Returns depth of the stack at the current clock cycle. pub fn depth(&self) -> usize { self.depth } - /// Returns the current step of the execution trace. + /// Returns the current clock cycle of the execution trace. #[allow(dead_code)] - pub fn current_step(&self) -> usize { - self.step + pub fn current_clk(&self) -> usize { + self.clk } /// Returns execution trace length for this stack. @@ -89,7 +89,7 @@ impl Stack { /// Returns a copy of the item currently at the top of the stack. pub fn peek(&self) -> Felt { - self.trace.peek_at(self.step) + self.trace.peek_at(self.clk) } /// Return n items from the top of the stack, including the values from the overflow table. @@ -100,7 +100,7 @@ impl Stack { let num_items = cmp::min(n, self.depth()); let num_top_items = cmp::min(MIN_STACK_DEPTH, num_items); - let mut result = self.trace.get_stack_values_at(self.step, num_top_items); + let mut result = self.trace.get_stack_values_at(self.clk, num_top_items); if num_items > MIN_STACK_DEPTH { let num_overflow_items = num_items - MIN_STACK_DEPTH; @@ -121,13 +121,13 @@ impl Stack { /// Returns the value located at the specified position on the stack at the current clock cycle. pub fn get(&self, pos: usize) -> Felt { debug_assert!(pos < MIN_STACK_DEPTH, "stack underflow"); - self.trace.get_stack_value_at(self.step, pos) + self.trace.get_stack_value_at(self.clk, pos) } /// Sets the value at the specified position on the stack at the next clock cycle. pub fn set(&mut self, pos: usize, value: Felt) { debug_assert!(pos < MIN_STACK_DEPTH, "stack underflow"); - self.trace.set_stack_value_at(self.step + 1, pos, value); + self.trace.set_stack_value_at(self.clk + 1, pos, value); } /// Copies stack values starting at the specified position at the current clock cycle to the @@ -137,7 +137,7 @@ impl Stack { start_pos < MIN_STACK_DEPTH, "start cannot exceed stack top size" ); - self.trace.copy_stack_state_at(self.step, start_pos); + self.trace.copy_stack_state_at(self.clk, start_pos); } /// Copies stack values starting at the specified position at the current clock cycle to @@ -158,17 +158,17 @@ impl Stack { MIN_STACK_DEPTH => { // Shift in a ZERO, to prevent depth shrinking below the minimum stack depth. self.trace - .stack_shift_left_at(self.step, start_pos, Felt::ZERO); - self.trace.copy_helpers_at(self.step); + .stack_shift_left_at(self.clk, start_pos, Felt::ZERO); + self.trace.copy_helpers_at(self.clk); } _ => { // Update the stack & overflow table. - let (from_overflow, prev_addr) = self.overflow.pop(self.step); + let (from_overflow, prev_addr) = self.overflow.pop(self.clk); self.trace - .stack_shift_left_at(self.step, start_pos, from_overflow); + .stack_shift_left_at(self.clk, start_pos, from_overflow); // Update the bookkeeping & helper columns. - self.trace.helpers_shift_left_at(self.step, prev_addr); + self.trace.helpers_shift_left_at(self.clk, prev_addr); // Stack depth only decreases when it is greater than the minimum stack depth. self.depth -= 1; @@ -187,14 +187,14 @@ impl Stack { ); // Update the stack. - self.trace.stack_shift_right_at(self.step, start_pos); + self.trace.stack_shift_right_at(self.clk, start_pos); // Update the overflow table. - let to_overflow = self.trace.get_stack_value_at(self.step, MAX_TOP_IDX); - self.overflow.push(to_overflow, self.step); + let to_overflow = self.trace.get_stack_value_at(self.clk, MAX_TOP_IDX); + self.overflow.push(to_overflow, self.clk); // Update the bookkeeping & helper columns. - self.trace.helpers_shift_right_at(self.step); + self.trace.helpers_shift_right_at(self.clk); // Stack depth always increases on right shift. self.depth += 1; @@ -202,7 +202,7 @@ impl Stack { /// Increments the clock cycle. pub fn advance_clock(&mut self) { - self.step += 1; + self.clk += 1; } // UTILITY METHODS @@ -212,18 +212,18 @@ impl Stack { /// /// Trace length is doubled every time it needs to be increased. pub fn ensure_trace_capacity(&mut self) { - self.trace.ensure_trace_capacity(self.step); + self.trace.ensure_trace_capacity(self.clk); } // TEST HELPERS // -------------------------------------------------------------------------------------------- - /// Returns trace state at the current step. + /// Returns trace state at the current clock cycle. /// /// Trace state is always 16 elements long and contains the top 16 values of the stack. When /// the stack depth is less than 16, the un-used slots contain ZEROs. #[cfg(test)] pub fn trace_state(&self) -> StackTopState { - self.trace.get_stack_state_at(self.step) + self.trace.get_stack_state_at(self.clk) } } diff --git a/processor/src/stack/tests.rs b/processor/src/stack/tests.rs index 762335d210..2a9e753736 100644 --- a/processor/src/stack/tests.rs +++ b/processor/src/stack/tests.rs @@ -19,7 +19,7 @@ fn initialize() { // Check the helper columns. assert_eq!( - stack.trace.get_helpers_state_at(stack.current_step()), + stack.trace.get_helpers_state_at(stack.current_clk()), expected_helpers ); } @@ -43,7 +43,7 @@ fn shift_left() { // Check the helper columns. assert_eq!( - stack.trace.get_helpers_state_at(stack.current_step()), + stack.trace.get_helpers_state_at(stack.current_clk()), expected_helpers ); @@ -51,7 +51,7 @@ fn shift_left() { let mut stack = Stack::new(&inputs, 4); // Shift right twice to add 2 items to the overflow table. stack.shift_right(0); - let prev_overflow_addr = stack.current_step(); + let prev_overflow_addr = stack.current_clk(); stack.advance_clock(); stack.shift_right(0); stack.advance_clock(); @@ -69,7 +69,7 @@ fn shift_left() { // Check the helper columns. assert_eq!( - stack.trace.get_helpers_state_at(stack.current_step()), + stack.trace.get_helpers_state_at(stack.current_clk()), expected_helpers ); @@ -88,7 +88,7 @@ fn shift_left() { // Check the helper columns. assert_eq!( - stack.trace.get_helpers_state_at(stack.current_step()), + stack.trace.get_helpers_state_at(stack.current_clk()), expected_helpers ); } @@ -100,7 +100,7 @@ fn shift_right() { // ---- right shift an entire stack of minimum depth ------------------------------------------ let expected_stack = build_stack(&[0, 4, 3, 2, 1]); - let expected_helpers = build_helpers_right(1, stack.current_step()); + let expected_helpers = build_helpers_right(1, stack.current_clk()); stack.shift_right(0); stack.advance_clock(); @@ -110,13 +110,13 @@ fn shift_right() { // Check the helper columns. assert_eq!( - stack.trace.get_helpers_state_at(stack.current_step()), + stack.trace.get_helpers_state_at(stack.current_clk()), expected_helpers ); // ---- right shift when the overflow table is non-empty -------------------------------------- let expected_stack = build_stack(&[0, 0, 4, 3, 2, 1]); - let expected_helpers = build_helpers_right(2, stack.current_step()); + let expected_helpers = build_helpers_right(2, stack.current_clk()); stack.shift_right(0); stack.advance_clock(); @@ -126,7 +126,7 @@ fn shift_right() { // Check the helper columns. assert_eq!( - stack.trace.get_helpers_state_at(stack.current_step()), + stack.trace.get_helpers_state_at(stack.current_clk()), expected_helpers ); } @@ -135,10 +135,10 @@ fn shift_right() { // ================================================================================================ /// Builds the trace row of stack helpers expected as the result of a right shift at clock cycle -/// `step` when there are `num_overflow` items in the overflow table. -fn build_helpers_right(num_overflow: usize, step: usize) -> [Felt; NUM_STACK_HELPER_COLS] { +/// `clk` when there are `num_overflow` items in the overflow table. +fn build_helpers_right(num_overflow: usize, clk: usize) -> [Felt; NUM_STACK_HELPER_COLS] { let b0 = Felt::new((MIN_STACK_DEPTH + num_overflow) as u64); - let b1 = Felt::new(step as u64); + let b1 = Felt::new(clk as u64); let h0 = Felt::ONE / (b0 - Felt::new(MIN_STACK_DEPTH as u64)); [b0, b1, h0] diff --git a/processor/src/stack/trace.rs b/processor/src/stack/trace.rs index fcc4de386c..e5410e8e93 100644 --- a/processor/src/stack/trace.rs +++ b/processor/src/stack/trace.rs @@ -73,50 +73,51 @@ impl StackTrace { // STACK ACCESSORS AND MUTATORS // -------------------------------------------------------------------------------------------- - /// Returns a copy of the item at the top of the stack at the specified step + /// Returns a copy of the item at the top of the stack at the specified clock cycle. #[inline(always)] - pub fn peek_at(&self, step: usize) -> Felt { - self.stack[0][step] + pub fn peek_at(&self, clk: usize) -> Felt { + self.stack[0][clk] } /// Returns the value located at the specified position on the stack at the specified clock /// cycle. #[inline(always)] - pub fn get_stack_value_at(&self, step: usize, pos: usize) -> Felt { - self.stack[pos][step] + pub fn get_stack_value_at(&self, clk: usize, pos: usize) -> Felt { + self.stack[pos][clk] } /// Sets the value at the specified position on the stack at the specified cycle. #[inline(always)] - pub fn set_stack_value_at(&mut self, step: usize, pos: usize, value: Felt) { - self.stack[pos][step] = value; + pub fn set_stack_value_at(&mut self, clk: usize, pos: usize, value: Felt) { + self.stack[pos][clk] = value; } - /// Return the specified number of states from the top of the stack at the specified step. - pub fn get_stack_values_at(&self, step: usize, num_items: usize) -> Vec { - self.get_stack_state_at(step)[..num_items].to_vec() + /// Return the specified number of states from the top of the stack at the specified clock + /// cycle. + pub fn get_stack_values_at(&self, clk: usize, num_items: usize) -> Vec { + self.get_stack_state_at(clk)[..num_items].to_vec() } - /// Returns the stack trace state at the specified step. + /// Returns the stack trace state at the specified clock cycle. /// /// Trace state is always 16 elements long and contains the top 16 values of the stack. - pub fn get_stack_state_at(&self, step: usize) -> StackTopState { + pub fn get_stack_state_at(&self, clk: usize) -> StackTopState { let mut result = [Felt::ZERO; MIN_STACK_DEPTH]; for (result, column) in result.iter_mut().zip(self.stack.iter()) { - *result = column[step]; + *result = column[clk]; } result } /// Copies the stack values starting at the specified position at the specified clock cycle to /// the same position at the next clock cycle. - pub fn copy_stack_state_at(&mut self, step: usize, start_pos: usize) { + pub fn copy_stack_state_at(&mut self, clk: usize, start_pos: usize) { debug_assert!( start_pos < MIN_STACK_DEPTH, "start cannot exceed stack top size" ); for i in start_pos..MIN_STACK_DEPTH { - self.stack[i][step + 1] = self.stack[i][step]; + self.stack[i][clk + 1] = self.stack[i][clk]; } } @@ -124,38 +125,38 @@ impl StackTrace { /// position - 1 at the next clock cycle. /// /// The final register is filled with the provided value in `last_value`. - pub fn stack_shift_left_at(&mut self, step: usize, start_pos: usize, last_value: Felt) { + pub fn stack_shift_left_at(&mut self, clk: usize, start_pos: usize, last_value: Felt) { for i in start_pos..=MAX_TOP_IDX { - self.stack[i - 1][step + 1] = self.stack[i][step]; + self.stack[i - 1][clk + 1] = self.stack[i][clk]; } - self.stack[MAX_TOP_IDX][step + 1] = last_value; + self.stack[MAX_TOP_IDX][clk + 1] = last_value; } /// Copies stack values starting at the specified position at the specified clock cycle to /// position + 1 at the next clock cycle. - pub fn stack_shift_right_at(&mut self, step: usize, start_pos: usize) { + pub fn stack_shift_right_at(&mut self, clk: usize, start_pos: usize) { for i in start_pos..MAX_TOP_IDX { - self.stack[i + 1][step + 1] = self.stack[i][step]; + self.stack[i + 1][clk + 1] = self.stack[i][clk]; } } // BOOKKEEPING & HELPER COLUMN ACCESSORS AND MUTATORS // -------------------------------------------------------------------------------------------- - /// Returns the trace state of the stack helper columns at the specified step. + /// Returns the trace state of the stack helper columns at the specified clock cycle. #[allow(dead_code)] - pub fn get_helpers_state_at(&self, step: usize) -> [Felt; NUM_STACK_HELPER_COLS] { + pub fn get_helpers_state_at(&self, clk: usize) -> [Felt; NUM_STACK_HELPER_COLS] { let mut result = [Felt::ZERO; NUM_STACK_HELPER_COLS]; for (result, column) in result.iter_mut().zip(self.helpers.iter()) { - *result = column[step]; + *result = column[clk]; } result } /// Copies the helper values at the specified clock cycle to the next clock cycle. - pub fn copy_helpers_at(&mut self, step: usize) { + pub fn copy_helpers_at(&mut self, clk: usize) { for i in 0..NUM_STACK_HELPER_COLS { - self.helpers[i][step + 1] = self.helpers[i][step]; + self.helpers[i][clk + 1] = self.helpers[i][clk]; } } @@ -168,14 +169,14 @@ impl StackTrace { /// b0: Increment the stack depth by one. /// b1: Save the address of the new top row in overflow table, which is the current clock cycle. /// h0: Set the value to 1 / (depth - 16). - pub fn helpers_shift_right_at(&mut self, step: usize) { + pub fn helpers_shift_right_at(&mut self, clk: usize) { // Increment b0 by one. - let b0 = self.helpers[0][step] + Felt::ONE; - self.helpers[0][step + 1] = b0; + let b0 = self.helpers[0][clk] + Felt::ONE; + self.helpers[0][clk + 1] = b0; // Set b1 to the curren tclock cycle. - self.helpers[1][step + 1] = Felt::new(step as u64); + self.helpers[1][clk + 1] = Felt::new(clk as u64); // Update the helper column to 1 / (b0 - 16). - self.helpers[2][step + 1] = Felt::ONE / (b0 - Felt::new(MIN_STACK_DEPTH as u64)); + self.helpers[2][clk + 1] = Felt::ONE / (b0 - Felt::new(MIN_STACK_DEPTH as u64)); } /// Updates the bookkeeping and helper columns to manage a left shift at the specified clock @@ -189,13 +190,13 @@ impl StackTrace { /// `next_overflow_addr`. /// h0: Set the value to 1 / (depth - 16) if the depth is still greater than the minimum stack /// depth, or to zero otherwise. - pub fn helpers_shift_left_at(&mut self, step: usize, next_overflow_addr: Felt) { + pub fn helpers_shift_left_at(&mut self, clk: usize, next_overflow_addr: Felt) { // Decrement b0 by one. - let b0 = self.helpers[0][step] - Felt::ONE; - self.helpers[0][step + 1] = b0; + let b0 = self.helpers[0][clk] - Felt::ONE; + self.helpers[0][clk + 1] = b0; // Set b1 to the overflow table address of the item at the top of the updated table. - self.helpers[1][step + 1] = next_overflow_addr; + self.helpers[1][clk + 1] = next_overflow_addr; // Update the helper column to 1 / (b0 - 16) if depth > MIN_STACK_DEPTH or 0 otherwise. let h0 = if b0.as_int() > MIN_STACK_DEPTH as u64 { @@ -203,7 +204,7 @@ impl StackTrace { } else { Felt::ZERO }; - self.helpers[2][step + 1] = h0; + self.helpers[2][clk + 1] = h0; } // UTILITY METHODS @@ -212,8 +213,8 @@ impl StackTrace { /// Makes sure there is enough memory allocated for the trace to accommodate a new row. /// /// Trace length is doubled every time it needs to be increased. - pub fn ensure_trace_capacity(&mut self, step: usize) { - if step + 1 >= self.trace_len() { + pub fn ensure_trace_capacity(&mut self, clk: usize) { + if clk + 1 >= self.trace_len() { let new_length = self.trace_len() * 2; for register in self.stack.iter_mut().chain(self.helpers.iter_mut()) { register.resize(new_length, Felt::ZERO); From ee082af31a46a42744eb024ba0fa8c7a57455873 Mon Sep 17 00:00:00 2001 From: grjte Date: Tue, 15 Mar 2022 15:50:18 +0000 Subject: [PATCH 26/26] feat: add values to RangeChecker in u32 ops --- processor/src/lib.rs | 3 ++ processor/src/operations/u32_ops.rs | 50 ++++++++++++++++++++++++++--- processor/src/trace.rs | 1 + 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/processor/src/lib.rs b/processor/src/lib.rs index ef5dff6816..e04ef9bcbc 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -22,6 +22,7 @@ mod stack; use stack::Stack; mod range; +use range::RangeChecker; mod hasher; use hasher::Hasher; @@ -71,6 +72,7 @@ struct Process { system: System, decoder: Decoder, stack: Stack, + range: RangeChecker, hasher: Hasher, bitwise: Bitwise, memory: Memory, @@ -83,6 +85,7 @@ impl Process { system: System::new(4), decoder: Decoder::new(), stack: Stack::new(&inputs, 4), + range: RangeChecker::new(), hasher: Hasher::new(), bitwise: Bitwise::new(), memory: Memory::new(), diff --git a/processor/src/operations/u32_ops.rs b/processor/src/operations/u32_ops.rs index fb0dd50807..05cde700dc 100644 --- a/processor/src/operations/u32_ops.rs +++ b/processor/src/operations/u32_ops.rs @@ -10,10 +10,11 @@ impl Process { let a = self.stack.get(0); let (lo, hi) = split_element(a); - // shift right first so stack depth is increased before we attempt to set the output values - self.stack.shift_right(1); + self.add_range_checks(lo, Some(hi)); + self.stack.set(0, hi); self.stack.set(1, lo); + self.stack.shift_right(1); Ok(()) } @@ -28,6 +29,8 @@ impl Process { let result = a + b; let (lo, hi) = split_element(result); + self.add_range_checks(lo, None); + self.stack.set(0, hi); self.stack.set(1, lo); self.stack.copy_state(2); @@ -46,6 +49,8 @@ impl Process { let result = Felt::new(a + b + c); let (lo, hi) = split_element(result); + self.add_range_checks(lo, None); + self.stack.set(0, hi); self.stack.set(1, lo); self.stack.shift_left(3); @@ -59,9 +64,13 @@ impl Process { let b = self.stack.get(0).as_int(); let a = self.stack.get(1).as_int(); let result = a.wrapping_sub(b); + let d = Felt::new(result >> 63); + let c = Felt::new((result as u32) as u64); + + self.add_range_checks(c, None); - self.stack.set(0, Felt::new(result >> 63)); - self.stack.set(1, Felt::new((result as u32) as u64)); + self.stack.set(0, d); + self.stack.set(1, c); self.stack.copy_state(2); Ok(()) } @@ -74,6 +83,8 @@ impl Process { let result = Felt::new(a * b); let (lo, hi) = split_element(result); + self.add_range_checks(lo, Some(hi)); + self.stack.set(0, hi); self.stack.set(1, lo); self.stack.copy_state(2); @@ -90,6 +101,8 @@ impl Process { let result = Felt::new(a * b + c); let (lo, hi) = split_element(result); + self.add_range_checks(lo, Some(hi)); + self.stack.set(0, hi); self.stack.set(1, lo); self.stack.shift_left(3); @@ -112,6 +125,12 @@ impl Process { let q = a / b; let r = a - q * b; + // These range checks help enforce that q <= a. + let lo = Felt::new(a - q); + // These range checks help enforce that r < b. + let hi = Felt::new(b - r - 1); + self.add_range_checks(lo, Some(hi)); + self.stack.set(0, Felt::new(r)); self.stack.set(1, Felt::new(q)); self.stack.copy_state(2); @@ -156,6 +175,20 @@ impl Process { self.stack.shift_left(2); Ok(()) } + + /// Adds 16-bit range checks to the RangeChecker for the high and low 16-bit limbs of one or two + /// field elements which are assumed to have 32-bit integer values. + fn add_range_checks(&mut self, lo: Felt, hi: Option) { + let (t0, t1) = split_element_to_u16(lo); + self.range.add_value(t0); + self.range.add_value(t1); + + if let Some(hi) = hi { + let (t2, t3) = split_element_to_u16(hi); + self.range.add_value(t2); + self.range.add_value(t3); + } + } } // HELPER FUNCTIONS @@ -169,6 +202,15 @@ fn split_element(value: Felt) -> (Felt, Felt) { (Felt::new(lo), Felt::new(hi)) } +/// Splits an element into two 16 bit integer limbs. It assumes that the field element contains a +/// valid 32-bit integer value. +fn split_element_to_u16(value: Felt) -> (u16, u16) { + let value = value.as_int() as u32; + let lo = value as u16; + let hi = (value >> 16) as u16; + (lo, hi) +} + // TESTS // ================================================================================================ diff --git a/processor/src/trace.rs b/processor/src/trace.rs index c7d2502e3e..936f8e76c7 100644 --- a/processor/src/trace.rs +++ b/processor/src/trace.rs @@ -32,6 +32,7 @@ impl ExecutionTrace { system, decoder: _, stack, + range: _, hasher, bitwise, memory,