1use std::collections::BTreeMap;
2use std::sync::{Arc, Mutex, OnceLock};
34use getset::{CopyGetters, Getters, Setters};
56use crate::ff::Field;
7use crate::halo2_proofs::{
8 circuit::{Region, Value},
9 plonk::{Advice, Column},
10};
11use crate::utils::halo2::{constrain_virtual_equals_external, raw_assign_advice};
12use crate::{AssignedValue, ContextTag};
1314use super::copy_constraints::SharedCopyConstraintManager;
15use super::manager::VirtualRegionManager;
1617/// Basic dynamic lookup table gadget.
18pub mod basic;
1920/// A manager that can be used for any lookup argument. This manager automates
21/// the process of copying cells to designed advice columns with lookup enabled.
22/// It also manages how many such advice columns are necessary.
23///
24/// ## Detailed explanation
25/// If we have a lookup argument that uses `ADVICE_COLS` advice columns and `TABLE_COLS` table columns, where
26/// the table is either fixed or dynamic (advice), then we want to dynamically allocate chunks of `ADVICE_COLS` columns
27/// that have the lookup into the table **always on** so that:
28/// - every time we want to lookup [_; ADVICE_COLS] values, we copy them over to a row in the special
29///
30/// lookup-enabled advice columns.
31/// - note that just for assignment, we don't need to know anything about the table itself.
32///
33/// Note: the manager does not need to know the value of `TABLE_COLS`.
34///
35/// We want this manager to be CPU thread safe, while ensuring that the resulting circuit is
36/// deterministic -- the order in which the cells to lookup are added matters.
37/// The current solution is to tag the cells to lookup with the context id from the [`Context`](crate::Context) in which
38/// it was called, and add virtual cells sequentially to buckets labelled by id.
39/// The virtual cells will be assigned to physical cells sequentially by id.
40/// We use a `BTreeMap` for the buckets instead of sorting to cells, to ensure that the order of the cells
41/// within a bucket is deterministic.
42/// The assumption is that the [`Context`](crate::Context) is thread-local.
43///
44/// Cheap to clone across threads because everything is in [Arc].
45#[derive(Clone, Debug, Getters, CopyGetters, Setters)]
46pub struct LookupAnyManager<F: Field + Ord, const ADVICE_COLS: usize> {
47/// Shared cells to lookup, tagged by (type id, context id).
48#[allow(clippy::type_complexity)]
49pub cells_to_lookup: Arc<Mutex<BTreeMap<ContextTag, Vec<[AssignedValue<F>; ADVICE_COLS]>>>>,
50/// Global shared copy manager
51#[getset(get = "pub", set = "pub")]
52copy_manager: SharedCopyConstraintManager<F>,
53/// Specify whether constraints should be imposed for additional safety.
54#[getset(get_copy = "pub")]
55witness_gen_only: bool,
56/// Flag for whether `assign_raw` has been called, for safety only.
57pub(crate) assigned: Arc<OnceLock<()>>,
58}
5960impl<F: Field + Ord, const ADVICE_COLS: usize> LookupAnyManager<F, ADVICE_COLS> {
61/// Creates a new [LookupAnyManager] with a given copy manager.
62pub fn new(witness_gen_only: bool, copy_manager: SharedCopyConstraintManager<F>) -> Self {
63Self {
64 witness_gen_only,
65 cells_to_lookup: Default::default(),
66 copy_manager,
67 assigned: Default::default(),
68 }
69 }
7071/// Add a lookup argument to the manager.
72pub fn add_lookup(&self, tag: ContextTag, cells: [AssignedValue<F>; ADVICE_COLS]) {
73self.cells_to_lookup
74 .lock()
75 .unwrap()
76 .entry(tag)
77 .and_modify(|thread| thread.push(cells))
78 .or_insert(vec![cells]);
79 }
8081/// The total number of virtual rows needed to special lookups
82pub fn total_rows(&self) -> usize {
83self.cells_to_lookup.lock().unwrap().iter().flat_map(|(_, advices)| advices).count()
84 }
8586/// The optimal number of `ADVICE_COLS` chunks of advice columns with lookup enabled for this
87 /// particular lookup argument that we should allocate.
88pub fn num_advice_chunks(&self, usable_rows: usize) -> usize {
89let total = self.total_rows();
90 total.div_ceil(usable_rows)
91 }
9293/// Clears state
94pub fn clear(&mut self) {
95self.cells_to_lookup.lock().unwrap().clear();
96self.copy_manager.lock().unwrap().clear();
97self.assigned = Arc::new(OnceLock::new());
98 }
99100/// Deep clone with the specified copy manager. Unsets `assigned`.
101pub fn deep_clone(&self, copy_manager: SharedCopyConstraintManager<F>) -> Self {
102Self {
103 witness_gen_only: self.witness_gen_only,
104 cells_to_lookup: Arc::new(Mutex::new(self.cells_to_lookup.lock().unwrap().clone())),
105 copy_manager,
106 assigned: Default::default(),
107 }
108 }
109}
110111impl<F: Field + Ord, const ADVICE_COLS: usize> Drop for LookupAnyManager<F, ADVICE_COLS> {
112/// Sanity checks whether the manager has assigned cells to lookup,
113 /// to prevent user error.
114fn drop(&mut self) {
115if Arc::strong_count(&self.cells_to_lookup) > 1 {
116return;
117 }
118if self.total_rows() > 0 && self.assigned.get().is_none() {
119dbg!("WARNING: LookupAnyManager was not assigned!");
120 }
121 }
122}
123124impl<F: Field + Ord, const ADVICE_COLS: usize> VirtualRegionManager<F>
125for LookupAnyManager<F, ADVICE_COLS>
126{
127type Config = Vec<[Column<Advice>; ADVICE_COLS]>;
128type Assignment = ();
129130fn assign_raw(&self, config: &Self::Config, region: &mut Region<F>) {
131let mut copy_manager =
132 (!self.witness_gen_only).then(|| self.copy_manager().lock().unwrap());
133let cells_to_lookup = self.cells_to_lookup.lock().unwrap();
134// Copy the cells to the config columns, going left to right, then top to bottom.
135 // Will panic if out of rows
136let mut lookup_offset = 0;
137let mut lookup_col = 0;
138for advices in cells_to_lookup.iter().flat_map(|(_, advices)| advices) {
139if lookup_col >= config.len() {
140 lookup_col = 0;
141 lookup_offset += 1;
142 }
143for (advice, &column) in advices.iter().zip(config[lookup_col].iter()) {
144let bcell =
145 raw_assign_advice(region, column, lookup_offset, Value::known(advice.value));
146if let Some(copy_manager) = copy_manager.as_mut() {
147 constrain_virtual_equals_external(region, *advice, bcell.cell(), copy_manager);
148 }
149 }
150151 lookup_col += 1;
152 }
153// We cannot clear `cells_to_lookup` because keygen_vk and keygen_pk both call this function
154let _ = self.assigned.set(());
155 }
156}