Skip to content

Commit

Permalink
Methods for combining blocks after inlining
Browse files Browse the repository at this point in the history
  • Loading branch information
alexcere committed Oct 25, 2024
1 parent 622ccab commit b2c9632
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 4 deletions.
26 changes: 26 additions & 0 deletions src/cfg_methods/preprocessing_methods.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""
Preprocess the graph by performing inlining and splitting of blocks
"""
from liveness.liveness_analysis import dot_from_analysis
from parser.cfg import CFG
from cfg_methods.function_inlining import inline_functions
from cfg_methods.sub_block_generation import combine_remove_blocks_cfg, split_blocks_cfg


def preprocess_cfg(cfg: CFG, dot_file_dir):
# First we inline the functions
inline_functions(cfg)
dot_file_dir.joinpath("inlined").mkdir(exist_ok=True, parents=True)
liveness_info = dot_from_analysis(cfg, dot_file_dir.joinpath("inlined"))

# Then we split by sub blocks
split_blocks_cfg(cfg)
dot_file_dir.joinpath("split").mkdir(exist_ok=True, parents=True)
liveness_info = dot_from_analysis(cfg, dot_file_dir.joinpath("split"))

# Then we combine and remove the blocks from the CFG
combine_remove_blocks_cfg(cfg)
dot_file_dir.joinpath("combined").mkdir(exist_ok=True, parents=True)
liveness_info = dot_from_analysis(cfg, dot_file_dir.joinpath("combined"))

# Finally, we introduce the jumps, tags and the stack requirements for each block
104 changes: 100 additions & 4 deletions src/cfg_methods/sub_block_generation.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
"""
Module used to generate the sub blocks and count how many times each function is invoked.
Same idea as optimizable_block_list but using the cfg actions and simplifying the process
Module used to generate the CFG representation that is fed into the layout generation and the greedy algorithm
"""
from typing import List, Tuple, Set
import networkx as nx
from global_params.types import block_id_T, function_name_T
from itertools import chain
from parser.cfg_block_list import CFGBlockList
from parser.cfg_block_actions.merge_blocks import MergeBlocks
from parser.cfg import CFG
import parser.constants as constants
from parser.cfg_block_actions.split_block import SplitBlock
from cfg_methods.utils import union_find_search


def split_blocks_cfg(cfg: CFG) -> None:
Expand Down Expand Up @@ -42,9 +47,8 @@ def modify_block_list_split(block_list: CFGBlockList) -> None:

is_split_instr = instr.get_op_name() in constants.split_block
is_function_call = instr.get_op_name() in cfg_block.function_calls
is_jump = instr.get_op_name() in ["JUMP", "JUMPI"]

if is_split_instr or is_function_call or is_jump:
if is_split_instr or is_function_call:
# Sub blocks contain a split instruction or a function call as the last instruction
split_block_action = SplitBlock(instr_idx, current_block, block_list)
split_block_action.perform_action()
Expand All @@ -57,3 +61,95 @@ def modify_block_list_split(block_list: CFGBlockList) -> None:
instr_idx = 0
else:
instr_idx += 1


# Methods for generating the CFG graph after the inlining and identifying the sub-blocks

def combine_remove_blocks_cfg(cfg: CFG):
"""
Combines the blocks that have just one inside and outside nodes.
Moreover, removes the blocks that are disconnected due to unnecessary splits
"""
for object_id, cfg_object in cfg.objectCFG.items():
function_names = list(cfg_object.functions.keys())
combine_remove_blocks_block_list(cfg_object.blocks, function_names)

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

sub_object = cfg.get_subobject()

if sub_object is not None:
split_blocks_cfg(sub_object)


def combine_remove_blocks_block_list(cfg_block_list: CFGBlockList, function_names: List[function_name_T]):
"""
Simplifies the representation of the block list by combining and removing unnecessary nodes in the CFG.
These blocks are the result of inlining functions
"""
combine_blocks_block_list(cfg_block_list, function_names)
remove_blocks_block_list(cfg_block_list)


def combine_blocks_block_list(cfg_block_list: CFGBlockList, function_names: List[function_name_T]) -> None:
"""
Updates both the blocks in the cfg block list and the cfg graph by combining empty blocks
and blocks with no split instruction
"""
cfg_graph = cfg_block_list.to_graph()
nodes_to_merge = _nodes_to_merge(cfg_graph)
renamed_nodes = dict()
for first_half_node, second_half_node in nodes_to_merge:
first_half_updated = union_find_search(first_half_node, renamed_nodes)
second_half_updated = union_find_search(second_half_node, renamed_nodes)

first_block = cfg_block_list.get_block(first_half_updated)
second_block = cfg_block_list.get_block(second_half_updated)

# Conditions to merge nodes: either they are empty (due to inlining certain functions)
# or if the first one has no split instruction or calls to functions
if len(first_block.get_instructions()) == 0 or len(second_block.get_instructions()) == 0 or \
first_block.get_instructions()[-1].get_op_name() in chain(constants.split_block, function_names):

nx.nx_agraph.write_dot(cfg_block_list.to_graph(), "antes.dot")

merge_blocks = MergeBlocks(first_block, second_block, cfg_block_list)
merge_blocks.perform_action()
combined_block = merge_blocks.combined_block
renamed_nodes[first_half_updated] = combined_block.block_id
renamed_nodes[second_half_updated] = combined_block.block_id

nx.nx_agraph.write_dot(cfg_block_list.to_graph(), "despues.dot")


def remove_blocks_block_list(cfg_block_list: CFGBlockList) -> None:
nodes_to_remove = _nodes_to_remove(cfg_block_list.start_block, cfg_block_list.to_graph())
print(cfg_block_list.start_block, cfg_block_list.to_graph())
for node_to_remove in nodes_to_remove:
cfg_block_list.remove_block(node_to_remove)


def _nodes_to_merge(graph: nx.DiGraph) -> List[Tuple[block_id_T, block_id_T]]:
"""
From a graph, detects which blocks must be combined
"""
nodes_to_merge = []
for node in list(graph.nodes):
# Check if node has exactly one outgoing edge
if graph.out_degree(node) == 1:
# Get the target node
target = next(graph.successors(node))

# Check if target node has exactly one incoming edge
if graph.in_degree(target) == 1:
nodes_to_merge.append((node, target))
return nodes_to_merge


def _nodes_to_remove(start_node: block_id_T, graph: nx.DiGraph) -> Set[block_id_T]:
"""
From a graph, detects which blocks are disconnected from the component of the start node
"""
return set(graph.nodes.keys()).difference({start_node}.union(nx.descendants(graph, start_node)))

0 comments on commit b2c9632

Please sign in to comment.