Skip to content

Commit

Permalink
Merge pull request #16 from costa-group/renaming
Browse files Browse the repository at this point in the history
Renaming
  • Loading branch information
alexcere authored Nov 13, 2024
2 parents be5bc3d + dbeaefa commit 383af1c
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 70 deletions.
10 changes: 6 additions & 4 deletions src/cfg_methods/cfg_block_actions/inline_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from parser.cfg_function import CFGFunction
from parser.cfg_object import CFGObject
from cfg_methods.cfg_block_actions.utils import modify_comes_from, modify_successors
from cfg_methods.variable_renaming import rename_variables_block_list, rename_cfg_function
from cfg_methods.variable_renaming import rename_block_list, rename_function


class InlineFunction(BlockAction):
Expand Down Expand Up @@ -125,7 +125,9 @@ def perform_action(self):
for exit_id in function_exists_ids:
exit_block = self._cfg_blocklist.get_block(exit_id)
# Finally, we remove the function return from the exit id
exit_block.remove_instruction(-1)
exit_function_return = exit_block.remove_instruction(-1)
assert exit_function_return.get_op_name() == "functionReturn", f"Last instruction of exit block " \
f"{exit_id} must be a functionReturn"

# is_correct, reason = validate_block_list_comes_from(self._cfg_blocklist)

Expand Down Expand Up @@ -159,7 +161,7 @@ def _rename_input_args(self, call_instruction: CFGInstruction) -> None:
"""
relabel_dict = self._function_input2call_input(call_instruction)
n_modified_vars = len(relabel_dict)
rename_cfg_function(self._cfg_function, set(), relabel_dict, 0)
rename_function(self._cfg_function, relabel_dict)

assert sum(1 for old_name, new_name in relabel_dict.items() if old_name != new_name) == n_modified_vars, \
f"Inlining {self._function_name} should not assign new variables"
Expand All @@ -171,7 +173,7 @@ def _rename_output_args(self, call_instruction: CFGInstruction) -> None:
"""
relabel_dict = self._call_output2function_output(call_instruction)
n_modified_vars = len(relabel_dict)
rename_variables_block_list(self._cfg_blocklist, set(), relabel_dict, 0)
rename_block_list(self._cfg_blocklist, relabel_dict)

assert sum(1 for old_name, new_name in relabel_dict.items() if old_name != new_name) == n_modified_vars, \
f"Inlining {self._function_name} should not assign new variables"
Expand Down
51 changes: 29 additions & 22 deletions src/cfg_methods/function_inlining.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Module to perform function inlining.
"""
import json
from copy import deepcopy
from copy import deepcopy, copy
from typing import Set, Dict, Tuple, List
from collections import defaultdict

Expand All @@ -15,7 +15,7 @@
from parser.cfg_object import CFGObject
from parser.cfg import CFG
from cfg_methods.cfg_block_actions.inline_function import InlineFunction
from cfg_methods.variable_renaming import rename_cfg_function
from cfg_methods.variable_renaming import rename_function
from cfg_methods.cost_computation import function2costs_T, compute_gas_bytes

# For each time a function is invoked, we store the position of the instruction (int) in the
Expand Down Expand Up @@ -82,11 +82,10 @@ def inline_functions_cfg_object(cfg_object: CFGObject, function_call_info: funct
function2costs: function2costs_T):
# Dict that maps each initial block name in the CFG to the set of blocks in which it can be split
block2current: Dict[block_id_T, List[block_id_T]] = dict()
free_index = _free_index_from_object(cfg_object)

function_call_info, topological_sort = prune_cycles_topological_sort(function_call_info)

for function_name in topological_sort:
for func_idx, function_name in enumerate(topological_sort):
call_info = function_call_info[function_name]
cfg_function = cfg_object.functions[function_name]

Expand All @@ -108,8 +107,7 @@ def inline_functions_cfg_object(cfg_object: CFGObject, function_call_info: funct
split_block_index = 0
position_index = instr_pos + _adjust_phi_function_idx_misalignment(cfg_block_list.blocks[split_blocks[split_block_index]])

function_to_inline, renaming_dict, free_index = _generate_function_to_inline(cfg_function, call_idx,
len(call_info), free_index)
function_to_inline, renaming_dict = _generate_function_to_inline(cfg_function, func_idx, call_idx, len(call_info))

# nx.nx_agraph.write_dot(cfg_block_list.to_graph_info(), f"antes.dot")

Expand Down Expand Up @@ -181,15 +179,15 @@ def _must_be_inlined(function_name: function_name_T, call_info_list: List[call_i
return (inlining_extra_size - no_inlining_extra_size) <= 20 * no_inlining_extra_gas


def _generate_function_to_inline(original_function: CFGFunction, current_call_idx: int,
n_calls: int, free_index: int) -> Tuple[CFGFunction, Dict[block_id_T, block_id_T], int]:
def _generate_function_to_inline(original_function: CFGFunction, func_idx: int, current_call_idx: int,
n_calls: int) -> Tuple[CFGFunction, Dict[block_id_T, block_id_T]]:
"""
We must rename the blocks when inlining to avoid conflicts, as the function can be inlined multiple times in the
same function (and hence, the same blocks would appear multiple times). We also return the renaming dict
"""
# If there is just one call, we avoid renaming the blocks
if n_calls == 1:
return original_function, dict(), 0
return original_function, dict()
# If we are making multiple copies, we copy it call_idx - 1 times, as the last one should remove it
elif current_call_idx == n_calls - 1:
copied_function = original_function
Expand All @@ -201,11 +199,19 @@ def _generate_function_to_inline(original_function: CFGFunction, current_call_id
renaming_dict = {block_name: f"{block_name}_copy_{current_call_idx}" for block_name in block_list.blocks}
block_list.rename_blocks(renaming_dict)

new_free_index = rename_cfg_function(copied_function, set(f"v{idx}"for idx in range(free_index)), dict(), free_index)
var_ids = _var_ids_from_list(block_list)
renaming_vars = {var_: f"{var_}_f{func_idx}_{current_call_idx}" for var_ in var_ids}
renaming_vars.update((var_, f"{var_}_f{func_idx}_{current_call_idx}") for var_ in copied_function.arguments)

n_renaming_vars = len(renaming_vars)
rename_function(copied_function, renaming_vars)

assert n_renaming_vars == len(renaming_vars), \
"Variable renaming in function duplication should not assign new variables"

copied_function.exits = [renaming_dict.get(exit_id, exit_id) for exit_id in copied_function.exits]
copied_function.name = f"{copied_function.name}_copy_{current_call_idx}"
return copied_function, renaming_dict, new_free_index
return copied_function, renaming_dict


# Methods to find a cycle in the call functions, remove them and generate the topological sort
Expand Down Expand Up @@ -244,22 +250,23 @@ def _prune_cycling_function_calls(function2call_info: function2call_info_T,
return function2call_info


def _free_index_from_object(cfg_object: CFGObject) -> int:
free_index = max(0, _free_index_from_list(cfg_object.blocks))
def _var_ids_from_object(cfg_object: CFGObject) -> Set[var_id_T]:
var_ids = _var_ids_from_list(cfg_object.blocks)
for function in cfg_object.functions.values():
free_index = max(free_index, _free_index_from_list(function.blocks))
return free_index
var_ids.update(_var_ids_from_list(function.blocks))
return var_ids


def _free_index_from_list(block_list: CFGBlockList) -> int:
max_idx = 0
def _var_ids_from_list(block_list: CFGBlockList) -> Set[var_id_T]:
var_ids = set()
for block in block_list.blocks.values():
for instr in block.get_instructions():
for arg in [*instr.get_in_args(), *instr.get_out_args()]:
if not arg.startswith("0x"):
max_idx = max(max_idx, _idx_from_var(arg))
return max_idx

var_ids.add(arg)
cond = block.get_condition()
if cond is not None:
if not cond.startswith("0x"):
var_ids.add(cond)
return var_ids

def _idx_from_var(var_: var_id_T) -> int:
return int(var_[1:])
9 changes: 5 additions & 4 deletions src/cfg_methods/sub_block_generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,20 @@ def split_blocks_cfg(cfg: CFG) -> None:
Splits the blocks in the cfg
"""
for object_id, cfg_object in cfg.objectCFG.items():
modify_block_list_split(cfg_object.blocks)
function_names = list(cfg_object.functions.keys())
modify_block_list_split(cfg_object.blocks, function_names)

# We also consider the information per function
for function_name, cfg_function in cfg_object.functions.items():
modify_block_list_split(cfg_function.blocks)
modify_block_list_split(cfg_function.blocks, function_names)

sub_object = cfg.get_subobject()

if sub_object is not None:
split_blocks_cfg(sub_object)


def modify_block_list_split(block_list: CFGBlockList) -> None:
def modify_block_list_split(block_list: CFGBlockList, function_calls: List[function_name_T]) -> None:
"""
Modifies a CFGBlockList by splitting blocks when function calls and split instructions are found
"""
Expand All @@ -46,7 +47,7 @@ def modify_block_list_split(block_list: CFGBlockList) -> None:
instr = current_block.get_instructions()[instr_idx]

is_split_instr = instr.get_op_name() in constants.split_block
is_function_call = instr.get_op_name() in cfg_block.function_calls
is_function_call = instr.get_op_name() in function_calls

if is_split_instr or is_function_call:
# Sub blocks contain a split instruction or a function call as the last instruction
Expand Down
80 changes: 44 additions & 36 deletions src/cfg_methods/variable_renaming.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,57 +13,75 @@
def rename_variables_cfg(cfg: CFG) -> None:
# Store which variable names have been already assigned
for object_id, cfg_object in cfg.objectCFG.items():
already_assigned = set()
free_index = 0

free_index = rename_variables_block_list(cfg_object.blocks, already_assigned, dict(), free_index)
renaming_dict, free_index = renaming_dict_from_instrs(cfg_object.blocks, [], free_index)
rename_block_list(cfg_object.blocks, renaming_dict)

# We also consider the information per function
for cfg_function in cfg_object.functions.values():
free_index = rename_cfg_function(cfg_function, already_assigned, dict(), free_index)
free_index = new_variables_function(cfg_function, free_index)

sub_object = cfg.get_subobject()

if sub_object is not None:
rename_variables_cfg(sub_object)


def rename_cfg_function(cfg_function: CFGFunction, assigned_global: Set[var_id_T],
renaming_dict: Dict[var_id_T, var_id_T], free_index: int) -> int:
cfg_function.arguments, free_index = modified_var_list(cfg_function.arguments, assigned_global,
renaming_dict, free_index)
free_index = rename_variables_block_list(cfg_function.blocks, assigned_global, renaming_dict, free_index)
def new_variables_function(cfg_function: CFGFunction, free_index: int) -> int:
renaming_dict, free_index = renaming_dict_from_instrs(cfg_function.blocks, cfg_function.arguments, free_index)
cfg_function.arguments = rename_var_list(cfg_function.arguments, renaming_dict)
rename_block_list(cfg_function.blocks, renaming_dict)
return free_index


def rename_variables_block_list(block_list: CFGBlockList, variables_assigned: Set[var_id_T],
renaming_dict: Dict[var_id_T, var_id_T], free_index: int) -> int:
def renaming_dict_from_instrs(block_list: CFGBlockList, input_args: List[var_id_T],
free_index: int) -> Tuple[Dict[var_id_T, var_id_T], int]:
"""
Generates a renaming dict from the output of the instructions, pointing to the next free index
"""
renaming_dict = dict()
# Note that input args from functions must be also assigned a new name
for input_arg in input_args:
renaming_dict[input_arg] = f"v{free_index}"
free_index += 1

for block in block_list.blocks.values():
for instruction in block.get_instructions():
for out_arg in instruction.out_args:
renaming_dict[out_arg] = f"v{free_index}"
free_index += 1

return renaming_dict, free_index


def rename_function(cfg_function: CFGFunction, renaming_dict: Dict[var_id_T, var_id_T]) -> None:
cfg_function.arguments = rename_var_list(cfg_function.arguments, renaming_dict)
rename_block_list(cfg_function.blocks, renaming_dict)


def rename_block_list(block_list: CFGBlockList, renaming_dict: Dict[var_id_T, var_id_T]) -> None:
# The renaming dict keeps track of the changes in this block to maintain the coherence
for block_name, block in block_list.blocks.items():

for instruction in block.get_instructions():
free_index = modify_vars_in_instr(instruction, variables_assigned, renaming_dict, free_index)
rename_vars_in_instr(instruction, renaming_dict)

if block.get_condition() is not None:
new_cond_list, free_index = modified_var_list([block.get_condition()], variables_assigned,
renaming_dict, free_index)
new_cond_list = rename_var_list([block.get_condition()], renaming_dict)
block.set_condition(new_cond_list[0])
# We have to update the names with the ones that have already been assigned
variables_assigned.update(renaming_dict.values())
return free_index
block.final_stack_elements = rename_var_list(block.final_stack_elements, renaming_dict)


def modify_vars_in_instr(instruction: CFGInstruction, assigned_global: Set[var_id_T],
renaming_dict: Dict[var_id_T, var_id_T], free_index: int) -> int:
instruction.out_args, free_index = modified_var_list(instruction.out_args, assigned_global,
renaming_dict, free_index)
instruction.in_args, free_index = modified_var_list(instruction.in_args, assigned_global,
renaming_dict, free_index)
return free_index
def rename_vars_in_instr(instruction: CFGInstruction, renaming_dict: Dict[var_id_T, var_id_T]) -> None:
instruction.out_args = rename_var_list(instruction.out_args, renaming_dict)
instruction.in_args = rename_var_list(instruction.in_args, renaming_dict)


def modified_var_list(var_list: List[var_id_T], assigned_global: Set[var_id_T],
renaming_dict: Dict[var_id_T, var_id_T], free_index: int) -> Tuple[List[var_id_T], int]:
def rename_var_list(var_list: List[var_id_T], renaming_dict: Dict[var_id_T, var_id_T]) -> List[var_id_T]:
"""
Only renames the variables that appear in the renaming dict
"""
updated_var_list = []
for variable in var_list:
new_variable_name = renaming_dict.get(variable, None)
Expand All @@ -76,17 +94,7 @@ def modified_var_list(var_list: List[var_id_T], assigned_global: Set[var_id_T],
elif new_variable_name is not None:
updated_var_list.append(new_variable_name)

# If it has already been assigned
elif variable in assigned_global:
new_variable_name = f"v{free_index}"
renaming_dict[variable] = new_variable_name
updated_var_list.append(new_variable_name)
free_index += 1

# Last case: first time we encounter this value in the block list
else:
free_index = max(free_index, int(variable[1:]) + 1)
renaming_dict[variable] = variable
updated_var_list.append(variable)

return updated_var_list, free_index
return updated_var_list
3 changes: 1 addition & 2 deletions src/liveness/layout_generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,8 +296,7 @@ def _construct_code_from_block(self, block: CFGBlock, input_stacks: Dict[str, Li
# We introduce the necessary args in the generation of the first output stack layout
# The stack elements we have to "force" a certain order correspond to the input parameters of
# the function
input_stack = output_stack_layout([], self._function_inputs[self._component_id],
liveness_info.in_state.live_vars, self._variable_order[block_id])
input_stack = self._function_inputs[self._block_list.name]

input_stacks[block.block_id] = input_stack

Expand Down
9 changes: 8 additions & 1 deletion src/liveness/liveness_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
# "terminal_blocks: the list of terminal block ids, in order to start the analysis
cfg_info_T = Dict[str, Union[Dict[str, LivenessBlockInfo], List[str]]]

i = 0

class LivenessAnalysisInfo(BlockAnalysisInfo):
"""
Expand Down Expand Up @@ -189,8 +190,14 @@ def dot_from_analysis_cfg(cfg: CFG, final_dir: Path = Path(".")) -> Dict[str, Di
renamed_digraph = nx.relabel_nodes(digraph, renaming_dict)

short_component_name = shorten_name(component_name)
try:
nx.nx_agraph.write_dot(renamed_digraph, final_dir.joinpath(f"{short_component_name}.dot"))
except:

nx.nx_agraph.write_dot(renamed_digraph, final_dir.joinpath(f"{short_component_name}.dot"))
global i

nx.nx_agraph.write_dot(renamed_digraph, final_dir.joinpath(f"too_long_name_{i}.dot"))
i += 1

return results

Expand Down
1 change: 0 additions & 1 deletion src/parser/utils_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,6 @@ def get_expression(var: var_id_T, instructions) -> expression_T:
return var
assert all(candidates[i] == candidates[i+1] for i in range(len(candidates) - 1)), \
"[ERROR]: A variable cannot be generated by more than one instruction"

# Case: build expression from subexpressions
new_instruction = candidates[0]

Expand Down

0 comments on commit 383af1c

Please sign in to comment.