From 5ca37a2a3f0cbd824046c3929a561ed0d7cc539f Mon Sep 17 00:00:00 2001 From: stefandesouza Date: Sun, 20 Aug 2023 21:01:44 +0200 Subject: [PATCH] Added 2 operand types and made changes for attribute usage --- osaca/parser/attr_dict.py | 0 osaca/parser/base_parser.py | 0 osaca/parser/identifier.py | 31 ++++++ osaca/parser/instruction_form.py | 35 ++++++ osaca/parser/parser_AArch64.py | 61 +++++------ osaca/parser/parser_x86att.py | 3 + osaca/parser/register.py | 87 +++++++++++++++ osaca/semantics/arch_semantics.py | 134 +++++++++++------------ osaca/semantics/hw_model.py | 16 +-- osaca/semantics/isa_semantics.py | 14 ++- osaca/semantics/kernel_dg.py | 29 +++-- osaca/semantics/marker_utils.py | 27 ++--- tests/test_base_parser.py | 4 +- tests/test_files/kernel_aarch64.s.copy.s | 26 +++++ tests/test_parser_AArch64.py | 4 +- tests/test_parser_x86att.py | 8 +- 16 files changed, 326 insertions(+), 153 deletions(-) mode change 100755 => 100644 osaca/parser/attr_dict.py mode change 100755 => 100644 osaca/parser/base_parser.py create mode 100644 osaca/parser/identifier.py mode change 100755 => 100644 osaca/parser/parser_AArch64.py mode change 100755 => 100644 osaca/parser/parser_x86att.py create mode 100644 osaca/parser/register.py mode change 100755 => 100644 osaca/semantics/arch_semantics.py mode change 100755 => 100644 osaca/semantics/hw_model.py mode change 100755 => 100644 osaca/semantics/isa_semantics.py mode change 100755 => 100644 osaca/semantics/kernel_dg.py mode change 100755 => 100644 osaca/semantics/marker_utils.py create mode 100644 tests/test_files/kernel_aarch64.s.copy.s diff --git a/osaca/parser/attr_dict.py b/osaca/parser/attr_dict.py old mode 100755 new mode 100644 diff --git a/osaca/parser/base_parser.py b/osaca/parser/base_parser.py old mode 100755 new mode 100644 diff --git a/osaca/parser/identifier.py b/osaca/parser/identifier.py new file mode 100644 index 0000000..dab736a --- /dev/null +++ b/osaca/parser/identifier.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 + +from osaca.parser.operand import Operand + +class IdentifierOperand(Operand): + def __init__(self, name, OFFSET = None, RELOCATION = None): + super().__init__(name) + self._OFFSET = OFFSET + self._RELOCATION = RELOCATION + + @property + def offset(self): + return self._OFFSET + + @offset.setter + def offset(self, offset): + self._OFFSET = offset + + @property + def relocation(self): + return self._RELOCATION + + @relocation.setter + def relocation(self, relocation): + self._RELOCATION = relocation + + def __str__(self): + return f"IdentifierOperand({self.name}, offset={self.offset}, relocation={self.relocation})" + + def __repr__(self): + return f"IdentifierOperand(name={self.name}, offset={self.offset}, relocation={self.relocation})" \ No newline at end of file diff --git a/osaca/parser/instruction_form.py b/osaca/parser/instruction_form.py index a1db8db..46c6abc 100644 --- a/osaca/parser/instruction_form.py +++ b/osaca/parser/instruction_form.py @@ -28,7 +28,18 @@ class InstructionForm: self._LABEL_ID = LABEL_ID self._LINE = LINE self._LINE_NUMBER = LINE_NUMBER + self._SEMANTIC_OPERANDS = SEMANTIC_OPERANDS + self._UOPS = None + #self.semantic_operands = {"source": [], "destination": [], "src_dst": []} + self._LATENCY = None + self._THROUGHPUT = None + self._LATENCY_CP = [] + self._LATENCY_LCD = [] + self._LATENCY_WO_LOAD = None + self._PORT_PRESSURE = [] + self._PORT_UOPS = [] + self._FLAGS = [] @property def semantic_operands(self): @@ -62,6 +73,18 @@ class InstructionForm: def operands(self): return self._OPERANDS_ID + @property + def port_pressure(self): + return self._PORT_PRESSURE + + @property + def port_uops(self): + return self._PORT_UOPS + + @property + def flags(self): + return self._FLAGS + @semantic_operands.setter def semantic_operands(self, semantic_operands): self._SEMANTIC_OPERANDS = semantic_operands @@ -93,7 +116,19 @@ class InstructionForm: @comment.setter def comment(self, comment): self._COMMENT_ID =comment + + @port_pressure.setter + def port_pressure(self, port_pressure): + self._PORT_PRESSURE = port_pressure + @port_uops.setter + def port_uops(self, port_uops): + self._PORT_UOPS = port_uops + + @flags.setter + def flags(self, flags): + self._FLAGS = flags + def __repr__(self): return f"InstructionForm(INSTRUCTION_ID={self._INSTRUCTION_ID}, OPERANDS_ID={self._OPERANDS_ID}, DIRECTIVE_ID={self._DIRECTIVE_ID}, COMMENT_ID={self._COMMENT_ID}, LABEL_ID={self._LABEL_ID}, LINE={self._LINE}, LINE_NUMBER={self._LINE_NUMBER}, SEMANTIC_OPERANDS={self._SEMANTIC_OPERANDS})" diff --git a/osaca/parser/parser_AArch64.py b/osaca/parser/parser_AArch64.py old mode 100755 new mode 100644 index 3504425..1e1a74f --- a/osaca/parser/parser_AArch64.py +++ b/osaca/parser/parser_AArch64.py @@ -8,6 +8,8 @@ from osaca.parser.operand import Operand from osaca.parser.directive import DirectiveOperand from osaca.parser.memory import MemoryOperand from osaca.parser.label import LabelOperand +from osaca.parser.register import RegisterOperand +from osaca.parser.identifier import IdentifierOperand from osaca.parser.immediate import ImmediateOperand class ParserAArch64(BaseParser): @@ -288,11 +290,10 @@ class ParserAArch64(BaseParser): if result is None: try: result = self.process_operand(self.label.parseString(line, parseAll=True).asDict()) - result = AttrDict.convert_dict(result) - instruction_form.label = result[self.LABEL_ID].name - if self.COMMENT_ID in result[self.LABEL_ID]: + instruction_form.label = result.name + if result.comment != None: instruction_form.comment= " ".join( - result[self.LABEL_ID][self.COMMENT_ID] + result.comment ) except pp.ParseException: pass @@ -325,9 +326,9 @@ class ParserAArch64(BaseParser): raise ValueError( "Unable to parse {!r} on line {}".format(line, line_number) ) from e - instruction_form.instruction = result[self.INSTRUCTION_ID] - instruction_form.operands = result[self.OPERANDS_ID] - instruction_form.comment = result[self.COMMENT_ID] + instruction_form.instruction = result.instruction + instruction_form.operands = result.operands + instruction_form.comment = result.comment return instruction_form @@ -339,7 +340,6 @@ class ParserAArch64(BaseParser): :returns: `dict` -- parsed instruction form """ result = self.instruction_parser.parseString(instruction, parseAll=True).asDict() - result = AttrDict.convert_dict(result) operands = [] # Add operands to list # Check first operand @@ -362,15 +362,12 @@ class ParserAArch64(BaseParser): if "operand5" in result: operand = self.process_operand(result["operand5"]) operands.extend(operand) if isinstance(operand, list) else operands.append(operand) - - return_dict = AttrDict( - { - self.INSTRUCTION_ID: result.mnemonic, - self.OPERANDS_ID: operands, - self.COMMENT_ID: " ".join(result[self.COMMENT_ID]) + return_dict = InstructionForm( + INSTRUCTION_ID = result['mnemonic'], + OPERANDS_ID = operands, + COMMENT_ID = " ".join(result[self.COMMENT_ID]) if self.COMMENT_ID in result else None, - } ) return return_dict @@ -416,30 +413,31 @@ class ParserAArch64(BaseParser): if "shift" in memory_address["index"]: if memory_address["index"]["shift_op"].lower() in valid_shift_ops: scale = 2 ** int(memory_address["index"]["shift"][0]["value"]) - new_dict = AttrDict({"offset": offset, "base": base, "index": index, "scale": scale}) + new_dict = MemoryOperand(OFFSET_ID = offset, BASE_ID = base, INDEX_ID = index, SCALE_ID = scale) if "pre_indexed" in memory_address: - new_dict["pre_indexed"] = True + new_dict.pre_indexed = True if "post_indexed" in memory_address: if "value" in memory_address["post_indexed"]: - new_dict["post_indexed"] = { + new_dict.post_indexed = { "value": int(memory_address["post_indexed"]["value"], 0) } else: - new_dict["post_indexed"] = memory_address["post_indexed"] - return AttrDict({self.MEMORY_ID: new_dict}) + new_dict.post_indexed = memory_address["post_indexed"] + return new_dict def process_sp_register(self, register): """Post-process stack pointer register""" reg = register - reg["prefix"] = "x" - return AttrDict({self.REGISTER_ID: reg}) + new_reg = RegisterOperand(PREFIX_ID = "x") + #reg["prefix"] = "x" + return new_reg def resolve_range_list(self, operand): """ Resolve range or list register operand to list of registers. Returns None if neither list nor range """ - if "register" in operand: + if "register" in operand.name: if "list" in operand.register: index = operand.register.get("index") range_list = [] @@ -447,7 +445,7 @@ class ParserAArch64(BaseParser): reg = deepcopy(reg) if index is not None: reg["index"] = int(index, 0) - range_list.append(AttrDict({self.REGISTER_ID: reg})) + range_list.append(reg) return range_list elif "range" in operand.register: base_register = operand.register.range[0] @@ -460,7 +458,7 @@ class ParserAArch64(BaseParser): if index is not None: reg["index"] = int(index, 0) reg["name"] = str(name) - range_list.append(AttrDict({self.REGISTER_ID: reg})) + range_list.append(reg) return range_list # neither register list nor range, return unmodified return operand @@ -513,19 +511,14 @@ class ParserAArch64(BaseParser): return AttrDict({self.IMMEDIATE_ID: immediate}) else: # change 'mantissa' key to 'value' - return AttrDict( - { - self.IMMEDIATE_ID: AttrDict( - {"value": immediate[dict_name]["mantissa"], "type": dict_name} - ) - } - ) + return ImmediateOperand(VALUE_ID = immediate[dict_name]["mantissa"], TYPE_ID = dict_name) def process_label(self, label): """Post-process label asm line""" # remove duplicated 'name' level due to identifier - label["name"] = label["name"]["name"] - return AttrDict({self.LABEL_ID: label}) + #label["name"] = label["name"]["name"] + new_label = LabelOperand(NAME_ID = label["name"]["name"]) + return new_label def process_identifier(self, identifier): """Post-process identifier operand""" diff --git a/osaca/parser/parser_x86att.py b/osaca/parser/parser_x86att.py old mode 100755 new mode 100644 index f6d3d80..802e795 --- a/osaca/parser/parser_x86att.py +++ b/osaca/parser/parser_x86att.py @@ -11,6 +11,9 @@ from osaca.parser.operand import Operand from osaca.parser.directive import DirectiveOperand from osaca.parser.memory import MemoryOperand from osaca.parser.label import LabelOperand +from osaca.parser.register import RegisterOperand +from osaca.parser.identifier import IdentifierOperand +from osaca.parser.immediate import ImmediateOperand class ParserX86ATT(BaseParser): _instance = None diff --git a/osaca/parser/register.py b/osaca/parser/register.py new file mode 100644 index 0000000..d993bd5 --- /dev/null +++ b/osaca/parser/register.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 + +from osaca.parser.operand import Operand + +class RegisterOperand(Operand): + def __init__(self, NAME_ID = None, WIDTH_ID = None, PREFIX_ID = None, REG_ID = None + , REGTYPE_ID = None, LANES = None, SHAPE = None, INDEX = False + , MASK = False, ZEROING = False): + super().__init__(NAME_ID) + self._WIDTH_ID = WIDTH_ID + self._PREFIX_ID = PREFIX_ID + self._REG_ID = REG_ID + self._LANES = LANES + self._SHAPE = SHAPE + self._INDEX = INDEX + self._MASK = MASK + self._ZEROING = ZEROING + + @property + def width(self): + return self._WIDTH_ID + + @width.setter + def width(self, width): + self._WIDTH_ID = width + + @property + def prefix(self): + return self._PREFIX_ID + + @prefix.setter + def prefix(self, prefix): + self._PREFIX = prefix + + @property + def reg_id(self): + return self._REG_ID + + @reg_id.setter + def reg_id(self, reg_id): + self._REG_ID = reg_id + + @property + def lanes(self): + return self._LANES + + @lanes.setter + def lanes(self, lanes): + self._LANES = lanes + + @property + def shape(self): + return self._SHAPE + + @shape.setter + def shape(self, shape): + self._SHAPE = shape + + @property + def index(self): + return self._INDEX + + @index.setter + def index(self, index): + self._INDEX = index + + @property + def mask(self): + return self._MASK + + @mask.setter + def mask(self, mask): + self._MASK = mask + + @property + def zeroing(self): + return self._ZEROING + + @zeroing.setter + def zeroing(self, zeroing): + self._ZEROING = zeroing + + def __str__(self): + return f"MemoryOperand({self.width_id}, {self.prefix_id}, {self.reg_id}, {self.lanes}, {self.shape}, {self.index}, {self.mask}, {self.zeroing})" + + def __repr__(self): + return f"MemoryOperand(width_id={self.width_id}, prefix_id={self.prefix_id}, reg_id={self.reg_id}, lanes={self.lanes}, shape={self.shape}, index={self.index}, mask={self.mask}, zeroing={self.zeroing})" \ No newline at end of file diff --git a/osaca/semantics/arch_semantics.py b/osaca/semantics/arch_semantics.py old mode 100755 new mode 100644 index 4be77ad..1aacf36 --- a/osaca/semantics/arch_semantics.py +++ b/osaca/semantics/arch_semantics.py @@ -46,10 +46,10 @@ class ArchSemantics(ISASemantics): for idx, instruction_form in enumerate(kernel[start:], start): multiple_assignments = False # if iform has multiple possible port assignments, check all in a DFS manner and take the best - if isinstance(instruction_form["port_uops"], dict): + if isinstance(instruction_form.port_uops, dict): best_kernel = None best_kernel_tp = sys.maxsize - for port_util_alt in list(instruction_form["port_uops"].values())[1:]: + for port_util_alt in list(instruction_form.port_uops.values())[1:]: k_tmp = deepcopy(kernel) k_tmp[idx]["port_uops"] = deepcopy(port_util_alt) k_tmp[idx]["port_pressure"] = self._machine_model.average_port_pressure( @@ -62,15 +62,15 @@ class ArchSemantics(ISASemantics): best_kernel_tp = max(self.get_throughput_sum(best_kernel)) # check the first option in the main branch and compare against the best option later multiple_assignments = True - kernel[idx]["port_uops"] = list(instruction_form["port_uops"].values())[0] - for uop in instruction_form["port_uops"]: + kernel[idx]["port_uops"] = list(instruction_form.port_uops.values())[0] + for uop in instruction_form.port_uops: cycles = uop[0] ports = list(uop[1]) indices = [port_list.index(p) for p in ports] # check if port sum of used ports for uop are unbalanced port_sums = self._to_list(itemgetter(*indices)(self.get_throughput_sum(kernel))) instr_ports = self._to_list( - itemgetter(*indices)(instruction_form["port_pressure"]) + itemgetter(*indices)(instruction_form.port_pressure) ) if len(set(port_sums)) > 1: # balance ports @@ -87,7 +87,7 @@ class ArchSemantics(ISASemantics): differences[max_port_idx] -= INC differences[min_port_idx] += INC # instr_ports = [round(p, 2) for p in instr_ports] - self._itemsetter(*indices)(instruction_form["port_pressure"], *instr_ports) + self._itemsetter(*indices)(instruction_form.port_pressure, *instr_ports) # check if min port is zero if round(min(instr_ports), 2) <= 0: # if port_pressure is not exactly 0.00, add the residual to @@ -100,21 +100,21 @@ class ArchSemantics(ISASemantics): # delete it del differences[instr_ports.index(min(instr_ports))] self._itemsetter(*indices)( - instruction_form["port_pressure"], *instr_ports + instruction_form.port_pressure, *instr_ports ) zero_index = [ p for p in indices - if round(instruction_form["port_pressure"][p], 2) == 0 - or instruction_form["port_pressure"][p] < 0.00 + if round(instruction_form.port_pressure[p], 2) == 0 + or instruction_form.port_pressure[p] < 0.00 ][0] - instruction_form["port_pressure"][zero_index] = 0.0 + instruction_form.port_pressure[zero_index] = 0.0 # Remove from further balancing indices = [ - p for p in indices if instruction_form["port_pressure"][p] > 0 + p for p in indices if instruction_form.port_pressure[p] > 0 ] instr_ports = self._to_list( - itemgetter(*indices)(instruction_form["port_pressure"]) + itemgetter(*indices)(instruction_form.port_pressure) ) # never remove more than the fixed utilization per uop and port, i.e., # cycles/len(ports) @@ -124,7 +124,7 @@ class ArchSemantics(ISASemantics): # pressure is not 0 del indices[differences.index(min(differences))] instr_ports = self._to_list( - itemgetter(*indices)(instruction_form["port_pressure"]) + itemgetter(*indices)(instruction_form.port_pressure) ) del differences[differences.index(min(differences))] port_sums = self._to_list( @@ -139,14 +139,14 @@ class ArchSemantics(ISASemantics): def set_hidden_loads(self, kernel): """Hide loads behind stores if architecture supports hidden loads (depricated)""" - loads = [instr for instr in kernel if INSTR_FLAGS.HAS_LD in instr["flags"]] - stores = [instr for instr in kernel if INSTR_FLAGS.HAS_ST in instr["flags"]] + loads = [instr for instr in kernel if INSTR_FLAGS.HAS_LD in instr.flags] + stores = [instr for instr in kernel if INSTR_FLAGS.HAS_ST in instr.flags] # Filter instructions including load and store - load_ids = [instr["line_number"] for instr in loads] - store_ids = [instr["line_number"] for instr in stores] + load_ids = [instr.line_number for instr in loads] + store_ids = [instr.line_number for instr in stores] shared_ldst = list(set(load_ids).intersection(set(store_ids))) - loads = [instr for instr in loads if instr["line_number"] not in shared_ldst] - stores = [instr for instr in stores if instr["line_number"] not in shared_ldst] + loads = [instr for instr in loads if instr.line_number not in shared_ldst] + stores = [instr for instr in stores if instr.line_number not in shared_ldst] if len(stores) == 0 or len(loads) == 0: # nothing to do @@ -182,35 +182,35 @@ class ArchSemantics(ISASemantics): """Assign throughput and latency to an instruction form.""" flags = [] port_number = len(self._machine_model["ports"]) - if instruction_form["instruction"] is None: + if instruction_form.instruction is None: # No instruction (label, comment, ...) --> ignore throughput = 0.0 latency = 0.0 latency_wo_load = latency - instruction_form["port_pressure"] = [0.0 for i in range(port_number)] - instruction_form["port_uops"] = [] + instruction_form.port_pressure = [0.0 for i in range(port_number)] + instruction_form.port_uops = [] else: instruction_data = self._machine_model.get_instruction( - instruction_form["instruction"], instruction_form["operands"] + instruction_form.instruction, instruction_form.operands ) if ( not instruction_data and self._isa == "x86" - and instruction_form["instruction"][-1] in self.GAS_SUFFIXES + and instruction_form.instruction[-1] in self.GAS_SUFFIXES ): # check for instruction without GAS suffix instruction_data = self._machine_model.get_instruction( - instruction_form["instruction"][:-1], instruction_form["operands"] + instruction_form.instruction[:-1], instruction_form.operands ) if ( instruction_data is None and self._isa == "aarch64" - and "." in instruction_form["instruction"] + and "." in instruction_form.instruction ): # Check for instruction without shape/cc suffix - suffix_start = instruction_form["instruction"].index(".") + suffix_start = instruction_form.instruction.index(".") instruction_data = self._machine_model.get_instruction( - instruction_form["instruction"][:suffix_start], instruction_form["operands"] + instruction_form.instruction[:suffix_start], instruction_form.operands ) if instruction_data: # instruction form in DB @@ -227,33 +227,33 @@ class ArchSemantics(ISASemantics): assign_unknown = True # check for equivalent register-operands DB entry if LD if ( - INSTR_FLAGS.HAS_LD in instruction_form["flags"] - or INSTR_FLAGS.HAS_ST in instruction_form["flags"] + INSTR_FLAGS.HAS_LD in instruction_form.flags + or INSTR_FLAGS.HAS_ST in instruction_form.flags ): # dynamically combine LD/ST and reg form of instruction form # substitute mem and look for reg-only variant - operands = self.substitute_mem_address(instruction_form["operands"]) + operands = self.substitute_mem_address(instruction_form.operands) instruction_data_reg = self._machine_model.get_instruction( - instruction_form["instruction"], operands + instruction_form.instruction, operands ) if ( not instruction_data_reg and self._isa == "x86" - and instruction_form["instruction"][-1] in self.GAS_SUFFIXES + and instruction_form.instruction[-1] in self.GAS_SUFFIXES ): # check for instruction without GAS suffix instruction_data_reg = self._machine_model.get_instruction( - instruction_form["instruction"][:-1], operands + instruction_form.instruction[:-1], operands ) if ( instruction_data_reg is None and self._isa == "aarch64" - and "." in instruction_form["instruction"] + and "." in instruction_form.instruction ): # Check for instruction without shape/cc suffix - suffix_start = instruction_form["instruction"].index(".") + suffix_start = instruction_form.instruction.index(".") instruction_data_reg = self._machine_model.get_instruction( - instruction_form["instruction"][:suffix_start], operands + instruction_form.instruction[:suffix_start], operands ) if instruction_data_reg: assign_unknown = False @@ -265,13 +265,13 @@ class ArchSemantics(ISASemantics): dummy_reg = {"class": "register", "name": reg_type} data_port_pressure = [0.0 for _ in range(port_number)] data_port_uops = [] - if INSTR_FLAGS.HAS_LD in instruction_form["flags"]: + if INSTR_FLAGS.HAS_LD in instruction_form.flags: # LOAD performance data load_perf_data = self._machine_model.get_load_throughput( [ x["memory"] - for x in instruction_form["semantic_operands"]["source"] - + instruction_form["semantic_operands"]["src_dst"] + for x in instruction_form.semantic_operands["source"] + + instruction_form.semantic_operands["src_dst"] if "memory" in x ][0] ) @@ -296,11 +296,11 @@ class ArchSemantics(ISASemantics): reg_type ] data_port_pressure = [pp * multiplier for pp in data_port_pressure] - if INSTR_FLAGS.HAS_ST in instruction_form["flags"]: + if INSTR_FLAGS.HAS_ST in instruction_form.flags: # STORE performance data destinations = ( - instruction_form["semantic_operands"]["destination"] - + instruction_form["semantic_operands"]["src_dst"] + instruction_form.semantic_operands["destination"] + + instruction_form.semantic_operands["src_dst"] ) store_perf_data = self._machine_model.get_store_throughput( [x["memory"] for x in destinations if "memory" in x][0], dummy_reg @@ -314,18 +314,18 @@ class ArchSemantics(ISASemantics): if ( self._isa == "aarch64" and "memory" - not in instruction_form["semantic_operands"]["destination"] + not in instruction_form.semantic_operands["destination"] and all( [ "post_indexed" in op["memory"] or "pre_indexed" in op["memory"] - for op in instruction_form["semantic_operands"]["src_dst"] + for op in instruction_form.semantic_operands["src_dst"] if "memory" in op ] ) ): st_data_port_uops = [] - instruction_form["flags"].remove(INSTR_FLAGS.HAS_ST) + instruction_form.flags.remove(INSTR_FLAGS.HAS_ST) # sum up all data ports in case for LOAD and STORE st_data_port_pressure = self._machine_model.average_port_pressure( @@ -349,12 +349,12 @@ class ArchSemantics(ISASemantics): # Add LD and ST latency latency += ( self._machine_model.get_load_latency(reg_type) - if INSTR_FLAGS.HAS_LD in instruction_form["flags"] + if INSTR_FLAGS.HAS_LD in instruction_form.flags else 0 ) latency += ( self._machine_model.get_store_latency(reg_type) - if INSTR_FLAGS.HAS_ST in instruction_form["flags"] + if INSTR_FLAGS.HAS_ST in instruction_form.flags else 0 ) latency_wo_load = instruction_data_reg["latency"] @@ -367,13 +367,13 @@ class ArchSemantics(ISASemantics): # [ # 'post_indexed' in op['memory'] or # 'pre_indexed' in op['memory'] - # for op in instruction_form['operands'] + # for op in instruction_form.operands # if 'memory' in op # ] # ) # ): # latency_wo_load = 1.0 - instruction_form["port_pressure"] = [ + instruction_form.port_pressure = [ sum(x) for x in zip( data_port_pressure, @@ -382,7 +382,7 @@ class ArchSemantics(ISASemantics): ), ) ] - instruction_form["port_uops"] = list( + instruction_form.port_uops = list( chain(instruction_data_reg["port_pressure"], data_port_uops) ) @@ -391,21 +391,21 @@ class ArchSemantics(ISASemantics): throughput = 0.0 latency = 0.0 latency_wo_load = latency - instruction_form["port_pressure"] = [0.0 for i in range(port_number)] - instruction_form["port_uops"] = [] + instruction_form.port_pressure = [0.0 for i in range(port_number)] + instruction_formport_uops = [] flags += [INSTR_FLAGS.TP_UNKWN, INSTR_FLAGS.LT_UNKWN] # flatten flag list flags = list(set(flags)) - if "flags" not in instruction_form: - instruction_form["flags"] = flags + if instruction_form.flags == []: + instruction_form.flags = flags else: - instruction_form["flags"] += flags - instruction_form["throughput"] = throughput - instruction_form["latency"] = latency - instruction_form["latency_wo_load"] = latency_wo_load + instruction_form.flags += flags + instruction_form.throughput = throughput + instruction_form.latency = latency + instruction_form.latency_wo_load = latency_wo_load # for later CP and loop-carried dependency analysis - instruction_form["latency_cp"] = 0 - instruction_form["latency_lcd"] = 0 + instruction_form.latency_cp = 0 + instruction_form.latency_lcd = 0 def _handle_instruction_found(self, instruction_data, port_number, instruction_form, flags): """Apply performance data to instruction if it was found in the archDB""" @@ -413,11 +413,11 @@ class ArchSemantics(ISASemantics): port_pressure = self._machine_model.average_port_pressure( instruction_data["port_pressure"] ) - instruction_form["port_uops"] = instruction_data["port_pressure"] + instruction_form.port_uops = instruction_data["port_pressure"] try: assert isinstance(port_pressure, list) assert len(port_pressure) == port_number - instruction_form["port_pressure"] = port_pressure + instruction_form.port_pressure = port_pressure if sum(port_pressure) == 0 and throughput is not None: # port pressure on all ports 0 --> not bound to a port flags.append(INSTR_FLAGS.NOT_BOUND) @@ -426,8 +426,8 @@ class ArchSemantics(ISASemantics): "Port pressure could not be imported correctly from database. " + "Please check entry for:\n {}".format(instruction_form) ) - instruction_form["port_pressure"] = [0.0 for i in range(port_number)] - instruction_form["port_uops"] = [] + instruction_form.port_pressure = [0.0 for i in range(port_number)] + instruction_form.port_uops = [] flags.append(INSTR_FLAGS.TP_UNKWN) if throughput is None: # assume 0 cy and mark as unknown @@ -440,7 +440,7 @@ class ArchSemantics(ISASemantics): latency = 0.0 latency_wo_load = latency flags.append(INSTR_FLAGS.LT_UNKWN) - if INSTR_FLAGS.HAS_LD in instruction_form["flags"]: + if INSTR_FLAGS.HAS_LD in instruction_form.flags: flags.append(INSTR_FLAGS.LD) return throughput, port_pressure, latency, latency_wo_load @@ -489,7 +489,7 @@ class ArchSemantics(ISASemantics): """Get the overall throughput sum separated by port of all instructions of a kernel.""" # ignoring all lines with throughput == 0.0, because there won't be anything to sum up # typically comment, label and non-instruction lines - port_pressures = [instr["port_pressure"] for instr in kernel if instr["throughput"] != 0.0] + port_pressures = [instr.port_pressure for instr in kernel if instr.throughput != 0.0] # Essentially summing up each columns of port_pressures, where each column is one port # and each row is one line of the kernel # round is necessary to ensure termination of ArchsSemantics.assign_optimal_throughput diff --git a/osaca/semantics/hw_model.py b/osaca/semantics/hw_model.py old mode 100755 new mode 100644 index 6b84538..aa23f21 --- a/osaca/semantics/hw_model.py +++ b/osaca/semantics/hw_model.py @@ -526,7 +526,7 @@ class MachineModel(object): def _check_operands(self, i_operand, operand): """Check if the types of operand ``i_operand`` and ``operand`` match.""" # check for wildcard - if self.WILDCARD in operand: + if self.WILDCARD in operand.name: if ( "class" in i_operand and i_operand["class"] == "register" @@ -601,24 +601,24 @@ class MachineModel(object): def _check_x86_operands(self, i_operand, operand): """Check if the types of operand ``i_operand`` and ``operand`` match.""" - if "class" in operand: + if "class" in operand.name: # compare two DB entries return self._compare_db_entries(i_operand, operand) # register - if "register" in operand: + if "register" in operand.name: if i_operand["class"] != "register": return False - return self._is_x86_reg_type(i_operand, operand["register"], consider_masking=False) + return self._is_x86_reg_type(i_operand, operand, consider_masking=False) # memory - if "memory" in operand: + if "memory" in operand.name: if i_operand["class"] != "memory": return False - return self._is_x86_mem_type(i_operand, operand["memory"]) + return self._is_x86_mem_type(i_operand, operand) # immediate - if "immediate" in operand or "value" in operand: + if "immediate" in operand.name or operand.value != None: return i_operand["class"] == "immediate" and i_operand["imd"] == "int" # identifier (e.g., labels) - if "identifier" in operand: + if "identifier" in operand.name: return i_operand["class"] == "identifier" def _compare_db_entries(self, operand_1, operand_2): diff --git a/osaca/semantics/isa_semantics.py b/osaca/semantics/isa_semantics.py old mode 100755 new mode 100644 index ae2fa6c..3eb2159 --- a/osaca/semantics/isa_semantics.py +++ b/osaca/semantics/isa_semantics.py @@ -3,6 +3,7 @@ from itertools import chain from osaca import utils from osaca.parser import AttrDict, ParserAArch64, ParserX86ATT +from osaca.parser.memory import MemoryOperand from .hw_model import MachineModel @@ -110,7 +111,8 @@ class ISASemantics(object): # Couldn't found instruction form in ISA DB assign_default = True # check for equivalent register-operands DB entry if LD/ST - if any(["memory" in op for op in operands]): + + if isinstance(operands, MemoryOperand) and operands.name == "memory": operands_reg = self.substitute_mem_address(instruction_form.operands) isa_data_reg = self._isa_model.get_instruction( instruction_form.instruction, operands_reg @@ -181,13 +183,13 @@ class ISASemantics(object): # store operand list in dict and reassign operand key/value pair instruction_form.semantic_operands = AttrDict.convert_dict(op_dict) # assign LD/ST flags - instruction_form["flags"] = ( - instruction_form["flags"] if "flags" in instruction_form else [] + instruction_form.flags = ( + instruction_form.flags if instruction_form.flags != [] else [] ) if self._has_load(instruction_form): - instruction_form["flags"] += [INSTR_FLAGS.HAS_LD] + instruction_form.flags += [INSTR_FLAGS.HAS_LD] if self._has_store(instruction_form): - instruction_form["flags"] += [INSTR_FLAGS.HAS_ST] + instruction_form.flags += [INSTR_FLAGS.HAS_ST] def get_reg_changes(self, instruction_form, only_postindexed=False): """ @@ -341,7 +343,7 @@ class ISASemantics(object): instruction_form.semantic_operands.source, instruction_form.semantic_operands.src_dst, ): - if "memory" in operand: + if operand.name == "memory": return True return False diff --git a/osaca/semantics/kernel_dg.py b/osaca/semantics/kernel_dg.py old mode 100755 new mode 100644 index a21beac..b36c4de --- a/osaca/semantics/kernel_dg.py +++ b/osaca/semantics/kernel_dg.py @@ -59,38 +59,37 @@ class KernelDG(nx.DiGraph): # 3. get LT value and set as edge weight dg = nx.DiGraph() for i, instruction_form in enumerate(kernel): - dg.add_node(instruction_form["line_number"]) - dg.nodes[instruction_form["line_number"]]["instruction_form"] = instruction_form + dg.add_node(instruction_form.line_number) + print(dg.nodes[instruction_form.line_number]) + dg.nodes[instruction_form.line_number].instruction_form = instruction_form # add load as separate node if existent if ( - INSTR_FLAGS.HAS_LD in instruction_form["flags"] - and INSTR_FLAGS.LD not in instruction_form["flags"] + INSTR_FLAGS.HAS_LD in instruction_form.flags + and INSTR_FLAGS.LD not in instruction_form.flags ): # add new node - dg.add_node(instruction_form["line_number"] + 0.1) - dg.nodes[instruction_form["line_number"] + 0.1][ - "instruction_form" - ] = instruction_form + dg.add_node(instruction_form.line_number + 0.1) + dg.nodes[instruction_form.line_number + 0.1].instruction_form = instruction_form # and set LD latency as edge weight dg.add_edge( - instruction_form["line_number"] + 0.1, - instruction_form["line_number"], - latency=instruction_form["latency"] - instruction_form["latency_wo_load"], + instruction_form.line_number + 0.1, + instruction_form.line_number, + latency=instruction_form.latency - instruction_form.latency_wo_load, ) for dep, dep_flags in self.find_depending( instruction_form, kernel[i + 1 :], flag_dependencies ): edge_weight = ( - instruction_form["latency"] - if "mem_dep" in dep_flags or "latency_wo_load" not in instruction_form - else instruction_form["latency_wo_load"] + instruction_form.latency + if "mem_dep" in dep_flags or instruction_form.latency_wo_load == None + else instruction_form.latency_wo_load ) if "storeload_dep" in dep_flags and self.model is not None: edge_weight += self.model.get("store_to_load_forward_latency", 0) if "p_indexed" in dep_flags and self.model is not None: edge_weight = self.model.get("p_index_latency", 1) dg.add_edge( - instruction_form["line_number"], + instruction_form.line_number, dep["line_number"], latency=edge_weight, ) diff --git a/osaca/semantics/marker_utils.py b/osaca/semantics/marker_utils.py old mode 100755 new mode 100644 index 62a28b0..fdb7e1e --- a/osaca/semantics/marker_utils.py +++ b/osaca/semantics/marker_utils.py @@ -146,11 +146,12 @@ def find_marked_section( source = line.operands[0 if not reverse else 1] destination = line.operands[1 if not reverse else 0] # instruction pair matches, check for operands + print(source) if ( "immediate" in source and parser.normalize_imd(source.immediate) == mov_vals[0] and "register" in destination - and parser.get_full_reg_name(destination.register) == mov_reg + and parser.get_full_reg_name(destination['register']) == mov_reg ): # operands of first instruction match start, check for second one match, line_count = match_bytes(lines, i + 1, nop_bytes) @@ -161,7 +162,7 @@ def find_marked_section( "immediate" in source and parser.normalize_imd(source.immediate) == mov_vals[1] and "register" in destination - and parser.get_full_reg_name(destination.register) == mov_reg + and parser.get_full_reg_name(destination['register']) == mov_reg ): # operand of first instruction match end, check for second one match, line_count = match_bytes(lines, i + 1, nop_bytes) @@ -203,14 +204,14 @@ def find_jump_labels(lines): labels = OrderedDict() current_label = None for i, line in enumerate(lines): - if line["label"] is not None: + if line.label is not None: # When a new label is found, add to blocks dict - labels[line["label"]] = (i,) + labels[line.label] = (i,) # End previous block at previous line if current_label is not None: labels[current_label] = (labels[current_label][0], i) # Update current block name - current_label = line["label"] + current_label = line.label elif current_label is None: # If no block has been started, skip end detection continue @@ -222,9 +223,9 @@ def find_jump_labels(lines): for label in list(labels): if all( [ - line["instruction"].startswith(".") + line.instruction.startswith(".") for line in lines[labels[label][0] : labels[label][1]] - if line["instruction"] is not None + if line.instruction is not None ] ): del labels[label] @@ -251,11 +252,11 @@ def find_basic_blocks(lines): terminate = False blocks[label].append(line) # Find end of block by searching for references to valid jump labels - if line["instruction"] and line["operands"]: - for operand in [o for o in line["operands"] if "identifier" in o]: + if line.instruction and line.operands: + for operand in [o for o in line.operands if "identifier" in o]: if operand["identifier"]["name"] in valid_jump_labels: terminate = True - elif line["label"] is not None: + elif line.label is not None: terminate = True if terminate: break @@ -280,13 +281,13 @@ def find_basic_loop_bodies(lines): terminate = False current_block.append(line) # Find end of block by searching for references to valid jump labels - if line["instruction"] and line["operands"]: + if line.instruction and line.operands: # Ignore `b.none` instructions (relevant von ARM SVE code) # This branch instruction is often present _within_ inner loop blocks, but usually # do not terminate - if line["instruction"] == "b.none": + if line.instruction == "b.none": continue - for operand in [o for o in line["operands"] if "identifier" in o]: + for operand in [o for o in line.operands if "identifier" in o]: if operand["identifier"]["name"] in valid_jump_labels: if operand["identifier"]["name"] == label: loop_bodies[label] = current_block diff --git a/tests/test_base_parser.py b/tests/test_base_parser.py index 77f92d0..5da2108 100755 --- a/tests/test_base_parser.py +++ b/tests/test_base_parser.py @@ -44,8 +44,8 @@ class TestBaseParser(unittest.TestCase): self.parser.parse_instruction(instr1) def test_register_funcs(self): - reg_a1 = AttrDict({"name": "rax"}) - reg_a2 = AttrDict({"name": "eax"}) + reg_a1 = {"name": "rax"} + reg_a2 = {"name": "eax"} register_string = "v1.2d" with self.assertRaises(NotImplementedError): self.parser.is_reg_dependend_of(reg_a1, reg_a2) diff --git a/tests/test_files/kernel_aarch64.s.copy.s b/tests/test_files/kernel_aarch64.s.copy.s new file mode 100644 index 0000000..22d2bc7 --- /dev/null +++ b/tests/test_files/kernel_aarch64.s.copy.s @@ -0,0 +1,26 @@ +// OSACA-BEGIN +.LBB0_32: + ldp q4, q5, [x9, #-32] + ldp q6, q7, [x9], #64 + ldp q16, q17, [x11, #-32]! + ldp q18, q19, [x11], #64 + fmul v4.2d, v4.2d, v16.2d + fmul v5.2d, v5.2d, v17.2d + fmul v6.2d, v6.2d, v18.2d + fmul v7.2d, v7.2d, v19.2d + ldp q0, q1, [x8, #-32] + ldp q2, q3, [x8], #64 + fadd v0.2d, v0.2d, v4.2d + fadd v1.2d, v1.2d, v5.2d + stp q0, q1, [x10, #-32] + fadd v2.2d, v2.2d, v6.2d + fadd v3.2d, v3.2d, v7.2d + stp q2, q3, [x10] + add x10, x10, #64 // =64 + adds x12, x12, #1 // =1 + fmov s0, -1.0e+0 + b.ne .LBB0_32 +// OSACA-END + fmov s1, #2.0e+2f + prfm pldl1keep, [x26, #2112] + add x11, x11, x11 diff --git a/tests/test_parser_AArch64.py b/tests/test_parser_AArch64.py index b302c2d..8b95c36 100755 --- a/tests/test_parser_AArch64.py +++ b/tests/test_parser_AArch64.py @@ -458,9 +458,7 @@ class TestParserAArch64(unittest.TestCase): ) def _get_label(self, parser, label): - return AttrDict.convert_dict( - parser.process_operand(parser.label.parseString(label, parseAll=True).asDict()) - ).label + return parser.process_operand(parser.label.parseString(label, parseAll=True).asDict()) def _get_directive(self, parser, directive): return AttrDict.convert_dict( diff --git a/tests/test_parser_x86att.py b/tests/test_parser_x86att.py index 7dfa76e..a02ac76 100755 --- a/tests/test_parser_x86att.py +++ b/tests/test_parser_x86att.py @@ -8,7 +8,7 @@ import unittest from pyparsing import ParseException -from osaca.parser import AttrDict, ParserX86ATT, InstructionForm +from osaca.parser import ParserX86ATT, InstructionForm class TestParserX86ATT(unittest.TestCase): @classmethod @@ -304,10 +304,8 @@ class TestParserX86ATT(unittest.TestCase): ################## def _get_comment(self, parser, comment): return " ".join( - AttrDict.convert_dict( - parser.process_operand(parser.comment.parseString(comment, parseAll=True).asDict()) - ).comment - ) + parser.process_operand(parser.comment.parseString(comment, parseAll=True).asDict())['comment'] + ) def _get_label(self, parser, label): return parser.process_operand(parser.label.parseString(label, parseAll=True).asDict())