darkfi/zkas/
compiler.rs

1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2026 Dyne.org foundation
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18
19use std::{io::Result, str::Chars};
20
21use darkfi_serial::{serialize, VarInt};
22
23use super::{
24    ast::{Arg, Constant, Literal, Statement, StatementType, Witness},
25    constants::{
26        SECTION_CIRCUIT, SECTION_CONSTANT, SECTION_DEBUG, SECTION_LITERAL, SECTION_WITNESS,
27    },
28    error::ErrorEmitter,
29    types::HeapType,
30};
31
32/// Version of the binary
33pub const BINARY_VERSION: u8 = 2;
34/// Magic bytes prepended to the binary
35pub const MAGIC_BYTES: [u8; 4] = [0x0b, 0x01, 0xb1, 0x35];
36
37pub struct Compiler {
38    namespace: String,
39    k: u32,
40    constants: Vec<Constant>,
41    witnesses: Vec<Witness>,
42    statements: Vec<Statement>,
43    literals: Vec<Literal>,
44    debug_info: bool,
45    error: ErrorEmitter,
46}
47
48impl Compiler {
49    #[allow(clippy::too_many_arguments)]
50    pub fn new(
51        filename: &str,
52        source: Chars,
53        namespace: String,
54        k: u32,
55        constants: Vec<Constant>,
56        witnesses: Vec<Witness>,
57        statements: Vec<Statement>,
58        literals: Vec<Literal>,
59        debug_info: bool,
60    ) -> Self {
61        // For nice error reporting, we'll load everything into a string
62        // vector so we have references to lines.
63        let lines: Vec<String> = source.as_str().lines().map(|x| x.to_string()).collect();
64        let error = ErrorEmitter::new("Compiler", filename, lines);
65
66        Self { namespace, k, constants, witnesses, statements, literals, debug_info, error }
67    }
68
69    pub fn compile(&self) -> Result<Vec<u8>> {
70        let mut bincode = vec![];
71
72        // Write the magic bytes and version
73        bincode.extend_from_slice(&MAGIC_BYTES);
74        bincode.push(BINARY_VERSION);
75
76        // Write the circuit's k param
77        bincode.extend_from_slice(&serialize(&self.k));
78
79        // Write the circuit's namespace
80        bincode.extend_from_slice(&serialize(&self.namespace));
81
82        // Temporary heap vector for lookups
83        let mut tmp_heap = vec![];
84
85        // In the .constant section of the binary, we write the constant's type,
86        // and the name so the VM can look it up from `src/crypto/constants/`.
87        bincode.extend_from_slice(SECTION_CONSTANT);
88        for i in &self.constants {
89            tmp_heap.push(i.name.as_str());
90            bincode.push(i.typ as u8);
91            bincode.extend_from_slice(&serialize(&i.name));
92        }
93
94        // Currently, our literals are only Uint64 types, in the binary we'll
95        // add them here in the .literal section. In the VM, they will be on
96        // their own heap, used for reference by opcodes.
97        bincode.extend_from_slice(SECTION_LITERAL);
98        for i in &self.literals {
99            bincode.push(i.typ as u8);
100            bincode.extend_from_slice(&serialize(&i.name));
101        }
102
103        // In the .witness section, we write all our witness types, on the heap
104        // they're in order of appearance.
105        bincode.extend_from_slice(SECTION_WITNESS);
106        for i in &self.witnesses {
107            tmp_heap.push(i.name.as_str());
108            bincode.push(i.typ as u8);
109        }
110
111        bincode.extend_from_slice(SECTION_CIRCUIT);
112        for i in &self.statements {
113            match i.typ {
114                StatementType::Assign => tmp_heap.push(&i.lhs.as_ref().unwrap().name),
115                // In case of a simple call, we don't append anything to the heap
116                StatementType::Call => {}
117                _ => unreachable!("Invalid statement type in circuit: {:?}", i.typ),
118            }
119
120            bincode.push(i.opcode as u8);
121            bincode.extend_from_slice(&serialize(&VarInt(i.rhs.len() as u64)));
122
123            for arg in &i.rhs {
124                match arg {
125                    Arg::Var(arg) => {
126                        let heap_idx =
127                            Compiler::lookup_heap(&tmp_heap, &arg.name).ok_or_else(|| {
128                                self.error.abort(
129                                    &format!("Failed finding a heap reference for `{}`", arg.name),
130                                    arg.line,
131                                    arg.column,
132                                )
133                            })?;
134
135                        bincode.push(HeapType::Var as u8);
136                        bincode.extend_from_slice(&serialize(&VarInt(heap_idx as u64)));
137                    }
138                    Arg::Lit(lit) => {
139                        let lit_idx = Compiler::lookup_literal(&self.literals, &lit.name)
140                            .ok_or_else(|| {
141                                self.error.abort(
142                                    &format!("Failed finding literal `{}`", lit.name),
143                                    lit.line,
144                                    lit.column,
145                                )
146                            })?;
147
148                        bincode.push(HeapType::Lit as u8);
149                        bincode.extend_from_slice(&serialize(&VarInt(lit_idx as u64)));
150                    }
151                    _ => unreachable!(),
152                };
153            }
154        }
155
156        // If we're not doing debug info, we're done here and can return.
157        if !self.debug_info {
158            return Ok(bincode)
159        }
160
161        // Otherwise, we proceed appending debug info.
162        bincode.extend_from_slice(SECTION_DEBUG);
163
164        // Write source locations for each opcode.
165        // This allows mapping runtime errors back to source lines.
166        bincode.extend_from_slice(&serialize(&VarInt(self.statements.len() as u64)));
167        for stmt in &self.statements {
168            bincode.extend_from_slice(&serialize(&VarInt(stmt.line as u64)));
169            // For column, use the lhs variable's column if available
170            let column = stmt.lhs.as_ref().map(|v| v.column).unwrap_or(0);
171            bincode.extend_from_slice(&serialize(&VarInt(column as u64)));
172        }
173
174        // Write heap variable names.
175        // The heap contains constants, witnesses, assigned variables (in order).
176        // This allows showing meaningful names instead of heap indices.
177        let heap_size = self.constants.len() +
178            self.witnesses.len() +
179            self.statements.iter().filter(|s| s.typ == StatementType::Assign).count();
180        bincode.extend_from_slice(&serialize(&VarInt(heap_size as u64)));
181
182        for constant in &self.constants {
183            bincode.extend_from_slice(&serialize(&constant.name));
184        }
185
186        for witness in &self.witnesses {
187            bincode.extend_from_slice(&serialize(&witness.name));
188        }
189
190        for stmt in &self.statements {
191            if stmt.typ == StatementType::Assign {
192                bincode.extend_from_slice(&serialize(&stmt.lhs.as_ref().unwrap().name));
193            }
194        }
195
196        // Write literal names (the literal values as strings, e.g. "42")
197        bincode.extend_from_slice(&serialize(&VarInt(self.literals.len() as u64)));
198        for literal in &self.literals {
199            bincode.extend_from_slice(&serialize(&literal.name));
200        }
201
202        Ok(bincode)
203    }
204
205    fn lookup_heap(heap: &[&str], name: &str) -> Option<usize> {
206        heap.iter().position(|&n| n == name)
207    }
208
209    fn lookup_literal(literals: &[Literal], name: &str) -> Option<usize> {
210        literals.iter().position(|n| n.name == name)
211    }
212}