frontend returns strings; added helper function to calc. unmatched ratio

This commit is contained in:
Julian Hammer
2020-01-28 17:24:00 +01:00
parent 421cf55af7
commit 530ad8484e
6 changed files with 100 additions and 95 deletions

View File

@@ -40,9 +40,7 @@ class KerncraftAPI(object):
def create_output(self, verbose=False): def create_output(self, verbose=False):
kernel_graph = KernelDG(self.kernel, self.parser, self.machine_model) kernel_graph = KernelDG(self.kernel, self.parser, self.machine_model)
frontend = Frontend(arch=self.machine_model.get_arch()) frontend = Frontend(arch=self.machine_model.get_arch())
with Capturing() as output: return frontend.full_analysis(self.kernel, kernel_graph, verbose=verbose)
frontend.print_full_analysis(self.kernel, kernel_graph, verbose=verbose)
return '\n'.join(output)
def get_unmatched_instruction_ratio(self): def get_unmatched_instruction_ratio(self):
unmatched_counter = 0 unmatched_counter = 0

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Frontend interface for OSACA. Does everything necessary for printing analysis to the terminal. Frontend interface for OSACA. Does everything necessary for analysis report generation.
""" """
import re import re
from datetime import datetime as dt from datetime import datetime as dt
@@ -42,11 +42,11 @@ class Frontend(object):
""" """
return instruction_form['comment'] is not None and instruction_form['instruction'] is None return instruction_form['comment'] is not None and instruction_form['instruction'] is None
def print_throughput_analysis(self, kernel, show_lineno=False, show_cmnts=True): def throughput_analysis(self, kernel, show_lineno=False, show_cmnts=True):
""" """
Print throughput analysis only. Build throughput analysis only.
:param kernel: Kernel to print throughput analysis for. :param kernel: Kernel to build throughput analysis for.
:type kernel: list :type kernel: list
:param show_lineno: flag for showing the line number of instructions, defaults to `False` :param show_lineno: flag for showing the line number of instructions, defaults to `False`
:type show_lineno: bool, optional :type show_lineno: bool, optional
@@ -62,10 +62,10 @@ class Frontend(object):
headline = 'Port pressure in cycles' headline = 'Port pressure in cycles'
headline_str = '{{:^{}}}'.format(len(separator)) headline_str = '{{:^{}}}'.format(len(separator))
print('\n\nThroughput Analysis Report\n' + '--------------------------') s = '\n\nThroughput Analysis Report\n--------------------------\n'
print(headline_str.format(headline)) s += headline_str.format(headline) + '\n'
print(lineno_filler + self._get_port_number_line(port_len)) s += lineno_filler + self._get_port_number_line(port_len) + '\n'
print(separator) s += separator + '\n'
for instruction_form in kernel: for instruction_form in kernel:
line = '{:4d} {} {} {}'.format( line = '{:4d} {} {} {}'.format(
instruction_form['line_number'], instruction_form['line_number'],
@@ -80,23 +80,24 @@ class Frontend(object):
line = line if show_lineno else col_sep + col_sep.join(line.split(col_sep)[1:]) line = line if show_lineno else col_sep + col_sep.join(line.split(col_sep)[1:])
if show_cmnts is False and self._is_comment(instruction_form): if show_cmnts is False and self._is_comment(instruction_form):
continue continue
print(line) s += line + '\n'
print() s += '\n'
tp_sum = ArchSemantics.get_throughput_sum(kernel) tp_sum = ArchSemantics.get_throughput_sum(kernel)
print(lineno_filler + self._get_port_pressure(tp_sum, port_len, separator=' ')) s += lineno_filler + self._get_port_pressure(tp_sum, port_len, separator=' ') + '\n'
return s
def print_latency_analysis(self, cp_kernel, separator='|'): def latency_analysis(self, cp_kernel, separator='|'):
""" """
Print a list-based CP analysis to the terminal. Build a list-based CP analysis report.
:param cp_kernel: loop kernel containing the CP information for each instruction form :param cp_kernel: loop kernel containing the CP information for each instruction form
:type cp_kernel: list :type cp_kernel: list
:separator: separator symbol for the columns, defaults to '|' :separator: separator symbol for the columns, defaults to '|'
:type separator: str, optional :type separator: str, optional
""" """
print('\n\nLatency Analysis Report\n' + '-----------------------') s = '\n\nLatency Analysis Report\n-----------------------\n'
for instruction_form in cp_kernel: for instruction_form in cp_kernel:
print( s += (
'{:4d} {} {:4.1f} {}{}{} {}'.format( '{:4d} {} {:4.1f} {}{}{} {}'.format(
instruction_form['line_number'], instruction_form['line_number'],
separator, separator,
@@ -106,16 +107,17 @@ class Frontend(object):
separator, separator,
instruction_form['line'], instruction_form['line'],
) )
) ) + '\n'
print( s += (
'\n{:4} {} {:4.1f}'.format( '\n{:4} {} {:4.1f}'.format(
' ' * max([len(str(instr_form['line_number'])) for instr_form in cp_kernel]), ' ' * max([len(str(instr_form['line_number'])) for instr_form in cp_kernel]),
' ' * len(separator), ' ' * len(separator),
sum([instr_form['latency_cp'] for instr_form in cp_kernel]), sum([instr_form['latency_cp'] for instr_form in cp_kernel]),
) )
) ) + '\n'
return s
def print_loopcarried_dependencies(self, dep_dict, separator='|'): def loopcarried_dependencies(self, dep_dict, separator='|'):
""" """
Print a list-based LCD analysis to the terminal. Print a list-based LCD analysis to the terminal.
@@ -124,13 +126,13 @@ class Frontend(object):
:separator: separator symbol for the columns, defaults to '|' :separator: separator symbol for the columns, defaults to '|'
:type separator: str, optional :type separator: str, optional
""" """
print( s = (
'\n\nLoop-Carried Dependencies Analysis Report\n' '\n\nLoop-Carried Dependencies Analysis Report\n'
+ '-----------------------------------------' + '-----------------------------------------\n'
) )
# TODO find a way to overcome padding for different tab-lengths # TODO find a way to overcome padding for different tab-lengths
for dep in dep_dict: for dep in dep_dict:
print( s += (
'{:4d} {} {:4.1f} {} {:36}{} {}'.format( '{:4d} {} {:4.1f} {} {:36}{} {}'.format(
dep, dep,
separator, separator,
@@ -143,15 +145,16 @@ class Frontend(object):
[node['line_number'] for node in dep_dict[dep]['dependencies']], [node['line_number'] for node in dep_dict[dep]['dependencies']],
) )
) )
return s
def print_full_analysis( def full_analysis(
self, kernel, kernel_dg: KernelDG, ignore_unknown=False, verbose=False self, kernel, kernel_dg: KernelDG, ignore_unknown=False, verbose=False
): ):
""" """
Prints the full analysis report including header, the symbol map, the combined TP/CP/LCD Build the full analysis report including header, the symbol map, the combined TP/CP/LCD
view and the list based LCD view. view and the list based LCD view.
:param kernel: kernel to print :param kernel: kernel to report on
:type kernel: list :type kernel: list
:param kernel_dg: directed graph containing CP and LCD :param kernel_dg: directed graph containing CP and LCD
:type kernel_dg: :class:`~osaca.semantics.KernelDG` :type kernel_dg: :class:`~osaca.semantics.KernelDG`
@@ -161,24 +164,24 @@ class Frontend(object):
:param verbose: flag for verbosity level, defaults to False :param verbose: flag for verbosity level, defaults to False
:type verbose: boolean, optional :type verbose: boolean, optional
""" """
self._print_header_report() return (
self._print_symbol_map() self._header_report() +
self.print_combined_view( self._symbol_map() +
kernel, self.combined_view(
kernel_dg.get_critical_path(), kernel,
kernel_dg.get_loopcarried_dependencies(), kernel_dg.get_critical_path(),
ignore_unknown, kernel_dg.get_loopcarried_dependencies(),
) ignore_unknown) +
self.print_loopcarried_dependencies(kernel_dg.get_loopcarried_dependencies()) self.loopcarried_dependencies(kernel_dg.get_loopcarried_dependencies()))
def print_combined_view( def combined_view(
self, kernel, cp_kernel: KernelDG, dep_dict, ignore_unknown=False, show_cmnts=True self, kernel, cp_kernel: KernelDG, dep_dict, ignore_unknown=False, show_cmnts=True
): ):
""" """
Prints the combined view of the kernel including the port pressure (TP), a CP column and a Build combined view of kernel including port pressure (TP), a CP column and a
LCD column. LCD column.
:param kernel: kernel to print :param kernel: kernel to report on
:type kernel: list :type kernel: list
:param kernel_dg: directed graph containing CP and LCD :param kernel_dg: directed graph containing CP and LCD
:type kernel_dg: :class:`~osaca.semantics.KernelDG` :type kernel_dg: :class:`~osaca.semantics.KernelDG`
@@ -190,7 +193,7 @@ class Frontend(object):
:param show_cmnts: flag for showing comment-only lines in kernel, defaults to `True` :param show_cmnts: flag for showing comment-only lines in kernel, defaults to `True`
:type show_cmnts: bool, optional :type show_cmnts: bool, optional
""" """
print('\n\nCombined Analysis Report\n' + '------------------------') s = '\n\nCombined Analysis Report\n------------------------\n'
lineno_filler = ' ' lineno_filler = ' '
port_len = self._get_max_port_len(kernel) port_len = self._get_max_port_len(kernel)
# Separator for ports # Separator for ports
@@ -216,20 +219,19 @@ class Frontend(object):
longest_lcd = [line_no for line_no in sums if sums[line_no] == lcd_sum][0] longest_lcd = [line_no for line_no in sums if sums[line_no] == lcd_sum][0]
lcd_lines = [d['line_number'] for d in dep_dict[longest_lcd]['dependencies']] lcd_lines = [d['line_number'] for d in dep_dict[longest_lcd]['dependencies']]
print(headline_str.format(headline)) s += headline_str.format(headline) + '\n'
print( s += (
lineno_filler lineno_filler
+ self._get_port_number_line(port_len, separator=col_sep) + self._get_port_number_line(port_len, separator=col_sep)
+ '{}{:^6}{}{:^6}{}'.format(col_sep, 'CP', col_sep, 'LCD', col_sep) + '{}{:^6}{}{:^6}{}'.format(col_sep, 'CP', col_sep, 'LCD', col_sep)
) ) + '\n' + separator + '\n'
print(separator)
for instruction_form in kernel: for instruction_form in kernel:
if show_cmnts is False and self._is_comment(instruction_form): if show_cmnts is False and self._is_comment(instruction_form):
continue continue
line_number = instruction_form['line_number'] line_number = instruction_form['line_number']
used_ports = [list(uops[1]) for uops in instruction_form['port_uops']] used_ports = [list(uops[1]) for uops in instruction_form['port_uops']]
used_ports = list(set([p for uops_ports in used_ports for p in uops_ports])) used_ports = list(set([p for uops_ports in used_ports for p in uops_ports]))
line = '{:4d} {}{} {} {}'.format( s += '{:4d} {}{} {} {}\n'.format(
line_number, line_number,
self._get_port_pressure( self._get_port_pressure(
instruction_form['port_pressure'], port_len, used_ports, sep_list instruction_form['port_pressure'], port_len, used_ports, sep_list
@@ -244,8 +246,7 @@ class Frontend(object):
else ' ', else ' ',
instruction_form['line'].strip(), instruction_form['line'].strip(),
) )
print(line) s += '\n'
print()
# check for unknown instructions and throw warning if called without --ignore-unknown # check for unknown instructions and throw warning if called without --ignore-unknown
if not ignore_unknown and INSTR_FLAGS.TP_UNKWN in [ if not ignore_unknown and INSTR_FLAGS.TP_UNKWN in [
flag for instr in kernel for flag in instr['flags'] flag for instr in kernel for flag in instr['flags']
@@ -253,33 +254,31 @@ class Frontend(object):
num_missing = len( num_missing = len(
[instr['flags'] for instr in kernel if INSTR_FLAGS.TP_UNKWN in instr['flags']] [instr['flags'] for instr in kernel if INSTR_FLAGS.TP_UNKWN in instr['flags']]
) )
self._print_missing_instruction_error(num_missing) self._missing_instruction_error(num_missing)
else: else:
# lcd_sum already calculated before # lcd_sum already calculated before
tp_sum = ArchSemantics.get_throughput_sum(kernel) tp_sum = ArchSemantics.get_throughput_sum(kernel)
cp_sum = sum([x['latency_cp'] for x in cp_kernel]) cp_sum = sum([x['latency_cp'] for x in cp_kernel])
print( s += (
lineno_filler lineno_filler
+ self._get_port_pressure(tp_sum, port_len, separator=' ') + self._get_port_pressure(tp_sum, port_len, separator=' ')
+ ' {:^6} {:^6}'.format(cp_sum, lcd_sum) + ' {:^6} {:^6}\n'.format(cp_sum, lcd_sum)
) )
return s
#################### ####################
# HELPER FUNCTIONS # HELPER FUNCTIONS
#################### ####################
def _print_missing_instruction_error(self, amount): def _missing_instruction_error(self, amount):
print( s = (
( '------------------ WARNING: The performance data for {} instructions is missing.'
'------------------ WARNING: The performance data for {} instructions is missing.' '------------------\n'
'------------------\n' ' No final analysis is given. If you want to ignore this\n'
' No final analysis is given. If you want to ignore this\n' ' warning and run the analysis anyway, start osaca with\n'
' warning and run the analysis anyway, start osaca with\n' ' --ignore_unknown flag.\n'
' --ignore_unknown flag.\n' '--------------------------------------------------------------------------------'
'--------------------------------------------------------------------------------' '----------------{}\n').format(amount, '-' * len(str(amount)))
'----------------{}'
).format(amount, '-' * len(str(amount)))
)
def _get_separator_list(self, separator, separator_2=' '): def _get_separator_list(self, separator, separator_2=' '):
"""Creates column view for seperators in the TP/combined view.""" """Creates column view for seperators in the TP/combined view."""
@@ -352,7 +351,7 @@ class Frontend(object):
string_result += substr.format(self._machine_model.get_ports()[i]) + separator_list[i] string_result += substr.format(self._machine_model.get_ports()[i]) + separator_list[i]
return string_result return string_result
def _print_header_report(self): def _header_report(self):
"""Prints header information""" """Prints header information"""
version = 'v0.3' version = 'v0.3'
adjust = 20 adjust = 20
@@ -363,9 +362,9 @@ class Frontend(object):
header += 'Timestamp:'.ljust(adjust) + '{}\n'.format( header += 'Timestamp:'.ljust(adjust) + '{}\n'.format(
dt.utcnow().strftime('%Y-%m-%d %H:%M:%S') dt.utcnow().strftime('%Y-%m-%d %H:%M:%S')
) )
print(header) return header + '\n'
def _print_symbol_map(self): def _symbol_map(self):
"""Prints instruction flag map.""" """Prints instruction flag map."""
symbol_dict = { symbol_dict = {
INSTR_FLAGS.NOT_BOUND: 'Instruction micro-ops not bound to a port', INSTR_FLAGS.NOT_BOUND: 'Instruction micro-ops not bound to a port',
@@ -378,7 +377,7 @@ class Frontend(object):
for flag in sorted(symbol_dict.keys()): for flag in sorted(symbol_dict.keys()):
symbol_map += ' {} - {}\n'.format(self._get_flag_symbols([flag]), symbol_dict[flag]) symbol_map += ' {} - {}\n'.format(self._get_flag_symbols([flag]), symbol_dict[flag])
print(symbol_map, end='') return symbol_map
def _print_port_binding_summary(self): def _port_binding_summary(self):
raise NotImplementedError raise NotImplementedError

View File

@@ -10,7 +10,7 @@ from osaca.db_interface import import_benchmark_output, sanity_check
from osaca.frontend import Frontend from osaca.frontend import Frontend
from osaca.parser import BaseParser, ParserAArch64v81, ParserX86ATT from osaca.parser import BaseParser, ParserAArch64v81, ParserX86ATT
from osaca.semantics import (ArchSemantics, KernelDG, MachineModel, from osaca.semantics import (ArchSemantics, KernelDG, MachineModel,
reduce_to_section) reduce_to_section, INSTR_FLAGS)
MODULE_DATA_DIR = os.path.join( MODULE_DATA_DIR = os.path.join(
os.path.dirname(os.path.split(os.path.abspath(__file__))[0]), 'osaca/data/' os.path.dirname(os.path.split(os.path.abspath(__file__))[0]), 'osaca/data/'
@@ -231,9 +231,9 @@ def inspect(args):
kernel_graph.export_graph(args.dotpath if args.dotpath != '.' else None) kernel_graph.export_graph(args.dotpath if args.dotpath != '.' else None)
# Print analysis # Print analysis
frontend = Frontend(args.file.name, arch=arch) frontend = Frontend(args.file.name, arch=arch)
frontend.print_full_analysis( print(frontend.full_analysis(
kernel, kernel_graph, ignore_unknown=ignore_unknown, verbose=verbose kernel, kernel_graph, ignore_unknown=ignore_unknown, verbose=verbose
) ))
def run(args, output_file=sys.stdout): def run(args, output_file=sys.stdout):
@@ -258,7 +258,6 @@ def run(args, output_file=sys.stdout):
inspect(args) inspect(args)
# ---------------------------------------------------
def get_asm_parser(arch) -> BaseParser: def get_asm_parser(arch) -> BaseParser:
""" """
Helper function to create the right parser for a specific architecture. Helper function to create the right parser for a specific architecture.
@@ -274,7 +273,14 @@ def get_asm_parser(arch) -> BaseParser:
return ParserAArch64v81() return ParserAArch64v81()
# --------------------------------------------------- def get_unmatched_instruction_ratio(kernel):
"""Return ratio of unmatched from total instructions in kernel."""
unmatched_counter = 0
for instruction in kernel:
if INSTR_FLAGS.TP_UNKWN in instruction['flags'] and \
INSTR_FLAGS.LT_UNKWN in instruction['flags']:
unmatched_counter += 1
return unmatched_counter / len(kernel)
def main(): def main():

View File

@@ -201,7 +201,7 @@ def find_basic_blocks(lines):
# an instruction referencing a valid jump label # an instruction referencing a valid jump label
blocks = OrderedDict() blocks = OrderedDict()
for label, label_line_idx in valid_jump_labels.items(): for label, label_line_idx in valid_jump_labels.items():
blocks[label] = [] blocks[label] = [lines[label_line_idx]]
for line in lines[label_line_idx + 1 :]: for line in lines[label_line_idx + 1 :]:
terminate = False terminate = False
blocks[label].append(line) blocks[label].append(line)
@@ -230,7 +230,7 @@ def find_basic_loop_bodies(lines):
# an instruction referencing a valid jump label # an instruction referencing a valid jump label
loop_bodies = OrderedDict() loop_bodies = OrderedDict()
for label, label_line_idx in valid_jump_labels.items(): for label, label_line_idx in valid_jump_labels.items():
current_block = [] current_block = [lines[label_line_idx]]
for line in lines[label_line_idx + 1 :]: for line in lines[label_line_idx + 1 :]:
terminate = False terminate = False
current_block.append(line) current_block.append(line)

View File

@@ -67,13 +67,15 @@ class TestFrontend(unittest.TestCase):
def test_frontend_x86(self): def test_frontend_x86(self):
dg = KernelDG(self.kernel_x86, self.parser_x86, self.machine_model_csx) dg = KernelDG(self.kernel_x86, self.parser_x86, self.machine_model_csx)
fe = Frontend(path_to_yaml=os.path.join(self.MODULE_DATA_DIR, 'csx.yml')) fe = Frontend(path_to_yaml=os.path.join(self.MODULE_DATA_DIR, 'csx.yml'))
fe.print_throughput_analysis(self.kernel_x86, show_cmnts=False) fe.throughput_analysis(self.kernel_x86, show_cmnts=False)
fe.print_latency_analysis(dg.get_critical_path()) fe.latency_analysis(dg.get_critical_path())
# TODO compare output with checked string
def test_frontend_AArch64(self): def test_frontend_AArch64(self):
dg = KernelDG(self.kernel_AArch64, self.parser_AArch64, self.machine_model_tx2) dg = KernelDG(self.kernel_AArch64, self.parser_AArch64, self.machine_model_tx2)
fe = Frontend(path_to_yaml=os.path.join(self.MODULE_DATA_DIR, 'tx2.yml')) fe = Frontend(path_to_yaml=os.path.join(self.MODULE_DATA_DIR, 'tx2.yml'))
fe.print_full_analysis(self.kernel_AArch64, dg, verbose=True) fe.full_analysis(self.kernel_AArch64, dg, verbose=True)
# TODO compare output with checked string
################## ##################
# Helper functions # Helper functions

View File

@@ -317,36 +317,36 @@ class TestMarkerUtils(unittest.TestCase):
self.assertEqual( self.assertEqual(
[(k, v[0]['line_number'], v[-1]['line_number']) [(k, v[0]['line_number'], v[-1]['line_number'])
for k, v in find_basic_blocks(self.parsed_x86).items()], for k, v in find_basic_blocks(self.parsed_x86).items()],
[('.LFB24', 12, 56), ('.L4', 67, 74), ('.L3', 81, 89), ('.L2', 104, 112), [('.LFB24', 11, 56), ('.L4', 66, 74), ('.L3', 80, 89), ('.L2', 103, 112),
('.L13', 113, 121), ('.L12', 122, 125), ('.L6', 134, 135), ('.L10', 147, 154), ('.L13', 112, 121), ('.L12', 121, 125), ('.L6', 133, 135), ('.L10', 146, 154),
('.L9', 163, 170), ('.L8', 185, 187), ('.L15', 254, 256), ('.L26', 258, 259), ('.L9', 162, 170), ('.L8', 184, 187), ('.L15', 253, 256), ('.L26', 257, 259),
('.L14', 261, 262), ('.LFB25', 279, 290), ('.L28', 291, 300)]) ('.L14', 260, 262), ('.LFB25', 278, 290), ('.L28', 290, 300)])
self.assertEqual( self.assertEqual(
[(k, v[0]['line_number'], v[-1]['line_number']) [(k, v[0]['line_number'], v[-1]['line_number'])
for k, v in find_basic_blocks(self.parsed_AArch).items()], for k, v in find_basic_blocks(self.parsed_AArch).items()],
[('triad', 20, 64), ('.LBB0_3', 73, 77), ('.LBB0_4', 78, 83), ('.LBB0_5', 86, 89), [('triad', 19, 64), ('.LBB0_3', 72, 77), ('.LBB0_4', 77, 83), ('.LBB0_5', 85, 89),
('.LBB0_7', 94, 95), ('.LBB0_8', 97, 105), ('.LBB0_9', 108, 114), ('.LBB0_7', 93, 95), ('.LBB0_8', 96, 105), ('.LBB0_9', 107, 114),
('.LBB0_11', 120, 134), ('.LBB0_12', 135, 173), ('.LBB0_14', 179, 191), ('.LBB0_11', 119, 134), ('.LBB0_12', 134, 173), ('.LBB0_14', 178, 191),
('.LBB0_15', 192, 205), ('.LBB0_16', 207, 208), ('.LBB0_17', 210, 222), ('.LBB0_15', 191, 205), ('.LBB0_16', 206, 208), ('.LBB0_17', 209, 222),
('.LBB0_18', 223, 228), ('.LBB0_19', 230, 261), ('.LBB0_20', 262, 269), ('.LBB0_18', 222, 228), ('.LBB0_19', 229, 261), ('.LBB0_20', 261, 269),
('.LBB0_22', 274, 280), ('.LBB0_24', 285, 286), ('.LBB0_26', 292, 293), ('.LBB0_22', 273, 280), ('.LBB0_24', 284, 286), ('.LBB0_26', 291, 293),
('.LBB0_28', 300, 307), ('.LBB0_29', 308, 444), ('.LBB0_31', 450, 459), ('.LBB0_28', 299, 307), ('.LBB0_29', 307, 444), ('.LBB0_31', 449, 459),
('.LBB0_32', 460, 480), ('.LBB0_33', 482, 484), ('.LBB0_34', 486, 494), ('.LBB0_32', 459, 480), ('.LBB0_33', 481, 484), ('.LBB0_34', 485, 494),
('.LBB0_35', 495, 504), ('.LBB0_36', 506, 508), ('.LBB0_37', 510, 518), ('.LBB0_35', 494, 504), ('.LBB0_36', 505, 508), ('.LBB0_37', 509, 518),
('.LBB0_38', 520, 568), ('main', 576, 590)]) ('.LBB0_38', 519, 568), ('main', 575, 590)])
def test_find_basic_loop_body(self): def test_find_basic_loop_body(self):
self.assertEqual( self.assertEqual(
[(k, v[0]['line_number'], v[-1]['line_number']) [(k, v[0]['line_number'], v[-1]['line_number'])
for k, v in find_basic_loop_bodies(self.parsed_x86).items()], for k, v in find_basic_loop_bodies(self.parsed_x86).items()],
[('.L4', 67, 74), ('.L10', 147, 154), ('.L28', 291, 300)]) [('.L4', 66, 74), ('.L10', 146, 154), ('.L28', 290, 300)])
self.assertEqual( self.assertEqual(
[(k, v[0]['line_number'], v[-1]['line_number']) [(k, v[0]['line_number'], v[-1]['line_number'])
for k, v in find_basic_loop_bodies(self.parsed_AArch).items()], for k, v in find_basic_loop_bodies(self.parsed_AArch).items()],
[('.LBB0_12', 135, 173), ('.LBB0_15', 192, 205), ('.LBB0_18', 223, 228), [('.LBB0_12', 134, 173), ('.LBB0_15', 191, 205), ('.LBB0_18', 222, 228),
('.LBB0_29', 308, 444), ('.LBB0_32', 460, 480), ('.LBB0_35', 495, 504)]) ('.LBB0_29', 307, 444), ('.LBB0_32', 459, 480), ('.LBB0_35', 494, 504)])
################## ##################
# Helper functions # Helper functions