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, // 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
253{
254 const auto bytecode_length = bytecode.size();
255
256 if (pos >= bytecode_length) {
257 std::string error_msg = format("Invalid program counter ", pos, ", max is ", bytecode_length - 1);
258 vinfo(error_msg);
260 }
261
262 const uint8_t opcode_byte = bytecode[pos];
263
264 if (!is_wire_opcode_valid(opcode_byte)) {
265 std::string error_msg = format("Opcode ",
266 static_cast<uint32_t>(opcode_byte),
267 " (0x",
268 to_hex(opcode_byte),
269 ") value is not in the range of valid opcodes (at PC ",
270 pos,
271 ").");
272 vinfo(error_msg);
274 }
275
276 const auto opcode = static_cast<WireOpCode>(opcode_byte);
277 const auto iter = get_wire_opcode_wire_format().find(opcode);
278 BB_ASSERT_DEBUG(iter != get_wire_opcode_wire_format().end(), "Wire opcode not found in wire opcode wire format");
279 const auto& inst_format = iter->second;
280
281 const uint32_t instruction_size = get_wire_instruction_spec().at(opcode).size_in_bytes;
282
283 if (pos + instruction_size > bytecode_length) {
284 std::string error_msg = format("Instruction at PC ",
285 pos,
286 " does not fit in bytecode (instruction size: ",
287 instruction_size,
288 ", remaining: ",
289 bytecode_length - pos,
290 ")");
291 vinfo(error_msg);
293 }
294
295 // Increment by 1 for the opcode byte.
296 pos++;
297
298 uint16_t addressing_mode = 0;
299 std::vector<Operand> operands;
300 for (const OperandType op_type : inst_format) {
301 const auto operand_size = get_operand_type_size_bytes().at(op_type);
302 // Guaranteed to hold due to pos + instruction_size <= bytecode_length
303 BB_ASSERT_DEBUG(pos + operand_size <= bytecode_length, "Operand size is out of range");
304
305 switch (op_type) {
306 case OperandType::TAG:
307 case OperandType::UINT8: {
308 operands.emplace_back(Operand::from<uint8_t>(bytecode[pos]));
309 break;
310 }
312 addressing_mode = bytecode[pos];
313 break;
314 }
316 uint16_t operand_u16 = 0;
317 uint8_t const* pos_ptr = &bytecode[pos];
318 serialize::read(pos_ptr, operand_u16);
319 addressing_mode = operand_u16;
320 break;
321 }
322 case OperandType::UINT16: {
323 uint16_t operand_u16 = 0;
324 uint8_t const* pos_ptr = &bytecode[pos];
325 serialize::read(pos_ptr, operand_u16);
326 operands.emplace_back(Operand::from<uint16_t>(operand_u16));
327 break;
328 }
329 case OperandType::UINT32: {
330 uint32_t operand_u32 = 0;
331 uint8_t const* pos_ptr = &bytecode[pos];
332 serialize::read(pos_ptr, operand_u32);
333 operands.emplace_back(Operand::from<uint32_t>(operand_u32));
334 break;
335 }
336 case OperandType::UINT64: {
337 uint64_t operand_u64 = 0;
338 uint8_t const* pos_ptr = &bytecode[pos];
339 serialize::read(pos_ptr, operand_u64);
340 operands.emplace_back(Operand::from<uint64_t>(operand_u64));
341 break;
342 }
344 uint128_t operand_u128 = 0;
345 uint8_t const* pos_ptr = &bytecode[pos];
346 serialize::read(pos_ptr, operand_u128);
347 operands.emplace_back(Operand::from<uint128_t>(operand_u128));
348 break;
349 }
350 case OperandType::FF: {
351 FF operand_ff;
352 uint8_t const* pos_ptr = &bytecode[pos];
353 read(pos_ptr, operand_ff);
354 operands.emplace_back(Operand::from<FF>(operand_ff));
355 }
356 }
357 pos += operand_size;
358 }
359
360 return {
361 .opcode = opcode,
362 .addressing_mode = addressing_mode,
363 .operands = std::move(operands),
364 };
365};
366
367std::string Instruction::to_string() const
368{
369 std::ostringstream oss;
370 oss << opcode << " ";
371 for (size_t operand_pos = 0; operand_pos < operands.size(); ++operand_pos) {
372 const auto& operand = operands[operand_pos];
373 oss << std::to_string(operand);
374 if (is_operand_relative(addressing_mode, static_cast<uint8_t>(operand_pos))) {
375 oss << "R";
376 }
377 if (is_operand_indirect(addressing_mode, static_cast<uint8_t>(operand_pos))) {
378 oss << "I";
379 }
380 oss << " ";
381 }
382 return oss.str();
383}
384
386{
387 BB_ASSERT_DEBUG(get_wire_instruction_spec().contains(opcode), "Wire instruction spec not found for opcode");
388 return get_wire_instruction_spec().at(opcode).size_in_bytes;
389}
390
392{
393 BB_ASSERT_DEBUG(get_wire_instruction_spec().contains(opcode), "Wire instruction spec not found for opcode");
394 return get_wire_instruction_spec().at(opcode).exec_opcode;
395}
396
397std::vector<uint8_t> Instruction::serialize() const
398{
399 std::vector<uint8_t> output;
400 output.reserve(get_wire_instruction_spec().at(opcode).size_in_bytes);
401 output.emplace_back(static_cast<uint8_t>(opcode));
402 size_t operand_pos = 0;
403
404 for (const auto& operand_type : get_wire_opcode_wire_format().at(opcode)) {
405 switch (operand_type) {
407 output.emplace_back(static_cast<uint8_t>(addressing_mode));
408 break;
410 const auto addressing_mode_vec = to_buffer(addressing_mode);
411 output.insert(output.end(),
412 std::make_move_iterator(addressing_mode_vec.begin()),
413 std::make_move_iterator(addressing_mode_vec.end()));
414 } break;
415 case OperandType::TAG:
417 output.emplace_back(operands.at(operand_pos++).as<uint8_t>());
418 break;
419 case OperandType::UINT16: {
420 const auto operand_vec = to_buffer(operands.at(operand_pos++).as<uint16_t>());
421 output.insert(
422 output.end(), std::make_move_iterator(operand_vec.begin()), std::make_move_iterator(operand_vec.end()));
423 } break;
424 case OperandType::UINT32: {
425 const auto operand_vec = to_buffer(operands.at(operand_pos++).as<uint32_t>());
426 output.insert(
427 output.end(), std::make_move_iterator(operand_vec.begin()), std::make_move_iterator(operand_vec.end()));
428 } break;
429 case OperandType::UINT64: {
430 const auto operand_vec = to_buffer(operands.at(operand_pos++).as<uint64_t>());
431 output.insert(
432 output.end(), std::make_move_iterator(operand_vec.begin()), std::make_move_iterator(operand_vec.end()));
433 } break;
435 const auto operand_vec = to_buffer(operands.at(operand_pos++).as<uint128_t>());
436 output.insert(
437 output.end(), std::make_move_iterator(operand_vec.begin()), std::make_move_iterator(operand_vec.end()));
438 } break;
439 case OperandType::FF: {
440 const auto operand_vec = to_buffer(operands.at(operand_pos++).as<FF>());
441 output.insert(
442 output.end(), std::make_move_iterator(operand_vec.begin()), std::make_move_iterator(operand_vec.end()));
443 } break;
444 }
445 }
446 return output;
447}
448
469{
470 // Guaranteed to hold as we only call this function after deserialize_instruction() where the opcode is checked.
472 "Instruction does not contain a valid wire opcode.");
473
474 const auto& wire_format = get_wire_opcode_wire_format().at(instruction.opcode);
475
476 size_t pos = 0; // Position in instruction operands
477
478 for (const OperandType& operand_type : wire_format) {
479 if (operand_type == OperandType::INDIRECT8 || operand_type == OperandType::INDIRECT16) {
480 continue; // No pos increment
481 }
482
483 if (operand_type == OperandType::TAG) {
484 // Guaranteed to hold as we only call this function after deserialize_instruction() where instruction size
485 // is checked.
486 BB_ASSERT(pos < instruction.operands.size(), "Instruction operands size is too small.");
487
488 try {
489 uint8_t tag = instruction.operands.at(pos).as<uint8_t>(); // Cast to uint8_t might throw CastException
490
491 if (tag > static_cast<uint8_t>(MemoryTag::MAX)) {
492 vinfo("Instruction tag operand at position: ",
493 pos,
494 " is invalid.",
495 " Tag value: ",
496 tag,
497 " WireOpCode: ",
499 return false;
500 }
501 } catch (const CastException&) {
502 vinfo("Instruction operand at position: ",
503 pos,
504 " is longer than a byte.",
505 " WireOpCode: ",
507 return false;
508 }
509 }
510
511 pos++;
512 }
513 return true;
514}
515
516} // 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