Skip to content

Commit

Permalink
Merge branch 'main' of github.com:costa-group/grey
Browse files Browse the repository at this point in the history
  • Loading branch information
tutugordillo committed Oct 22, 2024
2 parents a65937c + 720c773 commit d3be60a
Show file tree
Hide file tree
Showing 6 changed files with 257 additions and 8 deletions.
15 changes: 15 additions & 0 deletions src/parser/cfg_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,21 @@ def get_block_id(self) -> str:
def get_instructions(self) -> List[CFGInstruction]:
return self._instructions

def remove_instruction(self, instr_idx: int) -> None:
"""
Removes the instruction at position instr_index, updating the last split instruction if it affects
the last instruction
"""
instr_idx = (len(self._instructions) + instr_idx) % len(self._instructions)
if instr_idx >= len(self._instructions):
raise ValueError("Attempting to remove an instruction index out of bounds")
elif instr_idx == len(self._instructions) - 1:
self._instructions = self._instructions[:-1]
# There is no split instruction at this point
self._split_instruction = None
else:
self._instructions.pop(instr_idx)

def get_instructions_to_compute(self) -> List[CFGInstruction]:
return [instruction for instruction in self._instructions if instruction.must_be_computed()]

Expand Down
110 changes: 110 additions & 0 deletions src/parser/cfg_block_actions/inline_function.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
from typing import Optional
from parser.cfg_block_actions.actions_interface import BlockAction
from parser.cfg_block_actions.split_block import SplitBlock
from parser.cfg_block_list import CFGBlockList
from parser.cfg_block import CFGBlock
from parser.cfg_function import CFGFunction
from parser.cfg_object import CFGObject
from parser.cfg_block_actions.utils import modify_comes_from, modify_successors


def new_node_name(current_node: str) -> str:
"""
Given a node, generates a new name for the resulting split
"""
split_name = current_node.split("_")
if len(split_name) > 1:
split_name[1] = str(int(split_name[1]) + 1)
return '_'.join(split_name)
else:
return current_node + "_1"


class InlineFunction(BlockAction):
"""
Action for performing inlining of a different block list into an existing one.
"""

def __init__(self, instr_position: int, cfg_block: CFGBlock, cfg_blocklist: CFGBlockList,
function_name: str, cfg_object: CFGObject):
"""
It receives the position in which we want to split, the corresponding block in which we are appending
the corresponding block list, its block list, the function name and the block list
associated to this function name
"""
self._instr_position: int = instr_position
self._cfg_block: CFGBlock = cfg_block
self._cfg_blocklist: CFGBlockList = cfg_blocklist
self._function_name: str = function_name
self._cfg_function: CFGFunction = cfg_object.functions[function_name]
self._function_blocklist: CFGBlockList = self._cfg_function.blocks
self._cfg_object: CFGObject = cfg_object
self._first_sub_block: Optional[CFGObject] = None
self._second_sub_block: Optional[CFGObject] = None

def perform_action(self):
# First we need to split the block in the function call, which is given by the instr position.
# As a final check, we ensure the instruction in that position corresponds to the function name passed as
# an argument
assert self._cfg_block.get_instructions()[self._instr_position].get_op_name() == self._function_name, \
f"Expected function call {self._function_name} in position {self._instr_position}"

# Include the blocks from the function into the CFG block list
for block in self._function_blocklist.blocks.values():
self._cfg_blocklist.add_block(block)

function_start_id = self._cfg_function.entry
function_exists_ids = self._cfg_function.exits

# Even after splitting the blocks, we have to remove the first instruction
# from the first block (the function call)
split_action = SplitBlock(self._instr_position, self._cfg_block, self._cfg_blocklist)
split_action.perform_action()

first_sub_block = split_action.first_half
# We have to remove the function call
first_sub_block.remove_instruction(-1)
second_sub_block = split_action.second_half

self._first_sub_block = first_sub_block
self._second_sub_block = second_sub_block

# For now, we only connect the blocks without considering if some of them could be empty
# We need to modify both the comes from and the falls to/jumps to fields of the start and exits field and the
# halves
modify_successors(first_sub_block.block_id, second_sub_block.block_id, function_start_id, self._cfg_blocklist)
modify_comes_from(function_start_id, None, first_sub_block.block_id, self._cfg_blocklist)

# We set the "comes_from" from the second block to empty. This way, we can ensure only the terminal blocks
# appear in this list
self._cfg_blocklist.blocks[second_sub_block.block_id].set_comes_from([])
for exit_id in function_exists_ids:
# Now the exit id must jump to the second sub_block
modify_successors(exit_id, None, second_sub_block.block_id, self._cfg_blocklist)
self._cfg_blocklist.blocks[second_sub_block.block_id].add_comes_from(exit_id)

# Last step consists of removing the blocklist, the function and remove the function
# from the corresponding object
self._function_blocklist.blocks.clear()
del self._function_blocklist
del self._cfg_function
self._cfg_object.functions.pop(self._function_name)

@property
def first_sub_block(self) -> Optional[CFGBlock]:
"""
First sub block after splitting the function call. Only contains a None value if the
action has not been performed yet
"""
return self._first_sub_block

@property
def second_sub_block(self) -> Optional[CFGBlock]:
"""
Second sub block after splitting the function call. Only contains a None value if the
actions has not been performed yet
"""
return self._second_sub_block

def __str__(self):
return f"Inlining function {self._function_name}"
6 changes: 3 additions & 3 deletions src/parser/cfg_block_actions/split_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,11 @@ def perform_action(self):
def _update_first_half(self):
# We need to update the corresponding information from both the first and second half
self._first_half.set_comes_from(self._cfg_block.get_comes_from())
self._first_half.set_falls_to(self._second_half.block_id)
self._first_half.set_jump_to(None)
self._first_half.set_falls_to(None)
self._first_half.set_jump_to(self._second_half.block_id)

# We need to force the input arguments of the instruction we have use to split
self._first_half.final_stack_elements = self._cfg_block.get_instructions()[self._instr_idx].get_in_args()
self._first_half.final_stack_elements = self._cfg_block.get_instructions()[self._instr_idx - 1].get_in_args()

# Finally, we update the information from the blocks that jumped (or fell) to the first one

Expand Down
17 changes: 14 additions & 3 deletions src/parser/cfg_block_actions/utils.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from typing import Optional
from global_params.types import block_id_T
from parser.cfg_block_list import CFGBlockList


def modify_comes_from(block_to_modify: block_id_T, previous_pred_id: block_id_T,
new_pred_id: block_id_T, cfg_block_list: CFGBlockList) -> None:
def modify_comes_from(block_to_modify: block_id_T, previous_pred_id: Optional[block_id_T],
new_pred_id: Optional[block_id_T], cfg_block_list: CFGBlockList) -> None:
"""
Modifies the comes from the block id to replace the id of the initial block with the new one in the block list
"""
Expand All @@ -17,6 +18,13 @@ def modify_comes_from(block_to_modify: block_id_T, previous_pred_id: block_id_T,
new_comes_from.append(new_pred_id)
else:
new_comes_from.append(pred_block)

# None case for "previous_pred_id" means that there is no substitution to perform.
# Hence, we just add the new id to the list of comes from
if previous_pred_id is None:
found_previous = True
new_comes_from.append(new_pred_id)

block.set_comes_from(new_comes_from)
assert found_previous, f"Comes from list {comes_from} of {block_to_modify} does not contain {previous_pred_id}"

Expand All @@ -28,7 +36,10 @@ def modify_successors(block_to_modify: block_id_T, previous_successor_id: block_
"new_successor_id" instead
"""
pred_block = cfg_block_list.blocks[block_to_modify]
if pred_block.get_jump_to() == previous_successor_id:

# To avoid assigning the same successor id if the previous one was None,
# we force the "falls to" case not to be empty (same as unconditional jumps)
if previous_successor_id is not None and pred_block.get_jump_to() == previous_successor_id:
pred_block.set_jump_to(new_successor_id)
else:
falls_to = pred_block.get_falls_to()
Expand Down
5 changes: 3 additions & 2 deletions src/parser/cfg_block_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"""

from typing import List, Dict, Any, Tuple
import logging
import networkx
from parser.cfg_block import CFGBlock, include_function_call_tags
from parser.constants import split_block
Expand All @@ -29,8 +30,8 @@ def add_block(self, block: CFGBlock) -> None:
self.start_block = block_id

if block_id in self.blocks:
if block_id in self.blocks:
print("WARNING: You are overwritting an existing block")
logging.warning("You are overwritting an existing block")

self.graph = None
self.blocks[block_id] = block

Expand Down
112 changes: 112 additions & 0 deletions tests/test_inline_function.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
from hypothesis import given, strategies as st
from parser.cfg_instruction import CFGInstruction
from parser.cfg_block import CFGBlock
from parser.cfg_block_list import CFGBlockList
from parser.cfg_function import CFGFunction
from parser.cfg_object import CFGObject
from parser.cfg_block_actions.inline_function import InlineFunction
from utils import cfg_instruction_list


class TestInlineFunction:

@given(cfg_instruction_list(5, 36))
def test_inline_function_one_function(self, instructions):
split_list_index = len(instructions) // 12
cfg_block_0 = CFGBlock("block_0", instructions[:split_list_index], "unconditional", dict())
cfg_block_1 = CFGBlock("block_1", instructions[split_list_index:2*split_list_index], "unconditional", dict())
cfg_block_2 = CFGBlock("block_2", instructions[2*split_list_index:3*split_list_index], "unconditional", dict())
cfg_block_3 = CFGBlock("block_3", instructions[3*split_list_index:4*split_list_index], "conditional", dict())
cfg_block_4 = CFGBlock("block_4", instructions[4*split_list_index:5*split_list_index], "terminal", dict())
cfg_block_5 = CFGBlock("block_5", instructions[5*split_list_index:6*split_list_index], "terminal", dict())
cfg_block_6 = CFGBlock("block_6", instructions[6*split_list_index:7*split_list_index], "conditional", dict())

function_cfg_block_list = CFGBlockList()
function_cfg_block_list.add_block(cfg_block_0)
function_cfg_block_list.add_block(cfg_block_1)
function_cfg_block_list.add_block(cfg_block_2)
function_cfg_block_list.add_block(cfg_block_3)
function_cfg_block_list.add_block(cfg_block_4)
function_cfg_block_list.add_block(cfg_block_5)
function_cfg_block_list.add_block(cfg_block_6)

cfg_function = CFGFunction("function", [], [], "block_0", function_cfg_block_list)
cfg_function.add_exit_point(cfg_block_4.block_id)
cfg_function.add_exit_point(cfg_block_5.block_id)

# Function CFG structure:
# 6
# 0 1
# 2
# 3
# 4 5
edges = [(cfg_block_6.block_id, cfg_block_0.block_id, "falls_to"),
(cfg_block_6.block_id, cfg_block_1.block_id, "jumps_to"),
(cfg_block_0.block_id, cfg_block_2.block_id, "falls_to"),
(cfg_block_1.block_id, cfg_block_2.block_id, "jumps_to"),
(cfg_block_2.block_id, cfg_block_3.block_id, "jumps_to"),
(cfg_block_3.block_id, cfg_block_4.block_id, "falls_to"),
(cfg_block_3.block_id, cfg_block_5.block_id, "jumps_to")]

for u, v, jump_type in edges:
function_cfg_block_list.blocks[v].add_comes_from(u)
if jump_type == "jumps_to":
function_cfg_block_list.blocks[u].set_jump_to(v)
else:
function_cfg_block_list.blocks[u].set_falls_to(v)


# Creation of the main block list
instruction_call = CFGInstruction("function", [], [])
cfg_block_7 = CFGBlock("block_7", instructions[7*split_list_index:8*split_list_index], "conditional", dict())
cfg_block_8 = CFGBlock("block_8", instructions[8*split_list_index:9*split_list_index], "conditional", dict())
cfg_block_9 = CFGBlock("block_9", [instruction_call] + instructions[9*split_list_index:10*split_list_index], "conditional", dict())
cfg_block_10 = CFGBlock("block_10", instructions[10*split_list_index:11*split_list_index], "terminal", dict())
cfg_block_11 = CFGBlock("block_11", instructions[11*split_list_index:], "terminal", dict())

# We add a call to function in cfg_block_9

cfg_object_block_list = CFGBlockList()
cfg_object_block_list.add_block(cfg_block_7)
cfg_object_block_list.add_block(cfg_block_8)
cfg_object_block_list.add_block(cfg_block_9)
cfg_object_block_list.add_block(cfg_block_10)
cfg_object_block_list.add_block(cfg_block_11)

# Main CFG structure:
# 7 8
# 9
# 10 11

edges = [(cfg_block_7.block_id, cfg_block_9.block_id, "jumps_to"),
(cfg_block_8.block_id, cfg_block_9.block_id, "jumps_to"),
(cfg_block_9.block_id, cfg_block_10.block_id, "falls_to"),
(cfg_block_9.block_id, cfg_block_11.block_id, "jumps_to")]

for u, v, jump_type in edges:
cfg_object_block_list.blocks[v].add_comes_from(u)
if jump_type == "jumps_to":
cfg_object_block_list.blocks[u].set_jump_to(v)
else:
cfg_object_block_list.blocks[u].set_falls_to(v)

# Creation of the CFG object
cfg_object = CFGObject("object", cfg_object_block_list)
cfg_object.add_function(cfg_function)

inline_object = InlineFunction(0, cfg_block_9, cfg_object_block_list, "function", cfg_object)
inline_object.perform_action()
assert len(function_cfg_block_list.blocks) == 0, "There must be no sub block in the function"

# 13 blocks: initial 12 blocks + block 9 split
assert len(cfg_object_block_list.blocks) == 13, "There must be 13 sub blocks in the function"


first_split_sub_block = inline_object.first_sub_block
second_split_sub_block = inline_object.second_sub_block
# joined_block_id = merged_block_id("block_2", "block_3")
# assert joined_block_id in function_cfg_block_list.blocks, f"Block {joined_block_id} must appear in the block list"
# assert joined_block_id == cfg_block_0.get_falls_to(), "Joined block must be the falls to from block 0"
# assert joined_block_id == cfg_block_1.get_jump_to(), "Joined block must be the jumps to from block 1"
# assert joined_block_id in cfg_block_4.get_comes_from(), "Joined block must appear in the comes from block 4"
# assert joined_block_id in cfg_block_5.get_comes_from(), "Joined block must appear in the comes from block 5"

0 comments on commit d3be60a

Please sign in to comment.