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{
150 std::vector<ProgramBlock*> non_terminated_blocks = get_non_terminated_blocks();
151 if (non_terminated_blocks.size() == 0) {
152 return;
153 }
154 current_block = non_terminated_blocks.at(instruction.non_terminated_block_idx % non_terminated_blocks.size());
155}
156
158{
159 if (instruction_blocks->size() == 0) {
160 return;
161 }
162 auto target_instruction_block =
163 instruction_blocks->at(instruction.target_program_block_instruction_block_idx % instruction_blocks->size());
164 ProgramBlock* target_block = new ProgramBlock();
165 current_block->insert_internal_call(target_block);
166 for (const auto& instr : target_instruction_block) {
167 target_block->process_instruction(instr);
168 }
169 target_block->caller = current_block;
170 current_block = target_block;
171}
172
173std::vector<ProgramBlock*> ControlFlow::get_non_terminated_blocks()
174{
175 std::vector<ProgramBlock*> blocks = dfs_traverse(start_block);
176 std::vector<ProgramBlock*> non_terminated_blocks;
177 std::copy_if(blocks.begin(), blocks.end(), std::back_inserter(non_terminated_blocks), [](ProgramBlock* block) {
178 return block->terminator_type != TerminatorType::NONE;
179 });
180 return non_terminated_blocks;
181}
182
183std::vector<ProgramBlock*> ControlFlow::get_reachable_blocks(ProgramBlock* block)
184{
185 // traverse the graph in reverse order starting from the given block
186 // if we jump to one of the blocks in the forbidden list, we create a loop
187 std::vector<ProgramBlock*> forbidden_blocks = dfs_traverse(block, true);
188
189 std::vector<ProgramBlock*> all_blocks = dfs_traverse(start_block);
190 std::vector<ProgramBlock*> reachable_blocks;
191 // filter all forbidden blocks (that creates loops in the graph) and blocks with different caller from the list
192 // of all blocks we avoid blocks with different caller to prevent INTERNALRETURN from being executed in the
193 // context with empty callstack
194 std::copy_if(all_blocks.begin(),
195 all_blocks.end(),
196 std::back_inserter(reachable_blocks),
197 [forbidden_blocks, block](ProgramBlock* block_iter) {
198 return std::find(forbidden_blocks.begin(), forbidden_blocks.end(), block_iter) ==
199 forbidden_blocks.end() &&
200 block_iter->caller == block->caller;
201 });
202 return reachable_blocks;
203}
204
206{
207 if (std::getenv("AVM_FUZZER_LOGGING") != nullptr) {
208 info("Processing CFG instruction: ", instruction);
209 }
212 [&](JumpToNewBlock arg) { process_jump_to_new_block(arg); },
214 [&](JumpToBlock arg) { process_jump_to_block(arg); },
215 [&](JumpIfToBlock arg) { process_jump_if_to_block(arg); },
220}
221
222// Helper function to create bytecode from a vector of instructions
223std::vector<uint8_t> create_bytecode(const std::vector<bb::avm2::simulation::Instruction>& instructions)
224{
225 std::vector<uint8_t> bytecode;
226 for (const auto& instruction : instructions) {
227 auto serialized_instruction = instruction.serialize();
228 bytecode.insert(bytecode.end(),
229 std::make_move_iterator(serialized_instruction.begin()),
230 std::make_move_iterator(serialized_instruction.end()));
231 }
232 return bytecode;
233}
234
236{
237 const int JMP_SIZE = 1 + 4; // opcode + destination offset
238 const int JMP_IF_SIZE = 1 + 1 + 2 + 4; // opcode + direct/indirect + condition offset + destination offset
239 auto bytecode_length = static_cast<int>(create_bytecode(block->get_instructions()).size());
240 switch (block->terminator_type) {
242 return bytecode_length; // finalized with return, already counted
244 return bytecode_length + JMP_SIZE; // finalized with jump
246 // if boolean condition is not set adding SET_8 instruction to the bytecode
247 if (!block->get_terminating_condition_value().has_value()) {
248 for (uint16_t address = 0; address < 65535; address++) {
249 // if the memory address is already in use, we skip it
250 if (block->is_memory_address_set(address)) {
251 continue;
252 }
253 auto set_16_instruction =
255 .result_address =
257 .value = 0 };
258 block->process_instruction(set_16_instruction);
259 bytecode_length = static_cast<int>(create_bytecode(block->get_instructions()).size());
260 break;
261 }
262 }
263 return bytecode_length + JMP_IF_SIZE + JMP_SIZE; // finalized with jumpi
264 }
265 default:
266 throw std::runtime_error("Predict block size: Every block should be terminated with return, jump, or jumpi, "
267 "got " +
268 std::to_string(static_cast<int>(block->terminator_type)));
269 }
270 throw std::runtime_error("Unreachable");
271}
272
273size_t find_block_idx(ProgramBlock* block, const std::vector<ProgramBlock*>& blocks)
274{
275 for (size_t i = 0; i < blocks.size(); i++) {
276 if (blocks[i] == block) {
277 return i;
278 }
279 }
280 throw std::runtime_error("Block not found in the list");
281}
282
283std::vector<uint8_t> ControlFlow::build_bytecode(const ReturnOptions& return_options)
284{
285 // Step 1 "linarize" graph into a list of blocks
286 std::vector<ProgramBlock*> blocks = dfs_traverse(start_block);
287
288 // Step 2 terminate all non-terminated blocks with return
289 for (ProgramBlock* block : blocks) {
290 if (block->terminator_type == TerminatorType::NONE) {
291 block->finalize_with_return(
292 /*TODO(defkit) fix return size */ 1,
293 return_options.return_value_tag,
294 return_options.return_value_offset_index);
295 }
296 }
297
298 // Step 3 calculate offsets for each block
299 int last_offset = 0;
300 for (ProgramBlock* block : blocks) {
301 block->offset = last_offset;
302 last_offset += predict_block_size(block);
303 }
304
305 // Step 3.1 patch INTERNALCALL instructions with the actual offsets
306 for (ProgramBlock* block : blocks) {
307 block->patch_internal_calls();
308 }
309
310 // Step 4 terminate unterminated blocks with jumps with known offsets, get the bytecode for each block
311 std::vector<std::vector<uint8_t>> block_bytecodes;
312 for (ProgramBlock* block : blocks) {
313 std::vector<bb::avm2::simulation::Instruction> instructions = block->get_instructions();
314 switch (block->terminator_type) {
315 case TerminatorType::RETURN: // finalized with return
316 // already terminated with return
317 break;
318 case TerminatorType::JUMP: { // finalized with jump
319 ProgramBlock* target_block = block->successors.at(0);
320 size_t target_block_idx = find_block_idx(target_block, blocks);
321 uint32_t jump_offset = static_cast<uint32_t>(blocks.at(target_block_idx)->offset);
322 auto jump_instruction =
324 instructions.push_back(jump_instruction);
325 break;
326 }
327 case TerminatorType::JUMP_IF: { // finalized with jumpi
328 ProgramBlock* target_then_block = block->successors.at(0);
329 ProgramBlock* target_else_block = block->successors.at(1);
330 size_t target_then_block_idx = find_block_idx(target_then_block, blocks);
331 size_t target_else_block_idx = find_block_idx(target_else_block, blocks);
332 uint32_t jump_then_offset = static_cast<uint32_t>(blocks.at(target_then_block_idx)->offset);
333 uint32_t jump_else_offset = static_cast<uint32_t>(blocks.at(target_else_block_idx)->offset);
334 auto conditional_offset = block->get_terminating_condition_value();
335 if (!conditional_offset.has_value()) {
336 throw std::runtime_error("Condition value not found, should not happen");
337 }
339 .operand(conditional_offset.value())
340 .operand(jump_then_offset)
341 .build();
342 auto jump_else_instruction =
344 instructions.push_back(jumpi_instruction);
345 instructions.push_back(jump_else_instruction);
346 break;
347 }
348 default:
349 throw std::runtime_error(
350 "Inject terminators: Every block should be terminated with return, jump, or jumpi");
351 }
352 block_bytecodes.push_back(create_bytecode(instructions));
353 }
354
355 // Step 5 concatenate all block bytecodes into a single bytecode vector
356 std::vector<uint8_t> bytecode;
357 for (const auto& block_bytecode : block_bytecodes) {
358 bytecode.insert(bytecode.end(), block_bytecode.begin(), block_bytecode.end());
359 }
360 return bytecode;
361}
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_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 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:75
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, 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)
finalizes the current block with Return and switches to the first non-terminated block
ReturnOptions return_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