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// The format for WireOpCode::SET has to be handled separately as it is variable based on the tag.
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
163
164 // Gadgets
165 // Gadgets - Hashing
170 // TEMP ECADD without relative memory
173 OperandType::UINT16, // lhs.x
174 OperandType::UINT16, // lhs.y
175 OperandType::UINT16, // lhs.is_infinite
176 OperandType::UINT16, // rhs.x
177 OperandType::UINT16, // rhs.y
178 OperandType::UINT16, // rhs.is_infinite
179 OperandType::UINT16 } }, // dst_offset
180 // Gadget - Conversion
188};
189
203
204namespace {
205
206bool is_wire_opcode_valid(uint8_t w_opcode)
207{
208 return w_opcode < static_cast<uint8_t>(WireOpCode::LAST_OPCODE_SENTINEL);
209}
210
211} // namespace
212
214{
215 const auto bytecode_length = bytecode.size();
216
217 if (pos >= bytecode_length) {
218 vinfo("PC is out of range. Position: ", pos, " Bytecode length: ", bytecode_length);
220 }
221
222 const uint8_t opcode_byte = bytecode[pos];
223
224 if (!is_wire_opcode_valid(opcode_byte)) {
225 vinfo("Invalid wire opcode byte: 0x", to_hex(opcode_byte), " at position: ", pos);
227 }
228
229 const auto opcode = static_cast<WireOpCode>(opcode_byte);
230 const auto iter = WireOpCode_WIRE_FORMAT.find(opcode);
231 assert(iter != WireOpCode_WIRE_FORMAT.end());
232 const auto& inst_format = iter->second;
233
234 const uint32_t instruction_size = WIRE_INSTRUCTION_SPEC.at(opcode).size_in_bytes;
235
236 // We know we will encounter a parsing error, but continue processing because
237 // we need the partial instruction to be parsed for witness generation.
238 if (pos + instruction_size > bytecode_length) {
239 vinfo("Instruction does not fit in remaining bytecode. Wire opcode: ",
240 opcode,
241 " pos: ",
242 pos,
243 " instruction size: ",
244 instruction_size,
245 " bytecode length: ",
246 bytecode_length);
248 }
249
250 pos++; // move after opcode byte
251
252 uint16_t indirect = 0;
253 std::vector<Operand> operands;
254 for (const OperandType op_type : inst_format) {
255 const auto operand_size = OPERAND_TYPE_SIZE_BYTES.at(op_type);
256 assert(pos + operand_size <= bytecode_length); // Guaranteed to hold due to
257 // pos + instruction_size <= bytecode_length
258
259 switch (op_type) {
260 case OperandType::TAG:
261 case OperandType::UINT8: {
262 operands.emplace_back(Operand::from<uint8_t>(bytecode[pos]));
263 break;
264 }
266 indirect = bytecode[pos];
267 break;
268 }
270 uint16_t operand_u16 = 0;
271 uint8_t const* pos_ptr = &bytecode[pos];
272 serialize::read(pos_ptr, operand_u16);
273 indirect = operand_u16;
274 break;
275 }
276 case OperandType::UINT16: {
277 uint16_t operand_u16 = 0;
278 uint8_t const* pos_ptr = &bytecode[pos];
279 serialize::read(pos_ptr, operand_u16);
280 operands.emplace_back(Operand::from<uint16_t>(operand_u16));
281 break;
282 }
283 case OperandType::UINT32: {
284 uint32_t operand_u32 = 0;
285 uint8_t const* pos_ptr = &bytecode[pos];
286 serialize::read(pos_ptr, operand_u32);
287 operands.emplace_back(Operand::from<uint32_t>(operand_u32));
288 break;
289 }
290 case OperandType::UINT64: {
291 uint64_t operand_u64 = 0;
292 uint8_t const* pos_ptr = &bytecode[pos];
293 serialize::read(pos_ptr, operand_u64);
294 operands.emplace_back(Operand::from<uint64_t>(operand_u64));
295 break;
296 }
298 uint128_t operand_u128 = 0;
299 uint8_t const* pos_ptr = &bytecode[pos];
300 serialize::read(pos_ptr, operand_u128);
301 operands.emplace_back(Operand::from<uint128_t>(operand_u128));
302 break;
303 }
304 case OperandType::FF: {
305 FF operand_ff;
306 uint8_t const* pos_ptr = &bytecode[pos];
307 read(pos_ptr, operand_ff);
308 operands.emplace_back(Operand::from<FF>(operand_ff));
309 }
310 }
311 pos += operand_size;
312 }
313
314 return {
315 .opcode = opcode,
316 .indirect = indirect,
317 .operands = std::move(operands),
318 };
319};
320
321std::string Instruction::to_string() const
322{
323 std::ostringstream oss;
324 oss << opcode << " ";
325 for (size_t operand_pos = 0; operand_pos < operands.size(); ++operand_pos) {
326 const auto& operand = operands[operand_pos];
327 oss << std::to_string(operand);
328 if (is_operand_relative(indirect, static_cast<uint8_t>(operand_pos))) {
329 oss << "R";
330 }
331 if (is_operand_indirect(indirect, static_cast<uint8_t>(operand_pos))) {
332 oss << "I";
333 }
334 oss << " ";
335 }
336 return oss.str();
337}
338
340{
341 assert(WIRE_INSTRUCTION_SPEC.contains(opcode));
342 return WIRE_INSTRUCTION_SPEC.at(opcode).size_in_bytes;
343}
344
346{
347 assert(WIRE_INSTRUCTION_SPEC.contains(opcode));
348 return WIRE_INSTRUCTION_SPEC.at(opcode).exec_opcode;
349}
350
351std::vector<uint8_t> Instruction::serialize() const
352{
353 std::vector<uint8_t> output;
354 output.reserve(WIRE_INSTRUCTION_SPEC.at(opcode).size_in_bytes);
355 output.emplace_back(static_cast<uint8_t>(opcode));
356 size_t operand_pos = 0;
357
358 for (const auto& operand_type : WireOpCode_WIRE_FORMAT.at(opcode)) {
359 switch (operand_type) {
361 output.emplace_back(static_cast<uint8_t>(indirect));
362 break;
364 const auto indirect_vec = to_buffer(indirect);
365 output.insert(output.end(),
366 std::make_move_iterator(indirect_vec.begin()),
367 std::make_move_iterator(indirect_vec.end()));
368 } break;
369 case OperandType::TAG:
371 output.emplace_back(operands.at(operand_pos++).as<uint8_t>());
372 break;
373 case OperandType::UINT16: {
374 const auto operand_vec = to_buffer(operands.at(operand_pos++).as<uint16_t>());
375 output.insert(
376 output.end(), std::make_move_iterator(operand_vec.begin()), std::make_move_iterator(operand_vec.end()));
377 } break;
378 case OperandType::UINT32: {
379 const auto operand_vec = to_buffer(operands.at(operand_pos++).as<uint32_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::UINT64: {
384 const auto operand_vec = to_buffer(operands.at(operand_pos++).as<uint64_t>());
385 output.insert(
386 output.end(), std::make_move_iterator(operand_vec.begin()), std::make_move_iterator(operand_vec.end()));
387 } break;
389 const auto operand_vec = to_buffer(operands.at(operand_pos++).as<uint128_t>());
390 output.insert(
391 output.end(), std::make_move_iterator(operand_vec.begin()), std::make_move_iterator(operand_vec.end()));
392 } break;
393 case OperandType::FF: {
394 const auto operand_vec = to_buffer(operands.at(operand_pos++).as<FF>());
395 output.insert(
396 output.end(), std::make_move_iterator(operand_vec.begin()), std::make_move_iterator(operand_vec.end()));
397 } break;
398 }
399 }
400 return output;
401}
402
404{
406 vinfo("Instruction does not contain a valid wire opcode.");
407 return false;
408 }
409
410 const auto& wire_format = WireOpCode_WIRE_FORMAT.at(instruction.opcode);
411
412 size_t pos = 0; // Position in instruction operands
413
414 for (size_t i = 0; i < wire_format.size(); i++) {
415 if (wire_format[i] == OperandType::INDIRECT8 || wire_format[i] == OperandType::INDIRECT16) {
416 continue; // No pos increment
417 }
418
419 if (wire_format[i] == OperandType::TAG) {
420 if (pos >= instruction.operands.size()) {
421 vinfo("Instruction operands size is too small. Tag position: ",
422 pos,
423 " size: ",
424 instruction.operands.size(),
425 " WireOpCode: ",
427 return false;
428 }
429
430 try {
431 uint8_t tag = instruction.operands.at(pos).as<uint8_t>(); // Cast to uint8_t might throw
432
433 if (tag > static_cast<uint8_t>(MemoryTag::MAX)) {
434 vinfo("Instruction tag operand at position: ",
435 pos,
436 " is invalid.",
437 " Tag value: ",
438 tag,
439 " WireOpCode: ",
441 return false;
442 }
443
444 } catch (const std::runtime_error&) {
445 vinfo("Instruction operand at position: ",
446 pos,
447 " is longer than a byte.",
448 " WireOpCode: ",
450 return false;
451 }
452 }
453
454 pos++;
455 }
456 return true;
457}
458
459} // namespace bb::avm2::simulation
void vinfo(Args... args)
Definition log.hpp:76
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(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