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
#![forbid(unsafe_code)]
#![warn(unreachable_pub)]
#![warn(clippy::semicolon_if_nothing_returned)]
#![cfg_attr(not(test), warn(unused_crate_dependencies, unused_extern_crates))]

// Arbitrary Circuit Intermediate Representation

pub mod circuit;
pub mod native_types;

pub use acir_field;
pub use acir_field::FieldElement;
pub use brillig;
pub use circuit::black_box_functions::BlackBoxFunc;

#[cfg(test)]
mod reflection {
    //! Getting test failures? You've probably changed the ACIR serialization format.
    //!
    //! These tests generate C++ deserializers for [`ACIR bytecode`][super::circuit::Circuit]
    //! and the [`WitnessMap`] structs. These get checked against the C++ files committed to the `codegen` folder
    //! to see if changes have been to the serialization format. These are almost always a breaking change!
    //!
    //! If you want to make a breaking change to the ACIR serialization format, then just comment out the assertions
    //! that the file hashes must match and rerun the tests. This will overwrite the `codegen` folder with the new
    //! logic. Make sure to uncomment these lines afterwards and to commit the changes to the `codegen` folder.

    use std::{
        fs::File,
        io::Write,
        path::{Path, PathBuf},
    };

    use brillig::{
        BinaryFieldOp, BinaryIntOp, BlackBoxOp, Opcode as BrilligOpcode, RegisterOrMemory,
    };
    use serde_reflection::{Tracer, TracerConfig};

    use crate::{
        circuit::{
            brillig::{BrilligInputs, BrilligOutputs},
            directives::Directive,
            opcodes::BlackBoxFuncCall,
            Circuit, Opcode, OpcodeLocation,
        },
        native_types::{Witness, WitnessMap},
    };

    #[test]
    fn serde_acir_cpp_codegen() {
        let path = PathBuf::from("./codegen/acir.cpp");

        let old_hash = if path.is_file() {
            let old_source = std::fs::read(&path).unwrap();
            Some(fxhash::hash64(&old_source))
        } else {
            None
        };

        let mut tracer = Tracer::new(TracerConfig::default());
        tracer.trace_simple_type::<Circuit>().unwrap();
        tracer.trace_simple_type::<Opcode>().unwrap();
        tracer.trace_simple_type::<OpcodeLocation>().unwrap();
        tracer.trace_simple_type::<BinaryFieldOp>().unwrap();
        tracer.trace_simple_type::<BlackBoxFuncCall>().unwrap();
        tracer.trace_simple_type::<BrilligInputs>().unwrap();
        tracer.trace_simple_type::<BrilligOutputs>().unwrap();
        tracer.trace_simple_type::<BrilligOpcode>().unwrap();
        tracer.trace_simple_type::<BinaryIntOp>().unwrap();
        tracer.trace_simple_type::<BlackBoxOp>().unwrap();
        tracer.trace_simple_type::<Directive>().unwrap();
        tracer.trace_simple_type::<RegisterOrMemory>().unwrap();

        let registry = tracer.registry().unwrap();

        // Create C++ class definitions.
        let mut source = Vec::new();
        let config = serde_generate::CodeGeneratorConfig::new("Circuit".to_string())
            .with_encodings(vec![serde_generate::Encoding::Bincode]);
        let generator = serde_generate::cpp::CodeGenerator::new(&config);
        generator.output(&mut source, &registry).unwrap();

        // Comment this out to write updated C++ code to file.
        if let Some(old_hash) = old_hash {
            let new_hash = fxhash::hash64(&source);
            assert_eq!(new_hash, old_hash, "Serialization format has changed");
        }

        write_to_file(&source, &path);
    }

    #[test]
    fn serde_witness_map_cpp_codegen() {
        let path = PathBuf::from("./codegen/witness.cpp");

        let old_hash = if path.is_file() {
            let old_source = std::fs::read(&path).unwrap();
            Some(fxhash::hash64(&old_source))
        } else {
            None
        };

        let mut tracer = Tracer::new(TracerConfig::default());
        tracer.trace_simple_type::<Witness>().unwrap();
        tracer.trace_simple_type::<WitnessMap>().unwrap();

        let registry = tracer.registry().unwrap();

        // Create C++ class definitions.
        let mut source = Vec::new();
        let config = serde_generate::CodeGeneratorConfig::new("WitnessMap".to_string())
            .with_encodings(vec![serde_generate::Encoding::Bincode]);
        let generator = serde_generate::cpp::CodeGenerator::new(&config);
        generator.output(&mut source, &registry).unwrap();

        // Comment this out to write updated C++ code to file.
        if let Some(old_hash) = old_hash {
            let new_hash = fxhash::hash64(&source);
            assert_eq!(new_hash, old_hash, "Serialization format has changed");
        }

        write_to_file(&source, &path);
    }

    fn write_to_file(bytes: &[u8], path: &Path) -> String {
        let display = path.display();

        let parent_dir = path.parent().unwrap();
        if !parent_dir.is_dir() {
            std::fs::create_dir_all(parent_dir).unwrap();
        }

        let mut file = match File::create(path) {
            Err(why) => panic!("couldn't create {display}: {why}"),
            Ok(file) => file,
        };

        match file.write_all(bytes) {
            Err(why) => panic!("couldn't write to {display}: {why}"),
            Ok(_) => display.to_string(),
        }
    }
}