Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
addressing.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cstddef>
5#include <optional>
6#include <vector>
7
14
15namespace bb::avm2::simulation {
16
33{
34 BB_BENCH_NAME("Addressing::resolve");
35 // We'll be filling in the event as we progress.
37 // We initialize all the phases with the original operands.
38 // This is expected for non-address (i.e., immediate) operands.
39 // For address operands, we'll update them as we go.
40 for (const auto& operand : instruction.operands) {
41 event.resolution_info.push_back({
42 .after_relative = operand.as_ff(),
43 .resolved_operand = operand,
44 .error = std::nullopt,
45 });
46 }
47
48 // Note: it's fine to query instruction info here since it does not trigger events.
49 // Also, if addressing is being resolved, we can assume that instruction fetching succeeded.
50 ExecutionOpCode exec_opcode = instruction_info_db.get(instruction.opcode).exec_opcode;
51 const ExecInstructionSpec& spec = instruction_info_db.get(exec_opcode);
52
53 // This represents either: (1) wrong info in the spec, or (2) a wrong witgen deserialization.
54 // Therefore, it is not an error the circuit should be able to prove.
55 BB_ASSERT_LTE(spec.num_addresses, instruction.operands.size(), "Spec num addresses out of bounds");
56
57 // Check if there is any relative address.
58 bool has_relative_address = false;
59 for (size_t i = 0; i < spec.num_addresses; ++i) {
60 if (is_operand_relative(instruction.indirect, i)) {
61 has_relative_address = true;
62 break;
63 }
64 }
65
66 // If there is a relative address, we resolve the base address.
67 // We check the tag of the resolved base address and keep this information for later use.
68 bool base_address_invalid = false;
69 std::optional<MemoryValue> base_address;
70 if (has_relative_address) {
71 base_address = memory.get(0);
72 event.base_address = *base_address;
73 // Check if the base address is valid (has the right tag).
74 if (!memory.is_valid_address(*base_address)) {
75 base_address_invalid = true;
76 }
77 }
78
79 // We process each address separately.
80 // Even if one fails, we continue processing the other ones. We will throw an exception after event emission.
81 // This is to simplify error handling in the circuit.
82 for (size_t i = 0; i < spec.num_addresses; ++i) {
83 auto& resolution_info = event.resolution_info[i];
84 try {
85 // Simulation and the circuit assume that the operands are valid addresses.
86 // This should be guaranteed by instruction fetching and the wire format.
87 // The operand must fit in a MemoryAddress but does not need to be of the right tag.
88 // For instance, a 16-bit operand can be cast to a MemoryAddress and fit.
89 // NOTE: Only asserting in debug builds because these convertions are in the hot path.
90 BB_ASSERT_DEBUG(FF(static_cast<MemoryAddress>(instruction.operands[i].as_ff())) ==
91 instruction.operands[i].as_ff());
92
93 // Guarantees at this point:
94 // - original operand is a valid address IF interpreted as a MemoryAddress.
95
96 // Check if the base address is invalid. Even for non-relative addresses, we need to check this because
97 // we essentially stop any resolution if the base address is invalid and a relative address is used.
98 if (base_address_invalid) {
100 }
101
102 // Process relative addressing if applicable.
103 // That is, if relative addressing is used, after_relative[i] = base_address + operands[i].
104 // Note that the operands were stored as is, and then we'll update them if they are relative.
105 // Namely, the above initialization guarantees:
106 // resolution_info.after_relative = instruction.operands[i].as_ff(); // default value if not relative.
107 if (is_operand_relative(instruction.indirect, i)) {
108 // We extend the address to uint64_t to avoid overflows.
109 auto offset = static_cast<uint64_t>(resolution_info.after_relative);
110 // Note: Since we know that the offset and the base address are valid, the addition fits in 33 bits.
111 offset += (*base_address).as<MemoryAddress>();
112 // We store the offset as FF. If the circuit needs to prove overflow, it will
113 // need the full value.
114 resolution_info.after_relative = FF(offset);
115 // Check whether the relative address overflows.
117 // We need to update the default value for the resolved operand to the overflowed value.
118 // This is required for the circuit to be on par with the behavior of immediate operands.
119 // In this case, the tag of the resolved operand is not constrained.
120 resolution_info.resolved_operand = Operand::from_tag(ValueTag::FF, FF(offset));
121 // If this happens, it means that the relative computation overflowed. However, both the base and
122 // operand addresses by themselves were valid.
124 }
125 }
126
127 // Guarantees at this point:
128 // - original operand is a valid address IF interpreted as MemoryAddress.
129 // - after_relative is in the valid address range.
130
131 // Then indirection.
132 // That is, if indirection is used, resolved_operands[i] = memory[after_relative[i]].
133 // We first store the after_relative values as is, and then we'll update them if they are indirect.
134 const auto after_relative_address = static_cast<MemoryAddress>(resolution_info.after_relative);
135 resolution_info.resolved_operand = Operand::from(after_relative_address);
136 if (is_operand_indirect(instruction.indirect, i)) {
137 resolution_info.resolved_operand = memory.get(after_relative_address);
138 // Check the tag of the resolved operand.
139 if (!memory.is_valid_address(resolution_info.resolved_operand)) {
141 }
142 }
143
144 // Guarantees at this point:
145 // - original operand is a valid address IF interpreted as MemoryAddress.
146 // - after_relative is in the valid address range.
147 // - resolved_operand is a valid address.
148 } catch (const AddressingEventError& e) {
149 debug("Addressing error: ", to_string(e), " at operand ", i);
150 debug("Base address: ", event.base_address.to_string());
151 debug("After relative: ", resolution_info.after_relative);
152 debug("Resolved operand: ", resolution_info.resolved_operand.to_string());
153 resolution_info.error = e;
154 }
155 }
156
158
159 // If any entry in resolution_info has an error set, throw.
160 if (std::ranges::any_of(event.resolution_info, [](const auto& info) { return info.error.has_value(); })) {
161 // Signal the error to the caller.
162 // Detailed information is already logged above.
163 throw AddressingException();
164 }
165
166 // Collect resolved operands and return them.
167 std::vector<Operand> resolved_operands;
168 resolved_operands.reserve(event.resolution_info.size());
169 for (const auto& info : event.resolution_info) {
170 resolved_operands.push_back(info.resolved_operand);
171 }
172 return resolved_operands;
173}
174
186
187} // namespace bb::avm2::simulation
#define BB_ASSERT_DEBUG(expression,...)
Definition assert.hpp:55
#define BB_ASSERT_LTE(left, right,...)
Definition assert.hpp:168
#define AVM_HIGHEST_MEM_ADDRESS
#define BB_BENCH_NAME(name)
Definition bb_bench.hpp:219
static TaggedValue from(T value)
static TaggedValue from_tag(ValueTag tag, FF value)
EventEmitterInterface< AddressingEvent > & events
bool is_address_out_of_range(uint64_t address)
Checks if an address as uint64_t is out of range. Emits a gt event comparing the address to AVM_HIGHE...
std::vector< Operand > resolve(const Instruction &instruction, MemoryInterface &memory) override
Resolve the operands of an instruction. If the operands are non-addresses, they are returned as is....
const InstructionInfoDBInterface & instruction_info_db
virtual const ExecInstructionSpec & get(ExecutionOpCode opcode) const =0
#define debug(...)
Definition log.hpp:76
void info(Args... args)
Definition log.hpp:89
ssize_t offset
Definition engine.cpp:36
Instruction instruction
std::string to_string(const std::array< FF, N > &arr)
Definition stringify.hpp:31
bool is_operand_relative(uint16_t indirect_flag, size_t operand_index)
Checks if the operand at the given index is relative.
bool is_operand_indirect(uint16_t indirect_flag, size_t operand_index)
Checks if the operand at the given index is indirect.
uint32_t MemoryAddress
AvmFlavorSettings::FF FF
Definition field.hpp:10
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
simulation::PublicDataTreeReadWriteEvent event
std::vector< OperandResolutionInfo > resolution_info