essential_vm/
sync.rs

1//! Items related to stepping forward VM execution by synchronous operations.
2
3use crate::{
4    access, alu, asm, crypto,
5    error::{
6        EvalSyncError, EvalSyncResult, ExecSyncError, ExecSyncResult, OpSyncError, OpSyncResult,
7    },
8    pred, repeat, total_control_flow,
9    types::convert::bool_from_word,
10    Access, LazyCache, Memory, OpAccess, OpSync, ProgramControlFlow, Repeat, Stack, Vm,
11};
12
13impl From<asm::Access> for OpSync {
14    fn from(op: asm::Access) -> Self {
15        Self::Access(op)
16    }
17}
18
19impl From<asm::Alu> for OpSync {
20    fn from(op: asm::Alu) -> Self {
21        Self::Alu(op)
22    }
23}
24
25impl From<asm::TotalControlFlow> for OpSync {
26    fn from(op: asm::TotalControlFlow) -> Self {
27        Self::ControlFlow(op)
28    }
29}
30
31impl From<asm::Crypto> for OpSync {
32    fn from(op: asm::Crypto) -> Self {
33        Self::Crypto(op)
34    }
35}
36
37impl From<asm::Memory> for OpSync {
38    fn from(op: asm::Memory) -> Self {
39        Self::Memory(op)
40    }
41}
42
43impl From<asm::Pred> for OpSync {
44    fn from(op: asm::Pred) -> Self {
45        Self::Pred(op)
46    }
47}
48
49impl From<asm::Stack> for OpSync {
50    fn from(op: asm::Stack) -> Self {
51        Self::Stack(op)
52    }
53}
54
55/// Evaluate a slice of synchronous operations and return their boolean result.
56///
57/// This is the same as [`exec_ops`], but retrieves the boolean result from the resulting stack.
58pub fn eval_ops(ops: &[OpSync], access: Access) -> EvalSyncResult<bool> {
59    eval(ops, access)
60}
61
62/// Evaluate the operations of a single synchronous program and return its boolean result.
63///
64/// This is the same as [`exec`], but retrieves the boolean result from the resulting stack.
65pub fn eval<OA>(op_access: OA, access: Access) -> EvalSyncResult<bool>
66where
67    OA: OpAccess<Op = OpSync>,
68    OA::Error: Into<OpSyncError>,
69{
70    let stack = exec(op_access, access)?;
71    let word = match stack.last() {
72        Some(&w) => w,
73        None => return Err(EvalSyncError::InvalidEvaluation(stack)),
74    };
75    bool_from_word(word).ok_or_else(|| EvalSyncError::InvalidEvaluation(stack))
76}
77
78/// Execute a slice of synchronous operations and return the resulting stack.
79pub fn exec_ops(ops: &[OpSync], access: Access) -> ExecSyncResult<Stack> {
80    exec(ops, access)
81}
82
83/// Execute the given synchronous operations and return the resulting stack.
84pub fn exec<OA>(mut op_access: OA, access: Access) -> ExecSyncResult<Stack>
85where
86    OA: OpAccess<Op = OpSync>,
87    OA::Error: Into<OpSyncError>,
88{
89    let mut pc = 0;
90    let mut stack = Stack::default();
91    let mut memory = Memory::new();
92    let mut repeat = Repeat::new();
93    let cache = LazyCache::new();
94    while let Some(res) = op_access.op_access(pc) {
95        let op = res.map_err(|err| ExecSyncError(pc, err.into()))?;
96
97        let res = step_op(access, op, &mut stack, &mut memory, pc, &mut repeat, &cache);
98
99        #[cfg(feature = "tracing")]
100        crate::trace_op_res(&mut op_access, pc, &stack, &memory, res.as_ref());
101
102        let update = match res {
103            Ok(update) => update,
104            Err(err) => return Err(ExecSyncError(pc, err)),
105        };
106
107        match update {
108            Some(ProgramControlFlow::Pc(new_pc)) => pc = new_pc,
109            Some(ProgramControlFlow::Halt) => break,
110            None => pc += 1,
111        }
112    }
113    Ok(stack)
114}
115
116/// Step forward the VM by a single synchronous operation.
117///
118/// Returns a `Some(usize)` representing the new program counter resulting from
119/// this step, or `None` in the case that execution has halted.
120pub fn step_op_sync(op: OpSync, access: Access, vm: &mut Vm) -> OpSyncResult<Option<usize>> {
121    let Vm {
122        stack,
123        repeat,
124        pc,
125        memory,
126        cache,
127        ..
128    } = vm;
129    match step_op(access, op, stack, memory, *pc, repeat, cache)? {
130        Some(ProgramControlFlow::Pc(pc)) => return Ok(Some(pc)),
131        Some(ProgramControlFlow::Halt) => return Ok(None),
132        None => (),
133    }
134    // Every operation besides control flow steps forward program counter by 1.
135    let new_pc = vm.pc.checked_add(1).ok_or(OpSyncError::PcOverflow)?;
136    Ok(Some(new_pc))
137}
138
139/// Step forward execution by the given synchronous operation.
140pub fn step_op(
141    access: Access,
142    op: OpSync,
143    stack: &mut Stack,
144    memory: &mut Memory,
145    pc: usize,
146    repeat: &mut Repeat,
147    cache: &LazyCache,
148) -> OpSyncResult<Option<ProgramControlFlow>> {
149    match op {
150        OpSync::Access(op) => step_op_access(access, op, stack, repeat, cache).map(|_| None),
151        OpSync::Alu(op) => step_op_alu(op, stack).map(|_| None),
152        OpSync::Crypto(op) => step_op_crypto(op, stack).map(|_| None),
153        OpSync::Pred(op) => step_op_pred(op, stack).map(|_| None),
154        OpSync::Stack(op) => step_op_stack(op, pc, stack, repeat),
155        OpSync::ControlFlow(op) => step_op_total_control_flow(op, stack, pc),
156        OpSync::Memory(op) => step_op_memory(op, stack, memory).map(|_| None),
157    }
158}
159
160/// Step forward execution by the given access operation.
161pub fn step_op_access(
162    access: Access,
163    op: asm::Access,
164    stack: &mut Stack,
165    repeat: &mut Repeat,
166    cache: &LazyCache,
167) -> OpSyncResult<()> {
168    match op {
169        asm::Access::PredicateData => {
170            access::predicate_data(&access.this_solution().predicate_data, stack)
171        }
172        asm::Access::PredicateDataLen => {
173            access::predicate_data_len(&access.this_solution().predicate_data, stack)
174                .map_err(From::from)
175        }
176        asm::Access::PredicateDataSlots => {
177            access::predicate_data_slots(stack, &access.this_solution().predicate_data)
178        }
179        asm::Access::MutKeys => access::push_mut_keys(access, stack),
180        asm::Access::ThisAddress => access::this_address(access.this_solution(), stack),
181        asm::Access::ThisContractAddress => {
182            access::this_contract_address(access.this_solution(), stack)
183        }
184        asm::Access::RepeatCounter => access::repeat_counter(stack, repeat),
185        asm::Access::PredicateExists => access::predicate_exists(stack, access.solutions, cache),
186    }
187}
188
189/// Step forward execution by the given ALU operation.
190pub fn step_op_alu(op: asm::Alu, stack: &mut Stack) -> OpSyncResult<()> {
191    match op {
192        asm::Alu::Add => stack.pop2_push1(alu::add),
193        asm::Alu::Sub => stack.pop2_push1(alu::sub),
194        asm::Alu::Mul => stack.pop2_push1(alu::mul),
195        asm::Alu::Div => stack.pop2_push1(alu::div),
196        asm::Alu::Mod => stack.pop2_push1(alu::mod_),
197        asm::Alu::Shl => stack.pop2_push1(alu::shl),
198        asm::Alu::Shr => stack.pop2_push1(alu::shr),
199        asm::Alu::ShrI => stack.pop2_push1(alu::arithmetic_shr),
200    }
201}
202
203/// Step forward execution by the given crypto operation.
204pub fn step_op_crypto(op: asm::Crypto, stack: &mut Stack) -> OpSyncResult<()> {
205    match op {
206        asm::Crypto::Sha256 => crypto::sha256(stack),
207        asm::Crypto::VerifyEd25519 => crypto::verify_ed25519(stack),
208        asm::Crypto::RecoverSecp256k1 => crypto::recover_secp256k1(stack),
209    }
210}
211
212/// Step forward execution by the given predicate operation.
213pub fn step_op_pred(op: asm::Pred, stack: &mut Stack) -> OpSyncResult<()> {
214    match op {
215        asm::Pred::Eq => stack.pop2_push1(|a, b| Ok((a == b).into())),
216        asm::Pred::EqRange => pred::eq_range(stack),
217        asm::Pred::Gt => stack.pop2_push1(|a, b| Ok((a > b).into())),
218        asm::Pred::Lt => stack.pop2_push1(|a, b| Ok((a < b).into())),
219        asm::Pred::Gte => stack.pop2_push1(|a, b| Ok((a >= b).into())),
220        asm::Pred::Lte => stack.pop2_push1(|a, b| Ok((a <= b).into())),
221        asm::Pred::And => stack.pop2_push1(|a, b| Ok((a != 0 && b != 0).into())),
222        asm::Pred::Or => stack.pop2_push1(|a, b| Ok((a != 0 || b != 0).into())),
223        asm::Pred::Not => stack.pop1_push1(|a| Ok((a == 0).into())),
224        asm::Pred::EqSet => pred::eq_set(stack),
225        asm::Pred::BitAnd => stack.pop2_push1(|a, b| Ok(a & b)),
226        asm::Pred::BitOr => stack.pop2_push1(|a, b| Ok(a | b)),
227    }
228}
229
230/// Step forward execution by the given stack operation.
231pub fn step_op_stack(
232    op: asm::Stack,
233    pc: usize,
234    stack: &mut Stack,
235    repeat: &mut Repeat,
236) -> OpSyncResult<Option<ProgramControlFlow>> {
237    if let asm::Stack::RepeatEnd = op {
238        return Ok(repeat.repeat()?.map(ProgramControlFlow::Pc));
239    }
240    let r = match op {
241        asm::Stack::Dup => stack.pop1_push2(|w| Ok([w, w])),
242        asm::Stack::DupFrom => stack.dup_from().map_err(From::from),
243        asm::Stack::Push(word) => stack.push(word).map_err(From::from),
244        asm::Stack::Pop => stack.pop().map(|_| ()).map_err(From::from),
245        asm::Stack::Swap => stack.pop2_push2(|a, b| Ok([b, a])),
246        asm::Stack::SwapIndex => stack.swap_index().map_err(From::from),
247        asm::Stack::Select => stack.select().map_err(From::from),
248        asm::Stack::SelectRange => stack.select_range().map_err(From::from),
249        asm::Stack::Repeat => repeat::repeat(pc, stack, repeat),
250        asm::Stack::Reserve => stack.reserve_zeroed().map_err(From::from),
251        asm::Stack::Load => stack.load().map_err(From::from),
252        asm::Stack::Store => stack.store().map_err(From::from),
253        asm::Stack::RepeatEnd => unreachable!(),
254    };
255    r.map(|_| None)
256}
257
258/// Step forward execution by the given total control flow operation.
259pub fn step_op_total_control_flow(
260    op: asm::TotalControlFlow,
261    stack: &mut Stack,
262    pc: usize,
263) -> OpSyncResult<Option<ProgramControlFlow>> {
264    match op {
265        asm::TotalControlFlow::JumpForwardIf => total_control_flow::jump_forward_if(stack, pc),
266        asm::TotalControlFlow::HaltIf => total_control_flow::halt_if(stack),
267        asm::TotalControlFlow::Halt => Ok(Some(ProgramControlFlow::Halt)),
268        asm::TotalControlFlow::PanicIf => total_control_flow::panic_if(stack).map(|_| None),
269    }
270}
271
272/// Step forward execution by the given memory operation.
273pub fn step_op_memory(op: asm::Memory, stack: &mut Stack, memory: &mut Memory) -> OpSyncResult<()> {
274    match op {
275        asm::Memory::Alloc => {
276            let w = stack.pop()?;
277            let len = memory.len()?;
278            memory.alloc(w)?;
279            Ok(stack.push(len)?)
280        }
281        asm::Memory::Store => {
282            let [addr, w] = stack.pop2()?;
283            memory.store(addr, w)?;
284            Ok(())
285        }
286        asm::Memory::Load => stack.pop1_push1(|addr| {
287            let w = memory.load(addr)?;
288            Ok(w)
289        }),
290        asm::Memory::Free => {
291            let addr = stack.pop()?;
292            memory.free(addr)?;
293            Ok(())
294        }
295        asm::Memory::LoadRange => {
296            let [addr, size] = stack.pop2()?;
297            let words = memory.load_range(addr, size)?;
298            Ok(stack.extend(words)?)
299        }
300        asm::Memory::StoreRange => {
301            let value_len = stack.pop_len()?;
302            let addr_and_value_len = value_len.saturating_add(1); // Pop the addr too.
303            stack.pop_words(addr_and_value_len, |words| {
304                let (addr, value) = words.split_at(1);
305                memory.store_range(addr[0], value)?;
306                Ok::<_, OpSyncError>(())
307            })?;
308            Ok(())
309        }
310    }
311}
312
313#[cfg(test)]
314pub(crate) mod test_util {
315    use crate::{
316        types::{solution::Solution, ContentAddress, PredicateAddress},
317        *,
318    };
319    use asm::Word;
320    use std::collections::HashSet;
321
322    pub(crate) const TEST_SET_CA: ContentAddress = ContentAddress([0xFF; 32]);
323    pub(crate) const TEST_PREDICATE_CA: ContentAddress = ContentAddress([0xAA; 32]);
324    pub(crate) const TEST_PREDICATE_ADDR: PredicateAddress = PredicateAddress {
325        contract: TEST_SET_CA,
326        predicate: TEST_PREDICATE_CA,
327    };
328    pub(crate) const TEST_SOLUTION: Solution = Solution {
329        predicate_to_solve: TEST_PREDICATE_ADDR,
330        predicate_data: vec![],
331        state_mutations: vec![],
332    };
333
334    pub(crate) fn test_empty_keys() -> &'static HashSet<&'static [Word]> {
335        static INSTANCE: std::sync::LazyLock<HashSet<&[Word]>> =
336            std::sync::LazyLock::new(|| HashSet::with_capacity(0));
337        &INSTANCE
338    }
339
340    pub(crate) fn test_solutions() -> &'static [Solution] {
341        static INSTANCE: std::sync::LazyLock<[Solution; 1]> =
342            std::sync::LazyLock::new(|| [TEST_SOLUTION]);
343        &*INSTANCE
344    }
345
346    pub(crate) fn test_access() -> &'static Access<'static> {
347        static INSTANCE: std::sync::LazyLock<Access> = std::sync::LazyLock::new(|| Access {
348            solutions: test_solutions(),
349            index: 0,
350            mutable_keys: test_empty_keys(),
351        });
352        &INSTANCE
353    }
354}