Files
revng-revng/scripts/monotone_framework_lattice.py
Qian Matteo Chen 12c3ce2dba Introduce ABIAnalyses2
Architecture-agnostic and ABI-independent data-flow analyses that
traverse the recovered functions in order to detect arguments and
return values registers.
2021-12-14 10:59:56 +01:00

243 lines
8.2 KiB
Python
Executable File

#!/usr/bin/env python3
# This script generates C++ classes from a GraphViz file representing a monotone
# framework and a template file.
#
# The graph should meet the same requirements as monotone_framework.py
#
import argparse
import sys
from collections import defaultdict
import importlib
import monotone_framework
def gen_combine_values(lattice, reachability):
out = ''
# Emit the combine operator
out += """LatticeElement combineValues(const LatticeElement &LHS, const LatticeElement &RHS) {
"""
node_by_index = lambda index: monotone_framework.get_unique([x
for x, x_data in lattice.nodes(data=True)
if x_data["index"] == str(index)])
result = defaultdict(lambda: [])
for v1, v1_data in lattice.nodes(data=True):
for v2, v2_data in lattice.nodes(data=True):
if v1 != v2:
i1 = int(v1_data["index"])
i2 = int(v2_data["index"])
nonzero = lambda i: set([x[0]
for x in enumerate(reachability[i])
if x[1] != 0])
output = max(nonzero(i1) & nonzero(i2),
key=lambda i: reachability[i1][i] + reachability[i2][i])
output = node_by_index(output)
result[output].append((v1, v2))
assert output in result
first = True
for output, pairs in sorted(result.items(), key=lambda x: x[0]):
if first:
out += (""" if (""")
else:
out += (""" else if (""")
conditions = []
for this, other in sorted(pairs, key=lambda x: (x[0], x[1])):
condition = "(LHS == LatticeElement::{} && RHS == LatticeElement::{})"
condition = condition.format(this, other)
conditions.append(condition)
conditions[0] = conditions[0].lstrip()
conditions_string = ("\n || "
if first
else "\n || ")
out += conditions_string.join(conditions)
out += (""") {{
return LatticeElement::{};
}}""".format(output))
first = False
out += ("""
return LHS;
}
""")
return out
def gen_is_less_or_equal(lattice, reachability):
out = ''
# Emit the comparison operator of the lattice
out += ("""bool isLessOrEqual(const LatticeElement &LHS, const LatticeElement &RHS) {
return LHS == RHS
|| """)
result = []
for v1, v1_data in lattice.nodes(data=True):
for v2, v2_data in lattice.nodes(data=True):
if v1 != v2:
i1 = int(v1_data["index"])
i2 = int(v2_data["index"])
if reachability[i1][i2] != 0:
condition = """(LHS == LatticeElement::{} && RHS == LatticeElement::{})"""
condition = condition.format(v1, v2)
result.append(condition)
out += ("\n || ".join(result))
out += (""";
}
""")
return out
def gen_transfer_function(tf_names, transfer_functions):
out = ''
# Emit the transfer function implementation
out += """LatticeElement transfer(TransferFunction T, const LatticeElement &E) {
switch(T) {
"""
for tf in tf_names:
out += (""" case TransferFunction::{}:
switch(E) {{
""".format(tf))
for edge in transfer_functions[tf]:
source, destination = edge
out += (""" case LatticeElement::{}:
return LatticeElement::{};
""".format(source, destination))
out += (""" default:
revng_abort("Invalid LatticeElement value found");
}
""")
out += (""" default:
return E;
}
}
""")
return out
def gen_lattice_element_enum(lattice):
out = ''
# Print the enumeration of all the possible lattice values
out += ("""enum LatticeElement {
""")
# Get all the names
values = [v for v in lattice.nodes()]
out += (" " + ",\n ".join(values) + "\n")
out += ("""};
""")
return out
def gen_transfer_function_enum(tf_graph):
out = ''
# Print the enumeration of all the possible transfer functions
out += ("""enum TransferFunction {
""")
# Get all the transfer function names
tfs = [e_data["label"] for _, _, e_data in tf_graph.edges(data=True)]
out += (" " + ",\n ".join(sorted(tfs)) + "\n")
out += ("""};
""")
return out
def process_graph(path, call_arcs):
input_graph = monotone_framework.load_graph(path)
monotone_framework.enumerate_graph(input_graph)
lattice = monotone_framework.extract_lattice(input_graph)
# Check that the lattice is valid for a monotone framework
_ = monotone_framework.check_lattice(lattice)
reachability = monotone_framework.compute_reachability_matrix(lattice)
tf_graph = monotone_framework.extract_transfer_function_graph(input_graph, call_arcs)
transfer_functions = defaultdict(lambda: [])
for src, dst, edge_data in tf_graph.edges(data=True):
transfer_functions[edge_data["label"]].append((src, dst))
# Check the monotonicity of the transfer function
assert monotone_framework.check_transfer_functions(tf_graph, reachability, transfer_functions)
tf_names = sorted([edge_data["label"] for _, _, edge_data in tf_graph.edges(data=True)])
extremal_lattice_element = [v
for v, v_data in lattice.nodes(data=True)
if "peripheries" in v_data][0]
return {
'transfer_function_names': tf_names,
'lattice_name': input_graph.name,
'lattice_elements_enums': gen_lattice_element_enum(lattice),
'extremal_lattice_element': extremal_lattice_element,
'transfer_function_enums': gen_transfer_function_enum(tf_graph),
'is_less_or_equal_definition': gen_is_less_or_equal(lattice, reachability),
'combine_values_definition': gen_combine_values(lattice, reachability),
'transfer_function_definition': gen_transfer_function(tf_names, transfer_functions)
}
def main():
parser = argparse.ArgumentParser(description="Generate C++ code from dot \
files representing a monotone framework.")
parser.add_argument("--call-arcs",
action="store_true",
help="Add call arcs.")
parser.add_argument("template",
metavar="TEMPLATE",
help="""Lattice template file.
you can use the keywords:
- %LatticeName% the name of the lattice extracted from the graph name
- %LatticeElement% the C++ enum definition for the elements in the lattice `enum LatticeElement {...}`
- %ExtremalLatticeElement% the name for the default value for a lattice element
- %TransferFunction% the C++ enum definition for the possible transfer functions `enum TransferFunction {...}`
- %isLessOrEqual% the C++ function with signature `bool isLessOrEqual(const LatticeElement &LHS, const LatticeElement &RHS)`
- %combineValues% the C++ function with signature `bool combineValues(const LatticeElement &LHS, const LatticeElement &RHS)`
- %transfer% the C++ function with signature `bool transfer(TransferFunction T, const LatticeElement &RHS)`
""")
parser.add_argument("inputs",
metavar="GRAPH",
nargs="+",
help="GraphViz input file.")
args = parser.parse_args()
# Print the template file
with open(args.template) as template_file:
template = template_file.read()
# Process each input graph
all_transfer_functions = set()
for path in args.inputs:
generated_code = process_graph(
path, args.call_arcs)
all_transfer_functions |= set(generated_code['transfer_function_names'])
monotone_framework.out(template.replace('%LatticeName%', generated_code['lattice_name'])
.replace('%LatticeElement%', generated_code['lattice_elements_enums'])
.replace('%ExtremalLatticeElement%', generated_code['extremal_lattice_element'])
.replace('%TransferFunction%', generated_code['transfer_function_enums'])
.replace('%isLessOrEqual%', generated_code['is_less_or_equal_definition'])
.replace('%combineValues%', generated_code['combine_values_definition'])
.replace('%transfer%', generated_code['transfer_function_definition']))
return 0
if __name__ == '__main__':
sys.exit(main())