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 /*argsSizeOffset=*/OperandType::UINT16,
55 /*argsOffset=*/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
136 // Side Effects - Notes, Nullfiers, Logs, Messages
139
141 {
144 } },
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, // rhs.x
192 OperandType::UINT16, // rhs.y
193 OperandType::UINT16 } }, // dst_offset
194 // Gadget - Conversion
202 };
203 return WireOpCode_WIRE_FORMAT;
204}
205} // namespace
206
207namespace testonly {
208
210{
211 return get_wire_opcode_wire_format();
212}
213
215{
216 return get_operand_type_size_bytes();
217}
218
219} // namespace testonly
220
221namespace {
222
223bool is_wire_opcode_valid(uint8_t w_opcode)
224{
225 return w_opcode < static_cast<uint8_t>(WireOpCode::LAST_OPCODE_SENTINEL);
226}
227
228} // namespace
229
251{
252 const auto bytecode_length = bytecode.size();
253
254 if (pos >= bytecode_length) {
255 std::string error_msg = format("Invalid program counter ", pos, ", max is ", bytecode_length - 1);
256 vinfo(error_msg);
258 }
259
260 const uint8_t opcode_byte = bytecode[pos];
261
262 if (!is_wire_opcode_valid(opcode_byte)) {
263 std::string error_msg = format("Opcode ",
264 static_cast<uint32_t>(opcode_byte),
265 " (0x",
266 to_hex(opcode_byte),
267 ") value is not in the range of valid opcodes (at PC ",
268 pos,
269 ").");
270 vinfo(error_msg);
272 }
273
274 const auto opcode = static_cast<WireOpCode>(opcode_byte);
275 const auto iter = get_wire_opcode_wire_format().find(opcode);
276 BB_ASSERT_DEBUG(iter != get_wire_opcode_wire_format().end(), "Wire opcode not found in wire opcode wire format");
277 const auto& inst_format = iter->second;
278
279 const uint32_t instruction_size = get_wire_instruction_spec().at(opcode).size_in_bytes;
280
281 if (pos + instruction_size > bytecode_length) {
282 std::string error_msg = format("Instruction at PC ",
283 pos,
284 " does not fit in bytecode (instruction size: ",
285 instruction_size,
286 ", remaining: ",
287 bytecode_length - pos,
288 ")");
289 vinfo(error_msg);
291 }
292
293 // Increment by 1 for the opcode byte.
294 pos++;
295
296 uint16_t addressing_mode = 0;
297 std::vector<Operand> operands;
298 for (const OperandType op_type : inst_format) {
299 const auto operand_size = get_operand_type_size_bytes().at(op_type);
300 // Guaranteed to hold due to pos + instruction_size <= bytecode_length
301 BB_ASSERT_DEBUG(pos + operand_size <= bytecode_length, "Operand size is out of range");
302
303 switch (op_type) {
304 case OperandType::TAG:
305 case OperandType::UINT8: {
306 operands.emplace_back(Operand::from<uint8_t>(bytecode[pos]));
307 break;
308 }
310 addressing_mode = bytecode[pos];
311 break;
312 }
314 uint16_t operand_u16 = 0;
315 uint8_t const* pos_ptr = &bytecode[pos];
316 serialize::read(pos_ptr, operand_u16);
317 addressing_mode = operand_u16;
318 break;
319 }
320 case OperandType::UINT16: {
321 uint16_t operand_u16 = 0;
322 uint8_t const* pos_ptr = &bytecode[pos];
323 serialize::read(pos_ptr, operand_u16);
324 operands.emplace_back(Operand::from<uint16_t>(operand_u16));
325 break;
326 }
327 case OperandType::UINT32: {
328 uint32_t operand_u32 = 0;
329 uint8_t const* pos_ptr = &bytecode[pos];
330 serialize::read(pos_ptr, operand_u32);
331 operands.emplace_back(Operand::from<uint32_t>(operand_u32));
332 break;
333 }
334 case OperandType::UINT64: {
335 uint64_t operand_u64 = 0;
336 uint8_t const* pos_ptr = &bytecode[pos];
337 serialize::read(pos_ptr, operand_u64);
338 operands.emplace_back(Operand::from<uint64_t>(operand_u64));
339 break;
340 }
342 uint128_t operand_u128 = 0;
343 uint8_t const* pos_ptr = &bytecode[pos];
344 serialize::read(pos_ptr, operand_u128);
345 operands.emplace_back(Operand::from<uint128_t>(operand_u128));
346 break;
347 }
348 case OperandType::FF: {
349 FF operand_ff;
350 uint8_t const* pos_ptr = &bytecode[pos];
351 read(pos_ptr, operand_ff);
352 operands.emplace_back(Operand::from<FF>(operand_ff));
353 }
354 }
355 pos += operand_size;
356 }
357
358 return {
359 .opcode = opcode,
360 .addressing_mode = addressing_mode,
361 .operands = std::move(operands),
362 };
363};
364
365std::string Instruction::to_string() const
366{
367 std::ostringstream oss;
368 oss << opcode << " ";
369 for (size_t operand_pos = 0; operand_pos < operands.size(); ++operand_pos) {
370 const auto& operand = operands[operand_pos];
371 oss << std::to_string(operand);
372 if (is_operand_relative(addressing_mode, static_cast<uint8_t>(operand_pos))) {
373 oss << "R";
374 }
375 if (is_operand_indirect(addressing_mode, static_cast<uint8_t>(operand_pos))) {
376 oss << "I";
377 }
378 oss << " ";
379 }
380 return oss.str();
381}
382
384{
385 BB_ASSERT_DEBUG(get_wire_instruction_spec().contains(opcode), "Wire instruction spec not found for opcode");
386 return get_wire_instruction_spec().at(opcode).size_in_bytes;
387}
388
390{
391 BB_ASSERT_DEBUG(get_wire_instruction_spec().contains(opcode), "Wire instruction spec not found for opcode");
392 return get_wire_instruction_spec().at(opcode).exec_opcode;
393}
394
395std::vector<uint8_t> Instruction::serialize() const
396{
397 std::vector<uint8_t> output;
398 output.reserve(get_wire_instruction_spec().at(opcode).size_in_bytes);
399 output.emplace_back(static_cast<uint8_t>(opcode));
400 size_t operand_pos = 0;
401
402 for (const auto& operand_type : get_wire_opcode_wire_format().at(opcode)) {
403 switch (operand_type) {
405 output.emplace_back(static_cast<uint8_t>(addressing_mode));
406 break;
408 const auto addressing_mode_vec = to_buffer(addressing_mode);
409 output.insert(output.end(),
410 std::make_move_iterator(addressing_mode_vec.begin()),
411 std::make_move_iterator(addressing_mode_vec.end()));
412 } break;
413 case OperandType::TAG:
415 output.emplace_back(operands.at(operand_pos++).as<uint8_t>());
416 break;
417 case OperandType::UINT16: {
418 const auto operand_vec = to_buffer(operands.at(operand_pos++).as<uint16_t>());
419 output.insert(
420 output.end(), std::make_move_iterator(operand_vec.begin()), std::make_move_iterator(operand_vec.end()));
421 } break;
422 case OperandType::UINT32: {
423 const auto operand_vec = to_buffer(operands.at(operand_pos++).as<uint32_t>());
424 output.insert(
425 output.end(), std::make_move_iterator(operand_vec.begin()), std::make_move_iterator(operand_vec.end()));
426 } break;
427 case OperandType::UINT64: {
428 const auto operand_vec = to_buffer(operands.at(operand_pos++).as<uint64_t>());
429 output.insert(
430 output.end(), std::make_move_iterator(operand_vec.begin()), std::make_move_iterator(operand_vec.end()));
431 } break;
433 const auto operand_vec = to_buffer(operands.at(operand_pos++).as<uint128_t>());
434 output.insert(
435 output.end(), std::make_move_iterator(operand_vec.begin()), std::make_move_iterator(operand_vec.end()));
436 } break;
437 case OperandType::FF: {
438 const auto operand_vec = to_buffer(operands.at(operand_pos++).as<FF>());
439 output.insert(
440 output.end(), std::make_move_iterator(operand_vec.begin()), std::make_move_iterator(operand_vec.end()));
441 } break;
442 }
443 }
444 return output;
445}
446
467{
468 // Guaranteed to hold as we only call this function after deserialize_instruction() where the opcode is checked.
470 "Instruction does not contain a valid wire opcode.");
471
472 const auto& wire_format = get_wire_opcode_wire_format().at(instruction.opcode);
473
474 size_t pos = 0; // Position in instruction operands
475
476 for (const OperandType& operand_type : wire_format) {
477 if (operand_type == OperandType::INDIRECT8 || operand_type == OperandType::INDIRECT16) {
478 continue; // No pos increment
479 }
480
481 if (operand_type == OperandType::TAG) {
482 // Guaranteed to hold as we only call this function after deserialize_instruction() where instruction size
483 // is checked.
484 BB_ASSERT(pos < instruction.operands.size(), "Instruction operands size is too small.");
485
486 try {
487 uint8_t tag = instruction.operands.at(pos).as<uint8_t>(); // Cast to uint8_t might throw CastException
488
489 if (tag > static_cast<uint8_t>(MemoryTag::MAX)) {
490 vinfo("Instruction tag operand at position: ",
491 pos,
492 " is invalid.",
493 " Tag value: ",
494 tag,
495 " WireOpCode: ",
497 return false;
498 }
499 } catch (const CastException&) {
500 vinfo("Instruction operand at position: ",
501 pos,
502 " is longer than a byte.",
503 " WireOpCode: ",
505 return false;
506 }
507 }
508
509 pos++;
510 }
511 return true;
512}
513
514} // namespace bb::avm2::simulation
#define BB_ASSERT(expression,...)
Definition assert.hpp:70
#define BB_ASSERT_DEBUG(expression,...)
Definition assert.hpp:55
std::shared_ptr< Napi::ThreadSafeFunction > bytecode
std::string format(Args... args)
Definition log.hpp:23
#define vinfo(...)
Definition log.hpp:94
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()
AVM range check gadget for witness generation.
const std::vector< OperandType > external_call_format
bool check_tag(const Instruction &instruction)
Checks whether the tag operand of an instruction is a valid MemoryTag. Called by bytecode managers du...
const std::vector< OperandType > three_operand_format16
Instruction deserialize_instruction(std::span< const uint8_t > bytecode, size_t pos)
Attempts to deserialize the instruction at position pos in bytecode. Called by bytecode managers duri...
const std::vector< OperandType > kernel_input_operand_format
const std::vector< OperandType > three_operand_format8
std::string to_hex(T value)
Definition stringify.hpp:21
bool is_operand_relative(uint16_t indirect_flag, size_t operand_index)
Checks if the operand at the given index is relative.
AvmFlavorSettings::FF FF
Definition field.hpp:10
const std::unordered_map< WireOpCode, WireInstructionSpec > & get_wire_instruction_spec()
bool is_operand_indirect(uint16_t indirect_flag, size_t operand_index)
Checks if the operand at the given index is indirect.
void read(B &it, field2< base_field, Params > &value)
void read(auto &it, msgpack_concepts::HasMsgPack auto &obj)
Automatically derived read for any object that defines .msgpack() (implicitly defined by SERIALIZATIO...
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:45
std::vector< uint8_t > serialize() const
std::vector< Operand > operands
ExecutionOpCode get_exec_opcode() const