Skip to content

Commit 19dbe46

Browse files
authored
feat(avm): CMOV opcode (#5575)
Resolves #5557
1 parent 0b6b6f6 commit 19dbe46

22 files changed

Lines changed: 1563 additions & 235 deletions

barretenberg/cpp/pil/avm/avm_main.pil

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ namespace avm_main(256);
3030

3131
//===== MEMORY OPCODES ==========================================================
3232
pol commit sel_mov;
33+
pol commit sel_cmov;
3334

3435
//===== TABLE SUBOP-TR ========================================================
3536
// Boolean selectors for (sub-)operations. Only one operation is activated at
@@ -75,40 +76,45 @@ namespace avm_main(256);
7576
// A helper witness being the inverse of some value
7677
// to show a non-zero equality
7778
pol commit inv;
79+
pol commit id_zero; // Boolean telling whether id is zero (cmov opcode)
7880

7981
// Intermediate register values
8082
pol commit ia;
8183
pol commit ib;
8284
pol commit ic;
85+
pol commit id;
8386

8487
// Memory operation selector per intermediate register
8588
pol commit mem_op_a;
8689
pol commit mem_op_b;
8790
pol commit mem_op_c;
91+
pol commit mem_op_d;
8892

8993
// Read-write flag per intermediate register: Read = 0, Write = 1
9094
pol commit rwa;
9195
pol commit rwb;
9296
pol commit rwc;
97+
pol commit rwd;
9398

9499
// Indirect register values
95100
pol commit ind_a;
96101
pol commit ind_b;
97102
pol commit ind_c;
103+
pol commit ind_d;
98104

99105
// Indirect memory operation selector per indirect register
100106
pol commit ind_op_a;
101107
pol commit ind_op_b;
102108
pol commit ind_op_c;
103-
109+
pol commit ind_op_d;
104110

105111
// Memory index involved into a memory operation per pertaining intermediate register
106112
// We should range constrain it to 32 bits ultimately. For first version of the AVM,
107113
// we will assume that these columns are of the right type.
108114
pol commit mem_idx_a;
109115
pol commit mem_idx_b;
110116
pol commit mem_idx_c;
111-
117+
pol commit mem_idx_d;
112118

113119
// Track the last line of the execution trace. It does NOT correspond to the last row of the whole table
114120
// of size N. As this depends on the supplied bytecode, this polynomial cannot be constant.
@@ -135,27 +141,32 @@ namespace avm_main(256);
135141

136142
// Might be removed if derived from opcode based on a lookup of constants
137143
sel_mov * ( 1 - sel_mov) = 0;
144+
sel_cmov * ( 1 - sel_cmov) = 0;
138145

139146
op_err * (1 - op_err) = 0;
140147
tag_err * (1 - tag_err) = 0; // Potential optimization (boolean constraint derivation from equivalence check to avm_mem)?
148+
id_zero * (1 - id_zero) = 0;
141149

142150
// Might be removed if derived from opcode based on a lookup of constants
143151
mem_op_a * (1 - mem_op_a) = 0;
144152
mem_op_b * (1 - mem_op_b) = 0;
145153
mem_op_c * (1 - mem_op_c) = 0;
154+
mem_op_d * (1 - mem_op_d) = 0;
146155

147156
rwa * (1 - rwa) = 0;
148157
rwb * (1 - rwb) = 0;
149158
rwc * (1 - rwc) = 0;
159+
rwd * (1 - rwd) = 0;
150160

151161
// Might be removed if derived from opcode based on a lookup of constants
152162
ind_op_a * (1 - ind_op_a) = 0;
153163
ind_op_b * (1 - ind_op_b) = 0;
154164
ind_op_c * (1 - ind_op_c) = 0;
165+
ind_op_d * (1 - ind_op_d) = 0;
155166

156167
// TODO - Constraints:
157-
// - mem_idx_a, mem_idx_b, mem_idx_c to u32 type
158-
// - ind_a, ind_b, ind_c to u32 type
168+
// - mem_idx_a, mem_idx_b, mem_idx_c, mem_idx_d to u32 type
169+
// - ind_a, ind_b, ind_c, ind_d to u32 type
159170
// - 0 <= r_in_tag, w_in_tag <= 6 // Maybe not needed as probably derived by the operation decomposition.
160171

161172
//====== COMPARATOR OPCODES CONSTRAINTS =====================================
@@ -245,10 +256,36 @@ namespace avm_main(256);
245256
// TODO: we want to set an initial number for the reserved memory of the jump pointer
246257

247258
//====== MEMORY OPCODES CONSTRAINTS =========================================
248-
#[MOV_SAME_VALUE]
249-
sel_mov * (ia - ic) = 0; // Ensure that the correct value is moved/copied.
259+
260+
// TODO: consolidate with zero division error handling
261+
262+
// When sel_cmov == 1, we need id == 0 <==> id_zero == 0
263+
// This can be achieved with the 2 following relations.
264+
// inv is an extra witness to show that we can invert id, i.e., inv = id^(-1)
265+
// If id == 0, we have to set inv = 1 to satisfy the second relation,
266+
// because id_zero == 1 from the first relation.
267+
#[CMOV_CONDITION_RES_1]
268+
sel_cmov * (id * inv - 1 + id_zero) = 0;
269+
#[CMOV_CONDITION_RES_2]
270+
sel_cmov * id_zero * (1 - inv) = 0;
271+
272+
// Boolean selectors telling whether we move ia to ic or ib to ic.
273+
// Boolean constraints and mutual exclusivity are derived from their
274+
// respective definitions based on sel_mov, sel_cmov, and id_zero.
275+
pol commit sel_mov_a;
276+
pol commit sel_mov_b;
277+
278+
// For MOV, we copy ia to ic.
279+
// For CMOV, we copy ia to ic if id is NOT zero, otherwise we copy ib to ic.
280+
sel_mov_a = sel_mov + sel_cmov * (1 - id_zero);
281+
sel_mov_b = sel_cmov * id_zero;
282+
283+
#[MOV_SAME_VALUE_A]
284+
sel_mov_a * (ia - ic) = 0; // Ensure that the correct value is moved/copied.
285+
#[MOV_SAME_VALUE_B]
286+
sel_mov_b * (ib - ic) = 0; // Ensure that the correct value is moved/copied.
250287
#[MOV_MAIN_SAME_TAG]
251-
sel_mov * (r_in_tag - w_in_tag) = 0;
288+
(sel_mov + sel_cmov) * (r_in_tag - w_in_tag) = 0;
252289

253290
//====== Inter-table Constraints ============================================
254291
#[INCL_MAIN_TAG_ERR]
@@ -288,20 +325,31 @@ namespace avm_main(256);
288325
avm_binary.start {avm_binary.clk, avm_binary.acc_ia, avm_binary.acc_ib, avm_binary.acc_ic, avm_binary.op_id, avm_binary.in_tag};
289326

290327
#[PERM_MAIN_MEM_A]
291-
mem_op_a {clk, mem_idx_a, ia, rwa, r_in_tag, w_in_tag, sel_mov}
328+
mem_op_a {clk, mem_idx_a, ia, rwa
329+
, r_in_tag, w_in_tag, sel_mov_a, sel_cmov}
292330
is
293-
avm_mem.op_a {avm_mem.clk, avm_mem.addr, avm_mem.val, avm_mem.rw, avm_mem.r_in_tag, avm_mem.w_in_tag, avm_mem.sel_mov};
331+
avm_mem.op_a {avm_mem.clk, avm_mem.addr, avm_mem.val, avm_mem.rw
332+
, avm_mem.r_in_tag, avm_mem.w_in_tag, avm_mem.sel_mov_a, avm_mem.sel_cmov};
294333

295334
#[PERM_MAIN_MEM_B]
296-
mem_op_b {clk, mem_idx_b, ib, rwb, r_in_tag, w_in_tag}
335+
mem_op_b {clk, mem_idx_b, ib, rwb
336+
, r_in_tag, w_in_tag, sel_mov_b, sel_cmov}
297337
is
298-
avm_mem.op_b {avm_mem.clk, avm_mem.addr, avm_mem.val, avm_mem.rw, avm_mem.r_in_tag, avm_mem.w_in_tag};
338+
avm_mem.op_b {avm_mem.clk, avm_mem.addr, avm_mem.val, avm_mem.rw
339+
, avm_mem.r_in_tag, avm_mem.w_in_tag, avm_mem.sel_mov_b, avm_mem.sel_cmov};
299340

300341
#[PERM_MAIN_MEM_C]
301342
mem_op_c {clk, mem_idx_c, ic, rwc, r_in_tag, w_in_tag}
302343
is
303344
avm_mem.op_c {avm_mem.clk, avm_mem.addr, avm_mem.val, avm_mem.rw, avm_mem.r_in_tag, avm_mem.w_in_tag};
304345

346+
#[PERM_MAIN_MEM_D]
347+
mem_op_d {clk, mem_idx_d, id, rwd
348+
, r_in_tag, w_in_tag, sel_cmov}
349+
is
350+
avm_mem.op_d {avm_mem.clk, avm_mem.addr, avm_mem.val, avm_mem.rw
351+
, avm_mem.r_in_tag, avm_mem.w_in_tag, avm_mem.sel_cmov};
352+
305353
#[PERM_MAIN_MEM_IND_A]
306354
ind_op_a {clk, ind_a, mem_idx_a} is avm_mem.ind_op_a {avm_mem.clk, avm_mem.addr, avm_mem.val};
307355

@@ -311,6 +359,9 @@ namespace avm_main(256);
311359
#[PERM_MAIN_MEM_IND_C]
312360
ind_op_c {clk, ind_c, mem_idx_c} is avm_mem.ind_op_c {avm_mem.clk, avm_mem.addr, avm_mem.val};
313361

362+
#[PERM_MAIN_MEM_IND_D]
363+
ind_op_d {clk, ind_d, mem_idx_d} is avm_mem.ind_op_d {avm_mem.clk, avm_mem.addr, avm_mem.val};
364+
314365
//====== Inter-table Constraints (Range Checks) ============================================
315366
// TODO: Investigate optimising these range checks. Handling non-FF elements should require less range checks.
316367
#[LOOKUP_U8_0]

barretenberg/cpp/pil/avm/avm_mem.pil

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,26 @@ namespace avm_mem(256);
1515

1616
pol commit r_in_tag; // Instruction memory tag ("foreign key" pointing to avm_main.r_in_tag)
1717
pol commit w_in_tag; // Instruction memory tag ("foreign key" pointing to avm_main.w_in_tag)
18+
pol commit skip_check_tag; // A boolean value which relaxes the consistency check in memory
19+
// trace between tag and r_in_tag. Required for CMOV opcode.
1820

1921
// Indicator of the intermediate register pertaining to the memory operation (foreign key to avm_main.mem_op_XXX)
2022
pol commit op_a;
2123
pol commit op_b;
2224
pol commit op_c;
25+
pol commit op_d;
2326

2427
// Indicator of the indirect register pertaining to the memory operation (foreign key to avm_main.ind_op_XXX)
2528
pol commit ind_op_a;
2629
pol commit ind_op_b;
2730
pol commit ind_op_c;
31+
pol commit ind_op_d;
2832

29-
// Selector for MOV opcode (copied from main trace for loading operation on intermediated register ia)
33+
// Selectors related to MOV/CMOV opcodes (copied from main trace for loading operation on intermediated register ia/ib)
3034
// Boolean constraint is performed in main trace.
31-
pol commit sel_mov;
35+
pol commit sel_mov_a;
36+
pol commit sel_mov_b;
37+
pol commit sel_cmov;
3238

3339
// Error columns
3440
pol commit tag_err; // Boolean (1 if r_in_tag != tag is detected)
@@ -44,20 +50,22 @@ namespace avm_mem(256);
4450
op_a * (1 - op_a) = 0;
4551
op_b * (1 - op_b) = 0;
4652
op_c * (1 - op_c) = 0;
53+
op_d * (1 - op_d) = 0;
4754
ind_op_a * (1 - ind_op_a) = 0;
4855
ind_op_b * (1 - ind_op_b) = 0;
4956
ind_op_c * (1 - ind_op_c) = 0;
57+
ind_op_d * (1 - ind_op_d) = 0;
5058

5159
// TODO: addr is u32 and 0 <= tag <= 6
5260
// (r_in_tag, w_in_tag will be constrained through inclusion check to main trace)
5361

5462
// Maximum one memory operation enabled per row
55-
pol MEM_SEL = op_a + op_b + op_c + ind_op_a + ind_op_b + ind_op_c;
63+
pol MEM_SEL = op_a + op_b + op_c + op_d + ind_op_a + ind_op_b + ind_op_c + ind_op_d;
5664
MEM_SEL * (MEM_SEL - 1) = 0;
5765

5866
// sub_clk derivation
59-
pol IND_OP = ind_op_a + ind_op_b + ind_op_c;
60-
sub_clk = MEM_SEL * (ind_op_b + op_b + 2 * (ind_op_c + op_c) + 3 * (1 - IND_OP + rw));
67+
pol IND_OP = ind_op_a + ind_op_b + ind_op_c + ind_op_d;
68+
sub_clk = MEM_SEL * (ind_op_b + op_b + 2 * (ind_op_c + op_c) + 3 * (ind_op_d + op_d) + 4 * (1 - IND_OP + rw));
6169
// We need the MEM_SEL factor as the right factor is not zero when all columns are zero.
6270

6371
// Remark: lastAccess == 1 on first row and therefore any relation with the
@@ -106,6 +114,10 @@ namespace avm_mem(256);
106114
#[MEM_ZERO_INIT]
107115
lastAccess * (1 - rw') * val' = 0;
108116

117+
// Skip check tag
118+
#[SKIP_CHECK_TAG]
119+
skip_check_tag = sel_cmov * (op_d + op_a * (1-sel_mov_a) + op_b * (1-sel_mov_b));
120+
109121
// Memory tag consistency check for load operations, i.e., rw == 0.
110122
// We want to prove that r_in_tag == tag <==> tag_err == 0
111123
// We want to show that we can invert (r_in_tag - tag) when tag_err == 1,
@@ -120,10 +132,13 @@ namespace avm_mem(256);
120132
// The new column one_min_inv is set to 1 - (r_in_tag - tag)^(-1) when tag_err == 1
121133
// but must be set to 0 when tags are matching and tag_err = 0
122134
#[MEM_IN_TAG_CONSISTENCY_1]
123-
(1 - rw) * ((r_in_tag - tag) * (1 - one_min_inv) - tag_err) = 0;
135+
(1 - skip_check_tag) * (1 - rw) * ((r_in_tag - tag) * (1 - one_min_inv) - tag_err) = 0;
124136
#[MEM_IN_TAG_CONSISTENCY_2]
125137
(1 - tag_err) * one_min_inv = 0;
126138

139+
#[NO_TAG_ERR_WRITE_OR_SKIP]
140+
(skip_check_tag + rw) * tag_err = 0;
141+
127142
// Correctness of two above checks MEM_IN_TAG_CONSISTENCY_1/2 (assuming rw == 0):
128143
// r_in_tag == tag ==> tag_err == 0 (first relation)
129144
// tag_err == 0 ==> one_min_inv == 0 by second relation. First relation ==> r_in_tag - tag == 0
@@ -140,18 +155,20 @@ namespace avm_mem(256);
140155
ind_op_a * (r_in_tag - 3) = 0;
141156
ind_op_b * (r_in_tag - 3) = 0;
142157
ind_op_c * (r_in_tag - 3) = 0;
158+
ind_op_d * (r_in_tag - 3) = 0;
143159

144160
// Indirect operation is always a load
145161
ind_op_a * rw = 0;
146162
ind_op_b * rw = 0;
147163
ind_op_c * rw = 0;
164+
ind_op_d * rw = 0;
148165

149-
//====== MOV Opcode Tag Constraint =====================================
166+
//====== MOV/CMOV Opcode Tag Constraint =====================================
150167
// The following constraint ensures that the r_in_tag is set to tag for
151-
// the load operation pertaining to Ia.
152-
// The permutation check #[PERM_MAIN_MEM_A] guarantees that the r_in_tag
153-
// value load operation for Ia is copied back in the main trace.
168+
// the load operation pertaining to Ia resp. Ib.
169+
// The permutation check #[PERM_MAIN_MEM_A/B] guarantees that the r_in_tag
170+
// value load operation for Ia/Ib is copied back in the main trace.
154171
// Constraint #[MOV_MAIN_SAME_TAG] copies r_in_tag to w_in_tag in the main
155172
// trace. Then, #[PERM_MAIN_MEM_C] copies w_in_tag for store operation from Ic.
156173
#[MOV_SAME_TAG]
157-
sel_mov * tag_err = 0; // Equivalent to sel_mov * (r_in_tag - tag) = 0
174+
(sel_mov_a + sel_mov_b) * tag_err = 0; // Equivalent to (sel_mov_a + sel_mov_b) * (r_in_tag - tag) = 0

0 commit comments

Comments
 (0)