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
19
20namespace bb::avm2::simulation {
21
27
28// Instruction wire formats.
42
44 /*l2GasOffset=*/OperandType::UINT16,
45 /*daGasOffset=*/OperandType::UINT16,
46 /*addrOffset=*/OperandType::UINT16,
47 /*argsOffset=*/OperandType::UINT16,
48 /*argsSizeOffset=*/OperandType::UINT16 };
49
50// Contrary to TS, the format does not contain the WireOpCode byte which prefixes any instruction.
51// Entries are ordered to match WireOpCode enum.
53 // Compute
54 // Compute - Arithmetic
65 // Compute - Comparison
72 // Compute - Bitwise
85 // Compute - Type Conversions
88
89 // Execution Environment - Globals
91 {
94 OperandType::UINT8, // var idx
95 } },
96
97 // Execution Environment - Calldata
104
105 // Machine State - Internal Control Flow
110
111 // Machine State - Memory
120
121 // Side Effects - Public Storage
124 // Side Effects - Notes, Nullfiers, Logs, Messages
127
129 {
132 } },
136 {
139 } },
145 {
149 } },
151
152 // Control Flow - Contract Calls
156 // REVERT,
159
160 // Misc
168
169 // Gadgets
170 // Gadgets - Hashing
175 // TEMP ECADD without relative memory
178 OperandType::UINT16, // lhs.x
179 OperandType::UINT16, // lhs.y
180 OperandType::UINT16, // lhs.is_infinite
181 OperandType::UINT16, // rhs.x
182 OperandType::UINT16, // rhs.y
183 OperandType::UINT16, // rhs.is_infinite
184 OperandType::UINT16 } }, // dst_offset
185 // Gadget - Conversion
193};
194
208
209namespace {
210
211bool is_wire_opcode_valid(uint8_t w_opcode)
212{
213 return w_opcode < static_cast<uint8_t>(WireOpCode::LAST_OPCODE_SENTINEL);
214}
215
216} // namespace
217
219{
220 const auto bytecode_length = bytecode.size();
221
222 if (pos >= bytecode_length) {
223 vinfo("PC is out of range. Position: ", pos, " Bytecode length: ", bytecode_length);
225 }
226
227 const uint8_t opcode_byte = bytecode[pos];
228
229 if (!is_wire_opcode_valid(opcode_byte)) {
230 vinfo("Invalid wire opcode byte: 0x", to_hex(opcode_byte), " at position: ", pos);
232 }
233
234 const auto opcode = static_cast<WireOpCode>(opcode_byte);
235 const auto iter = WireOpCode_WIRE_FORMAT.find(opcode);
236 assert(iter != WireOpCode_WIRE_FORMAT.end());
237 const auto& inst_format = iter->second;
238
239 const uint32_t instruction_size = WIRE_INSTRUCTION_SPEC.at(opcode).size_in_bytes;
240
241 // We know we will encounter a parsing error, but continue processing because
242 // we need the partial instruction to be parsed for witness generation.
243 if (pos + instruction_size > bytecode_length) {
244 vinfo("Instruction does not fit in remaining bytecode. Wire opcode: ",
245 opcode,
246 " pos: ",
247 pos,
248 " instruction size: ",
249 instruction_size,
250 " bytecode length: ",
251 bytecode_length);
253 }
254
255 pos++; // move after opcode byte
256
257 uint16_t indirect = 0;
258 std::vector<Operand> operands;
259 for (const OperandType op_type : inst_format) {
260 const auto operand_size = OPERAND_TYPE_SIZE_BYTES.at(op_type);
261 assert(pos + operand_size <= bytecode_length); // Guaranteed to hold due to
262 // pos + instruction_size <= bytecode_length
263
264 switch (op_type) {
265 case OperandType::TAG:
266 case OperandType::UINT8: {
267 operands.emplace_back(Operand::from<uint8_t>(bytecode[pos]));
268 break;
269 }
271 indirect = bytecode[pos];
272 break;
273 }
275 uint16_t operand_u16 = 0;
276 uint8_t const* pos_ptr = &bytecode[pos];
277 serialize::read(pos_ptr, operand_u16);
278 indirect = operand_u16;
279 break;
280 }
281 case OperandType::UINT16: {
282 uint16_t operand_u16 = 0;
283 uint8_t const* pos_ptr = &bytecode[pos];
284 serialize::read(pos_ptr, operand_u16);
285 operands.emplace_back(Operand::from<uint16_t>(operand_u16));
286 break;
287 }
288 case OperandType::UINT32: {
289 uint32_t operand_u32 = 0;
290 uint8_t const* pos_ptr = &bytecode[pos];
291 serialize::read(pos_ptr, operand_u32);
292 operands.emplace_back(Operand::from<uint32_t>(operand_u32));
293 break;
294 }
295 case OperandType::UINT64: {
296 uint64_t operand_u64 = 0;
297 uint8_t const* pos_ptr = &bytecode[pos];
298 serialize::read(pos_ptr, operand_u64);
299 operands.emplace_back(Operand::from<uint64_t>(operand_u64));
300 break;
301 }
303 uint128_t operand_u128 = 0;
304 uint8_t const* pos_ptr = &bytecode[pos];
305 serialize::read(pos_ptr, operand_u128);
306 operands.emplace_back(Operand::from<uint128_t>(operand_u128));
307 break;
308 }
309 case OperandType::FF: {
310 FF operand_ff;
311 uint8_t const* pos_ptr = &bytecode[pos];
312 read(pos_ptr, operand_ff);
313 operands.emplace_back(Operand::from<FF>(operand_ff));
314 }
315 }
316 pos += operand_size;
317 }
318
319 return {
320 .opcode = opcode,
321 .indirect = indirect,
322 .operands = std::move(operands),
323 };
324};
325
326std::string Instruction::to_string() const
327{
328 std::ostringstream oss;
329 oss << opcode << " ";
330 for (size_t operand_pos = 0; operand_pos < operands.size(); ++operand_pos) {
331 const auto& operand = operands[operand_pos];
332 oss << std::to_string(operand);
333 if (is_operand_relative(indirect, static_cast<uint8_t>(operand_pos))) {
334 oss << "R";
335 }
336 if (is_operand_indirect(indirect, static_cast<uint8_t>(operand_pos))) {
337 oss << "I";
338 }
339 oss << " ";
340 }
341 return oss.str();
342}
343
345{
346 assert(WIRE_INSTRUCTION_SPEC.contains(opcode));
347 return WIRE_INSTRUCTION_SPEC.at(opcode).size_in_bytes;
348}
349
351{
352 assert(WIRE_INSTRUCTION_SPEC.contains(opcode));
353 return WIRE_INSTRUCTION_SPEC.at(opcode).exec_opcode;
354}
355
356std::vector<uint8_t> Instruction::serialize() const
357{
358 std::vector<uint8_t> output;
359 output.reserve(WIRE_INSTRUCTION_SPEC.at(opcode).size_in_bytes);
360 output.emplace_back(static_cast<uint8_t>(opcode));
361 size_t operand_pos = 0;
362
363 for (const auto& operand_type : WireOpCode_WIRE_FORMAT.at(opcode)) {
364 switch (operand_type) {
366 output.emplace_back(static_cast<uint8_t>(indirect));
367 break;
369 const auto indirect_vec = to_buffer(indirect);
370 output.insert(output.end(),
371 std::make_move_iterator(indirect_vec.begin()),
372 std::make_move_iterator(indirect_vec.end()));
373 } break;
374 case OperandType::TAG:
376 output.emplace_back(operands.at(operand_pos++).as<uint8_t>());
377 break;
378 case OperandType::UINT16: {
379 const auto operand_vec = to_buffer(operands.at(operand_pos++).as<uint16_t>());
380 output.insert(
381 output.end(), std::make_move_iterator(operand_vec.begin()), std::make_move_iterator(operand_vec.end()));
382 } break;
383 case OperandType::UINT32: {
384 const auto operand_vec = to_buffer(operands.at(operand_pos++).as<uint32_t>());
385 output.insert(
386 output.end(), std::make_move_iterator(operand_vec.begin()), std::make_move_iterator(operand_vec.end()));
387 } break;
388 case OperandType::UINT64: {
389 const auto operand_vec = to_buffer(operands.at(operand_pos++).as<uint64_t>());
390 output.insert(
391 output.end(), std::make_move_iterator(operand_vec.begin()), std::make_move_iterator(operand_vec.end()));
392 } break;
394 const auto operand_vec = to_buffer(operands.at(operand_pos++).as<uint128_t>());
395 output.insert(
396 output.end(), std::make_move_iterator(operand_vec.begin()), std::make_move_iterator(operand_vec.end()));
397 } break;
398 case OperandType::FF: {
399 const auto operand_vec = to_buffer(operands.at(operand_pos++).as<FF>());
400 output.insert(
401 output.end(), std::make_move_iterator(operand_vec.begin()), std::make_move_iterator(operand_vec.end()));
402 } break;
403 }
404 }
405 return output;
406}
407
409{
411 vinfo("Instruction does not contain a valid wire opcode.");
412 return false;
413 }
414
415 const auto& wire_format = WireOpCode_WIRE_FORMAT.at(instruction.opcode);
416
417 size_t pos = 0; // Position in instruction operands
418
419 for (size_t i = 0; i < wire_format.size(); i++) {
420 if (wire_format[i] == OperandType::INDIRECT8 || wire_format[i] == OperandType::INDIRECT16) {
421 continue; // No pos increment
422 }
423
424 if (wire_format[i] == OperandType::TAG) {
425 if (pos >= instruction.operands.size()) {
426 vinfo("Instruction operands size is too small. Tag position: ",
427 pos,
428 " size: ",
429 instruction.operands.size(),
430 " WireOpCode: ",
432 return false;
433 }
434
435 try {
436 uint8_t tag = instruction.operands.at(pos).as<uint8_t>(); // Cast to uint8_t might throw
437
438 if (tag > static_cast<uint8_t>(MemoryTag::MAX)) {
439 vinfo("Instruction tag operand at position: ",
440 pos,
441 " is invalid.",
442 " Tag value: ",
443 tag,
444 " WireOpCode: ",
446 return false;
447 }
448
449 } catch (const std::runtime_error&) {
450 vinfo("Instruction operand at position: ",
451 pos,
452 " is longer than a byte.",
453 " WireOpCode: ",
455 return false;
456 }
457 }
458
459 pos++;
460 }
461 return true;
462}
463
464} // namespace bb::avm2::simulation
#define vinfo(...)
Definition log.hpp:79
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
const std::unordered_map< OperandType, uint32_t > OPERAND_TYPE_SIZE_BYTES
const std::unordered_map< WireOpCode, std::vector< OperandType > > WireOpCode_WIRE_FORMAT
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
bool is_operand_indirect(uint16_t indirect_flag, size_t operand_index)
const std::unordered_map< WireOpCode, WireInstructionSpec > WIRE_INSTRUCTION_SPEC
AvmFlavorSettings::FF FF
Definition field.hpp:10
void read(uint8_t const *&it, ClientIVC::VerificationKey &vk)
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