Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
control_flow.cpp
Go to the documentation of this file.
2
3std::vector<ProgramBlock*> ControlFlow::dfs_traverse(ProgramBlock* start_block, bool reverse)
4{
5 std::vector<ProgramBlock*> blocks;
8
9 stack.push_back(start_block);
10 while (!stack.empty()) {
11 ProgramBlock* current_block = stack.front();
12 stack.pop_front();
13 if (visited.contains(current_block)) {
14 continue;
15 }
16 visited.insert(current_block);
17 blocks.push_back(current_block);
18 if (reverse) {
19 for (ProgramBlock* predecessor : current_block->predecessors) {
20 stack.push_front(predecessor);
21 }
22 } else {
23 for (ProgramBlock* successor : current_block->successors) {
24 stack.push_back(successor);
25 }
26 }
27 }
28
29 return blocks;
30}
31
33{
34 if (instruction_blocks->size() == 0) {
35 return;
36 }
38 return;
39 }
40 auto instruction_block = instruction_blocks->at(instruction.instruction_block_idx % instruction_blocks->size());
41 for (const auto& instr : instruction_block) {
43 }
44}
45
47{
48 if (instruction_blocks->size() == 0) {
49 return;
50 }
52 return;
53 }
54 auto target_instruction_block =
55 instruction_blocks->at(instruction.target_program_block_instruction_block_idx % instruction_blocks->size());
56 ProgramBlock* target_block = new ProgramBlock();
57 current_block->finalize_with_jump(target_block);
58 for (const auto& instr : target_instruction_block) {
59 target_block->process_instruction(instr);
60 }
61 current_block = target_block;
62}
63
65{
66 if (instruction_blocks->size() == 0) {
67 return;
68 }
70 return;
71 }
72 auto target_then_instruction_block =
73 instruction_blocks->at(instruction.then_program_block_instruction_block_idx % instruction_blocks->size());
74 auto target_else_instruction_block =
75 instruction_blocks->at(instruction.else_program_block_instruction_block_idx % instruction_blocks->size());
76 ProgramBlock* target_then_block = new ProgramBlock();
77 ProgramBlock* target_else_block = new ProgramBlock();
78 current_block->finalize_with_jump_if(target_then_block, target_else_block, instruction.condition_offset_index);
79 for (const auto& instr : target_then_instruction_block) {
80 target_then_block->process_instruction(instr);
81 }
82 for (const auto& instr : target_else_instruction_block) {
83 target_else_block->process_instruction(instr);
84 }
85 current_block = target_then_block;
86}
87
89{
91 return;
92 }
93 std::vector<ProgramBlock*> possible_target_blocks = get_reachable_blocks(current_block);
94 if (possible_target_blocks.size() == 0) {
95 return;
96 }
97 ProgramBlock* target_block =
98 possible_target_blocks.at(instruction.target_block_idx % possible_target_blocks.size());
99 current_block->finalize_with_jump(target_block, /*copy_memory_manager=*/false);
100 std::vector<ProgramBlock*> non_terminated_blocks = get_non_terminated_blocks();
101 if (non_terminated_blocks.size() == 0) {
102 return;
103 }
104 current_block = non_terminated_blocks.at(0);
105}
106
108{
110 return;
111 }
112 std::vector<ProgramBlock*> possible_target_blocks = get_reachable_blocks(current_block);
113 if (possible_target_blocks.size() == 0) {
114 return;
115 }
116 ProgramBlock* target_then_block =
117 possible_target_blocks.at(instruction.target_then_block_idx % possible_target_blocks.size());
118 ProgramBlock* target_else_block =
119 possible_target_blocks.at(instruction.target_else_block_idx % possible_target_blocks.size());
121 target_then_block, target_else_block, instruction.condition_offset_index, /*copy_memory_manager=*/false);
122 std::vector<ProgramBlock*> non_terminated_blocks = get_non_terminated_blocks();
123 if (non_terminated_blocks.size() == 0) {
124 return;
125 }
126 current_block = non_terminated_blocks.at(0);
127}
128
130{
132 return;
133 }
135 instruction.return_options.return_value_tag,
136 instruction.return_options.return_value_offset_index);
137 if (current_block->caller != nullptr) {
139 return;
140 }
141 std::vector<ProgramBlock*> non_terminated_blocks = get_non_terminated_blocks();
142 if (non_terminated_blocks.size() == 0) {
143 return;
144 }
145 current_block = non_terminated_blocks.at(0);
146}
147
149{
151 return;
152 }
154 instruction.revert_options.return_value_tag,
155 instruction.revert_options.return_value_offset_index);
156 std::vector<ProgramBlock*> non_terminated_blocks = get_non_terminated_blocks();
157 if (non_terminated_blocks.size() == 0) {
158 return;
159 }
160 current_block = non_terminated_blocks.at(0);
161}
162
164{
165 std::vector<ProgramBlock*> non_terminated_blocks = get_non_terminated_blocks();
166 if (non_terminated_blocks.size() == 0) {
167 return;
168 }
169 current_block = non_terminated_blocks.at(instruction.non_terminated_block_idx % non_terminated_blocks.size());
170}
171
173{
174 if (instruction_blocks->size() == 0) {
175 return;
176 }
177 auto target_instruction_block =
178 instruction_blocks->at(instruction.target_program_block_instruction_block_idx % instruction_blocks->size());
179 ProgramBlock* target_block = new ProgramBlock();
180 current_block->insert_internal_call(target_block);
181 for (const auto& instr : target_instruction_block) {
182 target_block->process_instruction(instr);
183 }
184 target_block->caller = current_block;
185 current_block = target_block;
186}
187
188std::vector<ProgramBlock*> ControlFlow::get_non_terminated_blocks()
189{
190 std::vector<ProgramBlock*> blocks = dfs_traverse(start_block);
191 std::vector<ProgramBlock*> non_terminated_blocks;
192 std::copy_if(blocks.begin(), blocks.end(), std::back_inserter(non_terminated_blocks), [](ProgramBlock* block) {
193 return block->terminator_type != TerminatorType::NONE;
194 });
195 return non_terminated_blocks;
196}
197
198std::vector<ProgramBlock*> ControlFlow::get_reachable_blocks(ProgramBlock* block)
199{
200 // traverse the graph in reverse order starting from the given block
201 // if we jump to one of the blocks in the forbidden list, we create a loop
202 std::vector<ProgramBlock*> forbidden_blocks = dfs_traverse(block, true);
203
204 std::vector<ProgramBlock*> all_blocks = dfs_traverse(start_block);
205 std::vector<ProgramBlock*> reachable_blocks;
206 // filter all forbidden blocks (that creates loops in the graph) and blocks with different caller from the list
207 // of all blocks we avoid blocks with different caller to prevent INTERNALRETURN from being executed in the
208 // context with empty callstack
209 std::copy_if(all_blocks.begin(),
210 all_blocks.end(),
211 std::back_inserter(reachable_blocks),
212 [forbidden_blocks, block](ProgramBlock* block_iter) {
213 return std::find(forbidden_blocks.begin(), forbidden_blocks.end(), block_iter) ==
214 forbidden_blocks.end() &&
215 block_iter->caller == block->caller;
216 });
217 return reachable_blocks;
218}
219
221{
222 if (std::getenv("AVM_FUZZER_LOGGING") != nullptr) {
223 info("Processing CFG instruction: ", instruction);
224 }
227 [&](JumpToNewBlock arg) { process_jump_to_new_block(arg); },
229 [&](JumpToBlock arg) { process_jump_to_block(arg); },
230 [&](JumpIfToBlock arg) { process_jump_if_to_block(arg); },
236}
237
238// Helper function to create bytecode from a vector of instructions
239std::vector<uint8_t> create_bytecode(const std::vector<bb::avm2::simulation::Instruction>& instructions)
240{
241 std::vector<uint8_t> bytecode;
242 for (const auto& instruction : instructions) {
243 auto serialized_instruction = instruction.serialize();
244 bytecode.insert(bytecode.end(),
245 std::make_move_iterator(serialized_instruction.begin()),
246 std::make_move_iterator(serialized_instruction.end()));
247 }
248 return bytecode;
249}
250
252{
253 const int JMP_SIZE = 1 + 4; // opcode + destination offset
254 const int JMP_IF_SIZE = 1 + 1 + 2 + 4; // opcode + direct/indirect + condition offset + destination offset
255 auto bytecode_length = static_cast<int>(create_bytecode(block->get_instructions()).size());
256 switch (block->terminator_type) {
259 return bytecode_length; // finalized with return/revert, already counted
261 return bytecode_length + JMP_SIZE; // finalized with jump
263 // if boolean condition is not set adding SET_8 instruction to the bytecode
264 if (!block->get_terminating_condition_value().has_value()) {
265 for (uint16_t address = 0; address < 65535; address++) {
266 // if the memory address is already in use, we skip it
267 if (block->is_memory_address_set(address)) {
268 continue;
269 }
270 auto set_16_instruction =
272 .result_address =
274 .value = 0 };
275 block->process_instruction(set_16_instruction);
276 bytecode_length = static_cast<int>(create_bytecode(block->get_instructions()).size());
277 break;
278 }
279 }
280 return bytecode_length + JMP_IF_SIZE + JMP_SIZE; // finalized with jumpi
281 }
282 default:
283 throw std::runtime_error(
284 "Predict block size: Every block should be terminated with return, revert, jump, or jumpi, got " +
285 std::to_string(static_cast<int>(block->terminator_type)));
286 }
287 throw std::runtime_error("Unreachable");
288}
289
290size_t find_block_idx(ProgramBlock* block, const std::vector<ProgramBlock*>& blocks)
291{
292 for (size_t i = 0; i < blocks.size(); i++) {
293 if (blocks[i] == block) {
294 return i;
295 }
296 }
297 throw std::runtime_error("Block not found in the list");
298}
299
300std::vector<uint8_t> ControlFlow::build_bytecode(const ReturnOptions& return_options)
301{
302 // Step 1 "linarize" graph into a list of blocks
303 std::vector<ProgramBlock*> blocks = dfs_traverse(start_block);
304
305 // Step 2 terminate all non-terminated blocks with return
306 for (ProgramBlock* block : blocks) {
307 if (block->terminator_type == TerminatorType::NONE) {
308 block->finalize_with_return(
309 /*TODO(defkit) fix return size */ 1,
310 return_options.return_value_tag,
311 return_options.return_value_offset_index);
312 }
313 }
314
315 // Step 3 calculate offsets for each block
316 int last_offset = 0;
317 for (ProgramBlock* block : blocks) {
318 block->offset = last_offset;
319 last_offset += predict_block_size(block);
320 }
321
322 // Step 3.1 patch INTERNALCALL instructions with the actual offsets
323 for (ProgramBlock* block : blocks) {
324 block->patch_internal_calls();
325 }
326
327 // Step 4 terminate unterminated blocks with jumps with known offsets, get the bytecode for each block
328 std::vector<std::vector<uint8_t>> block_bytecodes;
329 for (ProgramBlock* block : blocks) {
330 std::vector<bb::avm2::simulation::Instruction> instructions = block->get_instructions();
331 switch (block->terminator_type) {
332 case TerminatorType::RETURN: // finalized with return
333 // already terminated with return
334 break;
335 case TerminatorType::REVERT: // finalized with revert
336 // already terminated with revert
337 break;
338 case TerminatorType::JUMP: { // finalized with jump
339 ProgramBlock* target_block = block->successors.at(0);
340 size_t target_block_idx = find_block_idx(target_block, blocks);
341 uint32_t jump_offset = static_cast<uint32_t>(blocks.at(target_block_idx)->offset);
342 auto jump_instruction =
344 instructions.push_back(jump_instruction);
345 break;
346 }
347 case TerminatorType::JUMP_IF: { // finalized with jumpi
348 ProgramBlock* target_then_block = block->successors.at(0);
349 ProgramBlock* target_else_block = block->successors.at(1);
350 size_t target_then_block_idx = find_block_idx(target_then_block, blocks);
351 size_t target_else_block_idx = find_block_idx(target_else_block, blocks);
352 uint32_t jump_then_offset = static_cast<uint32_t>(blocks.at(target_then_block_idx)->offset);
353 uint32_t jump_else_offset = static_cast<uint32_t>(blocks.at(target_else_block_idx)->offset);
354 auto conditional_offset = block->get_terminating_condition_value();
355 if (!conditional_offset.has_value()) {
356 throw std::runtime_error("Condition value not found, should not happen");
357 }
359 .operand(conditional_offset.value())
360 .operand(jump_then_offset)
361 .build();
362 auto jump_else_instruction =
364 instructions.push_back(jumpi_instruction);
365 instructions.push_back(jump_else_instruction);
366 break;
367 }
368 default:
369 throw std::runtime_error(
370 "Inject terminators: Every block should be terminated with return, revert, jump, or jumpi");
371 }
372 block_bytecodes.push_back(create_bytecode(instructions));
373 }
374
375 // Step 5 concatenate all block bytecodes into a single bytecode vector
376 std::vector<uint8_t> bytecode;
377 for (const auto& block_bytecode : block_bytecodes) {
378 bytecode.insert(bytecode.end(), block_bytecode.begin(), block_bytecode.end());
379 }
380 return bytecode;
381}
std::shared_ptr< Napi::ThreadSafeFunction > bytecode
void process_jump_to_block(JumpToBlock instruction)
terminates the current block with a jump to the block, which does not create a loop in the graph (def...
ProgramBlock * current_block
std::vector< std::vector< FuzzInstruction > > * instruction_blocks
std::vector< ProgramBlock * > get_reachable_blocks(ProgramBlock *block)
get the list of blocks which are can be reached from the given block without creating a loop in the g...
ProgramBlock * start_block
the entry block of the program
void process_cfg_instruction(CFGInstruction instruction)
void process_insert_simple_instruction_block(InsertSimpleInstructionBlock instruction)
add instructions to the current block from the instruction block at the given index taken modulo leng...
void process_finalize_with_revert(FinalizeWithRevert instruction)
terminates the current block with Revert and switches to the first non-terminated block
void process_insert_internal_call(InsertInternalCall instruction)
inserts INTERNALCALL instruction to the current block creates a new block and sets it as the current ...
void process_switch_to_non_terminated_block(SwitchToNonTerminatedBlock instruction)
switches to the non-terminated block with the chosen index
std::vector< ProgramBlock * > get_non_terminated_blocks()
get the list of non-terminated blocks
void process_finalize_with_return(FinalizeWithReturn instruction)
terminates the current block with Return and switches to the first non-terminated block
void process_jump_to_new_block(JumpToNewBlock instruction)
terminates the current block with a jump and creates a new block
std::vector< uint8_t > build_bytecode(const ReturnOptions &return_options)
build the bytecode, finalizing the current block with return
void process_jump_if_to_new_block(JumpIfToNewBlock instruction)
terminates the current block with a jump if and creates two new blocks, sets the first as the then bl...
void process_jump_if_to_block(JumpIfToBlock instruction)
terminates the current block with a jumpi and jump instructions to the blocks, which does not create ...
static std::vector< ProgramBlock * > dfs_traverse(ProgramBlock *start_block, bool reverse=false)
traverse the control flow graph using DFS
std::vector< ProgramBlock * > predecessors
void finalize_with_return(uint8_t return_size, MemoryTagWrapper return_value_tag, uint16_t return_value_offset_index)
finalize the program block with a return instruction Tries to find memory address with the given retu...
std::vector< bb::avm2::simulation::Instruction > get_instructions()
void insert_internal_call(ProgramBlock *target_block)
insert INTERNALCALL instruction with 0 offset
bool is_memory_address_set(uint16_t address)
ProgramBlock * caller
the block that called this block by INTERNALCALL This field is copied to predecessors on every CFG in...
void finalize_with_jump(ProgramBlock *target_block, bool copy_memory_manager=true)
finalize the block with a jump Sets the terminator type to JUMP, adds the target block to the success...
uint16_t condition_offset_index
the offset index of the condition variable (for JUMP_IF)
void finalize_with_revert(uint8_t revert_size, MemoryTagWrapper revert_value_tag, uint16_t revert_value_offset_index)
finalize the program block with a revert instruction Similar to finalize_with_return but uses REVERT ...
void process_instruction(FuzzInstruction instruction)
process the instruction
std::vector< ProgramBlock * > successors
std::optional< uint16_t > get_terminating_condition_value()
void finalize_with_jump_if(ProgramBlock *target_then_block, ProgramBlock *target_else_block, uint16_t condition_offset, bool copy_memory_manager=true)
finalize the block with a jump if Sets the terminator type to JUMP_IF, adds the target blocks to the ...
TerminatorType terminator_type
simulation::Instruction build() const
InstructionBuilder & operand(OperandBuilder operand)
void info(Args... args)
Definition log.hpp:89
size_t find_block_idx(ProgramBlock *block, const std::vector< ProgramBlock * > &blocks)
std::vector< uint8_t > create_bytecode(const std::vector< bb::avm2::simulation::Instruction > &instructions)
int predict_block_size(ProgramBlock *block)
std::variant< InsertSimpleInstructionBlock, JumpToNewBlock, JumpIfToNewBlock, JumpToBlock, JumpIfToBlock, FinalizeWithReturn, FinalizeWithRevert, SwitchToNonTerminatedBlock, InsertInternalCall > CFGInstruction
Instruction instruction
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::string to_string(bb::avm2::ValueTag tag)
uint32_t address
finalizes the current block with Return and switches to the first non-terminated block
ReturnOptions return_options
finalizes the current block with Revert and switches to the first non-terminated block
ReturnOptions revert_options
inserts INTERNALCALL instruction to the current block creates a new block and sets it as the current ...
insert instruction block to the current block
finalizes the current block with a JumpI and Jump instructions to the block, which does not create a ...
finalizes the current block with jump if, creates two new blocks, sets the first as the then block an...
finalizes the current block with a jump to the block, which does not create a loop in the graph (defi...
finalizes the current block with jump, creates a new block and sets it as the current block
uint8_t return_size
MemoryTagWrapper return_value_tag
uint16_t return_value_offset_index
SET_16 instruction.
MemoryTagWrapper value_tag
switches to the non-terminated block with the chosen index