1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
use super::{
brillig::{BrilligInputs, BrilligOutputs},
directives::Directive,
};
use crate::native_types::{Expression, Witness};
use serde::{Deserialize, Serialize};
mod black_box_function_call;
mod memory_operation;
pub use black_box_function_call::{BlackBoxFuncCall, FunctionInput};
pub use memory_operation::{BlockId, MemOp};
#[allow(clippy::large_enum_variant)]
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Opcode {
AssertZero(Expression),
/// Calls to "gadgets" which rely on backends implementing support for specialized constraints.
///
/// Often used for exposing more efficient implementations of SNARK-unfriendly computations.
BlackBoxFuncCall(BlackBoxFuncCall),
Directive(Directive),
/// Atomic operation on a block of memory
MemoryOp {
block_id: BlockId,
op: MemOp,
/// Predicate of the memory operation - indicates if it should be skipped
predicate: Option<Expression>,
},
MemoryInit {
block_id: BlockId,
init: Vec<Witness>,
},
/// Calls to unconstrained functions
BrilligCall {
/// Id for the function being called. It is the responsibility of the executor
/// to fetch the appropriate Brillig bytecode from this id.
id: u32,
/// Inputs to the function call
inputs: Vec<BrilligInputs>,
/// Outputs to the function call
outputs: Vec<BrilligOutputs>,
/// Predicate of the Brillig execution - indicates if it should be skipped
predicate: Option<Expression>,
},
/// Calls to functions represented as a separate circuit. A call opcode allows us
/// to build a call stack when executing the outer-most circuit.
Call {
/// Id for the function being called. It is the responsibility of the executor
/// to fetch the appropriate circuit from this id.
id: u32,
/// Inputs to the function call
inputs: Vec<Witness>,
/// Outputs of the function call
outputs: Vec<Witness>,
/// Predicate of the circuit execution - indicates if it should be skipped
predicate: Option<Expression>,
},
}
impl std::fmt::Display for Opcode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Opcode::AssertZero(expr) => {
write!(f, "EXPR [ ")?;
for i in &expr.mul_terms {
write!(f, "({}, _{}, _{}) ", i.0, i.1.witness_index(), i.2.witness_index())?;
}
for i in &expr.linear_combinations {
write!(f, "({}, _{}) ", i.0, i.1.witness_index())?;
}
write!(f, "{}", expr.q_c)?;
write!(f, " ]")
}
Opcode::BlackBoxFuncCall(g) => write!(f, "{g}"),
Opcode::Directive(Directive::ToLeRadix { a, b, radix: _ }) => {
write!(f, "DIR::TORADIX ")?;
write!(
f,
// TODO (Note): this assumes that the decomposed bits have contiguous witness indices
// This should be the case, however, we can also have a function which checks this
"(_{}, [_{}..._{}] )",
a,
b.first().unwrap().witness_index(),
b.last().unwrap().witness_index(),
)
}
Opcode::MemoryOp { block_id, op, predicate } => {
write!(f, "MEM ")?;
if let Some(pred) = predicate {
writeln!(f, "PREDICATE = {pred}")?;
}
let is_read = op.operation.is_zero();
let is_write = op.operation == Expression::one();
if is_read {
write!(f, "(id: {}, read at: {}, value: {}) ", block_id.0, op.index, op.value)
} else if is_write {
write!(f, "(id: {}, write {} at: {}) ", block_id.0, op.value, op.index)
} else {
write!(f, "(id: {}, op {} at: {}) ", block_id.0, op.operation, op.index)
}
}
Opcode::MemoryInit { block_id, init } => {
write!(f, "INIT ")?;
write!(f, "(id: {}, len: {}) ", block_id.0, init.len())
}
// We keep the display for a BrilligCall and circuit Call separate as they
// are distinct in their functionality and we should maintain this separation for debugging.
Opcode::BrilligCall { id, inputs, outputs, predicate } => {
write!(f, "BRILLIG CALL func {}: ", id)?;
if let Some(pred) = predicate {
writeln!(f, "PREDICATE = {pred}")?;
}
write!(f, "inputs: {:?}, ", inputs)?;
write!(f, "outputs: {:?}", outputs)
}
Opcode::Call { id, inputs, outputs, predicate } => {
write!(f, "CALL func {}: ", id)?;
if let Some(pred) = predicate {
writeln!(f, "PREDICATE = {pred}")?;
}
write!(f, "inputs: {:?}, ", inputs)?;
write!(f, "outputs: {:?}", outputs)
}
}
}
}
impl std::fmt::Debug for Opcode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self, f)
}
}