Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
serialization.cpp
Go to the documentation of this file.
2
3#include <cassert>
4#include <cstdint>
5#include <iomanip>
6#include <span>
7#include <sstream>
8#include <string>
9#include <unordered_map>
10#include <variant>
11#include <vector>
12
20
21namespace bb::avm2::simulation {
22
23namespace {
24const std::unordered_map<OperandType, uint32_t>& get_operand_type_size_bytes()
25{
26 static const std::unordered_map<OperandType, uint32_t> OPERAND_TYPE_SIZE_BYTES = {
30 };
31 return OPERAND_TYPE_SIZE_BYTES;
32}
33} // namespace
34
35// Instruction wire formats.
49
51 /*l2GasOffset=*/OperandType::UINT16,
52 /*daGasOffset=*/OperandType::UINT16,
53 /*addrOffset=*/OperandType::UINT16,
54 /*argsOffset=*/OperandType::UINT16,
55 /*argsSizeOffset=*/OperandType::UINT16 };
56
57namespace {
58// Contrary to TS, the format does not contain the WireOpCode byte which prefixes any instruction.
59// Entries are ordered to match WireOpCode enum.
60const std::unordered_map<WireOpCode, std::vector<OperandType>>& get_wire_opcode_wire_format()
61{
62 static const std::unordered_map<WireOpCode, std::vector<OperandType>> WireOpCode_WIRE_FORMAT = {
63 // Compute
64 // Compute - Arithmetic
75 // Compute - Comparison
82 // Compute - Bitwise
95 // Compute - Type Conversions
98
99 // Execution Environment - Globals
101 {
104 OperandType::UINT8, // var idx
105 } },
106
107 // Execution Environment - Calldata
114
115 // Machine State - Internal Control Flow
120
121 // Machine State - Memory
131
132 // Side Effects - Public Storage
135 // Side Effects - Notes, Nullfiers, Logs, Messages
138
140 {
143 } },
147 {
150 } },
156 {
160 } },
162
163 // Control Flow - Contract Calls
167 // REVERT,
170
171 // Misc
179
180 // Gadgets
181 // Gadgets - Hashing
186 // TEMP ECADD without relative memory
189 OperandType::UINT16, // lhs.x
190 OperandType::UINT16, // lhs.y
191 OperandType::UINT16, // lhs.is_infinite
192 OperandType::UINT16, // rhs.x
193 OperandType::UINT16, // rhs.y
194 OperandType::UINT16, // rhs.is_infinite
195 OperandType::UINT16 } }, // dst_offset
196 // Gadget - Conversion
204 };
205 return WireOpCode_WIRE_FORMAT;
206}
207} // namespace
208
209namespace testonly {
210
212{
213 return get_wire_opcode_wire_format();
214}
215
217{
218 return get_operand_type_size_bytes();
219}
220
221} // namespace testonly
222
223namespace {
224
225bool is_wire_opcode_valid(uint8_t w_opcode)
226{
227 return w_opcode < static_cast<uint8_t>(WireOpCode::LAST_OPCODE_SENTINEL);
228}
229
230} // namespace
231
233{
234 const auto bytecode_length = bytecode.size();
235
236 if (pos >= bytecode_length) {
237 std::string error_msg = format("Invalid program counter ", pos, ", max is ", bytecode_length - 1);
238 vinfo(error_msg);
240 }
241
242 const uint8_t opcode_byte = bytecode[pos];
243
244 if (!is_wire_opcode_valid(opcode_byte)) {
245 std::string error_msg = format("Opcode ",
246 static_cast<uint32_t>(opcode_byte),
247 " (0x",
248 to_hex(opcode_byte),
249 ") value is not in the range of valid opcodes (at PC ",
250 pos,
251 ").");
252 vinfo(error_msg);
254 }
255
256 const auto opcode = static_cast<WireOpCode>(opcode_byte);
257 const auto iter = get_wire_opcode_wire_format().find(opcode);
258 assert(iter != get_wire_opcode_wire_format().end());
259 const auto& inst_format = iter->second;
260
261 const uint32_t instruction_size = get_wire_instruction_spec().at(opcode).size_in_bytes;
262
263 // We know we will encounter a parsing error, but continue processing because
264 // we need the partial instruction to be parsed for witness generation.
265 if (pos + instruction_size > bytecode_length) {
266 std::string error_msg = format("Instruction at PC ",
267 pos,
268 " does not fit in bytecode (instruction size: ",
269 instruction_size,
270 ", remaining: ",
271 bytecode_length - pos,
272 ")");
273 vinfo(error_msg);
275 }
276
277 pos++; // move after opcode byte
278
279 uint16_t indirect = 0;
280 std::vector<Operand> operands;
281 for (const OperandType op_type : inst_format) {
282 const auto operand_size = get_operand_type_size_bytes().at(op_type);
283 assert(pos + operand_size <= bytecode_length); // Guaranteed to hold due to
284 // pos + instruction_size <= bytecode_length
285
286 switch (op_type) {
287 case OperandType::TAG:
288 case OperandType::UINT8: {
289 operands.emplace_back(Operand::from<uint8_t>(bytecode[pos]));
290 break;
291 }
293 indirect = bytecode[pos];
294 break;
295 }
297 uint16_t operand_u16 = 0;
298 uint8_t const* pos_ptr = &bytecode[pos];
299 serialize::read(pos_ptr, operand_u16);
300 indirect = operand_u16;
301 break;
302 }
303 case OperandType::UINT16: {
304 uint16_t operand_u16 = 0;
305 uint8_t const* pos_ptr = &bytecode[pos];
306 serialize::read(pos_ptr, operand_u16);
307 operands.emplace_back(Operand::from<uint16_t>(operand_u16));
308 break;
309 }
310 case OperandType::UINT32: {
311 uint32_t operand_u32 = 0;
312 uint8_t const* pos_ptr = &bytecode[pos];
313 serialize::read(pos_ptr, operand_u32);
314 operands.emplace_back(Operand::from<uint32_t>(operand_u32));
315 break;
316 }
317 case OperandType::UINT64: {
318 uint64_t operand_u64 = 0;
319 uint8_t const* pos_ptr = &bytecode[pos];
320 serialize::read(pos_ptr, operand_u64);
321 operands.emplace_back(Operand::from<uint64_t>(operand_u64));
322 break;
323 }
325 uint128_t operand_u128 = 0;
326 uint8_t const* pos_ptr = &bytecode[pos];
327 serialize::read(pos_ptr, operand_u128);
328 operands.emplace_back(Operand::from<uint128_t>(operand_u128));
329 break;
330 }
331 case OperandType::FF: {
332 FF operand_ff;
333 uint8_t const* pos_ptr = &bytecode[pos];
334 read(pos_ptr, operand_ff);
335 operands.emplace_back(Operand::from<FF>(operand_ff));
336 }
337 }
338 pos += operand_size;
339 }
340
341 return {
342 .opcode = opcode,
343 .indirect = indirect,
344 .operands = std::move(operands),
345 };
346};
347
348std::string Instruction::to_string() const
349{
350 std::ostringstream oss;
351 oss << opcode << " ";
352 for (size_t operand_pos = 0; operand_pos < operands.size(); ++operand_pos) {
353 const auto& operand = operands[operand_pos];
354 oss << std::to_string(operand);
355 if (is_operand_relative(indirect, static_cast<uint8_t>(operand_pos))) {
356 oss << "R";
357 }
358 if (is_operand_indirect(indirect, static_cast<uint8_t>(operand_pos))) {
359 oss << "I";
360 }
361 oss << " ";
362 }
363 return oss.str();
364}
365
367{
368 assert(get_wire_instruction_spec().contains(opcode));
369 return get_wire_instruction_spec().at(opcode).size_in_bytes;
370}
371
373{
374 assert(get_wire_instruction_spec().contains(opcode));
375 return get_wire_instruction_spec().at(opcode).exec_opcode;
376}
377
378std::vector<uint8_t> Instruction::serialize() const
379{
380 std::vector<uint8_t> output;
381 output.reserve(get_wire_instruction_spec().at(opcode).size_in_bytes);
382 output.emplace_back(static_cast<uint8_t>(opcode));
383 size_t operand_pos = 0;
384
385 for (const auto& operand_type : get_wire_opcode_wire_format().at(opcode)) {
386 switch (operand_type) {
388 output.emplace_back(static_cast<uint8_t>(indirect));
389 break;
391 const auto indirect_vec = to_buffer(indirect);
392 output.insert(output.end(),
393 std::make_move_iterator(indirect_vec.begin()),
394 std::make_move_iterator(indirect_vec.end()));
395 } break;
396 case OperandType::TAG:
398 output.emplace_back(operands.at(operand_pos++).as<uint8_t>());
399 break;
400 case OperandType::UINT16: {
401 const auto operand_vec = to_buffer(operands.at(operand_pos++).as<uint16_t>());
402 output.insert(
403 output.end(), std::make_move_iterator(operand_vec.begin()), std::make_move_iterator(operand_vec.end()));
404 } break;
405 case OperandType::UINT32: {
406 const auto operand_vec = to_buffer(operands.at(operand_pos++).as<uint32_t>());
407 output.insert(
408 output.end(), std::make_move_iterator(operand_vec.begin()), std::make_move_iterator(operand_vec.end()));
409 } break;
410 case OperandType::UINT64: {
411 const auto operand_vec = to_buffer(operands.at(operand_pos++).as<uint64_t>());
412 output.insert(
413 output.end(), std::make_move_iterator(operand_vec.begin()), std::make_move_iterator(operand_vec.end()));
414 } break;
416 const auto operand_vec = to_buffer(operands.at(operand_pos++).as<uint128_t>());
417 output.insert(
418 output.end(), std::make_move_iterator(operand_vec.begin()), std::make_move_iterator(operand_vec.end()));
419 } break;
420 case OperandType::FF: {
421 const auto operand_vec = to_buffer(operands.at(operand_pos++).as<FF>());
422 output.insert(
423 output.end(), std::make_move_iterator(operand_vec.begin()), std::make_move_iterator(operand_vec.end()));
424 } break;
425 }
426 }
427 return output;
428}
429
431{
433 vinfo("Instruction does not contain a valid wire opcode.");
434 return false;
435 }
436
437 const auto& wire_format = get_wire_opcode_wire_format().at(instruction.opcode);
438
439 size_t pos = 0; // Position in instruction operands
440
441 for (size_t i = 0; i < wire_format.size(); i++) {
442 if (wire_format[i] == OperandType::INDIRECT8 || wire_format[i] == OperandType::INDIRECT16) {
443 continue; // No pos increment
444 }
445
446 if (wire_format[i] == OperandType::TAG) {
447 if (pos >= instruction.operands.size()) {
448 vinfo("Instruction operands size is too small. Tag position: ",
449 pos,
450 " size: ",
451 instruction.operands.size(),
452 " WireOpCode: ",
454 return false;
455 }
456
457 try {
458 uint8_t tag = instruction.operands.at(pos).as<uint8_t>(); // Cast to uint8_t might throw
459
460 if (tag > static_cast<uint8_t>(MemoryTag::MAX)) {
461 vinfo("Instruction tag operand at position: ",
462 pos,
463 " is invalid.",
464 " Tag value: ",
465 tag,
466 " WireOpCode: ",
468 return false;
469 }
470
471 } catch (const std::runtime_error&) {
472 vinfo("Instruction operand at position: ",
473 pos,
474 " is longer than a byte.",
475 " WireOpCode: ",
477 return false;
478 }
479 }
480
481 pos++;
482 }
483 return true;
484}
485
486} // namespace bb::avm2::simulation
std::shared_ptr< Napi::ThreadSafeFunction > bytecode
std::string format(Args... args)
Definition log.hpp:22
#define vinfo(...)
Definition log.hpp:80
Instruction instruction
const std::unordered_map< OperandType, uint32_t > & get_operand_type_sizes()
const std::unordered_map< WireOpCode, std::vector< OperandType > > & get_instruction_wire_formats()
const std::vector< OperandType > external_call_format
bool check_tag(const Instruction &instruction)
Check whether the instruction must have a tag operand and whether the operand value is in the value t...
const std::vector< OperandType > three_operand_format16
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...
const std::vector< OperandType > kernel_input_operand_format
const std::vector< OperandType > three_operand_format8
std::string to_hex(T value)
Definition stringify.hpp:19
bool is_operand_relative(uint16_t indirect_flag, size_t operand_index)
Definition addressing.hpp:8
const std::unordered_map< WireOpCode, WireInstructionSpec > & get_wire_instruction_spec()
bool is_operand_indirect(uint16_t indirect_flag, size_t operand_index)
AvmFlavorSettings::FF FF
Definition field.hpp:10
void read(uint8_t const *&it, Chonk::VerificationKey &vk)
Definition chonk.hpp:350
void read(auto &it, msgpack_concepts::HasMsgPack auto &obj)
Automatically derived read for any object that defines .msgpack() (implicitly defined by MSGPACK_FIEL...
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::string to_string(bb::avm2::ValueTag tag)
std::vector< uint8_t > to_buffer(T const &value)
unsigned __int128 uint128_t
Definition serialize.hpp:44
std::vector< uint8_t > serialize() const
std::vector< Operand > operands
ExecutionOpCode get_exec_opcode() const