miden-processor 0.14.0

Miden VM processor
Documentation
use super::*;

#[rstest]
// ---- syscalls --------------------------------

// check stack is preserved after syscall
#[case(Some("export.foo add end"), "begin push.1 syscall.foo swap.8 drop end", vec![16_u32.into(); 16])]
// check that `fn_hash` register is updated correctly
#[case(Some("export.foo caller end"), "begin syscall.foo end", vec![16_u32.into(); 16])]
#[case(Some("export.foo caller end"), "proc.bar syscall.foo end begin call.bar end", vec![16_u32.into(); 16])]
// check that clk works correctly through syscalls
#[case(Some("export.foo clk add end"), "begin syscall.foo end", vec![16_u32.into(); 16])]
// check that fmp register is updated correctly after syscall
#[case(Some("export.foo.2 locaddr.0 locaddr.1 swap.8 drop swap.8 drop end"), "proc.bar syscall.foo end begin call.bar end", vec![16_u32.into(); 16])]
// check that memory context is updated correctly across a syscall (i.e. anything stored before the
// syscall is retrievable after, but not during)
#[case(Some("export.foo add end"), "proc.bar push.100 mem_store.44 syscall.foo mem_load.44 swap.8 drop end begin call.bar end", vec![16_u32.into(); 16])]
// check that syscalls share the same memory context
#[case(Some("export.foo push.100 mem_store.44 end export.baz mem_load.44 swap.8 drop end"), 
    "proc.bar 
        syscall.foo syscall.baz 
    end 
    begin call.bar end", 
    vec![16_u32.into(); 16]
)]
// ---- calls ------------------------

// check stack is preserved after call
#[case(None, "proc.foo add end begin push.1 call.foo swap.8 drop end", vec![16_u32.into(); 16])]
// check that `clk` works correctly though calls
#[case(None, "
    proc.foo clk add end 
    begin push.1 
    if.true call.foo else swap end 
    clk swap.8 drop
    end", 
    vec![16_u32.into(); 16]
)]
// check that fmp register is updated correctly after call
#[case(None,"
    proc.foo.2 locaddr.0 locaddr.1 swap.8 drop swap.8 drop end
    begin call.foo end", 
    vec![16_u32.into(); 16]
)]
// check that 2 functions creating different memory contexts don't interfere with each other
#[case(None,"
    proc.foo push.100 mem_store.44 end
    proc.bar mem_load.44 assertz end
    begin call.foo mem_load.44 assertz call.bar end", 
    vec![16_u32.into(); 16]
)]
// check that memory context is updated correctly across a call (i.e. anything stored before the
// call is retrievable after, but not during)
#[case(None,"
    proc.foo mem_load.44 assertz end
    proc.bar push.100 mem_store.44 call.foo mem_load.44 swap.8 drop end
    begin call.bar end", 
    vec![16_u32.into(); 16]
)]
// ---- dyncalls ------------------------

// check stack is preserved after dyncall
#[case(None, "
    proc.foo add end 
    begin 
        procref.foo mem_storew.100 dropw push.100
        dyncall swap.8 drop 
    end", 
    vec![16_u32.into(); 16]
)]
// check that `clk` works correctly though dyncalls
#[case(None, "
    proc.foo clk add end 
    begin 
        push.1 
        if.true 
            procref.foo mem_storew.100 dropw
            push.100 dyncall
            push.100 dyncall
        else 
            swap 
        end 
        clk swap.8 drop
    end", 
    vec![16_u32.into(); 16]
)]
// check that fmp register is updated correctly after dyncall
#[case(None,"
    proc.foo.2 locaddr.0 locaddr.1 swap.8 drop swap.8 drop end
    begin 
        procref.foo mem_storew.100 dropw push.100
        dyncall
    end", 
    vec![16_u32.into(); 16]
)]
// check that 2 functions creating different memory contexts don't interfere with each other
#[case(None,"
    proc.foo push.100 mem_store.44 end
    proc.bar mem_load.44 assertz end
    begin 
        procref.foo mem_storew.100 dropw push.100 dyncall
        mem_load.44 assertz 
        procref.bar mem_storew.104 dropw push.104 dyncall
    end", 
    vec![16_u32.into(); 16]
)]
// check that memory context is updated correctly across a dyncall (i.e. anything stored before the
// call is retrievable after, but not during)
#[case(None,"
    proc.foo mem_load.44 assertz end
    proc.bar 
        push.100 mem_store.44 
        procref.foo mem_storew.104 dropw push.104 dyncall
        mem_load.44 swap.8 drop 
    end
    begin 
        procref.bar mem_storew.104 dropw push.104 dyncall
    end", 
    vec![16_u32.into(); 16]
)]
// ---- dyn ------------------------

// check stack is preserved after dynexec
#[case(None, "
    proc.foo add end 
    begin 
        procref.foo mem_storew.100 dropw push.100
        dynexec swap.8 drop 
    end", 
    vec![16_u32.into(); 16]
)]
// check that `clk` works correctly though dynexecs
#[case(None, "
    proc.foo clk add end 
    begin 
        push.1 
        if.true 
            procref.foo mem_storew.100 dropw
            push.100 dynexec
            push.100 dynexec
        else 
            swap 
        end 
        clk swap.8 drop
    end", 
    vec![16_u32.into(); 16]
)]
// check that fmp register is updated correctly after dynexec
#[case(None,"
    proc.foo.2 locaddr.0 locaddr.1 swap.8 drop swap.8 drop end
    begin 
        procref.foo mem_storew.100 dropw push.100
        dynexec
    end", 
    vec![16_u32.into(); 16]
)]
// check that dynexec doesn't create a new memory context
#[case(None,"
    proc.foo push.100 mem_store.44 end
    proc.bar mem_load.44 sub.100 assertz end
    begin 
        procref.foo mem_storew.104 dropw push.104 dynexec
        mem_load.44 sub.100 assertz 
        procref.bar mem_storew.108 dropw push.108 dynexec
    end", 
    vec![16_u32.into(); 16]
)]
// ---- loop --------------------------------

// check that the loop is never entered if the condition is false (and that clk is properly updated)
#[case(None, "begin while.true push.1 assertz end clk swap.8 drop end", vec![3_u32.into(), 2_u32.into(), 1_u32.into(), ZERO])]
// check that the loop is entered if the condition is true, and that the stack and clock are managed
// properly
#[case(None,
    "begin 
        while.true 
            clk swap.15 drop
        end 
        clk swap.8 drop 
        end",
    vec![42_u32.into(), ZERO, ONE, ONE, ONE, ONE]
)]
// ---- horner ops --------------------------------
#[case(None,
    "begin 
        push.1.2.3.4 mem_storew.40 dropw
        horner_eval_base
        end",
    // first 3 addresses in the vec are the alpha_ptr, acc_high and acc_low, respectively.
    vec![100_u32.into(), 4_u32.into(), 40_u32.into(), 4_u32.into(), 5_u32.into(), 6_u32.into(), 7_u32.into(),
        8_u32.into(), 9_u32.into(), 10_u32.into(), 11_u32.into(), 12_u32.into(), 13_u32.into(),
        14_u32.into(), 15_u32.into(), 16_u32.into()]
)]
#[case(None,
    "begin 
        push.1.2.3.4 mem_storew.40 dropw
        horner_eval_ext
        end",
    // first 3 addresses in the vec are the alpha_ptr, acc_high and acc_low, respectively.
    vec![100_u32.into(), 4_u32.into(), 40_u32.into(), 4_u32.into(), 5_u32.into(), 6_u32.into(), 7_u32.into(),
        8_u32.into(), 9_u32.into(), 10_u32.into(), 11_u32.into(), 12_u32.into(), 13_u32.into(),
        14_u32.into(), 15_u32.into(), 16_u32.into()]
)]
// ---- u32 ops --------------------------------
// check that u32 6/3 works as expected
#[case(None,"
    begin 
        u32divmod
    end", 
    vec![6_u32.into(), 3_u32.into()]
)]
// check that overflowing add properly sets the overflow bit
#[case(None,"
    begin 
        u32overflowing_add sub.1 assertz
    end", 
    vec![Felt::from(u32::MAX), ONE]
)]
fn test_masm_consistency(
    #[case] kernel_source: Option<&'static str>,
    #[case] program_source: &'static str,
    #[case] stack_inputs: Vec<Felt>,
) {
    let (program, kernel_lib) = {
        let source_manager = Arc::new(DefaultSourceManager::default());

        match kernel_source {
            Some(kernel_source) => {
                let kernel_lib =
                    Assembler::new(source_manager.clone()).assemble_kernel(kernel_source).unwrap();
                let program = Assembler::with_kernel(source_manager, kernel_lib.clone())
                    .assemble_program(program_source)
                    .unwrap();

                (program, Some(kernel_lib))
            },
            None => {
                let program =
                    Assembler::new(source_manager).assemble_program(program_source).unwrap();
                (program, None)
            },
        }
    };

    let mut host = DefaultHost::default();
    if let Some(kernel_lib) = &kernel_lib {
        host.load_mast_forest(kernel_lib.mast_forest().clone()).unwrap();
    }

    // fast processor
    let processor = FastProcessor::new(&stack_inputs);
    let fast_stack_outputs = processor.execute(&program, &mut host).unwrap();

    // slow processor
    let mut slow_processor = Process::new(
        kernel_lib.map(|k| k.kernel().clone()).unwrap_or_default(),
        StackInputs::new(stack_inputs).unwrap(),
        ExecutionOptions::default(),
    );
    let slow_stack_outputs = slow_processor.execute(&program, &mut host).unwrap();

    assert_eq!(fast_stack_outputs, slow_stack_outputs);
}

/// Tests that emitted errors are consistent between the fast and slow processors.
#[rstest]
// check that error is returned if condition is not a boolean
#[case(None, "begin while.true swap end end", vec![2_u32.into(); 16])]
#[case(None, "begin while.true push.100 end end", vec![ONE; 16])]
// check that dynamically calling a hash that doesn't exist fails
#[case(None,"
    begin 
        dyncall
    end", 
    vec![16_u32.into(); 16]
)]
// check that dynamically calling a hash that doesn't exist fails
#[case(None,"
    begin 
        dynexec
    end", 
    vec![16_u32.into(); 16]
)]
// check that u32 division by 0 results in an error
#[case(None,"
    begin 
        u32divmod
    end", 
    vec![ZERO; 16]
)]
// check that adding any value to a u32::MAX results in an error
#[case(None,"
    begin 
        u32overflowing_add
    end", 
    vec![Felt::from(u32::MAX) + ONE, ZERO]
)]
fn test_masm_errors_consistency(
    #[case] kernel_source: Option<&'static str>,
    #[case] program_source: &'static str,
    #[case] stack_inputs: Vec<Felt>,
) {
    let (program, kernel_lib) = {
        let source_manager = Arc::new(DefaultSourceManager::default());

        match kernel_source {
            Some(kernel_source) => {
                let kernel_lib =
                    Assembler::new(source_manager.clone()).assemble_kernel(kernel_source).unwrap();
                let program = Assembler::with_kernel(source_manager, kernel_lib.clone())
                    .assemble_program(program_source)
                    .unwrap();

                (program, Some(kernel_lib))
            },
            None => {
                let program =
                    Assembler::new(source_manager).assemble_program(program_source).unwrap();
                (program, None)
            },
        }
    };

    let mut host = DefaultHost::default();
    if let Some(kernel_lib) = &kernel_lib {
        host.load_mast_forest(kernel_lib.mast_forest().clone()).unwrap();
    }

    // fast processor
    let processor = FastProcessor::new(&stack_inputs);
    let fast_stack_outputs = processor.execute(&program, &mut host).unwrap_err();

    // slow processor
    let mut slow_processor = Process::new(
        kernel_lib.map(|k| k.kernel().clone()).unwrap_or_default(),
        StackInputs::new(stack_inputs).unwrap(),
        ExecutionOptions::default(),
    );
    let slow_stack_outputs = slow_processor.execute(&program, &mut host).unwrap_err();

    assert_eq!(fast_stack_outputs.to_string(), slow_stack_outputs.to_string());
}