Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
data_copy.cpp
Go to the documentation of this file.
2
3#include <vector>
4
10
11namespace bb::avm2::simulation {
12
13namespace {
14
19DataCopyEvent create_cd_event(ContextInterface& context,
20 uint32_t clk,
21 uint32_t copy_size,
22 uint32_t offset,
24 std::vector<MemoryValue> calldata = {})
25{
26 return DataCopyEvent{
27 .execution_clk = clk,
28 .operation = DataCopyOperation::CD_COPY,
29 .copying_data = std::move(calldata),
30 .write_context_id = context.get_context_id(),
31 .read_context_id = context.get_parent_id(),
32 .data_copy_size = copy_size,
33 .data_offset = offset,
34 .src_data_addr = context.get_parent_cd_addr(),
35 .src_data_size = context.get_parent_cd_size(),
36 .is_nested = context.has_parent(),
37 .dst_addr = dst_addr,
38 };
39}
40
45DataCopyEvent create_rd_event(ContextInterface& context,
46 uint32_t clk,
47 uint32_t copy_size,
48 uint32_t offset,
51{
52 return DataCopyEvent{
53 .execution_clk = clk,
54 .operation = DataCopyOperation::RD_COPY,
55 .copying_data = std::move(returndata),
56 .write_context_id = context.get_context_id(),
57 // This handles the case where there is no last child (i.e. new enqueued call)
58 .read_context_id = context.get_last_child_id(),
59 .data_copy_size = copy_size,
60 .data_offset = offset,
61 .src_data_addr = context.get_last_rd_addr(),
62 .src_data_size = context.get_last_rd_size(),
63 .is_nested = context.has_parent(),
64 .dst_addr = dst_addr,
65 };
66}
67
68} // namespace
69
70// This is std::min but creates the relevant greater than event
71uint64_t DataCopy::min(uint64_t a, uint64_t b)
72{
73 // Looks weird but ironically similar to the std::min implementation
74 // i.e if a == b, return a
75 if (gt.gt(a, b)) {
76 return b;
77 }
78 return a;
79}
80
102{
103 auto& memory = context.get_memory();
104 uint32_t clk = execution_id_manager.get_execution_id();
105
106 // This section is a bit leaky, but is necessary to ensure the correct gt events are generated.
107 // This work is duplicated in context.get_calldata() - but it avoids us having a gt there.
108
109 // Operations are performed over uint64_t in case the addition overflows, but the result is guaranteed to
110 // fit in 32 bits since get_parent_cd_size() returns a u32 (constrained by a CALL or 0 if an enqueued
111 uint64_t read_index_upper_bound = min(static_cast<uint64_t>(offset) + copy_size, context.get_parent_cd_size());
112 uint64_t read_addr_upper_bound = read_index_upper_bound + context.get_parent_cd_addr();
113
114 uint64_t write_addr_upper_bound = static_cast<uint64_t>(dst_addr) + copy_size;
115
116 // Both GT events must be emitted regardless of outcome (circuit requires them).
117 bool src_reads_exceed_mem = gt.gt(read_addr_upper_bound, AVM_MEMORY_SIZE);
118 bool write_out_of_range = gt.gt(write_addr_upper_bound, AVM_MEMORY_SIZE);
119
120 // Only dst out of range is an error. Src out of range is handled by clamping reads and padding
121 if (write_out_of_range) {
122 events.emit(create_cd_event(context, clk, copy_size, offset, dst_addr));
123 throw DataCopyException(
124 format("Attempting to write out of bounds memory: write_addr_upper_bound = ", write_addr_upper_bound));
125 }
126
127 // Clamp reads at the memory boundary.
128 uint64_t clamped_read_index_upper_bound =
129 src_reads_exceed_mem ? (AVM_MEMORY_SIZE - context.get_parent_cd_addr()) : read_index_upper_bound;
130
131 // Read the clamped amount and pad to copy_size with zeros.
132 std::vector<MemoryValue> padded_calldata;
133 if (gt.gt(clamped_read_index_upper_bound, static_cast<uint64_t>(offset))) {
134 uint32_t effective_reads = static_cast<uint32_t>(clamped_read_index_upper_bound - offset);
135 padded_calldata = context.get_calldata(offset, effective_reads);
136 }
137 padded_calldata.resize(copy_size, MemoryValue::from<FF>(0));
138
139 // We do not enforce any tag check and upcast to FF transparently.
140 for (uint32_t i = 0; i < copy_size; i++) {
141 memory.set(dst_addr + i, MemoryValue::from<FF>(padded_calldata[i].as_ff()));
142 }
143
144 // We need to pass the original tags of the calldata to the circuit.
145 events.emit(create_cd_event(context, clk, copy_size, offset, dst_addr, std::move(padded_calldata)));
146}
147
156{
157 auto& memory = context.get_memory();
158 uint32_t clk = execution_id_manager.get_execution_id();
159
160 // Check cd_copy for why we do this here even though it is in get_returndata()
161 uint64_t read_index_upper_bound = min(static_cast<uint64_t>(offset) + copy_size, context.get_last_rd_size());
162 uint64_t read_addr_upper_bound = read_index_upper_bound + context.get_last_rd_addr();
163
164 uint64_t write_addr_upper_bound = static_cast<uint64_t>(dst_addr) + copy_size;
165
166 // Both GT events must be emitted regardless of outcome (circuit requires them).
167 bool src_reads_exceed_mem = gt.gt(read_addr_upper_bound, AVM_MEMORY_SIZE);
168 bool write_out_of_range = gt.gt(write_addr_upper_bound, AVM_MEMORY_SIZE);
169
170 // Only dst out of range is an error. Src out of range is handled by clamping reads and padding
171 if (write_out_of_range) {
172 events.emit(create_rd_event(context, clk, copy_size, offset, dst_addr));
173 throw DataCopyException(
174 format("Attempting to write out of bounds memory: write_addr_upper_bound = ", write_addr_upper_bound));
175 }
176
177 // Clamp reads at the memory boundary.
178 uint64_t clamped_read_index_upper_bound =
179 src_reads_exceed_mem ? (AVM_MEMORY_SIZE - context.get_last_rd_addr()) : read_index_upper_bound;
180
181 // Read the clamped amount and pad to copy_size with zeros.
182 std::vector<MemoryValue> padded_returndata;
183 if (gt.gt(clamped_read_index_upper_bound, static_cast<uint64_t>(offset))) {
184 uint32_t effective_reads = static_cast<uint32_t>(clamped_read_index_upper_bound - offset);
185 padded_returndata = context.get_returndata(offset, effective_reads);
186 }
187 padded_returndata.resize(copy_size, MemoryValue::from<FF>(0));
188
189 // We do not enforce any tag check and upcast to FF transparently.
190 for (uint32_t i = 0; i < copy_size; i++) {
191 memory.set(dst_addr + i, MemoryValue::from<FF>(padded_returndata[i].as_ff()));
192 }
193
194 // We need to pass the original tags of the returndata to the circuit.
195 events.emit(create_rd_event(context, clk, copy_size, offset, dst_addr, std::move(padded_returndata)));
196}
197
198} // namespace bb::avm2::simulation
#define AVM_MEMORY_SIZE
ExecutionIdGetterInterface & execution_id_manager
Definition data_copy.hpp:31
uint64_t min(uint64_t a, uint64_t b)
Definition data_copy.cpp:71
void rd_copy(ContextInterface &context, uint32_t copy_size, uint32_t offset, MemoryAddress dst_addr) override
Copies returndata from the last executed context to the dst_addr.
void cd_copy(ContextInterface &context, uint32_t copy_size, uint32_t offset, MemoryAddress dst_addr) override
Writes calldata into dst_addr. There is slight difference in how enqueued and nested contexts are han...
EventEmitterInterface< DataCopyEvent > & events
Definition data_copy.hpp:33
virtual uint32_t get_execution_id() const =0
std::string format(Args... args)
Definition log.hpp:23
uint32_t dst_addr
StrictMock< MockContext > context
FF a
FF b
ssize_t offset
Definition engine.cpp:62
AVM range check gadget for witness generation.
uint32_t MemoryAddress
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::vector< MemoryValue > returndata