Skip to content

Commit c1f1da0

Browse files
authored
chore(avm)!: refactor data copy (#16556)
Updates the documentation in the PIL code and replaces instances of using the range check with the use of greater than.
2 parents b250eab + 43d460f commit c1f1da0

File tree

13 files changed

+381
-420
lines changed

13 files changed

+381
-420
lines changed

barretenberg/cpp/pil/vm2/data_copy.pil

Lines changed: 71 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,33 @@ include "range_check.pil";
66
include "context.pil";
77

88
/** This trace handles CALLDATACOPY and RETURNDATACOPY
9-
* The data_copy gadget handles CALLDATACOPY (both enqueued and nested) and RETURNDATACOPY
9+
* The data_copy gadget handles CALLDATACOPY and RETURNDATACOPY (both enqueued and nested)
10+
*
11+
* Opcode operands (relevant in EXECUTION when interacting with this gadget):
12+
* - register[0]: copy_size
13+
* - register[1]: copy_offset
14+
* - rop[2]: dst_addr
15+
*
16+
* Memory I/O, this subtrace can potentially read and write across two different memory space ids (indicated by the context_ids)
17+
* All reads are performed in the src context (using the src_context_id) and writes are performed in the current executing
18+
* context (using dst_context_id).
19+
* - M[src_addr]: aka value[0] (the first value read from the src context)
20+
* - the memory tag is ignored for these reads
21+
* - M[src_addr + max_read_index]: aka value[max_read_index] (the last value read from the src context)
22+
* - max_read_index is derived from the copy_size, see pil relations for an explanation
23+
* - the memory tag is ignored for these reads
24+
* - M[dst_addr]: aka output[0] (the first value written to the dst context)
25+
* - guaranteed by this gadget to be FF
26+
* - M[dst_addr + copy_size]: aka output[copy_size] (the last value written to the dst context)
27+
* - guaranteed by this gadget to be FF
28+
*
1029
* ERROR HANDLING:
1130
* There is one potential errors that is checked: memory out of range accesses
1231
* If there are no errors, we read and write the calldata/returndata from the parent/child context to the current context
1332
* COMPUTING AMOUNT OF DATA TO READ
1433
* We need to ensure that we do not read outside the bounds designated by the parent/child context for their respective data.
1534
* this max_read_index is computed via min(data_size, copy_size + copy_offset).
35+
*
1636
* READING / WRITING DATA
1737
* At each row, the i-th data is simultaneously read from the parent/child and written into the current context
1838
* For top level calldatacopy, the data is retrieved from the calldata column.
@@ -104,21 +124,9 @@ namespace data_copy;
104124
pol commit clk;
105125

106126
// Things are range checked to 32 bits
107-
pol commit thirty_two;
127+
pol commit thirty_two; // todo: while we do not support constants in lookups
108128
SEL * (thirty_two - 32) = 0;
109129

110-
//////////////////////////////
111-
// Error Flags
112-
//////////////////////////////
113-
pol commit src_out_of_range_err; // Read slices should be in MEM range
114-
src_out_of_range_err * (1 - src_out_of_range_err) = 0;
115-
pol commit dst_out_of_range_err; // Write slices should be in MEM range
116-
dst_out_of_range_err * (1 - dst_out_of_range_err) = 0;
117-
118-
// Consolidate the errors
119-
pol commit err;
120-
err = 1 - (1 - dst_out_of_range_err) * (1 - src_out_of_range_err);
121-
122130
///////////////////////////////
123131
// Inputs from execution trace
124132
///////////////////////////////
@@ -137,14 +145,14 @@ namespace data_copy;
137145
//////////////////////////////
138146
// These relations occur independent of if we error (mainly because they help in finding out if there is an error)
139147
// Start and end constrained in "Control Flow Management" section
140-
pol commit sel_start;
141-
sel_start * (1 - sel_start) = 0;
148+
pol commit sel_start; // sel_start = 1 <==> SEL = 1
149+
sel_start * (1 - SEL) = 0;
142150

143151
// End controls most of the row propagation, so if we error we also set end to turn off row propagation
144-
pol commit sel_end;
145-
sel_end * (1 - sel_end) = 0;
152+
pol commit sel_end; // sel_end = 1 <==> SEL = 1
153+
sel_end * (1 - SEL) = 0;
146154

147-
// Check if this is a nested or enqueued call
155+
// is_top_level if this is an enqueued call
148156
pol commit is_top_level; // == 1 iff parent_id == 0
149157
is_top_level * (1 - is_top_level) = 0;
150158
pol commit parent_id_inv; // For zero-check of has_parent_ctx
@@ -161,48 +169,51 @@ namespace data_copy;
161169
// 1) (offset + copy_size) > src_data_size or
162170
// 2) (offset + copy_size) <= src_data_size
163171
// if (1) then max_read_index = src_data_size, otherwise max_read_index = (offset + copy_size)
164-
// these are enforced to be correct by are 32 bit range check of |a - b|
165-
166-
// Convert comparisons to subtractions - check we don't underflow by range checking the absolute difference
167-
pol OFFSET_PLUS_SIZE = offset + copy_size;
168-
pol DATA_SIZE_LT = OFFSET_PLUS_SIZE - src_data_size - 1; // (offset + copy_size) > src_data_size
169-
pol DATA_SIZE_GTE = src_data_size - OFFSET_PLUS_SIZE; // (offset + copy_size) <= src_data_size
172+
pol commit offset_plus_size;
173+
offset_plus_size = sel_start * (offset + copy_size);
174+
pol commit offset_plus_size_is_gt;
170175

171-
pol commit src_data_size_is_lt; // Prover claims which one is the smaller of the two
172-
src_data_size_is_lt * (1 - src_data_size_is_lt) = 0;
173-
174-
pol MAX_READ_DIFF = src_data_size_is_lt * DATA_SIZE_LT + (1 - src_data_size_is_lt) * DATA_SIZE_GTE;
175-
pol commit abs_diff_max_read_index; // Needed for the range check lookup
176-
SEL * sel_start * (abs_diff_max_read_index - MAX_READ_DIFF) = 0;
177-
#[RANGE_MAX_READ_SIZE_DIFF]
178-
sel_start { abs_diff_max_read_index, thirty_two } in range_check.sel { range_check.value, range_check.rng_chk_bits };
176+
#[MAX_READ_INDEX_GT]
177+
sel_start { offset_plus_size, src_data_size, offset_plus_size_is_gt }
178+
in
179+
gt.sel { gt.input_a, gt.input_b, gt.res };
179180

180-
// Based on the prover's claim, we select the smaller of the two
181-
pol MAX_READ_INDEX = src_data_size_is_lt * src_data_size + (1 - src_data_size_is_lt) * OFFSET_PLUS_SIZE;
181+
// Set max_read_index based on the conditions (1) or (2) from above
182+
pol commit max_read_index;
183+
max_read_index = sel_start * ((src_data_size - offset_plus_size) * offset_plus_size_is_gt + offset_plus_size);
182184

183185
//////////////////////////////
184186
// Error Handling
185187
//////////////////////////////
186-
// The one error that we need to handle
187-
// Memory Out of Range: If reading or writing would access an address outside of the AVM memory range
188-
// If there is an error, no data copy operation is performed
188+
pol commit src_out_of_range_err; // Read slices should be in MEM range
189+
src_out_of_range_err * (1 - src_out_of_range_err) = 0;
190+
pol commit dst_out_of_range_err; // Write slices should be in MEM range
191+
dst_out_of_range_err * (1 - dst_out_of_range_err) = 0;
189192

190-
// Memory Out of Range, this section checks that the maximum number of reads ans writes do not
191-
// If top level, we trivially succeed since there is no mem read i.e. we cannot have a src_out_of_range_err
192-
pol MAX_READ_ADDR = (src_addr + MAX_READ_INDEX) * (1 - is_top_level);
193-
pol commit abs_read_diff;
194-
#[SRC_OUT_OF_RANGE] // MAX_MEM_ADDR < MAX_READ_ADDR or MAX_MEM_ADDR >= MAX_READ_ADDR
195-
SEL * sel_start * (src_out_of_range_err * (MAX_READ_ADDR - MAX_MEM_ADDR - 1) + (1 - src_out_of_range_err) * (MAX_MEM_ADDR - MAX_READ_ADDR) - abs_read_diff) = 0;
193+
pol commit max_mem_addr; // todo: While we do not support constants
194+
sel_start * (max_mem_addr - constants.AVM_HIGHEST_MEM_ADDRESS) = 0;
195+
196+
// MAX_READ_ADDR = 0 if this is a top level call since there aren't any memory reads at the top level
197+
// conceptually cd copy and rd copy don't perform reads at the top level.
198+
pol MAX_READ_ADDR = src_addr + max_read_index;
199+
pol commit max_read_addr;
200+
max_read_addr = sel_start * MAX_READ_ADDR;
201+
#[CHECK_SRC_ADDR_IN_RANGE]
202+
sel_start { max_read_addr, max_mem_addr, src_out_of_range_err }
203+
in
204+
gt.sel { gt.input_a, gt.input_b, gt.res };
196205

197206
pol MAX_WRITE_ADDR = dst_addr + copy_size;
198-
pol commit abs_write_diff;
199-
#[DST_OUT_OF_RANGE] // MAX_MEM_ADDR < MAX_WRITE_ADDR or MAX_MEM_ADDR >= MAX_WRITE_ADDR
200-
SEL * sel_start * (dst_out_of_range_err * (MAX_WRITE_ADDR - MAX_MEM_ADDR - 1) + (1 - dst_out_of_range_err) * (MAX_MEM_ADDR - MAX_WRITE_ADDR) - abs_write_diff) = 0;
207+
pol commit max_write_addr;
208+
max_write_addr = sel_start * MAX_WRITE_ADDR;
209+
#[CHECK_DST_ADDR_IN_RANGE]
210+
sel_start { max_write_addr, max_mem_addr, dst_out_of_range_err }
211+
in
212+
gt.sel { gt.input_a, gt.input_b, gt.res };
201213

202-
#[RANGE_READ]
203-
sel_start { abs_read_diff, thirty_two } in range_check.sel { range_check.value, range_check.rng_chk_bits };
204-
#[RANGE_WRITE]
205-
sel_start { abs_write_diff, thirty_two } in range_check.sel { range_check.value, range_check.rng_chk_bits };
214+
// Consolidate the errors
215+
pol commit err;
216+
err = 1 - (1 - dst_out_of_range_err) * (1 - src_out_of_range_err);
206217

207218
//////////////////////////////
208219
// Control flow management
@@ -225,23 +236,19 @@ namespace data_copy;
225236
err * (sel_end - 1) = 0;
226237

227238
pol commit reads_left; // Number of reads of the src data, if reads_left = 0 but copy_size != 0 then it is a padding row
228-
// Src data elements are read from indicies [offset, MAX_READ_INDEX], therefore reads_left = MAX_READ_INDEX - offset
229-
// We need to be careful that MAX_READ_INDEX - offset does not underflow (i.e. when offset > MAX_READ_INDEX, reads_left = 0)
230-
pol OFFSET_GT_MAX_READ = offset - MAX_READ_INDEX - 1; // offset > MAX_READ_INDEX
231-
pol OFFSET_LTE_MAX_READ = MAX_READ_INDEX - offset; // offset <= MAX_READ_INDEX
232-
pol commit sel_offset_gt_max_read;
233-
sel_offset_gt_max_read * (1 - sel_offset_gt_max_read) = 0;
234-
235-
pol commit abs_max_read_offset; // Needed for lookup
236-
abs_max_read_offset = SEL * sel_start_no_err * (sel_offset_gt_max_read * OFFSET_GT_MAX_READ + (1 - sel_offset_gt_max_read) * OFFSET_LTE_MAX_READ);
237-
238-
#[RANGE_READS_LEFT]
239-
sel_start_no_err { abs_max_read_offset, thirty_two } in range_check.sel { range_check.value, range_check.rng_chk_bits };
239+
// src data elements are read from indicies [offset, max_read_index], therefore reads_left = max_read_index - offset
240+
// We need to be careful that max_read_index - offset does not underflow (i.e. when offset > max_read_index, reads_left = 0)
241+
// We test that condition here
242+
pol commit offset_gt_max_read_index;
243+
#[OFFSET_GT_MAX_READ_INDEX]
244+
sel_start_no_err { offset, max_read_index, offset_gt_max_read_index }
245+
in
246+
gt.sel { gt.input_a, gt.input_b, gt.res };
240247

241248
// If sel_offset_gt_max_read = 1 (i.e. when offset > MAX_READ_INDEX, reads_left = 0)
242249
// otherwise, reads_left = MAX_READ_INDEX - offset
243250
#[INIT_READS_LEFT]
244-
SEL * sel_start_no_err * (reads_left - OFFSET_LTE_MAX_READ * (1 - sel_offset_gt_max_read)) = 0;
251+
sel_start_no_err * ( reads_left - (max_read_index - offset) * (1 - offset_gt_max_read_index)) = 0;
245252

246253
//////////////////////////////
247254
// Execute Data Copy

0 commit comments

Comments
 (0)