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
21
22namespace bb::avm2::simulation {
23
24namespace {
25const std::unordered_map<OperandType, uint32_t>& get_operand_type_size_bytes()
26{
27 static const std::unordered_map<OperandType, uint32_t> OPERAND_TYPE_SIZE_BYTES = {
31 };
32 return OPERAND_TYPE_SIZE_BYTES;
33}
34} // namespace
35
36// Instruction wire formats.
50
52 /*l2GasOffset=*/OperandType::UINT16,
53 /*daGasOffset=*/OperandType::UINT16,
54 /*addrOffset=*/OperandType::UINT16,
55 /*argsOffset=*/OperandType::UINT16,
56 /*argsSizeOffset=*/OperandType::UINT16 };
57
58namespace {
59// Contrary to TS, the format does not contain the WireOpCode byte which prefixes any instruction.
60// Entries are ordered to match WireOpCode enum.
61const std::unordered_map<WireOpCode, std::vector<OperandType>>& get_wire_opcode_wire_format()
62{
63 static const std::unordered_map<WireOpCode, std::vector<OperandType>> WireOpCode_WIRE_FORMAT = {
64 // Compute
65 // Compute - Arithmetic
76 // Compute - Comparison
83 // Compute - Bitwise
96 // Compute - Type Conversions
99
100 // Execution Environment - Globals
102 {
105 OperandType::UINT8, // var idx
106 } },
107
108 // Execution Environment - Calldata
115
116 // Machine State - Internal Control Flow
121
122 // Machine State - Memory
132
133 // Side Effects - Public Storage
136 // Side Effects - Notes, Nullfiers, Logs, Messages
139
141 {
144 } },
148 {
151 } },
157 {
161 } },
163
164 // Control Flow - Contract Calls
168 // REVERT,
171
172 // Misc
180
181 // Gadgets
182 // Gadgets - Hashing
187 // TEMP ECADD without relative memory
190 OperandType::UINT16, // lhs.x
191 OperandType::UINT16, // lhs.y
192 OperandType::UINT16, // lhs.is_infinite
193 OperandType::UINT16, // rhs.x
194 OperandType::UINT16, // rhs.y
195 OperandType::UINT16, // rhs.is_infinite
196 OperandType::UINT16 } }, // dst_offset
197 // Gadget - Conversion
205 };
206 return WireOpCode_WIRE_FORMAT;
207}
208} // namespace
209
210namespace testonly {
211
213{
214 return get_wire_opcode_wire_format();
215}
216
218{
219 return get_operand_type_size_bytes();
220}
221
222} // namespace testonly
223
224namespace {
225
226bool is_wire_opcode_valid(uint8_t w_opcode)
227{
228 return w_opcode < static_cast<uint8_t>(WireOpCode::LAST_OPCODE_SENTINEL);
229}
230
231} // namespace
232
234{
235 const auto bytecode_length = bytecode.size();
236
237 if (pos >= bytecode_length) {
238 std::string error_msg = format("Invalid program counter ", pos, ", max is ", bytecode_length - 1);
239 vinfo(error_msg);
241 }
242
243 const uint8_t opcode_byte = bytecode[pos];
244
245 if (!is_wire_opcode_valid(opcode_byte)) {
246 std::string error_msg = format("Opcode ",
247 static_cast<uint32_t>(opcode_byte),
248 " (0x",
249 to_hex(opcode_byte),
250 ") value is not in the range of valid opcodes (at PC ",
251 pos,
252 ").");
253 vinfo(error_msg);
255 }
256
257 const auto opcode = static_cast<WireOpCode>(opcode_byte);
258 const auto iter = get_wire_opcode_wire_format().find(opcode);
259 BB_ASSERT_DEBUG(iter != get_wire_opcode_wire_format().end(), "Wire opcode not found in wire opcode wire format");
260 const auto& inst_format = iter->second;
261
262 const uint32_t instruction_size = get_wire_instruction_spec().at(opcode).size_in_bytes;
263
264 // We know we will encounter a parsing error, but continue processing because
265 // we need the partial instruction to be parsed for witness generation.
266 if (pos + instruction_size > bytecode_length) {
267 std::string error_msg = format("Instruction at PC ",
268 pos,
269 " does not fit in bytecode (instruction size: ",
270 instruction_size,
271 ", remaining: ",
272 bytecode_length - pos,
273 ")");
274 vinfo(error_msg);
276 }
277
278 pos++; // move after opcode byte
279
280 uint16_t indirect = 0;
281 std::vector<Operand> operands;
282 for (const OperandType op_type : inst_format) {
283 const auto operand_size = get_operand_type_size_bytes().at(op_type);
284 // Guaranteed to hold due to pos + instruction_size <= bytecode_length
285 BB_ASSERT_DEBUG(pos + operand_size <= bytecode_length, "Operand size is out of range");
286
287 switch (op_type) {
288 case OperandType::TAG:
289 case OperandType::UINT8: {
290 operands.emplace_back(Operand::from<uint8_t>(bytecode[pos]));
291 break;
292 }
294 indirect = bytecode[pos];
295 break;
296 }
298 uint16_t operand_u16 = 0;
299 uint8_t const* pos_ptr = &bytecode[pos];
300 serialize::read(pos_ptr, operand_u16);
301 indirect = operand_u16;
302 break;
303 }
304 case OperandType::UINT16: {
305 uint16_t operand_u16 = 0;
306 uint8_t const* pos_ptr = &bytecode[pos];
307 serialize::read(pos_ptr, operand_u16);
308 operands.emplace_back(Operand::from<uint16_t>(operand_u16));
309 break;
310 }
311 case OperandType::UINT32: {
312 uint32_t operand_u32 = 0;
313 uint8_t const* pos_ptr = &bytecode[pos];
314 serialize::read(pos_ptr, operand_u32);
315 operands.emplace_back(Operand::from<uint32_t>(operand_u32));
316 break;
317 }
318 case OperandType::UINT64: {
319 uint64_t operand_u64 = 0;
320 uint8_t const* pos_ptr = &bytecode[pos];
321 serialize::read(pos_ptr, operand_u64);
322 operands.emplace_back(Operand::from<uint64_t>(operand_u64));
323 break;
324 }
326 uint128_t operand_u128 = 0;
327 uint8_t const* pos_ptr = &bytecode[pos];
328 serialize::read(pos_ptr, operand_u128);
329 operands.emplace_back(Operand::from<uint128_t>(operand_u128));
330 break;
331 }
332 case OperandType::FF: {
333 FF operand_ff;
334 uint8_t const* pos_ptr = &bytecode[pos];
335 read(pos_ptr, operand_ff);
336 operands.emplace_back(Operand::from<FF>(operand_ff));
337 }
338 }
339 pos += operand_size;
340 }
341
342 return {
343 .opcode = opcode,
344 .indirect = indirect,
345 .operands = std::move(operands),
346 };
347};
348
349std::string Instruction::to_string() const
350{
351 std::ostringstream oss;
352 oss << opcode << " ";
353 for (size_t operand_pos = 0; operand_pos < operands.size(); ++operand_pos) {
354 const auto& operand = operands[operand_pos];
355 oss << std::to_string(operand);
356 if (is_operand_relative(indirect, static_cast<uint8_t>(operand_pos))) {
357 oss << "R";
358 }
359 if (is_operand_indirect(indirect, static_cast<uint8_t>(operand_pos))) {
360 oss << "I";
361 }
362 oss << " ";
363 }
364 return oss.str();
365}
366
368{
369 BB_ASSERT_DEBUG(get_wire_instruction_spec().contains(opcode), "Wire instruction spec not found for opcode");
370 return get_wire_instruction_spec().at(opcode).size_in_bytes;
371}
372
374{
375 BB_ASSERT_DEBUG(get_wire_instruction_spec().contains(opcode), "Wire instruction spec not found for opcode");
376 return get_wire_instruction_spec().at(opcode).exec_opcode;
377}
378
379std::vector<uint8_t> Instruction::serialize() const
380{
381 std::vector<uint8_t> output;
382 output.reserve(get_wire_instruction_spec().at(opcode).size_in_bytes);
383 output.emplace_back(static_cast<uint8_t>(opcode));
384 size_t operand_pos = 0;
385
386 for (const auto& operand_type : get_wire_opcode_wire_format().at(opcode)) {
387 switch (operand_type) {
389 output.emplace_back(static_cast<uint8_t>(indirect));
390 break;
392 const auto indirect_vec = to_buffer(indirect);
393 output.insert(output.end(),
394 std::make_move_iterator(indirect_vec.begin()),
395 std::make_move_iterator(indirect_vec.end()));
396 } break;
397 case OperandType::TAG:
399 output.emplace_back(operands.at(operand_pos++).as<uint8_t>());
400 break;
401 case OperandType::UINT16: {
402 const auto operand_vec = to_buffer(operands.at(operand_pos++).as<uint16_t>());
403 output.insert(
404 output.end(), std::make_move_iterator(operand_vec.begin()), std::make_move_iterator(operand_vec.end()));
405 } break;
406 case OperandType::UINT32: {
407 const auto operand_vec = to_buffer(operands.at(operand_pos++).as<uint32_t>());
408 output.insert(
409 output.end(), std::make_move_iterator(operand_vec.begin()), std::make_move_iterator(operand_vec.end()));
410 } break;
411 case OperandType::UINT64: {
412 const auto operand_vec = to_buffer(operands.at(operand_pos++).as<uint64_t>());
413 output.insert(
414 output.end(), std::make_move_iterator(operand_vec.begin()), std::make_move_iterator(operand_vec.end()));
415 } break;
417 const auto operand_vec = to_buffer(operands.at(operand_pos++).as<uint128_t>());
418 output.insert(
419 output.end(), std::make_move_iterator(operand_vec.begin()), std::make_move_iterator(operand_vec.end()));
420 } break;
421 case OperandType::FF: {
422 const auto operand_vec = to_buffer(operands.at(operand_pos++).as<FF>());
423 output.insert(
424 output.end(), std::make_move_iterator(operand_vec.begin()), std::make_move_iterator(operand_vec.end()));
425 } break;
426 }
427 }
428 return output;
429}
430
432{
434 vinfo("Instruction does not contain a valid wire opcode.");
435 return false;
436 }
437
438 const auto& wire_format = get_wire_opcode_wire_format().at(instruction.opcode);
439
440 size_t pos = 0; // Position in instruction operands
441
442 for (const OperandType& operand_type : wire_format) {
443 if (operand_type == OperandType::INDIRECT8 || operand_type == OperandType::INDIRECT16) {
444 continue; // No pos increment
445 }
446
447 if (operand_type == OperandType::TAG) {
448 if (pos >= instruction.operands.size()) {
449 vinfo("Instruction operands size is too small. Tag position: ",
450 pos,
451 " size: ",
452 instruction.operands.size(),
453 " WireOpCode: ",
455 return false;
456 }
457
458 try {
459 uint8_t tag = instruction.operands.at(pos).as<uint8_t>(); // Cast to uint8_t might throw CastException
460
461 if (tag > static_cast<uint8_t>(MemoryTag::MAX)) {
462 vinfo("Instruction tag operand at position: ",
463 pos,
464 " is invalid.",
465 " Tag value: ",
466 tag,
467 " WireOpCode: ",
469 return false;
470 }
471 } catch (const CastException&) {
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
#define BB_ASSERT_DEBUG(expression,...)
Definition assert.hpp:55
std::shared_ptr< Napi::ThreadSafeFunction > bytecode
std::string format(Args... args)
Definition log.hpp:24
#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()
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:21
bool is_operand_relative(uint16_t indirect_flag, size_t operand_index)
Checks if the operand at the given index is relative.
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.
AvmFlavorSettings::FF FF
Definition field.hpp:10
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 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