Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
bytecode_trace.test.cpp
Go to the documentation of this file.
1#include <algorithm>
2#include <cstddef>
3#include <gmock/gmock.h>
4#include <gtest/gtest.h>
5
6#include <cstdint>
7#include <memory>
8#include <sys/types.h>
9#include <vector>
10
20
21namespace bb::avm2::tracegen {
22namespace {
23
24using C = Column;
26
27using simulation::Instruction;
28using simulation::InstructionFetchingEvent;
29
30TEST(BytecodeTraceGenTest, BasicShortLength)
31{
32 TestTraceContainer trace;
33 BytecodeTraceBuilder builder;
34
35 builder.process_decomposition(
36 {
37 simulation::BytecodeDecompositionEvent{
38 .bytecode_id = 43,
39 .bytecode = std::make_shared<std::vector<uint8_t>>(std::vector<uint8_t>{ 12, 31, 5, 2 }),
40 },
41 },
42 trace);
43 auto rows = trace.as_rows();
44
45 // One extra empty row is prepended. Note that precomputed_first_row is not set through process_decomposition()
46 // because it pertains to another subtrace.
47 ASSERT_EQ(rows.size(), 4 + 1);
48
49 // We do not inspect row at index 0 as it is completely empty.
50 EXPECT_THAT(rows.at(1),
51 AllOf(ROW_FIELD_EQ(bc_decomposition_sel, 1),
52 ROW_FIELD_EQ(bc_decomposition_id, 43),
53 ROW_FIELD_EQ(bc_decomposition_bytes, 12),
54 ROW_FIELD_EQ(bc_decomposition_bytes_pc_plus_1, 31),
55 ROW_FIELD_EQ(bc_decomposition_bytes_pc_plus_2, 5),
56 ROW_FIELD_EQ(bc_decomposition_bytes_pc_plus_3, 2),
57 ROW_FIELD_EQ(bc_decomposition_bytes_pc_plus_4, 0),
58 ROW_FIELD_EQ(bc_decomposition_pc, 0),
59 ROW_FIELD_EQ(bc_decomposition_bytes_remaining, 4),
60 ROW_FIELD_EQ(bc_decomposition_sel_windows_gt_remaining, 1),
61 ROW_FIELD_EQ(bc_decomposition_windows_min_remaining_inv, FF(DECOMPOSE_WINDOW_SIZE - 4).invert()),
62 ROW_FIELD_EQ(bc_decomposition_sel_windows_eq_remaining, 0),
63 ROW_FIELD_EQ(bc_decomposition_bytes_to_read, 4),
64 ROW_FIELD_EQ(bc_decomposition_last_of_contract, 0),
65 ROW_FIELD_EQ(bc_decomposition_sel_packed, 1),
66 ROW_FIELD_EQ(bc_decomposition_start, 1),
67 ROW_FIELD_EQ(bc_decomposition_next_packed_pc, 0),
68 ROW_FIELD_EQ(bc_decomposition_next_packed_pc_min_pc_inv, 0)));
69
70 EXPECT_THAT(rows.at(2),
71 AllOf(ROW_FIELD_EQ(bc_decomposition_sel, 1),
72 ROW_FIELD_EQ(bc_decomposition_id, 43),
73 ROW_FIELD_EQ(bc_decomposition_bytes, 31),
74 ROW_FIELD_EQ(bc_decomposition_bytes_pc_plus_1, 5),
75 ROW_FIELD_EQ(bc_decomposition_bytes_pc_plus_2, 2),
76 ROW_FIELD_EQ(bc_decomposition_bytes_pc_plus_3, 0),
77 ROW_FIELD_EQ(bc_decomposition_pc, 1),
78 ROW_FIELD_EQ(bc_decomposition_bytes_remaining, 3),
79 ROW_FIELD_EQ(bc_decomposition_sel_windows_gt_remaining, 1),
80 ROW_FIELD_EQ(bc_decomposition_windows_min_remaining_inv, FF(DECOMPOSE_WINDOW_SIZE - 3).invert()),
81 ROW_FIELD_EQ(bc_decomposition_sel_windows_eq_remaining, 0),
82 ROW_FIELD_EQ(bc_decomposition_bytes_to_read, 3),
83 ROW_FIELD_EQ(bc_decomposition_sel_packed, 0),
84 ROW_FIELD_EQ(bc_decomposition_next_packed_pc, 31),
85 ROW_FIELD_EQ(bc_decomposition_next_packed_pc_min_pc_inv, FF(31 - 1).invert()),
86 ROW_FIELD_EQ(bc_decomposition_last_of_contract, 0)));
87
88 EXPECT_THAT(rows.at(3),
89 AllOf(ROW_FIELD_EQ(bc_decomposition_sel, 1),
90 ROW_FIELD_EQ(bc_decomposition_id, 43),
91 ROW_FIELD_EQ(bc_decomposition_bytes, 5),
92 ROW_FIELD_EQ(bc_decomposition_bytes_pc_plus_1, 2),
93 ROW_FIELD_EQ(bc_decomposition_bytes_pc_plus_2, 0),
94 ROW_FIELD_EQ(bc_decomposition_pc, 2),
95 ROW_FIELD_EQ(bc_decomposition_bytes_remaining, 2),
96 ROW_FIELD_EQ(bc_decomposition_sel_windows_gt_remaining, 1),
97 ROW_FIELD_EQ(bc_decomposition_windows_min_remaining_inv, FF(DECOMPOSE_WINDOW_SIZE - 2).invert()),
98 ROW_FIELD_EQ(bc_decomposition_sel_windows_eq_remaining, 0),
99 ROW_FIELD_EQ(bc_decomposition_bytes_to_read, 2),
100 ROW_FIELD_EQ(bc_decomposition_sel_packed, 0),
101 ROW_FIELD_EQ(bc_decomposition_next_packed_pc, 31),
102 ROW_FIELD_EQ(bc_decomposition_next_packed_pc_min_pc_inv, FF(31 - 2).invert()),
103 ROW_FIELD_EQ(bc_decomposition_last_of_contract, 0)));
104
105 EXPECT_THAT(rows.at(4),
106 AllOf(ROW_FIELD_EQ(bc_decomposition_sel, 1),
107 ROW_FIELD_EQ(bc_decomposition_id, 43),
108 ROW_FIELD_EQ(bc_decomposition_bytes, 2),
109 ROW_FIELD_EQ(bc_decomposition_bytes_pc_plus_1, 0),
110 ROW_FIELD_EQ(bc_decomposition_pc, 3),
111 ROW_FIELD_EQ(bc_decomposition_bytes_remaining, 1),
112 ROW_FIELD_EQ(bc_decomposition_sel_windows_gt_remaining, 1),
113 ROW_FIELD_EQ(bc_decomposition_windows_min_remaining_inv, FF(DECOMPOSE_WINDOW_SIZE - 1).invert()),
114 ROW_FIELD_EQ(bc_decomposition_sel_windows_eq_remaining, 0),
115 ROW_FIELD_EQ(bc_decomposition_bytes_to_read, 1),
116 ROW_FIELD_EQ(bc_decomposition_sel_packed, 0),
117 ROW_FIELD_EQ(bc_decomposition_next_packed_pc, 31),
118 ROW_FIELD_EQ(bc_decomposition_next_packed_pc_min_pc_inv, FF(31 - 3).invert()),
119 ROW_FIELD_EQ(bc_decomposition_last_of_contract, 1)));
120}
121
122TEST(BytecodeTraceGenTest, BasicSingleByte)
123{
124 TestTraceContainer trace;
125 BytecodeTraceBuilder builder;
126
127 builder.process_decomposition(
128 {
129 simulation::BytecodeDecompositionEvent{
130 .bytecode_id = 43,
131 .bytecode = std::make_shared<std::vector<uint8_t>>(std::vector<uint8_t>{ 24 }),
132 },
133 },
134 trace);
135 auto rows = trace.as_rows();
136
137 // One extra empty row is prepended. Note that precomputed_first_row is not set through process_decomposition()
138 // because it pertains to another subtrace.
139 ASSERT_EQ(rows.size(), 1 + 1);
140
141 // We do not inspect row at index 0 as it is completely empty.
142 EXPECT_THAT(rows.at(1),
143 AllOf(ROW_FIELD_EQ(bc_decomposition_sel, 1),
144 ROW_FIELD_EQ(bc_decomposition_id, 43),
145 ROW_FIELD_EQ(bc_decomposition_bytes, 24),
146 ROW_FIELD_EQ(bc_decomposition_bytes_pc_plus_1, 0),
147 ROW_FIELD_EQ(bc_decomposition_bytes_pc_plus_2, 0),
148 ROW_FIELD_EQ(bc_decomposition_bytes_pc_plus_3, 0),
149 ROW_FIELD_EQ(bc_decomposition_bytes_pc_plus_4, 0),
150 ROW_FIELD_EQ(bc_decomposition_pc, 0),
151 ROW_FIELD_EQ(bc_decomposition_bytes_remaining, 1),
152 ROW_FIELD_EQ(bc_decomposition_sel_windows_gt_remaining, 1),
153 ROW_FIELD_EQ(bc_decomposition_windows_min_remaining_inv, FF(DECOMPOSE_WINDOW_SIZE - 1).invert()),
154 ROW_FIELD_EQ(bc_decomposition_sel_windows_eq_remaining, 0),
155 ROW_FIELD_EQ(bc_decomposition_bytes_to_read, 1),
156 ROW_FIELD_EQ(bc_decomposition_last_of_contract, 1),
157 ROW_FIELD_EQ(bc_decomposition_sel_packed, 1),
158 ROW_FIELD_EQ(bc_decomposition_start, 1),
159 ROW_FIELD_EQ(bc_decomposition_next_packed_pc, 0),
160 ROW_FIELD_EQ(bc_decomposition_next_packed_pc_min_pc_inv, 0)));
161}
162
163TEST(BytecodeTraceGenTest, BasicLongerThanWindowSize)
164{
165 TestTraceContainer trace;
166 BytecodeTraceBuilder builder;
167
168 constexpr auto bytecode_size = DECOMPOSE_WINDOW_SIZE + 8;
169 std::vector<uint8_t> bytecode(bytecode_size);
170 const uint8_t first_byte = 17; // Arbitrary start value and we increment by one. We will hit invalid opcodes
171 // but it should not matter.
172
173 for (uint8_t i = 0; i < bytecode_size; i++) {
174 bytecode[i] = i + first_byte;
175 }
176
177 builder.process_decomposition(
178 {
179 simulation::BytecodeDecompositionEvent{
180 .bytecode_id = 7,
182 },
183 },
184 trace);
185 auto rows = trace.as_rows();
186
187 // One extra empty row is prepended. Note that precomputed_first_row is not set through process_decomposition()
188 // because it pertains to another subtrace.
189 ASSERT_EQ(rows.size(), bytecode_size + 1);
190
191 // We do not inspect row at index 0 as it is completely empty.
192 EXPECT_THAT(rows.at(1),
193 AllOf(ROW_FIELD_EQ(bc_decomposition_sel, 1),
194 ROW_FIELD_EQ(bc_decomposition_id, 7),
195 ROW_FIELD_EQ(bc_decomposition_bytes, first_byte),
196 ROW_FIELD_EQ(bc_decomposition_pc, 0),
197 ROW_FIELD_EQ(bc_decomposition_bytes_remaining, bytecode_size),
198 ROW_FIELD_EQ(bc_decomposition_sel_windows_gt_remaining, 0),
199 ROW_FIELD_EQ(bc_decomposition_windows_min_remaining_inv, FF(-8).invert()),
200 ROW_FIELD_EQ(bc_decomposition_sel_windows_eq_remaining, 0),
201 ROW_FIELD_EQ(bc_decomposition_bytes_to_read, DECOMPOSE_WINDOW_SIZE),
202 ROW_FIELD_EQ(bc_decomposition_sel_packed, 1),
203 ROW_FIELD_EQ(bc_decomposition_start, 1),
204 ROW_FIELD_EQ(bc_decomposition_next_packed_pc, 0),
205 ROW_FIELD_EQ(bc_decomposition_next_packed_pc_min_pc_inv, 0),
206 ROW_FIELD_EQ(bc_decomposition_last_of_contract, 0)));
207
208 // We are interested to inspect the boundary aroud bytes_remaining == windows size
209
210 EXPECT_THAT(rows.at(9),
211 AllOf(ROW_FIELD_EQ(bc_decomposition_sel, 1),
212 ROW_FIELD_EQ(bc_decomposition_id, 7),
213 ROW_FIELD_EQ(bc_decomposition_bytes, first_byte + 8),
214 ROW_FIELD_EQ(bc_decomposition_pc, 8),
215 ROW_FIELD_EQ(bc_decomposition_bytes_remaining, DECOMPOSE_WINDOW_SIZE),
216 ROW_FIELD_EQ(bc_decomposition_sel_windows_gt_remaining, 0),
217 ROW_FIELD_EQ(bc_decomposition_windows_min_remaining_inv, 0),
218 ROW_FIELD_EQ(bc_decomposition_sel_windows_eq_remaining, 1),
219 ROW_FIELD_EQ(bc_decomposition_bytes_to_read, DECOMPOSE_WINDOW_SIZE),
220 ROW_FIELD_EQ(bc_decomposition_last_of_contract, 0)));
221
222 EXPECT_THAT(rows.at(10),
223 AllOf(ROW_FIELD_EQ(bc_decomposition_sel, 1),
224 ROW_FIELD_EQ(bc_decomposition_id, 7),
225 ROW_FIELD_EQ(bc_decomposition_bytes, first_byte + 9),
226 ROW_FIELD_EQ(bc_decomposition_pc, 9),
227 ROW_FIELD_EQ(bc_decomposition_bytes_remaining, DECOMPOSE_WINDOW_SIZE - 1),
228 ROW_FIELD_EQ(bc_decomposition_sel_windows_gt_remaining, 1),
229 ROW_FIELD_EQ(bc_decomposition_windows_min_remaining_inv, 1),
230 ROW_FIELD_EQ(bc_decomposition_sel_windows_eq_remaining, 0),
231 ROW_FIELD_EQ(bc_decomposition_bytes_to_read, DECOMPOSE_WINDOW_SIZE - 1),
232 ROW_FIELD_EQ(bc_decomposition_sel_packed, 0),
233 ROW_FIELD_EQ(bc_decomposition_next_packed_pc, 31),
234 ROW_FIELD_EQ(bc_decomposition_next_packed_pc_min_pc_inv, FF(31 - 9).invert()),
235 ROW_FIELD_EQ(bc_decomposition_last_of_contract, 0)));
236
237 // Last row
238 EXPECT_THAT(rows.at(bytecode_size),
239 AllOf(ROW_FIELD_EQ(bc_decomposition_sel, 1),
240 ROW_FIELD_EQ(bc_decomposition_id, 7),
241 ROW_FIELD_EQ(bc_decomposition_bytes, first_byte + bytecode_size - 1),
242 ROW_FIELD_EQ(bc_decomposition_pc, bytecode_size - 1),
243 ROW_FIELD_EQ(bc_decomposition_bytes_remaining, 1),
244 ROW_FIELD_EQ(bc_decomposition_sel_windows_gt_remaining, 1),
245 ROW_FIELD_EQ(bc_decomposition_windows_min_remaining_inv, FF(DECOMPOSE_WINDOW_SIZE - 1).invert()),
246 ROW_FIELD_EQ(bc_decomposition_sel_windows_eq_remaining, 0),
247 ROW_FIELD_EQ(bc_decomposition_bytes_to_read, 1),
248 ROW_FIELD_EQ(bc_decomposition_sel_packed, 0),
249 ROW_FIELD_EQ(bc_decomposition_next_packed_pc, 62),
250 ROW_FIELD_EQ(bc_decomposition_next_packed_pc_min_pc_inv, FF(62 - (bytecode_size - 1)).invert()),
251 ROW_FIELD_EQ(bc_decomposition_last_of_contract, 1)));
252}
253
254TEST(BytecodeTraceGenTest, MultipleEvents)
255{
256 TestTraceContainer trace;
257 BytecodeTraceBuilder builder;
258
259 std::vector<uint32_t> bc_sizes = { DECOMPOSE_WINDOW_SIZE + 2, 17, DECOMPOSE_WINDOW_SIZE, 1 };
261
262 std::transform(bc_sizes.begin(), bc_sizes.end(), bytecodes.begin(), [](uint32_t bc_size) -> std::vector<uint8_t> {
263 std::vector<uint8_t> bytecode(bc_size);
264 for (uint8_t i = 0; i < bc_size; i++) {
265 bytecode[i] = i * i; // Arbitrary bytecode that we will not inspect below
266 }
267
268 return bytecode;
269 });
270
271 builder.process_decomposition(
272 {
273 simulation::BytecodeDecompositionEvent{
274 .bytecode_id = 0,
275 .bytecode = std::make_shared<std::vector<uint8_t>>(bytecodes[0]),
276 },
277 simulation::BytecodeDecompositionEvent{
278 .bytecode_id = 1,
279 .bytecode = std::make_shared<std::vector<uint8_t>>(bytecodes[1]),
280 },
281 simulation::BytecodeDecompositionEvent{
282 .bytecode_id = 2,
283 .bytecode = std::make_shared<std::vector<uint8_t>>(bytecodes[2]),
284 },
285 simulation::BytecodeDecompositionEvent{
286 .bytecode_id = 3,
287 .bytecode = std::make_shared<std::vector<uint8_t>>(bytecodes[3]),
288 },
289 },
290 trace);
291 auto rows = trace.as_rows();
292
293 // One extra empty row is prepended.
294 ASSERT_EQ(rows.size(), 2 * DECOMPOSE_WINDOW_SIZE + 20 + 1);
295
296 size_t row_pos = 1;
297 for (uint32_t i = 0; i < 4; i++) {
298 uint32_t next_packed_pc = 0;
299 for (uint32_t j = 0; j < bc_sizes[i]; j++) {
300 const auto bytes_rem = bc_sizes[i] - j;
301 EXPECT_THAT(
302 rows.at(row_pos),
303 AllOf(
304 ROW_FIELD_EQ(bc_decomposition_sel, 1),
305 ROW_FIELD_EQ(bc_decomposition_id, i),
306 ROW_FIELD_EQ(bc_decomposition_pc, j),
307 ROW_FIELD_EQ(bc_decomposition_bytes_remaining, bytes_rem),
308 ROW_FIELD_EQ(bc_decomposition_sel_windows_gt_remaining, DECOMPOSE_WINDOW_SIZE > bytes_rem ? 1 : 0),
310 bc_decomposition_windows_min_remaining_inv,
311 bytes_rem == DECOMPOSE_WINDOW_SIZE ? 0 : (FF(DECOMPOSE_WINDOW_SIZE) - FF(bytes_rem)).invert()),
312 ROW_FIELD_EQ(bc_decomposition_sel_windows_eq_remaining, bytes_rem == DECOMPOSE_WINDOW_SIZE ? 1 : 0),
313 ROW_FIELD_EQ(bc_decomposition_bytes_to_read, std::min(DECOMPOSE_WINDOW_SIZE, bytes_rem)),
314 ROW_FIELD_EQ(bc_decomposition_sel_packed, j == next_packed_pc ? 1 : 0),
315 ROW_FIELD_EQ(bc_decomposition_next_packed_pc, next_packed_pc),
316 ROW_FIELD_EQ(bc_decomposition_next_packed_pc_min_pc_inv,
317 j == next_packed_pc ? 0 : FF(next_packed_pc - j).invert()),
318 ROW_FIELD_EQ(bc_decomposition_start, j == 0 ? 1 : 0),
319 ROW_FIELD_EQ(bc_decomposition_last_of_contract, j == bc_sizes[i] - 1 ? 1 : 0)));
320 row_pos++;
321 next_packed_pc += j % 31 == 0 ? 31 : 0;
322 }
323 }
324}
325
326TEST(BytecodeTraceGenTest, BasicHashing)
327{
328 TestTraceContainer trace;
329 BytecodeTraceBuilder builder;
330
331 builder.process_hashing(
332 {
333 simulation::BytecodeHashingEvent{
334 .bytecode_id = 1,
335 .bytecode_length = 93,
336 .bytecode_fields = { 10, 20, 30 },
337 },
338 },
339 trace);
340 const auto rows = trace.as_rows();
341
342 // One extra empty row is prepended.
343 EXPECT_THAT(
344 rows.at(1),
345 AllOf(ROW_FIELD_EQ(bc_hashing_sel, 1),
346 ROW_FIELD_EQ(bc_hashing_start, 1),
347 ROW_FIELD_EQ(bc_hashing_sel_not_start, 0),
348 ROW_FIELD_EQ(bc_hashing_sel_not_padding_1, 1),
349 ROW_FIELD_EQ(bc_hashing_sel_not_padding_2, 1),
350 ROW_FIELD_EQ(bc_hashing_latch, 0),
351 ROW_FIELD_EQ(bc_hashing_bytecode_id, 1),
352 ROW_FIELD_EQ(bc_hashing_pc_index, 0),
353 // We don't increment at start to account for the prepended first field length | separator:
354 ROW_FIELD_EQ(bc_hashing_pc_index_1, 0),
355 ROW_FIELD_EQ(bc_hashing_pc_index_2, 31),
356 ROW_FIELD_EQ(bc_hashing_packed_fields_0, simulation::compute_public_bytecode_first_field(93)),
357 ROW_FIELD_EQ(bc_hashing_packed_fields_1, 10),
358 ROW_FIELD_EQ(bc_hashing_packed_fields_2, 20),
359 ROW_FIELD_EQ(bc_hashing_size_in_bytes, 93),
360 ROW_FIELD_EQ(bc_hashing_input_len, 4),
361 ROW_FIELD_EQ(bc_hashing_rounds_rem, 2),
362 ROW_FIELD_EQ(bc_hashing_output_hash,
364 ROW_FIELD_EQ(bc_hashing_pc_at_final_field, 0)));
365
366 // Latched row
367 EXPECT_THAT(
368 rows.at(2),
369 AllOf(ROW_FIELD_EQ(bc_hashing_sel, 1),
370 ROW_FIELD_EQ(bc_hashing_start, 0),
371 ROW_FIELD_EQ(bc_hashing_sel_not_start, 1),
372 ROW_FIELD_EQ(bc_hashing_sel_not_padding_1, 0),
373 ROW_FIELD_EQ(bc_hashing_sel_not_padding_2, 0),
374 ROW_FIELD_EQ(bc_hashing_latch, 1),
375 ROW_FIELD_EQ(bc_hashing_bytecode_id, 1),
376 ROW_FIELD_EQ(bc_hashing_pc_index, 62),
377 ROW_FIELD_EQ(bc_hashing_pc_index_1, 93),
378 ROW_FIELD_EQ(bc_hashing_pc_index_2, 124),
379 ROW_FIELD_EQ(bc_hashing_packed_fields_0, 30),
380 ROW_FIELD_EQ(bc_hashing_packed_fields_1, 0),
381 ROW_FIELD_EQ(bc_hashing_packed_fields_2, 0),
382 ROW_FIELD_EQ(bc_hashing_input_len, 4),
383 ROW_FIELD_EQ(bc_hashing_rounds_rem, 1),
384 ROW_FIELD_EQ(bc_hashing_output_hash,
386 ROW_FIELD_EQ(bc_hashing_pc_at_final_field, 62)));
387}
388
389std::vector<Instruction> gen_random_instructions(std::span<const WireOpCode> opcodes)
390{
391 std::vector<Instruction> instructions;
392 instructions.reserve(opcodes.size());
393 for (const auto& opcode : opcodes) {
394 instructions.emplace_back(testing::random_instruction(opcode));
395 }
396 return instructions;
397}
398
399std::vector<uint8_t> create_bytecode(std::span<const Instruction> instructions)
400{
401 std::vector<uint8_t> bytecode;
402 for (const auto& instruction : instructions) {
403 auto serialized_instruction = instruction.serialize();
404 bytecode.insert(bytecode.end(),
405 std::make_move_iterator(serialized_instruction.begin()),
406 std::make_move_iterator(serialized_instruction.end()));
407 }
408 return bytecode;
409}
410
411std::vector<size_t> gen_pcs(std::span<const WireOpCode> opcodes)
412{
413 std::vector<size_t> pcs;
414 pcs.reserve(opcodes.size());
415 size_t pc = 0;
416 for (const auto& opcode : opcodes) {
417 pcs.emplace_back(pc);
418 pc += get_wire_instruction_spec().at(opcode).size_in_bytes;
419 }
420 return pcs;
421}
422
423std::vector<InstructionFetchingEvent> create_instruction_fetching_events(
424 const std::vector<Instruction>& instructions,
425 const std::vector<size_t>& pcs,
426 const std::shared_ptr<std::vector<uint8_t>>& bytecode_ptr,
427 const BytecodeId bytecode_id)
428{
430 events.reserve(instructions.size());
431
432 for (size_t i = 0; i < instructions.size(); i++) {
433 events.emplace_back(InstructionFetchingEvent{
434 .bytecode_id = bytecode_id,
435 .pc = static_cast<uint32_t>(pcs.at(i)),
436 .instruction = instructions.at(i),
437 .bytecode = bytecode_ptr,
438 });
439 }
440 return events;
441}
442
443// We build a random InstructionFetchingEvent for each wire opcode.
444// We then verify that the bytes (bd0, bd1, ...) correspond to the serialized instruction.
445TEST(BytecodeTraceGenTest, InstrDecompositionInBytesEachOpcode)
446{
447 TestTraceContainer trace;
448 BytecodeTraceBuilder builder;
449
450 constexpr std::array<C, 37> bd_columns = {
451 C::instr_fetching_bd0, C::instr_fetching_bd1, C::instr_fetching_bd2, C::instr_fetching_bd3,
452 C::instr_fetching_bd4, C::instr_fetching_bd5, C::instr_fetching_bd6, C::instr_fetching_bd7,
453 C::instr_fetching_bd8, C::instr_fetching_bd9, C::instr_fetching_bd10, C::instr_fetching_bd11,
454 C::instr_fetching_bd12, C::instr_fetching_bd13, C::instr_fetching_bd14, C::instr_fetching_bd15,
455 C::instr_fetching_bd16, C::instr_fetching_bd17, C::instr_fetching_bd18, C::instr_fetching_bd19,
456 C::instr_fetching_bd20, C::instr_fetching_bd21, C::instr_fetching_bd22, C::instr_fetching_bd23,
457 C::instr_fetching_bd24, C::instr_fetching_bd25, C::instr_fetching_bd26, C::instr_fetching_bd27,
458 C::instr_fetching_bd28, C::instr_fetching_bd29, C::instr_fetching_bd30, C::instr_fetching_bd31,
459 C::instr_fetching_bd32, C::instr_fetching_bd33, C::instr_fetching_bd34, C::instr_fetching_bd35,
460 C::instr_fetching_bd36,
461 };
462
463 constexpr std::array<C, 7> operand_columns = {
464 C::instr_fetching_op1, C::instr_fetching_op2, C::instr_fetching_op3, C::instr_fetching_op4,
465 C::instr_fetching_op5, C::instr_fetching_op6, C::instr_fetching_op7,
466 };
467
468 constexpr BytecodeId bytecode_id = 1;
469 constexpr auto num_opcodes = static_cast<size_t>(WireOpCode::LAST_OPCODE_SENTINEL);
470
472 opcodes.reserve(num_opcodes);
473 for (size_t i = 0; i < num_opcodes; i++) {
474 opcodes.emplace_back(static_cast<WireOpCode>(i));
475 }
476
477 std::vector<Instruction> instructions = gen_random_instructions(opcodes);
478 std::vector<size_t> pcs = gen_pcs(opcodes);
479 std::vector<uint8_t> bytecode = create_bytecode(instructions);
480
483 create_instruction_fetching_events(instructions, pcs, bytecode_ptr, bytecode_id);
484
485 builder.process_instruction_fetching(events, trace);
486
487 for (uint32_t i = 0; i < num_opcodes; i++) {
488 const auto instr = instructions.at(i);
489 const auto instr_encoded = instr.serialize();
490 const auto w_opcode = static_cast<WireOpCode>(i);
491
492 // Check size_in_bytes column
493 const auto expected_size_in_bytes = get_wire_instruction_spec().at(w_opcode).size_in_bytes;
494 ASSERT_EQ(instr_encoded.size(), expected_size_in_bytes);
495 EXPECT_EQ(FF(expected_size_in_bytes), trace.get(C::instr_fetching_instr_size, i + 1));
496
497 // Inspect each byte
498 for (size_t j = 0; j < static_cast<size_t>(expected_size_in_bytes); j++) {
499 EXPECT_EQ(FF(instr_encoded.at(j)), trace.get(bd_columns.at(j), i + 1));
500 }
501
502 // Check exection opcode
503 EXPECT_EQ(FF(static_cast<uint8_t>(get_wire_instruction_spec().at(w_opcode).exec_opcode)),
504 trace.get(C::instr_fetching_exec_opcode, i + 1));
505
506 // Check indirect
507 EXPECT_EQ(FF(instr.addressing_mode), trace.get(C::instr_fetching_addressing_mode, i + 1));
508
509 // Check PCs
510 EXPECT_EQ(FF(pcs.at(i)), trace.get(C::instr_fetching_pc, i + 1));
511
512 // Check operands
513 size_t operand_idx = 0;
514 for (const auto& operand : instr.operands) {
515 EXPECT_EQ(FF(operand), trace.get(operand_columns.at(operand_idx++), i + 1));
516 }
517 }
518}
519
520TEST(BytecodeTraceGenTest, InstrFetchingSingleBytecode)
521{
522 TestTraceContainer trace;
523 BytecodeTraceBuilder builder;
524
525 constexpr BytecodeId bytecode_id = 1;
526 constexpr size_t num_of_opcodes = 10;
527 constexpr std::array<WireOpCode, num_of_opcodes> opcodes = {
532 };
533
534 std::vector<Instruction> instructions = gen_random_instructions(opcodes);
535 std::vector<size_t> pcs = gen_pcs(opcodes);
536 std::vector<uint8_t> bytecode = create_bytecode(instructions);
537
538 std::vector<InstructionFetchingEvent> events = create_instruction_fetching_events(
539 instructions, pcs, std::make_shared<std::vector<uint8_t>>(bytecode), bytecode_id);
540
541 builder.process_instruction_fetching(events, trace);
542
543 // One extra empty row is prepended.
544 const auto rows = trace.as_rows();
545 const auto bytecode_size = bytecode.size();
546 EXPECT_EQ(rows.size(), num_of_opcodes + 1);
547
548 for (size_t i = 0; i < num_of_opcodes; i++) {
549 const auto pc = pcs.at(i);
550 const auto instr_size = get_wire_instruction_spec().at(opcodes.at(i)).size_in_bytes;
551 const auto has_tag = get_wire_instruction_spec().at(opcodes.at(i)).tag_operand_idx.has_value();
552 const auto tag_is_op2 =
553 has_tag ? get_wire_instruction_spec().at(opcodes.at(i)).tag_operand_idx.value() == 2 : 0;
554 const auto bytes_remaining = bytecode_size - pc;
555 const auto bytes_to_read = std::min<size_t>(DECOMPOSE_WINDOW_SIZE, bytes_remaining);
556
557 EXPECT_LE(instr_size, bytes_to_read);
558 const auto instr_abs_diff = bytes_to_read - instr_size;
559
560 EXPECT_LT(pc, bytecode_size);
561 const auto pc_abs_diff = bytecode_size - pc - 1;
562
563 ASSERT_LE(bytecode_size, UINT16_MAX);
564
565 EXPECT_THAT(rows.at(i + 1),
566 AllOf(ROW_FIELD_EQ(instr_fetching_sel, 1),
567 ROW_FIELD_EQ(instr_fetching_pc, pc),
568 ROW_FIELD_EQ(instr_fetching_bd0, static_cast<uint8_t>(opcodes.at(i))),
569 ROW_FIELD_EQ(instr_fetching_bytecode_id, bytecode_id),
570 ROW_FIELD_EQ(instr_fetching_bytes_to_read, bytes_to_read),
571 ROW_FIELD_EQ(instr_fetching_bytecode_size, bytecode_size),
572 ROW_FIELD_EQ(instr_fetching_instr_size, instr_size),
573 ROW_FIELD_EQ(instr_fetching_instr_abs_diff, instr_abs_diff),
574 ROW_FIELD_EQ(instr_fetching_pc_abs_diff, pc_abs_diff),
575 ROW_FIELD_EQ(instr_fetching_pc_out_of_range, 0),
576 ROW_FIELD_EQ(instr_fetching_opcode_out_of_range, 0),
577 ROW_FIELD_EQ(instr_fetching_instr_out_of_range, 0),
578 ROW_FIELD_EQ(instr_fetching_tag_out_of_range, 0),
579 ROW_FIELD_EQ(instr_fetching_sel_parsing_err, 0),
580 ROW_FIELD_EQ(instr_fetching_sel_pc_in_range, 1),
581 ROW_FIELD_EQ(instr_fetching_sel_has_tag, has_tag),
582 ROW_FIELD_EQ(instr_fetching_sel_tag_is_op2, tag_is_op2),
583 ROW_FIELD_EQ(instr_fetching_sel_pc_in_range, 1)));
584 }
585}
586
587// Test involving 3 different bytecode_id's for each 2 opcodes (same bytecode).
588TEST(BytecodeTraceGenTest, InstrFetchingMultipleBytecodes)
589{
590 TestTraceContainer trace;
591 BytecodeTraceBuilder builder;
592
593 constexpr size_t num_of_opcodes = 2;
594 constexpr std::array<WireOpCode, num_of_opcodes> opcodes = {
597 };
598
599 std::vector<Instruction> instructions = gen_random_instructions(opcodes);
600 std::vector<size_t> pcs = gen_pcs(opcodes);
601 std::vector<uint8_t> bytecode = create_bytecode(instructions);
602
604 for (size_t i = 0; i < 3; i++) {
606 auto new_events =
607 create_instruction_fetching_events(instructions, pcs, bytecode_ptr, static_cast<BytecodeId>(i + 1));
608 events.insert(events.end(), new_events.begin(), new_events.end());
609 }
610
611 builder.process_instruction_fetching(events, trace);
612
613 // One extra empty row is prepended.
614 const auto rows = trace.as_rows();
615 EXPECT_EQ(rows.size(), 6 + 1);
616
617 for (size_t i = 0; i < 3; i++) {
618 EXPECT_THAT(rows.at(2 * i + 1), ROW_FIELD_EQ(instr_fetching_pc, 0));
619 }
620}
621
622// Test which processes three single instruction events, each of one with a different parsing error.
623// The bytecode can be filled with trivial bytes of size 20 with all bytes being increasing from 0 to 19.
624// First byte at index 0 is set to LAST_OPCODE_SENTINEL + 1.
625// Then consider for the instruction events pc = 0, pc = 19, pc = 38.
626// pc == 0 will correspond to the error OPCODE_OUT_OF_RANGE
627// pc == 19 will have INSTRUCTION_OUT_OF_RANGE
628// pc == 38 will have PC_OUT_OF_RANGE
629// Check for each row that column instr_fetching_parsing_err in addition to the column of the respective error.
630// It is not an issue that the instruction is generated at random in the event and is not consistent with the
631// bytecode for this test case.
632TEST(BytecodeTraceGenTest, InstrFetchingParsingErrors)
633{
634 TestTraceContainer trace;
635 BytecodeTraceBuilder builder;
636
637 constexpr BytecodeId bytecode_id = 1;
638 constexpr size_t bytecode_size = 20;
639 std::vector<uint8_t> bytecode(bytecode_size);
640 for (size_t i = 0; i < bytecode_size; i++) {
641 bytecode[i] = static_cast<uint8_t>(i);
642 }
643 bytecode[0] = static_cast<uint8_t>(WireOpCode::LAST_OPCODE_SENTINEL) + 1;
644
647 events.emplace_back(InstructionFetchingEvent{
648 .bytecode_id = bytecode_id,
649 .pc = 0,
650 .bytecode = bytecode_ptr,
652 });
653 events.emplace_back(InstructionFetchingEvent{
654 .bytecode_id = bytecode_id,
655 .pc = 19,
656 .bytecode = bytecode_ptr,
658 });
659 events.emplace_back(InstructionFetchingEvent{
660 .bytecode_id = bytecode_id,
661 .pc = 38,
662 .bytecode = bytecode_ptr,
664 });
665
666 builder.process_instruction_fetching(events, trace);
667
668 // One extra empty row is prepended.
669 const auto rows = trace.as_rows();
670 ASSERT_EQ(rows.size(), 3 + 1);
671
672 EXPECT_THAT(rows.at(1),
673 AllOf(ROW_FIELD_EQ(instr_fetching_sel, 1),
674 ROW_FIELD_EQ(instr_fetching_sel_pc_in_range, 1),
675 ROW_FIELD_EQ(instr_fetching_pc, 0),
676 ROW_FIELD_EQ(instr_fetching_bytes_to_read, 20),
677 ROW_FIELD_EQ(instr_fetching_instr_size, 0),
678 ROW_FIELD_EQ(instr_fetching_instr_abs_diff,
679 20), // instr_size <= bytes_to_read: bytes_to_read - instr_size
680 ROW_FIELD_EQ(instr_fetching_sel_parsing_err, 1),
681 ROW_FIELD_EQ(instr_fetching_pc_abs_diff, 19), // bytecode_size - pc - 1 if bytecode_size > pc
682 ROW_FIELD_EQ(instr_fetching_opcode_out_of_range, 1)));
683
684 EXPECT_THAT(rows.at(2),
685 AllOf(ROW_FIELD_EQ(instr_fetching_sel, 1),
686 ROW_FIELD_EQ(instr_fetching_sel_pc_in_range, 1),
687 ROW_FIELD_EQ(instr_fetching_pc, 19), // OR_16 opcode
688 ROW_FIELD_EQ(instr_fetching_bytes_to_read, 1),
689 ROW_FIELD_EQ(instr_fetching_instr_size, 8), // OR_16 is 8 bytes long
690 ROW_FIELD_EQ(instr_fetching_instr_abs_diff,
691 6), // instr_size > bytes_to_read: instr_size - bytes_to_read - 1
692 ROW_FIELD_EQ(instr_fetching_sel_parsing_err, 1),
693 ROW_FIELD_EQ(instr_fetching_pc_abs_diff, 0), // bytecode_size - pc - 1 if bytecode_size > pc
694 ROW_FIELD_EQ(instr_fetching_instr_out_of_range, 1)));
695
696 EXPECT_THAT(
697 rows.at(3),
698 AllOf(ROW_FIELD_EQ(instr_fetching_sel, 1),
699 ROW_FIELD_EQ(instr_fetching_sel_pc_in_range, 0),
700 ROW_FIELD_EQ(instr_fetching_pc, 38),
701 ROW_FIELD_EQ(instr_fetching_bytes_to_read, 0),
702 ROW_FIELD_EQ(instr_fetching_instr_size, 0),
703 ROW_FIELD_EQ(instr_fetching_instr_abs_diff, 0), // instr_size <= bytes_to_read: bytes_to_read - instr_size
704 ROW_FIELD_EQ(instr_fetching_sel_parsing_err, 1),
705 ROW_FIELD_EQ(instr_fetching_pc_abs_diff, 18), // pc - bytecode_size if bytecode_size <= pc
706 ROW_FIELD_EQ(instr_fetching_pc_out_of_range, 1)));
707}
708
709// Test on error tag out of range
710TEST(BytecodeTraceGenTest, InstrFetchingErrorTagOutOfRange)
711{
715 TestTraceContainer trace;
716 BytecodeTraceBuilder builder;
717
718 auto instr_cast = random_instruction(WireOpCode::CAST_16);
719 auto instr_set = random_instruction(WireOpCode::SET_64);
720 constexpr uint32_t cast_size = 7;
721 constexpr uint32_t set_64_size = 13;
722
723 instr_cast.operands.at(2) = Operand::from<uint8_t>(0x09); // tag operand mutation to 0x09 which is out of range
724 instr_set.operands.at(1) = Operand::from<uint8_t>(0x0A); // tag operand mutation to 0x0A which is out of range
725
726 auto bytecode = instr_cast.serialize();
727 ASSERT_EQ(bytecode.size(), cast_size);
728
729 auto instr_set_bytecode = instr_set.serialize();
730 ASSERT_EQ(instr_set_bytecode.size(), set_64_size);
731
732 bytecode.insert(bytecode.end(), instr_set_bytecode.begin(), instr_set_bytecode.end());
733
734 const auto bytecode_ptr = std::make_shared<std::vector<uint8_t>>(bytecode);
735
737 events.emplace_back(InstructionFetchingEvent{
738 .bytecode_id = 1,
739 .pc = 0,
740 .instruction = deserialize_instruction(bytecode, 0), // Reflect more the real code path than passing instr_cast.
741 .bytecode = bytecode_ptr,
743 });
744
745 events.emplace_back(InstructionFetchingEvent{
746 .bytecode_id = 1,
747 .pc = cast_size,
748 .instruction =
749 deserialize_instruction(bytecode, cast_size), // Reflect more the real code path than passing instr_set.
750 .bytecode = bytecode_ptr,
752 });
753
754 builder.process_instruction_fetching(events, trace);
755
756 // One extra empty row is prepended.
757 const auto rows = trace.as_rows();
758 ASSERT_EQ(rows.size(), 2 + 1);
759
760 EXPECT_THAT(rows.at(1),
761 AllOf(ROW_FIELD_EQ(instr_fetching_sel, 1),
762 ROW_FIELD_EQ(instr_fetching_sel_pc_in_range, 1),
763 ROW_FIELD_EQ(instr_fetching_sel_has_tag, 1),
764 ROW_FIELD_EQ(instr_fetching_sel_tag_is_op2, 0),
765 ROW_FIELD_EQ(instr_fetching_tag_value, 9),
766 ROW_FIELD_EQ(instr_fetching_pc, 0),
767 ROW_FIELD_EQ(instr_fetching_bytes_to_read, cast_size + set_64_size),
768 ROW_FIELD_EQ(instr_fetching_instr_size, cast_size),
769 ROW_FIELD_EQ(instr_fetching_instr_abs_diff,
770 set_64_size), // instr_size <= bytes_to_read: bytes_to_read - instr_size
771 ROW_FIELD_EQ(instr_fetching_sel_parsing_err, 1),
772 ROW_FIELD_EQ(instr_fetching_pc_abs_diff,
773 cast_size + set_64_size - 1), // bytecode_size - pc - 1 if bytecode_size > pc
774 ROW_FIELD_EQ(instr_fetching_tag_out_of_range, 1)));
775
776 EXPECT_THAT(
777 rows.at(2),
778 AllOf(ROW_FIELD_EQ(instr_fetching_sel, 1),
779 ROW_FIELD_EQ(instr_fetching_sel_pc_in_range, 1),
780 ROW_FIELD_EQ(instr_fetching_sel_has_tag, 1),
781 ROW_FIELD_EQ(instr_fetching_sel_tag_is_op2, 1),
782 ROW_FIELD_EQ(instr_fetching_tag_value, 10),
783 ROW_FIELD_EQ(instr_fetching_pc, cast_size),
784 ROW_FIELD_EQ(instr_fetching_bytes_to_read, set_64_size),
785 ROW_FIELD_EQ(instr_fetching_instr_size, set_64_size),
786 ROW_FIELD_EQ(instr_fetching_instr_abs_diff, 0), // instr_size <= bytes_to_read: bytes_to_read - instr_size
787 ROW_FIELD_EQ(instr_fetching_sel_parsing_err, 1),
788 ROW_FIELD_EQ(instr_fetching_pc_abs_diff, set_64_size - 1), // bytecode_size - pc - 1 if bytecode_size > pc
789 ROW_FIELD_EQ(instr_fetching_tag_out_of_range, 1)));
790}
791
792} // namespace
793} // namespace bb::avm2::tracegen
std::shared_ptr< Napi::ThreadSafeFunction > bytecode
std::vector< AvmFullRowConstRef > as_rows() const
const FF & get(Column col, uint32_t row) const
static FF hash(const std::vector< FF > &input)
Hashes a vector of field elements.
AluTraceBuilder builder
Definition alu.test.cpp:124
TestTraceContainer trace
std::vector< uint8_t > create_bytecode(const std::vector< bb::avm2::simulation::Instruction > &instructions)
Instruction instruction
#define ROW_FIELD_EQ(field_name, expression)
Definition macros.hpp:7
FF compute_public_bytecode_first_field(size_t bytecode_size)
Instruction deserialize_instruction(std::span< const uint8_t > bytecode, size_t pos)
Parsing of an instruction in the supplied bytecode at byte position pos. This checks that the WireOpC...
Instruction random_instruction(WireOpCode w_opcode)
Definition fixtures.cpp:125
AvmFlavorSettings::FF FF
Definition field.hpp:10
const std::unordered_map< WireOpCode, WireInstructionSpec > & get_wire_instruction_spec()
constexpr uint32_t DECOMPOSE_WINDOW_SIZE
TEST(BoomerangMegaCircuitBuilder, BasicCircuit)
STL namespace.
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13