-
Notifications
You must be signed in to change notification settings - Fork 244
/
Copy pathexecute.rs
208 lines (185 loc) · 8.97 KB
/
execute.rs
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
use acvm::acir::circuit::brillig::BrilligBytecode;
use acvm::acir::circuit::{
OpcodeLocation, Program, ResolvedAssertionPayload, ResolvedOpcodeLocation,
};
use acvm::acir::native_types::WitnessStack;
use acvm::pwg::{ACVMStatus, ErrorLocation, OpcodeNotSolvable, OpcodeResolutionError, ACVM};
use acvm::{acir::circuit::Circuit, acir::native_types::WitnessMap};
use acvm::{AcirField, BlackBoxFunctionSolver};
use crate::errors::ExecutionError;
use crate::NargoError;
use super::foreign_calls::ForeignCallExecutor;
struct ProgramExecutor<'a, F, B: BlackBoxFunctionSolver<F>, E: ForeignCallExecutor<F>> {
functions: &'a [Circuit<F>],
unconstrained_functions: &'a [BrilligBytecode<F>],
// This gets built as we run through the program looking at each function call
witness_stack: WitnessStack<F>,
blackbox_solver: &'a B,
foreign_call_executor: &'a mut E,
// The Noir compiler codegens per function and call stacks are not shared across ACIR function calls.
// We must rebuild a call stack when executing a program of many circuits.
call_stack: Vec<ResolvedOpcodeLocation>,
// Tracks the index of the current function we are executing.
// This is used to fetch the function we want to execute
// and to resolve call stack locations across many function calls.
current_function_index: usize,
}
impl<'a, F: AcirField, B: BlackBoxFunctionSolver<F>, E: ForeignCallExecutor<F>>
ProgramExecutor<'a, F, B, E>
{
fn new(
functions: &'a [Circuit<F>],
unconstrained_functions: &'a [BrilligBytecode<F>],
blackbox_solver: &'a B,
foreign_call_executor: &'a mut E,
) -> Self {
ProgramExecutor {
functions,
unconstrained_functions,
witness_stack: WitnessStack::default(),
blackbox_solver,
foreign_call_executor,
call_stack: Vec::default(),
current_function_index: 0,
}
}
fn finalize(self) -> WitnessStack<F> {
self.witness_stack
}
#[tracing::instrument(level = "trace", skip_all)]
fn execute_circuit(
&mut self,
initial_witness: WitnessMap<F>,
) -> Result<WitnessMap<F>, NargoError<F>> {
let circuit = &self.functions[self.current_function_index];
let mut acvm = ACVM::new(
self.blackbox_solver,
&circuit.opcodes,
initial_witness,
self.unconstrained_functions,
&circuit.assert_messages,
);
loop {
let solver_status = acvm.solve();
match solver_status {
ACVMStatus::Solved => break,
ACVMStatus::InProgress => {
unreachable!("Execution should not stop while in `InProgress` state.")
}
ACVMStatus::Failure(error) => {
let call_stack = match &error {
OpcodeResolutionError::UnsatisfiedConstrain {
opcode_location: ErrorLocation::Resolved(opcode_location),
..
}
| OpcodeResolutionError::IndexOutOfBounds {
opcode_location: ErrorLocation::Resolved(opcode_location),
..
}
| OpcodeResolutionError::InvalidInputBitSize {
opcode_location: ErrorLocation::Resolved(opcode_location),
..
} => {
let resolved_location = ResolvedOpcodeLocation {
acir_function_index: self.current_function_index,
opcode_location: *opcode_location,
};
self.call_stack.push(resolved_location);
Some(self.call_stack.clone())
}
OpcodeResolutionError::BrilligFunctionFailed { call_stack, .. } => {
let brillig_call_stack =
call_stack.iter().map(|location| ResolvedOpcodeLocation {
acir_function_index: self.current_function_index,
opcode_location: *location,
});
self.call_stack.extend(brillig_call_stack);
Some(self.call_stack.clone())
}
_ => None,
};
let assertion_payload: Option<ResolvedAssertionPayload<F>> = match &error {
OpcodeResolutionError::BrilligFunctionFailed { payload, .. }
| OpcodeResolutionError::UnsatisfiedConstrain { payload, .. } => {
payload.clone()
}
_ => None,
};
let brillig_function_id = match &error {
OpcodeResolutionError::BrilligFunctionFailed { function_id, .. } => {
Some(*function_id)
}
_ => None,
};
return Err(NargoError::ExecutionError(match assertion_payload {
Some(payload) => ExecutionError::AssertionFailed(
payload,
call_stack.expect("Should have call stack for an assertion failure"),
brillig_function_id,
),
None => ExecutionError::SolvingError(error, call_stack),
}));
}
ACVMStatus::RequiresForeignCall(foreign_call) => {
let foreign_call_result = self.foreign_call_executor.execute(&foreign_call)?;
acvm.resolve_pending_foreign_call(foreign_call_result);
}
ACVMStatus::RequiresAcirCall(call_info) => {
// Store the parent function index whose context we are currently executing
let acir_function_caller = self.current_function_index;
// Add call opcode to the call stack with a reference to the parent function index
self.call_stack.push(ResolvedOpcodeLocation {
acir_function_index: acir_function_caller,
opcode_location: OpcodeLocation::Acir(acvm.instruction_pointer()),
});
// Set current function to the circuit we are about to execute
self.current_function_index = call_info.id.as_usize();
// Execute the ACIR call
let acir_to_call = &self.functions[call_info.id.as_usize()];
let initial_witness = call_info.initial_witness;
let call_solved_witness = self.execute_circuit(initial_witness)?;
// Set tracking index back to the parent function after ACIR call execution
self.current_function_index = acir_function_caller;
let mut call_resolved_outputs = Vec::new();
for return_witness_index in acir_to_call.return_values.indices() {
if let Some(return_value) =
call_solved_witness.get_index(return_witness_index)
{
call_resolved_outputs.push(*return_value);
} else {
return Err(ExecutionError::SolvingError(
OpcodeNotSolvable::MissingAssignment(return_witness_index).into(),
None, // Missing assignment errors do not supply user-facing diagnostics so we do not need to attach a call stack
)
.into());
}
}
acvm.resolve_pending_acir_call(call_resolved_outputs);
self.witness_stack.push(call_info.id.0, call_solved_witness);
}
}
}
// Clear the call stack if we have succeeded in executing the circuit.
// This needs to be done or else all successful ACIR call stacks will also be
// included in a failure case.
self.call_stack.clear();
Ok(acvm.finalize())
}
}
#[tracing::instrument(level = "trace", skip_all)]
pub fn execute_program<F: AcirField, B: BlackBoxFunctionSolver<F>, E: ForeignCallExecutor<F>>(
program: &Program<F>,
initial_witness: WitnessMap<F>,
blackbox_solver: &B,
foreign_call_executor: &mut E,
) -> Result<WitnessStack<F>, NargoError<F>> {
let mut executor = ProgramExecutor::new(
&program.functions,
&program.unconstrained_functions,
blackbox_solver,
foreign_call_executor,
);
let main_witness = executor.execute_circuit(initial_witness)?;
executor.witness_stack.push(0, main_witness);
Ok(executor.finalize())
}