From 2fa92bc0b0b6a386219bca1bb9ad322516b137af Mon Sep 17 00:00:00 2001 From: JanLJL Date: Tue, 30 Apr 2019 09:59:30 +0200 Subject: [PATCH 001/112] prepared repo for version 3 --- osaca/create_testcase.py | 41 - osaca/eu_sched.py | 442 --------- osaca/get_instr.py | 240 ----- osaca/osaca.py | 841 ------------------ osaca/param.py | 142 --- osaca/testcase.py | 402 --------- tests/all_tests.py | 5 +- tests/test_osaca.py | 69 -- tests/test_osaca_iaca.out | 26 - tests/test_osaca_iaca_asm.out | 26 - .../3d-7pt.icc.skx.avx512.iaca_marked.s | 653 -------------- tests/testfiles/taxCalc-ivb-iaca | Bin 23112 -> 0 bytes tests/testfiles/taxCalc-ivb-iaca.S | 196 ---- tests/testfiles/taxCalc-ivb-iaca2.S | 201 ----- 14 files changed, 1 insertion(+), 3283 deletions(-) delete mode 100755 osaca/create_testcase.py delete mode 100755 osaca/eu_sched.py delete mode 100755 osaca/get_instr.py delete mode 100755 osaca/osaca.py delete mode 100755 osaca/param.py delete mode 100755 osaca/testcase.py delete mode 100755 tests/test_osaca.py delete mode 100644 tests/test_osaca_iaca.out delete mode 100644 tests/test_osaca_iaca_asm.out delete mode 100644 tests/testfiles/3d-7pt.icc.skx.avx512.iaca_marked.s delete mode 100755 tests/testfiles/taxCalc-ivb-iaca delete mode 100644 tests/testfiles/taxCalc-ivb-iaca.S delete mode 100644 tests/testfiles/taxCalc-ivb-iaca2.S diff --git a/osaca/create_testcase.py b/osaca/create_testcase.py deleted file mode 100755 index a7f6578..0000000 --- a/osaca/create_testcase.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env python3 - -from param import Register, MemAddr, Parameter -from testcase import Testcase - -# Choose out of various operands -reg8 = Register('al') -reg16 = Register('ax') -reg32 = Register('eax') -reg64 = Register('rax') -xmm = Register('xmm0') -ymm = Register('ymm0') -zmm = Register('zmm0') -mem0 = MemAddr('(%rax, %esi, 4)') -imd1 = Parameter('IMD') - - -# ----------------------------------------------- -# -USER INPUT------------------------------------ -# ----------------------------------------------- -# Enter your mnemonic -mnemonic = 'add' - -# Define your operands. If you don't need it, just type in None -dst = mem0 -op1 = imd1 -op2 = None - -# Define the number of instructions per loop (default: 12) -per_loop = '32' - -# ----------------------------------------------- -# ----------------------------------------------- - -# Start -operands = [x for x in [dst, op1, op2] if x is not None] -opListStr = ', '.join([str(x) for x in operands]) -print('Create Testcase for {} {}'.format(mnemonic, opListStr), end='') -tc = Testcase(mnemonic, operands, per_loop) -tc.write_testcase() -print(' --------> SUCCEEDED') diff --git a/osaca/eu_sched.py b/osaca/eu_sched.py deleted file mode 100755 index 765d7ab..0000000 --- a/osaca/eu_sched.py +++ /dev/null @@ -1,442 +0,0 @@ -#!/usr/bin/env python3 - -import sys -import os -import math -import ast -from operator import add -import pandas as pd - -from osaca.param import Register, MemAddr -#from param import Register, MemAddr - - -class Scheduler(object): - arch_dict = { - # Intel - 'NHM': 5, 'WSM': 5, # Nehalem, Westmere - 'SNB': 6, 'IVB': 6, # Sandy Bridge, Ivy Bridge - 'HSW': 8, 'BDW': 8, # Haswell, Broadwell - 'SKL': 8, 'SKX': 8, # Skylake(-X) - 'KBL': 8, 'CFL': 8, # Kaby Lake, Coffee Lake - # AMD - 'ZEN': 10, # Zen/Ryzen/EPYC - } - arch_pipeline_ports = { - 'NHM': ['0DV'], 'WSM': ['0DV'], - 'SNB': ['0DV'], 'IVB': ['0DV'], - 'HSW': ['0DV'], 'BDW': ['0DV'], - 'SKL': ['0DV'], 'SKX': ['0DV'], - 'KBL': ['0DV'], 'CFL': ['0DV'], - 'ZEN': ['3DV'],} - # content of most inner list in instrList: instr, operand(s), instr form - df = None # type: DataFrame - # for parallel ld/st in archs with 1 st/cy and >1 ld/cy, able to do 1 st and 1 ld in 1cy - ld_ports = None # type: list - # enable flag for parallel ld/st - en_par_ldst = False # type: boolean - - def __init__(self, arch, instruction_list): - arch = arch.upper() - try: - self.ports = self.arch_dict[arch] - except KeyError: - print('Architecture not supported for EU scheduling.', file=sys.stderr) - sys.exit(1) - # check for parallel ld/st in a cycle - if arch == 'ZEN': - self.en_par_ldst = True - self.ld_ports = [9, 10] - # check for DV port - self.pipeline_ports = self.arch_pipeline_ports.get(arch, []) - self.instrList = instruction_list - # curr_dir = os.path.realpath(__file__)[:-11] - osaca_dir = os.path.expanduser('~/.osaca/') - self.df = pd.read_csv(osaca_dir + 'data/' + arch.lower() + '_data.csv', quotechar='"', - converters={'ports': ast.literal_eval}) - - def new_schedule(self, machine_readable=False): - """ - Schedule Instruction Form list and calculate port bindings. - - Parameters - ---------- - machine_readable : bool - Boolean for indicating if the return value should be human readable (if False) or - machine readable (if True) - - Returns - ------- - (str, [float, ...]) or ([[float, ...], ...], [float, ...]) - A tuple containing the output of the schedule as string (if machine_readable is not - given or False) or as list of lists (if machine_readable is True) and the port bindings - as list of float. - """ - sched = self.get_head() - # Initialize ports - # Add DV port, if it is existing - occ_ports = [[0] * (self.ports + len(self.pipeline_ports)) for x in range(len(self.instrList))] - port_bndgs = [0] * (self.ports + len(self.pipeline_ports)) - # Store instruction counter for parallel ld/st - par_ldst = 0 - # Count the number of store instr if we schedule for an architecture with par ld/st - if self.en_par_ldst: - for i, instrForm in enumerate(self.instrList): - if (isinstance(instrForm[1], MemAddr) and len(instrForm) > 3 - and not instrForm[0].startswith('cmp')): - # print('({}, {}) is st --> par_ldst = {}'.format(i, instrForm[0], par_ldst + 1)) - par_ldst += 1 - # Check if there's a port occupation stored in the CSV, otherwise leave the - # occ_port list item empty - for i, instrForm in enumerate(self.instrList): - search_string = instrForm[0] + '-' + self.get_operand_suffix(instrForm) - try: - entry = self.df.loc[lambda df, sStr=search_string: df.instr == sStr] - tup = entry.ports.values[0] - if len(tup) == 1 and tup[0] == -1: - raise IndexError() - except IndexError: - # Instruction form not in CSV - if instrForm[0][:3] == 'nop': - sched += self.format_port_occupation_line(occ_ports[i], '* ' + instrForm[-1]) - else: - sched += self.format_port_occupation_line(occ_ports[i], 'X ' + instrForm[-1]) - continue - occ_ports[i] = list(tup) - # Check if it's a ld including instr - p_flg = '' - if self.en_par_ldst: - # Check for ld - # FIXME remove special load handling from here and place in machine model - if (isinstance(instrForm[-2], MemAddr) or - (len(instrForm) > 4 and isinstance(instrForm[2], MemAddr))): - if par_ldst > 0: - par_ldst -= 1 - p_flg = 'P ' - for port in self.ld_ports: - occ_ports[i][port] = 0.0 # '(' + str(occ_ports[i][port]) + ')' - # Write schedule line - if len(p_flg) > 0: - sched += self.format_port_occupation_line(occ_ports[i], p_flg + instrForm[-1]) - for port in self.ld_ports: - occ_ports[i][port] = 0 - else: - sched += self.format_port_occupation_line(occ_ports[i], instrForm[-1]) - # Add throughput to total port binding - port_bndgs = list(map(add, port_bndgs, occ_ports[i])) - if machine_readable: - list(map(self.append, occ_ports, self.instrList)) - return occ_ports, port_bndgs - return sched, port_bndgs - - def schedule(self): - """ - Schedule Instruction Form list and calculate port bindings. - - Returns - ------- - (str, [int, ...]) - A tuple containing the graphic output of the schedule as string and - the port bindings as list of ints. - """ - wTP = False - sched = self.get_head() - # Initialize ports - port_bndgs = [0] * self.ports - # Check if there's a port occupation stored in the CSV, otherwise leave the - # occ_port list item empty - for i, instrForm in enumerate(self.instrList): - try: - search_string = instrForm[0] + '-' + self.get_operand_suffix(instrForm) - entry = self.df.loc[lambda df, sStr=search_string: df.instr == sStr] - tup = entry.ports.values[0] - if len(tup) == 1 and tup[0][0] == -1: - raise IndexError() - except IndexError: - # Instruction form not in CSV - if instrForm[0][:3] == 'nop': - sched += self.format_port_occupation_line(occ_ports[i], '* ' + instrForm[-1]) - else: - sched += self.format_port_occupation_line(occ_ports[i], 'X ' + instrForm[-1]) - continue - if wTP: - # Get the occurance of each port from the occupation list - port_occurances = self.get_port_occurances(tup) - # Get 'occurance groups' - occurance_groups = self.get_occurance_groups(port_occurances) - # Calculate port dependent throughput - tp_ges = entry.TP.values[0] * len(occurance_groups[0]) - for occGroup in occurance_groups: - for port in occGroup: - occ_ports[i][port] = tp_ges / len(occGroup) - else: - variations = len(tup) - t_all = self.flatten(tup) - if entry.TP.values[0] == 0: - t_all = () - if variations == 1: - for j in tup[0]: - occ_ports[i][j] = entry.TP.values[0] - else: - for j in range(0, self.ports): - occ_ports[i][j] = t_all.count(j) / variations - # Write schedule line - sched += self.format_port_occupation_line(occ_ports[i], instrForm[-1]) - # Add throughput to total port binding - port_bndgs = list(map(add, port_bndgs, occ_ports[i])) - return sched, port_bndgs - - def flatten(self, l): - if len(l) == 0: - return l - if isinstance(l[0], type(l)): - return self.flatten(l[0]) + self.flatten(l[1:]) - return l[:1] + self.flatten(l[1:]) - - def append(self, l, e): - if(isinstance(l, list)): - l.append(e) - - def schedule_fcfs(self): - """ - Schedule Instruction Form list for a single run with latencies. - - Returns - ------- - (str, int) - A tuple containing the graphic output as string and the total throughput time as int. - """ - sched = self.get_head() - total = 0 - # Initialize ports - occ_ports = [0] * self.ports - for instrForm in self.instrList: - try: - search_string = instrForm[0] + '-' + self.get_operand_suffix(instrForm) - entry = self.df.loc[lambda df, sStr=search_string: df.instr == sStr] - tup = entry.ports.values[0] - if len(tup) == 1 and tup[0][0] == -1: - raise IndexError() - except IndexError: - # Instruction form not in CSV - sched += self.format_port_occupation_line([0] * self.ports, '* ' + instrForm[-1]) - continue - found = False - while not found: - for portOcc in tup: - # Test if chosen instruction form port occupation suits the current CPU port - # occupation - if self.test_ports_fcfs(occ_ports, portOcc): - # Current port occupation fits for chosen port occupation of instruction! - found = True - good = [entry.LT.values[0] if (j in portOcc) else 0 for j in - range(0, self.ports)] - sched += self.format_port_occupation_line(good, instrForm[-1]) - # Add new occupation - occ_ports = [occ_ports[j] + good[j] for j in range(0, self.ports)] - break - # Step - occ_ports = [j - 1 if (j > 0) else 0 for j in occ_ports] - if entry.LT.values[0] != 0: - total += 1 - total += max(occ_ports) - return sched, total - - def get_occurance_groups(self, port_occurances): - """ - Group ports in groups by the number of their occurrence and sorts groups by cardinality. - - Parameters - ---------- - port_occurances : [int, ...] - List with the length of ports containing the number of occurances - of each port - - Returns - ------- - [[int, ...], ...] - List of lists with all occurance groups sorted by cardinality - (smallest group first) - """ - groups = [[] for x in range(len(set(port_occurances))-1)] - for i, groupInd in enumerate(range(min(list(filter(lambda x: x > 0, port_occurances))), - max(port_occurances) + 1)): - for p, occurs in enumerate(port_occurances): - if groupInd == occurs: - groups[i].append(p) - # Sort groups by cardinality - groups.sort(key=len) - return groups - - def get_port_occurances(self, tups): - """ - Return the number of each port occurrence for the possible port occupations. - - Parameters - ---------- - tups : ((int, ...), ...) - Tuple of tuples of possible port occupations - - Returns - ------- - [int, ...] - List in the length of the number of ports for the current architecture, - containing the amount of occurances for each port - """ - ports = [0] * self.ports - for tup in tups: - for elem in tup: - ports[elem] += 1 - return ports - - def test_ports_fcfs(self, occ_ports, needed_ports): - """ - Test if current configuration of ports is possible and returns boolean. - - Parameters - ---------- - occ_ports : [int] - Tuple to inspect for current port occupation - needed_ports : (int) - Tuple with needed port(s) for particular instruction form - - Returns - ------- - bool - True if needed ports can get scheduled on current port occupation - False if not - """ - for port in needed_ports: - if occ_ports[port] != 0: - return False - return True - - def get_report_info(self): - """ - Create Report information including all needed annotations. - - Returns - ------- - str - String containing the report information - """ - analysis = 'Throughput Analysis Report\n' + ('-' * 26) + '\n' - annotations = ( - 'P - Load operation can be hidden behind a past or future store instruction\n' - 'X - No information for this instruction in data file\n' - '* - Instruction micro-ops not bound to a port\n\n') - return analysis + annotations - - def get_head(self): - """ - Create right heading for CPU architecture. - - Returns - ------- - str - String containing the header - """ - port_names = self.get_port_naming() - - port_line = ''.join('|{:^6}'.format(pn) for pn in port_names) + '|\n' - horiz_line = '-' * (len(port_line) - 1) + '\n' - port_anno = ' ' * ((len(port_line) - 25) // 2) + 'Ports Pressure in cycles\n' - - return port_anno + port_line + horiz_line - - def format_port_occupation_line(self, occ_ports, instr_name): - """ - Create line with port occupation for output. - - Parameters - ---------- - occ_ports : (int, ...) - Integer tuple containing needed ports - instr_name : str - Name of instruction form for output - - Returns - ------- - str - String for output containing port scheduling for instr_name - """ - line = '' - for cycles in occ_ports: - if cycles == 0: - line += '|' + ' ' * 6 - elif cycles >= 10: - line += '|{:^6.1f}'.format(cycles) - else: - line += '|{:^6.2f}'.format(cycles) - line += '| ' + instr_name + '\n' - return line - - def get_port_naming(self): - """ - Return list of port names - - :return: list of strings - """ - return sorted([str(i) for i in range(self.ports)] + self.pipeline_ports) - - def get_port_binding(self, port_bndg): - """ - Create port binding out of scheduling result. - - Parameters - ---------- - port_bndg : [int, ...] - Integer list containing port bindings - - Returns - ------- - str - String containing the port binding graphical output - """ - col_widths = self.get_column_widths(port_bndg) - header = 'Port Binding in Cycles Per Iteration:\n' - horiz_line = '-' * 10 + '-' * (sum(col_widths) + len(col_widths)) + '\n' - port_line = '| Port |' - for i, port_name in enumerate(self.get_port_naming()): - port_line += port_name.center(col_widths[i]) + '|' - port_line += '\n' - cyc_line = '| Cycles |' - for i in range(len(port_bndg)): - cyc_line += '{}|'.format(str(round(port_bndg[i], 2)).center(col_widths[i])) - cyc_line += '\n' - binding = header + horiz_line + port_line + horiz_line + cyc_line + horiz_line - return binding - - def get_column_widths(self, port_bndg): - return [max(len(str(round(x, 2))), len(name)) + 2 - for x, name in zip(port_bndg, self.get_port_naming())] - - def get_operand_suffix(self, instr_form): - """ - Create operand suffix out of list of Parameters. - - Parameters - ---------- - instr_form : [str, Parameter, ..., Parameter, str] - Instruction Form data structure - - Returns - ------- - str - Operand suffix for searching in data file - """ - op_ext = [] - for i in range(1, len(instr_form) - 1): - if isinstance(instr_form[i], Register) and instr_form[i].reg_type == 'GPR': - optmp = 'r' + str(instr_form[i].size) - elif isinstance(instr_form[i], MemAddr): - optmp = 'mem' - else: - optmp = str(instr_form[i]).lower() - op_ext.append(optmp) - operands = '_'.join(op_ext) - return operands - - -if __name__ == '__main__': - print('Nothing to do.') diff --git a/osaca/get_instr.py b/osaca/get_instr.py deleted file mode 100755 index ecf4b1d..0000000 --- a/osaca/get_instr.py +++ /dev/null @@ -1,240 +0,0 @@ -#!/usr/bin/env python3 -import os -import re -import argparse - -from osaca.testcase import Testcase -from osaca.param import Register, MemAddr, Parameter -#from testcase import Testcase -#from param import Register, MemAddr, Parameter - - -class InstrExtractor(object): - filepaths = [] - # Variables for checking lines - numSeps = 0 - sem = 0 - db = {} - sorted_db = [] - lncnt = 1 - cntChar = '' - first = True - # Constant variables - MARKER = r'//STARTLOOP' - ASM_LINE = re.compile(r'\s[0-9a-f]+[:]') - - def __init__(self, filepath): - self.filepaths = filepath - - def check_all(self): - for i in range(0, len(self.filepaths)): - self.extract_instr(self.filepaths[i]) - - def is_elffile(self, filepath): - if os.path.isfile(filepath): - with open(filepath) as f: - src = f.read() - if 'format elf64' in src: - return True - return False - - def extract_instr(self, asm_file): - # Check if parameter is in the correct file format - if not self.is_elffile(asm_file): - print('Invalid argument') - return - # Open file - f = open(asm_file, 'r') - # Analyse code line by line and check the instructions - self.lncnt = 1 - for line in f: - self.check_line(line) - self.lncnt += 1 - f.close() - - def check_line(self, line): - # Check if MARKER is in line and count the number of whitespaces if so - if self.MARKER in line: - # But first, check if high level code ist indented with whitespaces or tabs - if self.first: - self.set_counter_char(line) - self.first = False - self.numSeps = (re.split(self.MARKER, line)[0]).count(self.cntChar) - self.sem = 2 - elif self.sem > 0: - # We're in the marked code snipped - # Check if the line is ASM code and - if not - check if we're still in the loop - match = re.search(self.ASM_LINE, line) - if match: - # Further analysis of instructions - # Check if there are commetns in line - if r'//' in line: - return - self.check_instr(''.join(re.split(r'\t', line)[-1:])) - elif (re.split(r'\S', line)[0]).count(self.cntChar) <= self.numSeps: - # Not in the loop anymore - or yet - so we decrement the semaphore - self.sem = self.sem - 1 - - # Check if seperator is either tabulator or whitespace - def set_counter_char(self, line): - num_spaces = (re.split(self.MARKER, line)[0]).count(' ') - num_tabs = (re.split(self.MARKER, line)[0]).count('\t') - if num_spaces != 0 and num_tabs == 0: - self.cntChar = ' ' - elif num_spaces == 0 and num_tabs != 0: - self.cntChar = '\t' - else: - err_msg = 'Indentation of code is only supported for whitespaces and tabs.' - raise NotImplementedError(err_msg) - - def check_instr(self, instr): - # Check for strange clang padding bytes - while instr.startswith('data32'): - instr = instr[7:] - # Seperate mnemonic and operands - mnemonic = instr.split()[0] - params = ''.join(instr.split()[1:]) - # Check if line is not only a byte - empty_byte = re.compile(r'[0-9a-f]{2}') - if re.match(empty_byte, mnemonic) and len(mnemonic) == 2: - return - # Check if there's one or more operand and store all in a list - param_list = self.flatten(self.separate_params(params)) - op_list = list(param_list) - # Check operands and seperate them by IMMEDIATE (IMD), REGISTER (REG), MEMORY (MEM) or - # LABEL (LBL) - for i in range(len(param_list)): - op = param_list[i] - if len(op) <= 0: - op = Parameter('NONE') - elif op[0] == '$': - op = Parameter('IMD') - elif op[0] == '%' and '(' not in op: - j = len(op) - opmask = False - if '{' in op: - j = op.index('{') - opmask = True - op = Register(op[1:j], opmask) - elif '<' in op: - op = Parameter('LBL') - else: - op = MemAddr(op) - param_list[i] = str(op) if (type(op) is not Register) else str(op) + str(op.size) - op_list[i] = op - # Join mnemonic and operand(s) to an instruction form - if len(mnemonic) > 7: - tabs = '\t' - else: - tabs = '\t\t' - instr_form = mnemonic + tabs + (' '.join(param_list)) - # Check in data file for instruction form and increment the counter - if instr_form in self.db: - self.db[instr_form] = self.db[instr_form] + 1 - else: - self.db[instr_form] = 1 - # Create testcase for instruction form, since it is the first appearance of it - # Only create benchmark if no label (LBL) is part of the operands - do_bench = True - for par in op_list: - if str(par) == 'LBL' or str(par) == '': - do_bench = False - if do_bench: - # Create testcase with reversed param list, due to the fact its intel syntax! - tc = Testcase(mnemonic, list(reversed(op_list)), '64') - tc.write_testcase() - - def separate_params(self, params): - param_list = [params] - if ',' in params: - if ')' in params: - if params.index(')') < len(params) - 1 and params[params.index(')') + 1] == ',': - i = params.index(')') + 1 - elif params.index('(') < params.index(','): - return param_list - else: - i = params.index(',') - else: - i = params.index(',') - param_list = [params[:i], self.separate_params(params[i + 1:])] - elif '#' in params: - i = params.index('#') - param_list = [params[:i]] - return param_list - - def sort_db(self): - self.sorted_db = sorted(self.db.items(), key=lambda x: x[1], reverse=True) - - def print_sorted_db(self): - self.sort_db() - total = 0 - print('Number of\tmnemonic') - print('calls\n') - for i in range(len(self.sorted_db)): - print(str(self.sorted_db[i][1]) + '\t\t' + self.sorted_db[i][0]) - total += self.sorted_db[i][1] - print('\nCumulated number of instructions: ' + str(total)) - - def save_db(self): - file = open('.cnt_asm_ops.db', 'w') - for i in self.db.items(): - file.write(i[0] + '\t' + str(i[1]) + '\n') - file.close() - - def load_db(self): - try: - file = open('.cnt_asm_ops.db', 'r') - except FileNotFoundError: - print('no data file found in current directory') - return - for line in file: - mnemonic = line.split('\t')[0] - # Join mnemonic and operand(s) to an instruction form - if len(mnemonic) > 7: - tabs = '\t' - params = line.split('\t')[1] - num_calls = line.split('\t')[2][:-1] - else: - tabs = '\t\t' - params = line.split('\t')[2] - num_calls = line.split('\t')[3][:-1] - instr_form = mnemonic + tabs + params - self.db[instr_form] = int(num_calls) - file.close() - - def flatten(self, l): - if not l: - return l - if isinstance(l[0], list): - return self.flatten(l[0]) + self.flatten(l[1:]) - return l[:1] + self.flatten(l[1:]) - - -def main(): - # Parse args - parser = argparse.ArgumentParser(description='Returns a list of all instruction forms in the ' - 'given files sorted by their number of ' - 'occurrences.') - parser.add_argument('-V', '--version', action='version', version='%(prog)s 0.2') - parser.add_argument('filepath', nargs='+', help='path to objdump(s)') - parser.add_argument('-l', '--load', dest='load', action='store_true', - help='load data file before checking new files') - parser.add_argument('-s', '--store', dest='store', action='store_true', - help='store data file before checking new files') - - # Create object and store arguments as attribute - inp = parser.parse_args() - ie = InstrExtractor(inp.filepath) - - # Do work - if inp.load: - ie.load_db() - ie.check_all() - ie.print_sorted_db() - if inp.store: - ie.save_db() - - -# ---------main method---------- -if __name__ == '__main__': - main() diff --git a/osaca/osaca.py b/osaca/osaca.py deleted file mode 100755 index 56012d0..0000000 --- a/osaca/osaca.py +++ /dev/null @@ -1,841 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import collections -import filecmp -import sys -import os -import io -import re -import subprocess -from datetime import datetime -from pprint import pprint - -import pandas as pd -import numpy as np - -from osaca.param import Register, MemAddr, Parameter -from osaca.eu_sched import Scheduler -from osaca.testcase import Testcase - -DATA_DIR = os.path.expanduser('~') + '/.osaca/' -MODULE_DATA_DIR = os.path.join((os.path.split(__file__)[0]), 'data') - -# Matches every variation of the IACA start marker -IACA_START_MARKER = re.compile(r'\s*movl?[ \t]+\$(?:111|0x6f)[ \t]*,[ \t]*%ebx.*\n\s*' - r'(?:\.byte[ \t]+100.*((,[ \t]*103.*((,[ \t]*144)|' - r'(\n\s*\.byte[ \t]+144)))|' - r'(\n\s*\.byte[ \t]+103.*((,[ \t]*144)|' - r'(\n\s*\.byte[ \t]+144))))|(?:fs addr32 )?nop)') -# Matches every variation of the IACA end marker -IACA_END_MARKER = re.compile(r'\s*movl?[ \t]+\$(?:222|0x1f3)[ \t]*,[ \t]*%ebx.*\n\s*' - r'(?:\.byte[ \t]+100.*((,[ \t]*103.*((,[ \t]*144)|' - r'(\n\s*\.byte[ \t]+144)))|' - r'(\n\s*\.byte[ \t]+103.*((,[ \t]*144)|' - r'(\n\s*\.byte[ \t]+144))))|(?:fs addr32 )?nop)') - - -def flatten(l): - """ - Flatten a nested list of strings. - - Parameters - ---------- - l : [[...[str]]] - Nested list of strings - - Returns - ------- - [str] - List of strings - """ - if not l: - return l - if isinstance(l[0], list): - return flatten(l[0]) + flatten(l[1:]) - return l[:1] + flatten(l[1:]) - - -def get_assembly_from_binary(bin_path): - """ - Disassemble binary with llvm-objdump and transform into a canonical from. - - Replace jump and call target offsets with labels. - - :param bin_path: path to binary file to disassemble - - :return assembly string - """ - asm_lines = subprocess.run( - ['objdump', '-d', '--no-show-raw-insn', bin_path], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE).stdout.decode('utf-8').split('\n') - - asm = [] - - # Separate label, offsets and instructions - # Store offset with each label (thus iterate in reverse) - label_offsets = {} - for l in reversed(asm_lines): - m = re.match(r'^(?:(?P