Skip to content

Commit 7feb658

Browse files
authored
feat(tooling): Skip program transformation when loaded from cache (#6689)
1 parent 304403f commit 7feb658

20 files changed

Lines changed: 102 additions & 46 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

acvm-repo/acir/src/circuit/brillig.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
55

66
/// Inputs for the Brillig VM. These are the initial inputs
77
/// that the Brillig VM will use to start.
8-
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
8+
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug, Hash)]
99
pub enum BrilligInputs<F> {
1010
Single(Expression<F>),
1111
Array(Vec<Expression<F>>),
@@ -14,7 +14,7 @@ pub enum BrilligInputs<F> {
1414

1515
/// Outputs for the Brillig VM. Once the VM has completed
1616
/// execution, this will be the object that is returned.
17-
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
17+
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug, Hash)]
1818
pub enum BrilligOutputs {
1919
Simple(Witness),
2020
Array(Vec<Witness>),
@@ -23,7 +23,7 @@ pub enum BrilligOutputs {
2323
/// This is purely a wrapper struct around a list of Brillig opcode's which represents
2424
/// a full Brillig function to be executed by the Brillig VM.
2525
/// This is stored separately on a program and accessed through a [BrilligPointer].
26-
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default, Debug)]
26+
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default, Debug, Hash)]
2727
pub struct BrilligBytecode<F> {
2828
pub bytecode: Vec<BrilligOpcode<F>>,
2929
}

acvm-repo/acir/src/circuit/mod.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use self::{brillig::BrilligBytecode, opcodes::BlockId};
2525
/// Bounded Expressions are useful if you are eventually going to pass the ACIR
2626
/// into a proving system which supports PLONK, where arithmetic expressions have a
2727
/// finite fan-in.
28-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
28+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default, Hash)]
2929
pub enum ExpressionWidth {
3030
#[default]
3131
Unbounded,
@@ -36,13 +36,13 @@ pub enum ExpressionWidth {
3636

3737
/// A program represented by multiple ACIR circuits. The execution trace of these
3838
/// circuits is dictated by construction of the [crate::native_types::WitnessStack].
39-
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
39+
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default, Hash)]
4040
pub struct Program<F> {
4141
pub functions: Vec<Circuit<F>>,
4242
pub unconstrained_functions: Vec<BrilligBytecode<F>>,
4343
}
4444

45-
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
45+
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default, Hash)]
4646
pub struct Circuit<F> {
4747
// current_witness_index is the highest witness index in the circuit. The next witness to be added to this circuit
4848
// will take on this value. (The value is cached here as an optimization.)
@@ -69,13 +69,13 @@ pub struct Circuit<F> {
6969
pub assert_messages: Vec<(OpcodeLocation, AssertionPayload<F>)>,
7070
}
7171

72-
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
72+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
7373
pub enum ExpressionOrMemory<F> {
7474
Expression(Expression<F>),
7575
Memory(BlockId),
7676
}
7777

78-
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
78+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
7979
pub struct AssertionPayload<F> {
8080
pub error_selector: u64,
8181
pub payload: Vec<ExpressionOrMemory<F>>,
@@ -355,7 +355,7 @@ impl<F: AcirField> std::fmt::Debug for Program<F> {
355355
}
356356
}
357357

358-
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
358+
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default, Hash)]
359359
pub struct PublicInputs(pub BTreeSet<Witness>);
360360

361361
impl PublicInputs {

acvm-repo/acir/src/circuit/opcodes.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ pub use black_box_function_call::{
1515
};
1616
pub use memory_operation::{BlockId, MemOp};
1717

18-
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
18+
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
1919
pub enum BlockType {
2020
Memory,
2121
CallData(u32),
@@ -29,7 +29,7 @@ impl BlockType {
2929
}
3030

3131
#[allow(clippy::large_enum_variant)]
32-
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
32+
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
3333
pub enum Opcode<F> {
3434
/// An `AssertZero` opcode adds the constraint that `P(w) = 0`, where
3535
/// `w=(w_1,..w_n)` is a tuple of `n` witnesses, and `P` is a multi-variate

acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ use thiserror::Error;
99
// Note: Some functions will not use all of the witness
1010
// So we need to supply how many bits of the witness is needed
1111

12-
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
12+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)]
1313
pub enum ConstantOrWitnessEnum<F> {
1414
Constant(F),
1515
Witness(Witness),
1616
}
1717

18-
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
18+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)]
1919
pub struct FunctionInput<F> {
2020
input: ConstantOrWitnessEnum<F>,
2121
num_bits: u32,
@@ -79,7 +79,7 @@ impl<F: std::fmt::Display> std::fmt::Display for FunctionInput<F> {
7979
}
8080
}
8181

82-
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
82+
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
8383
pub enum BlackBoxFuncCall<F> {
8484
AES128Encrypt {
8585
inputs: Vec<FunctionInput<F>>,

acvm-repo/acir/src/circuit/opcodes/memory_operation.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ pub struct BlockId(pub u32);
77

88
/// Operation on a block of memory
99
/// We can either write or read at an index in memory
10-
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
10+
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug, Hash)]
1111
pub struct MemOp<F> {
1212
/// A constant expression that can be 0 (read) or 1 (write)
1313
pub operation: Expression<F>,

acvm-repo/brillig/src/black_box.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
33

44
/// These opcodes provide an equivalent of ACIR blackbox functions.
55
/// They are implemented as native functions in the VM.
6-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
6+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
77
pub enum BlackBoxOp {
88
/// Encrypts a message using AES128.
99
AES128Encrypt {

acvm-repo/brillig/src/opcodes.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ impl MemoryAddress {
5656
}
5757

5858
/// Describes the memory layout for an array/vector element
59-
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
59+
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Hash)]
6060
pub enum HeapValueType {
6161
// A single field element is enough to represent the value with a given bit size
6262
Simple(BitSize),
@@ -81,7 +81,7 @@ impl HeapValueType {
8181
}
8282

8383
/// A fixed-sized array starting from a Brillig memory location.
84-
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy)]
84+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, Hash)]
8585
pub struct HeapArray {
8686
pub pointer: MemoryAddress,
8787
pub size: usize,
@@ -94,13 +94,13 @@ impl Default for HeapArray {
9494
}
9595

9696
/// A memory-sized vector passed starting from a Brillig memory location and with a memory-held size
97-
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy)]
97+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, Hash)]
9898
pub struct HeapVector {
9999
pub pointer: MemoryAddress,
100100
pub size: MemoryAddress,
101101
}
102102

103-
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord)]
103+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)]
104104
pub enum IntegerBitSize {
105105
U1,
106106
U8,
@@ -152,7 +152,7 @@ impl std::fmt::Display for IntegerBitSize {
152152
}
153153
}
154154

155-
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord)]
155+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)]
156156
pub enum BitSize {
157157
Field,
158158
Integer(IntegerBitSize),
@@ -181,7 +181,7 @@ impl BitSize {
181181
/// While we are usually agnostic to how memory is passed within Brillig,
182182
/// this needs to be encoded somehow when dealing with an external system.
183183
/// For simplicity, the extra type information is given right in the ForeignCall instructions.
184-
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy)]
184+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, Hash)]
185185
pub enum ValueOrArray {
186186
/// A single value passed to or from an external call
187187
/// It is an 'immediate' value - used without dereferencing.
@@ -198,7 +198,7 @@ pub enum ValueOrArray {
198198
HeapVector(HeapVector),
199199
}
200200

201-
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
201+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
202202
pub enum BrilligOpcode<F> {
203203
/// Takes the fields in addresses `lhs` and `rhs`
204204
/// Performs the specified binary operation
@@ -314,7 +314,7 @@ pub enum BrilligOpcode<F> {
314314
}
315315

316316
/// Binary fixed-length field expressions
317-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
317+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
318318
pub enum BinaryFieldOp {
319319
Add,
320320
Sub,
@@ -332,7 +332,7 @@ pub enum BinaryFieldOp {
332332
}
333333

334334
/// Binary fixed-length integer expressions
335-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
335+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
336336
pub enum BinaryIntOp {
337337
Add,
338338
Sub,

compiler/noirc_driver/src/debug.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::{
88

99
/// For a given file, we store the source code and the path to the file
1010
/// so consumers of the debug artifact can reconstruct the original source code structure.
11-
#[derive(Clone, Debug, Serialize, Deserialize)]
11+
#[derive(Clone, Debug, Serialize, Deserialize, Hash)]
1212
pub struct DebugFile {
1313
pub source: String,
1414
pub path: PathBuf,

compiler/noirc_driver/src/lib.rs

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,8 @@ pub fn compute_function_abi(
334334
///
335335
/// On success this returns the compiled program alongside any warnings that were found.
336336
/// On error this returns the non-empty list of warnings and errors.
337+
///
338+
/// See [compile_no_check] for further information about the use of `cached_program`.
337339
pub fn compile_main(
338340
context: &mut Context,
339341
crate_id: CrateId,
@@ -555,6 +557,15 @@ pub const DEFAULT_EXPRESSION_WIDTH: ExpressionWidth = ExpressionWidth::Bounded {
555557
/// Compile the current crate using `main_function` as the entrypoint.
556558
///
557559
/// This function assumes [`check_crate`] is called beforehand.
560+
///
561+
/// If the program is not returned from cache, it is backend-agnostic and must go through a transformation
562+
/// pass before usage in proof generation; if it's returned from cache these transformations might have
563+
/// already been applied.
564+
///
565+
/// The transformations are _not_ covered by the check that decides whether we can use the cached artifact.
566+
/// That comparison is based on on [CompiledProgram::hash] which is a persisted version of the hash of the input
567+
/// [`ast::Program`][noirc_frontend::monomorphization::ast::Program], whereas the output [`circuit::Program`][acir::circuit::Program]
568+
/// contains the final optimized ACIR opcodes, including the transformation done after this compilation.
558569
#[tracing::instrument(level = "trace", skip_all, fields(function_name = context.function_name(&main_function)))]
559570
pub fn compile_no_check(
560571
context: &mut Context,
@@ -569,8 +580,6 @@ pub fn compile_no_check(
569580
monomorphize(main_function, &mut context.def_interner)?
570581
};
571582

572-
let hash = fxhash::hash64(&program);
573-
let hashes_match = cached_program.as_ref().map_or(false, |program| program.hash == hash);
574583
if options.show_monomorphized {
575584
println!("{program}");
576585
}
@@ -584,10 +593,16 @@ pub fn compile_no_check(
584593
|| options.show_ssa
585594
|| options.emit_ssa;
586595

587-
if !force_compile && hashes_match {
588-
info!("Program matches existing artifact, returning early");
589-
return Ok(cached_program.expect("cache must exist for hashes to match"));
596+
// Hash the AST program, which is going to be used to fingerprint the compilation artifact.
597+
let hash = fxhash::hash64(&program);
598+
599+
if let Some(cached_program) = cached_program {
600+
if !force_compile && cached_program.hash == hash {
601+
info!("Program matches existing artifact, returning early");
602+
return Ok(cached_program);
603+
}
590604
}
605+
591606
let return_visibility = program.return_visibility;
592607
let ssa_evaluator_options = noirc_evaluator::ssa::SsaEvaluatorOptions {
593608
ssa_logging: match &options.show_ssa_pass_name {

0 commit comments

Comments
 (0)