diff --git a/m2isar/backends/coverage/coverage_dbg.py b/m2isar/backends/coverage/coverage_dbg.py index 0e1798ec..141b9652 100644 --- a/m2isar/backends/coverage/coverage_dbg.py +++ b/m2isar/backends/coverage/coverage_dbg.py @@ -14,12 +14,12 @@ from tqdm import tqdm -from ...metamodel import M2_METAMODEL_VERSION, M2Model, arch, patch_model +from ...metamodel import M2_METAMODEL_VERSION, M2Model, arch from ...metamodel.code_info import FunctionInfo, LineInfo from ...metamodel.utils.expr_preprocessor import (process_attributes, process_functions, process_instructions) -from . import id_transform +from .id_transform import IdTransformVisitor from .utils import IdMatcherContext logger = logging.getLogger("coverage_lcov") @@ -66,7 +66,7 @@ def main(): process_attributes(core_obj) - patch_model(id_transform) + id_transform_visitor = IdTransformVisitor() ctx = IdMatcherContext() @@ -77,12 +77,12 @@ def main(): if fn_obj.function_info is not None: ctx.id_to_obj_map[core_name][fn_obj.function_info.id] = fn_obj - fn_obj.operation.generate(ctx) + id_transform_visitor.generate(fn_obj.operation, ctx) for instr_name, instr_obj in core_obj.instructions.items(): ctx.id_to_obj_map[core_name][instr_obj.function_info.id] = instr_obj - instr_obj.operation.generate(ctx) + id_transform_visitor.generate(instr_obj.operation, ctx) id_to_obj_map = {} diff --git a/m2isar/backends/coverage/coverage_lcov.py b/m2isar/backends/coverage/coverage_lcov.py index a13d3b4a..910815e3 100644 --- a/m2isar/backends/coverage/coverage_lcov.py +++ b/m2isar/backends/coverage/coverage_lcov.py @@ -16,13 +16,13 @@ from tqdm.contrib.concurrent import process_map -from ...metamodel import M2_METAMODEL_VERSION, M2Model, patch_model +from ...metamodel import M2_METAMODEL_VERSION, M2Model from ...metamodel.code_info import (BranchInfo, CodeInfoBase, FunctionInfo, LineInfo) from ...metamodel.utils.expr_preprocessor import (process_attributes, process_functions, process_instructions) -from . import id_transform +from .id_transform import IdTransformVisitor from .utils import IdMatcherContext logger = logging.getLogger("coverage_lcov") @@ -137,7 +137,7 @@ def main(): logger.info("building model-specific coverage database") - patch_model(id_transform) + id_transform_visitor = IdTransformVisitor() ctx = IdMatcherContext() @@ -148,12 +148,12 @@ def main(): if fn_obj.function_info is not None: ctx.id_to_obj_map[core_name][fn_obj.function_info.id] = fn_obj - fn_obj.operation.generate(ctx) + id_transform_visitor.generate(fn_obj.operation, ctx) for instr_name, instr_obj in core_obj.instructions.items(): ctx.id_to_obj_map[core_name][instr_obj.function_info.id] = instr_obj - instr_obj.operation.generate(ctx) + id_transform_visitor.generate(instr_obj.operation, ctx) logger.info("initializing coverage counters") diff --git a/m2isar/backends/coverage/id_transform.py b/m2isar/backends/coverage/id_transform.py index 161cdbe3..8748f67c 100644 --- a/m2isar/backends/coverage/id_transform.py +++ b/m2isar/backends/coverage/id_transform.py @@ -6,138 +6,138 @@ # Chair of Electrical Design Automation # Technical University of Munich +"""Visitor for mapping line/function IDs to model objects for coverage backends.""" from ...metamodel import behav +from ...metamodel.utils.ExprVisitor import ExprVisitor from .utils import IdMatcherContext - - -def operation(self: behav.Operation, context: "IdMatcherContext"): - if self.line_info is not None: - context.id_to_obj_map[context.arch_name][self.line_info.id] = self - - for stmt in self.statements: - stmt.generate(context) - -def block(self: behav.Block, context: "IdMatcherContext"): - if self.line_info is not None: - context.id_to_obj_map[context.arch_name][self.line_info.id] = self - - for stmt in self.statements: - stmt.generate(context) - -def binary_operation(self: behav.BinaryOperation, context: "IdMatcherContext"): - if self.line_info is not None: - context.id_to_obj_map[context.arch_name][self.line_info.id] = self - - self.left.generate(context) - self.right.generate(context) - -def slice_operation(self: behav.SliceOperation, context: "IdMatcherContext"): - if self.line_info is not None: - context.id_to_obj_map[context.arch_name][self.line_info.id] = self - - self.expr.generate(context) - self.left.generate(context) - self.right.generate(context) - -def concat_operation(self: behav.ConcatOperation, context: "IdMatcherContext"): - if self.line_info is not None: - context.id_to_obj_map[context.arch_name][self.line_info.id] = self - - self.left.generate(context) - self.right.generate(context) - -def number_literal(self: behav.IntLiteral, context: "IdMatcherContext"): - if self.line_info is not None: - context.id_to_obj_map[context.arch_name][self.line_info.id] = self - -def int_literal(self: behav.IntLiteral, context: "IdMatcherContext"): - if self.line_info is not None: - context.id_to_obj_map[context.arch_name][self.line_info.id] = self - -def scalar_definition(self: behav.ScalarDefinition, context: "IdMatcherContext"): - if self.line_info is not None: - context.id_to_obj_map[context.arch_name][self.line_info.id] = self - -def break_(self: behav.Break, context: "IdMatcherContext"): - if self.line_info is not None: - context.id_to_obj_map[context.arch_name][self.line_info.id] = self - -def assignment(self: behav.Assignment, context: "IdMatcherContext"): - if self.line_info is not None: - context.id_to_obj_map[context.arch_name][self.line_info.id] = self - - self.target.generate(context) - self.expr.generate(context) - -def conditional(self: behav.Conditional, context: "IdMatcherContext"): - if self.line_info is not None: - context.id_to_obj_map[context.arch_name][self.line_info.id] = self - - for cond in self.conds: - cond.generate(context) - - for stmt in self.stmts: - # - #for stmt in op: - stmt.generate(context) - # - -def loop(self: behav.Loop, context: "IdMatcherContext"): - if self.line_info is not None: - context.id_to_obj_map[context.arch_name][self.line_info.id] = self - - self.cond.generate(context) - - for stmt in self.stmts: - stmt.generate(context) - -def ternary(self: behav.Ternary, context: "IdMatcherContext"): - if self.line_info is not None: - context.id_to_obj_map[context.arch_name][self.line_info.id] = self - - self.cond.generate(context) - self.then_expr.generate(context) - self.else_expr.generate(context) - -def return_(self: behav.Return, context: "IdMatcherContext"): - if self.line_info is not None: - context.id_to_obj_map[context.arch_name][self.line_info.id] = self - - if self.expr is not None: - self.expr.generate(context) - -def unary_operation(self: behav.UnaryOperation, context: "IdMatcherContext"): - if self.line_info is not None: - context.id_to_obj_map[context.arch_name][self.line_info.id] = self - - self.right.generate(context) - -def named_reference(self: behav.NamedReference, context: "IdMatcherContext"): - if self.line_info is not None: - context.id_to_obj_map[context.arch_name][self.line_info.id] = self - -def indexed_reference(self: behav.IndexedReference, context: "IdMatcherContext"): - if self.line_info is not None: - context.id_to_obj_map[context.arch_name][self.line_info.id] = self - - self.index.generate(context) - -def type_conv(self: behav.TypeConv, context: "IdMatcherContext"): - if self.line_info is not None: - context.id_to_obj_map[context.arch_name][self.line_info.id] = self - - self.expr.generate(context) - -def callable_(self: behav.Callable, context: "IdMatcherContext"): - if self.line_info is not None: - context.id_to_obj_map[context.arch_name][self.line_info.id] = self - - for arg, arg_descr in zip(self.args, self.ref_or_name.args): - arg.generate(context) - -def group(self: behav.Group, context: "IdMatcherContext"): - if self.line_info is not None: - context.id_to_obj_map[context.arch_name][self.line_info.id] = self - - self.expr.generate(context) +from functools import singledispatchmethod + + +class IdTransformVisitor(ExprVisitor): + """Visitor that builds a mapping from code info IDs to behavior objects.""" + + @singledispatchmethod + def generate(self, expr: behav.BaseNode, context=None): + raise NotImplementedError(f"No visit method implemented for type {type(expr).__name__} in {type(expr).__name__}") + + def _store_id(self, expr: behav.BaseNode, context: "IdMatcherContext"): + if expr.line_info is not None: + context.id_to_obj_map[context.arch_name][expr.line_info.id] = expr + + @generate.register + def _(self, expr: behav.Operation, context: "IdMatcherContext"): + self._store_id(expr, context) + for stmt in expr.statements: + self.generate(stmt, context) + + @generate.register + def _(self, expr: behav.Block, context: "IdMatcherContext"): + self._store_id(expr, context) + for stmt in expr.statements: + self.generate(stmt, context) + + @generate.register + def _(self, expr: behav.BinaryOperation, context: "IdMatcherContext"): + self._store_id(expr, context) + self.generate(expr.left, context) + self.generate(expr.right, context) + + @generate.register + def _(self, expr: behav.SliceOperation, context: "IdMatcherContext"): + self._store_id(expr, context) + self.generate(expr.expr, context) + self.generate(expr.left, context) + self.generate(expr.right, context) + + @generate.register + def _(self, expr: behav.ConcatOperation, context: "IdMatcherContext"): + self._store_id(expr, context) + self.generate(expr.left, context) + self.generate(expr.right, context) + + @generate.register + def _(self, expr: behav.NumberLiteral, context: "IdMatcherContext"): + self._store_id(expr, context) + + @generate.register + def _(self, expr: behav.IntLiteral, context: "IdMatcherContext"): + self._store_id(expr, context) + + @generate.register + def _(self, expr: behav.ScalarDefinition, context: "IdMatcherContext"): + self._store_id(expr, context) + + @generate.register + def _(self, expr: behav.Break, context: "IdMatcherContext"): + self._store_id(expr, context) + + @generate.register + def _(self, expr: behav.Assignment, context: "IdMatcherContext"): + self._store_id(expr, context) + self.generate(expr.target, context) + self.generate(expr.expr, context) + + @generate.register + def _(self, expr: behav.Conditional, context: "IdMatcherContext"): + self._store_id(expr, context) + for cond in expr.conds: + self.generate(cond, context) + for stmt in expr.stmts: + self.generate(stmt, context) + + @generate.register + def _(self, expr: behav.Loop, context: "IdMatcherContext"): + self._store_id(expr, context) + self.generate(expr.cond, context) + for stmt in expr.stmts: + self.generate(stmt, context) + + @generate.register + def _(self, expr: behav.Ternary, context: "IdMatcherContext"): + self._store_id(expr, context) + self.generate(expr.cond, context) + self.generate(expr.then_expr, context) + self.generate(expr.else_expr, context) + + @generate.register + def _(self, expr: behav.Return, context: "IdMatcherContext"): + self._store_id(expr, context) + if expr.expr is not None: + self.generate(expr.expr, context) + + @generate.register + def _(self, expr: behav.UnaryOperation, context: "IdMatcherContext"): + self._store_id(expr, context) + self.generate(expr.right, context) + + @generate.register + def _(self, expr: behav.NamedReference, context: "IdMatcherContext"): + self._store_id(expr, context) + + @generate.register + def _(self, expr: behav.IndexedReference, context: "IdMatcherContext"): + self._store_id(expr, context) + self.generate(expr.index, context) + + @generate.register + def _(self, expr: behav.TypeConv, context: "IdMatcherContext"): + self._store_id(expr, context) + self.generate(expr.expr, context) + + @generate.register + def _(self, expr: behav.Callable, context: "IdMatcherContext"): + self._store_id(expr, context) + for arg in expr.args: + self.generate(arg, context) + + @generate.register + def _(self, expr: behav.ProcedureCall, context: "IdMatcherContext"): + self._store_id(expr, context) + for arg in expr.args: + self.generate(arg, context) + + @generate.register + def _(self, expr: behav.Group, context: "IdMatcherContext"): + self._store_id(expr, context) + self.generate(expr.expr, context) diff --git a/m2isar/backends/etiss/instruction_generator.py b/m2isar/backends/etiss/instruction_generator.py index 49acb45b..ac32ff01 100644 --- a/m2isar/backends/etiss/instruction_generator.py +++ b/m2isar/backends/etiss/instruction_generator.py @@ -12,8 +12,9 @@ from mako.template import Template -from ...metamodel import arch, behav, patch_model -from . import BlockEndType, instruction_transform, instruction_utils +from ...metamodel import arch, behav +from . import BlockEndType, instruction_utils +from .instruction_transform import InstructionTransformVisitor from .templates import template_dir logger = logging.getLogger("instruction_generator") @@ -28,7 +29,7 @@ def generate_functions(core: arch.CoreDef, static_scalars: bool, decls_only: boo """ # load the instruction_transform generators - patch_model(instruction_transform) + visitor = InstructionTransformVisitor() fn_template = Template(filename=str(template_dir/'etiss_function.mako')) @@ -55,7 +56,7 @@ def generate_functions(core: arch.CoreDef, static_scalars: bool, decls_only: boo if not decls_only: fn_def.operation.line_info = fn_def.function_info - out_code = fn_def.operation.generate(context) + out_code = visitor.generate(fn_def.operation, context) out_code.format(ARCH_NAME=core_name) #fn_def.static = not context.used_arch_data @@ -140,7 +141,7 @@ def generate_fields(core_default_width, instr_def: arch.Instruction): return (fields_code, asm_printer_code, seen_fields, enc_idx) def generate_instruction_callback(core: arch.CoreDef, instr_def: arch.Instruction, fields, static_scalars: bool, block_end_on: BlockEndType, generate_coverage: bool): - patch_model(instruction_transform) + visitor = InstructionTransformVisitor() instr_name = instr_def.name core_name = core.name @@ -151,7 +152,7 @@ def generate_instruction_callback(core: arch.CoreDef, instr_def: arch.Instructio callback_template = Template(filename=str(template_dir/'etiss_instruction_callback.mako')) context = instruction_utils.TransformerContext(core.constants, core.memories, core.memory_aliases, instr_def.fields, instr_def.attributes, - core.functions, enc_idx, core_default_width, core_name, static_scalars, core.intrinsics, generate_coverage) + core.functions, enc_idx, core_default_width, core_name, static_scalars, core.intrinsics, generate_coverage, False) # force a block end if necessary if ((arch.InstrAttribute.NO_CONT in instr_def.attributes @@ -169,7 +170,7 @@ def generate_instruction_callback(core: arch.CoreDef, instr_def: arch.Instructio logger.debug("generating behavior code for %s", instr_def.name) instr_def.operation.line_info = instr_def.function_info - out_code = instr_def.operation.generate(context) + out_code = visitor.generate(instr_def.operation, context) out_code.format(ARCH_NAME=core_name) logger.debug("rendering template for %s", instr_def.name) @@ -200,11 +201,24 @@ def generate_instructions(core: arch.CoreDef, static_scalars: bool, block_end_on break core_name = core.name + seen_instr_names = set() for (code, mask), instr_def in core.instructions.items(): logger.debug("setting up instruction generator for %s", instr_def.name) instr_name = instr_def.name + instr_name2 = instr_name + if instr_name2 in seen_instr_names: + def gen_rand_suffix(length: int = 8): + # TODO: fixed seed? + import uuid + return str(uuid.uuid4()).replace('-', '')[:length] + logger.info("Found duplicate instr name: %s", instr_name2) + suffix = gen_rand_suffix(8) + instr_name2_new = f"{instr_name2}_{suffix}" + logger.info(f"Changing to %s...", instr_name2_new) + instr_name2 = instr_name2_new + seen_instr_names.add(instr_name) if instr_def.attributes is None: instr_def.attributes = [] @@ -235,6 +249,7 @@ def generate_instructions(core: arch.CoreDef, static_scalars: bool, block_end_on # render code for whole instruction templ_str = instr_template.render( instr_name=instr_name, + instr_name2=instr_name2, seen_fields=seen_fields, enc_idx=enc_idx, core_name=core_name, diff --git a/m2isar/backends/etiss/instruction_transform.py b/m2isar/backends/etiss/instruction_transform.py index 0e0d62c5..1050c91b 100644 --- a/m2isar/backends/etiss/instruction_transform.py +++ b/m2isar/backends/etiss/instruction_transform.py @@ -12,10 +12,12 @@ from math import log2 from itertools import chain from string import Template +from functools import singledispatchmethod from ... import M2NameError, M2SyntaxError, M2ValueError, flatten from ...metamodel import arch, behav from ...metamodel.code_info import LineInfoPlacement +from ...metamodel.utils.ExprVisitor import ExprVisitor from . import CodeInfoTracker, replacements from .instruction_utils import (FN_VAL_REPL, MEM_VAL_REPL, CodePartsContainer, CodeString, FnID, MemID, StaticType, @@ -25,814 +27,821 @@ logger = logging.getLogger("instr_transform") -def operation(self: behav.Operation, context: TransformerContext): - """Generate an `Operation` model object. Essentially generate all children, - concatenate their code, and add exception behavior if needed. - """ - args: "list[CodeString]" = [] - code_lines = [] +class InstructionTransformVisitor(ExprVisitor): + """Visitor to transform M2-ISA-R model expressions to C code strings for ETISS.""" - for stmt in self.statements: - c = stmt.generate(context) + @singledispatchmethod + def generate(self, expr: behav.BaseNode, context: TransformerContext): + raise NotImplementedError(f"No visit method implemented for type {type(expr).__name__} in {type(self).__name__}") - if isinstance(c, list): - args.extend(flatten(c)) - else: - args.append(c) + @generate.register + def _(self, expr: behav.Operation, context: TransformerContext): + """Generate an `Operation` model object. Essentially generate all children, + concatenate their code, and add exception behavior if needed. + """ - if self.line_info is not None and context.generate_coverage: - CodeInfoTracker.insert(context.arch_name, self.line_info) - code_lines.append(context.wrap_codestring(f"etiss_coverage_count(1, {self.line_info.id});")) + args: "list[CodeString]" = [] + code_lines = [] - for arg in args: - if arg.is_mem_access: - raise_fn_call = behav.Conditional( - [behav.CodeLiteral('cpu->exception')], - [behav.ProcedureCall( - context.mem_raise_fn, - [behav.CodeLiteral("cpu->exception")] - )] - ).generate(context) + for stmt in expr.statements: + c = self.generate(stmt, context) - raise_fn_str = [context.wrap_codestring(c.code, c.static) for c in raise_fn_call] + if isinstance(c, list): + args.extend(flatten(c)) + else: + args.append(c) - before_line_infos = [] - after_line_infos = [] + if expr.line_info is not None and context.generate_coverage: + CodeInfoTracker.insert(context.arch_name, expr.line_info) + code_lines.append(context.wrap_codestring(f"etiss_coverage_count(1, {expr.line_info.id});")) - if context.generate_coverage: - for l in flatten(arg.line_infos): - if l is not None: - CodeInfoTracker.insert(context.arch_name, l) + for arg in args: + if arg.is_mem_access: + raise_fn_call = self.generate(behav.Conditional( + [behav.CodeLiteral('cpu->exception')], + [behav.ProcedureCall( + context.mem_raise_fn, + [behav.CodeLiteral("cpu->exception")] + )] + ), context) - if l.placement == LineInfoPlacement.BEFORE: - before_line_infos.append(str(l.id)) + raise_fn_str = [context.wrap_codestring(c.code, c.static) for c in raise_fn_call] - elif l.placement == LineInfoPlacement.AFTER: - after_line_infos.append(str(l.id)) + before_line_infos = [] + after_line_infos = [] - if len(before_line_infos) > 0: - code_lines.append(context.wrap_codestring(f"etiss_coverage_count({len(before_line_infos)}, {', '.join(before_line_infos)});")) + if context.generate_coverage: + for l in flatten(arg.line_infos): + if l is not None: + CodeInfoTracker.insert(context.arch_name, l) - for f_id in arg.function_calls: # TODO: rework so that cascaded function calls and memory reads work properly - code_lines.append(context.wrap_codestring(f'{data_type_map[f_id.fn_call.data_type]}{f_id.fn_call.actual_size} {FN_VAL_REPL}{f_id.fn_id};', arg.static)) - code_lines.append(context.wrap_codestring(f'{FN_VAL_REPL}{f_id.fn_id} = {f_id.args};', arg.static)) - code_lines.append(context.wrap_codestring('if (cpu->return_pending) goto instr_exit_" + std::to_string(ic.current_address_) + ";', arg.static)) + if l.placement == LineInfoPlacement.BEFORE: + before_line_infos.append(str(l.id)) - for m_id in arg.read_mem_ids: - code_lines.append(context.wrap_codestring(f'etiss_uint{m_id.access_size} {MEM_VAL_REPL}{m_id.mem_id};')) - code_lines.append(context.wrap_codestring(f'cpu->exception |= (*(system->dread))(system->handle, cpu, {m_id.index.code}, (etiss_uint8*)&{MEM_VAL_REPL}{m_id.mem_id}, {int(m_id.access_size / 8)});')) - code_lines.extend(raise_fn_str) + elif l.placement == LineInfoPlacement.AFTER: + after_line_infos.append(str(l.id)) - for m_id in arg.write_mem_ids: - code_lines.append(context.wrap_codestring(f'etiss_uint{m_id.access_size} {MEM_VAL_REPL}{m_id.mem_id};')) + if len(before_line_infos) > 0: + code_lines.append(context.wrap_codestring(f"etiss_coverage_count({len(before_line_infos)}, {', '.join(before_line_infos)});")) - code_lines.append(context.wrap_codestring(f'{arg.code}', arg.static)) + for f_id in arg.function_calls: + code_lines.append(context.wrap_codestring(f'{data_type_map[f_id.fn_call.data_type]}{f_id.fn_call.actual_size} {FN_VAL_REPL}{f_id.fn_id};', arg.static)) + code_lines.append(context.wrap_codestring(f'{FN_VAL_REPL}{f_id.fn_id} = {f_id.args};', arg.static)) + code_lines.append(context.wrap_codestring('if (cpu->return_pending) goto instr_exit_" + std::to_string(ic.current_address_) + ";', arg.static)) - if len(after_line_infos) > 0: - code_lines.append(context.wrap_codestring(f"etiss_coverage_count({len(after_line_infos)}, {', '.join(after_line_infos)});")) + for m_id in arg.read_mem_ids: + code_lines.append(context.wrap_codestring(f'etiss_uint{m_id.access_size} {MEM_VAL_REPL}{m_id.mem_id};')) + code_lines.append(context.wrap_codestring(f'cpu->exception |= (*(system->dread))(system->handle, cpu, {m_id.index.code}, (etiss_uint8*)&{MEM_VAL_REPL}{m_id.mem_id}, {int(m_id.access_size / 8)});')) + code_lines.extend(raise_fn_str) - #if arg.check_trap: - # code_lines.append(context.wrap_codestring('goto instr_exit_" + std::to_string(ic.current_address_) + ";')) + for m_id in arg.write_mem_ids: + code_lines.append(context.wrap_codestring(f'etiss_uint{m_id.access_size} {MEM_VAL_REPL}{m_id.mem_id};')) - for m_id in arg.write_mem_ids: - code_lines.append(context.wrap_codestring(f'cpu->exception |= (*(system->dwrite))(system->handle, cpu, {m_id.index.code}, (etiss_uint8*)&{MEM_VAL_REPL}{m_id.mem_id}, {int(m_id.access_size / 8)});')) - code_lines.extend(raise_fn_str) + code_lines.append(context.wrap_codestring(f'{arg.code}', arg.static)) - container = CodePartsContainer() + if len(after_line_infos) > 0: + code_lines.append(context.wrap_codestring(f"etiss_coverage_count({len(after_line_infos)}, {', '.join(after_line_infos)});")) - container.initial_required = '\n'.join(code_lines) + for m_id in arg.write_mem_ids: + code_lines.append(context.wrap_codestring(f'cpu->exception |= (*(system->dwrite))(system->handle, cpu, {m_id.index.code}, (etiss_uint8*)&{MEM_VAL_REPL}{m_id.mem_id}, {int(m_id.access_size / 8)});')) + code_lines.extend(raise_fn_str) - # only generate return statements if not in a function - if not context.ignore_static: - container.initial_required += '\ncp.code() += "instr_exit_" + std::to_string(ic.current_address_) + ":\\n";' - container.initial_required += '\ncp.code() += "cpu->instructionPointer = cpu->nextPc;\\n";'# + code_str - return_conditions = [] - return_needed = any(( - context.generates_exception, - arch.InstrAttribute.NO_CONT in context.attributes, - arch.InstrAttribute.COND in context.attributes, - arch.InstrAttribute.FLUSH in context.attributes - )) + container = CodePartsContainer() + container.initial_required = '\n'.join(code_lines) - if context.generates_exception: - return_conditions.append("cpu->return_pending") - return_conditions.append("cpu->exception") + # only generate return statements if not in a function + if not context.ignore_static: + container.initial_required += '\ncp.code() += "instr_exit_" + std::to_string(ic.current_address_) + ":\\n";' + container.initial_required += '\ncp.code() += "cpu->instructionPointer = cpu->nextPc;\\n";' + return_conditions = [] + return_needed = any(( + context.generates_exception, + arch.InstrAttribute.NO_CONT in context.attributes, + arch.InstrAttribute.COND in context.attributes, + arch.InstrAttribute.FLUSH in context.attributes + )) - if arch.InstrAttribute.NO_CONT in context.attributes and arch.InstrAttribute.COND in context.attributes: - return_conditions.append(f'cpu->nextPc != " + std::to_string(ic.current_address_ + {int(context.instr_size / 8)}) + "ULL') + if context.generates_exception: + return_conditions.append("cpu->return_pending") + return_conditions.append("cpu->exception") - elif arch.InstrAttribute.NO_CONT in context.attributes: - return_conditions.clear() + if arch.InstrAttribute.NO_CONT in context.attributes and arch.InstrAttribute.COND in context.attributes: + return_conditions.append(f'cpu->nextPc != " + std::to_string(ic.current_address_ + {int(context.instr_size / 8)}) + "ULL') - if arch.InstrAttribute.FLUSH in context.attributes: - container.initial_required = 'cp.code() += "cpu->exception = ETISS_RETURNCODE_RELOADBLOCKS;\\n";\n' + container.initial_required - return_conditions.clear() + elif arch.InstrAttribute.NO_CONT in context.attributes: + return_conditions.clear() - if return_needed: - cond_str = ("if (" + " || ".join(return_conditions) + ") ") if return_conditions else "" - container.appended_returning_required = f'cp.code() += "{cond_str}return cpu->exception;\\n";' + if arch.InstrAttribute.FLUSH in context.attributes: + container.initial_required = 'cp.code() += "cpu->exception = ETISS_RETURNCODE_RELOADBLOCKS;\\n";\n' + container.initial_required + return_conditions.clear() - elif arch.FunctionAttribute.ETISS_TRAP_ENTRY_FN in context.attributes: - container.initial_required = "cpu->return_pending = 1;\ncpu->exception = 0;\n" + container.initial_required + if return_needed: + cond_str = ("if (" + " || ".join(return_conditions) + ") ") if return_conditions else "" + container.appended_returning_required = f'cp.code() += "{cond_str}return cpu->exception;\\n";' - return container + elif arch.FunctionAttribute.ETISS_TRAP_ENTRY_FN in context.attributes: + container.initial_required = "cpu->return_pending = 1;\ncpu->exception = 0;\n" + container.initial_required -def block(self: behav.Block, context: TransformerContext): - stmts = [stmt.generate(context) for stmt in self.statements] + return container - pre = [CodeString("{ // block", StaticType.READ, None, None, line_infos=self.line_info)] - post = [CodeString("} // block", StaticType.READ, None, None)] + @generate.register + def _(self, expr: behav.Block, context: TransformerContext): + stmts = [self.generate(stmt, context) for stmt in expr.statements] - if not context.ignore_static: - pre.append(CodeString("{ // block", StaticType.NONE, None, None)) - post.insert(0, CodeString("} // block", StaticType.NONE, None, None)) + pre = [CodeString("{ // block", StaticType.READ, None, None, line_infos=expr.line_info)] + post = [CodeString("} // block", StaticType.READ, None, None)] - return pre + stmts + post + if not context.ignore_static: + pre.append(CodeString("{ // block", StaticType.NONE, None, None)) + post.insert(0, CodeString("} // block", StaticType.NONE, None, None)) -def return_(self: behav.Return, context: TransformerContext): - if context.instr_size != 0: - raise M2SyntaxError('Return statements are not allowed in instruction behavior!') + return pre + stmts + post - if self.expr is not None: - c = self.expr.generate(context) - c.code = f'return {c.code};' - c.line_infos.append(self.line_info) - else: - c = CodeString("return;", StaticType.RW, None, None, line_infos=self.line_info) + @generate.register + def _(self, expr: behav.Return, context: TransformerContext): + if context.instr_size != 0: + raise M2SyntaxError('Return statements are not allowed in instruction behavior!') - return c + if expr.expr is not None: + c = self.generate(expr.expr, context) + c.code = f'return {c.code};' + c.line_infos.append(expr.line_info) + else: + c = CodeString("return;", StaticType.RW, None, None, line_infos=expr.line_info) -def break_(self: behav.Break, context: TransformerContext): - return CodeString("break;", StaticType.RW, None, None, line_infos=self.line_info) + return c -def scalar_definition(self: behav.ScalarDefinition, context: TransformerContext): - """Generate a scalar definition. Calculates the actual required data width and generates - a variable instantiation.""" + @generate.register + def _(self, expr: behav.Break, context: TransformerContext): + return CodeString("break;", StaticType.RW, None, None, line_infos=expr.line_info) - if context.static_scalars: - if context.ignore_static: - static = StaticType.RW + @generate.register + def _(self, expr: behav.ScalarDefinition, context: TransformerContext): + """Generate a scalar definition. Calculates the actual required data width and generates + a variable instantiation.""" + if context.static_scalars: + if context.ignore_static: + static = StaticType.RW + else: + static = expr.scalar.static else: - static = self.scalar.static - else: - static = StaticType.NONE + static = StaticType.NONE - actual_size = 1 << (self.scalar.size - 1).bit_length() - actual_size = max(actual_size, 8) + actual_size = 1 << (expr.scalar.size - 1).bit_length() + actual_size = max(actual_size, 8) - c = CodeString(f'{data_type_map[self.scalar.data_type]}{actual_size} {self.scalar.name}', static, self.scalar.size, self.scalar.data_type == arch.DataType.S, line_infos=self.line_info) - #c.scalar = self.scalar - return c + return CodeString( + f'{data_type_map[expr.scalar.data_type]}{actual_size} {expr.scalar.name}', + static, + expr.scalar.size, + expr.scalar.data_type == arch.DataType.S, + line_infos=expr.line_info, + ) -def procedure_call(self: behav.ProcedureCall, context: TransformerContext): - """Generate a procedure call (Function call without usage of the return value).""" + @generate.register + def _(self, expr: behav.ProcedureCall, context: TransformerContext): + """Generate a procedure call (Function call without usage of the return value).""" + fn_args = [self.generate(arg, context) for arg in expr.args] - fn_args = [arg.generate(context) for arg in self.args] + # extract function object reference + ref = expr.ref_or_name if isinstance(expr.ref_or_name, arch.Function) else None + name = ref.name if isinstance(expr.ref_or_name, arch.Function) else expr.ref_or_name - # extract function object reference - ref = self.ref_or_name if isinstance(self.ref_or_name, arch.Function) else None - name = ref.name if isinstance(self.ref_or_name, arch.Function) else self.ref_or_name + if ref is not None: + # if there is a function object, use its information + fn = ref - if ref is not None: - # if there is a function object, use its information - fn = ref - # determine if procedure call is entirely static - static = StaticType.READ if fn.static and all(arg.static != StaticType.NONE for arg in fn_args) else StaticType.NONE + # determine if procedure call is entirely static + static = StaticType.READ if fn.static and all(arg.static != StaticType.NONE for arg in fn_args) else StaticType.NONE - # convert singular static arguments - if not static: - context.used_arch_data = True - for arg in fn_args: - if arg.static and not arg.is_literal: - arg.code = context.make_static(arg.code, arg.signed) + # convert singular static arguments + if not static: + context.used_arch_data = True + for arg in fn_args: + if arg.static and not arg.is_literal: + arg.code = context.make_static(arg.code, arg.signed) - # generate argument string, add ETISS arch data if required - arch_args = ['cpu', 'system', 'plugin_pointers'] if arch.FunctionAttribute.ETISS_NEEDS_ARCH in fn.attributes or (not fn.static and not fn.extern) else [] - arg_str = ', '.join(arch_args + [arg.code for arg in fn_args]) + # generate argument string, add ETISS arch data if required + arch_args = ['cpu', 'system', 'plugin_pointers'] if arch.FunctionAttribute.ETISS_NEEDS_ARCH in fn.attributes or (not fn.static and not fn.extern) else [] + arg_str = ', '.join(arch_args + [arg.code for arg in fn_args]) - # check if any argument is a memory access - mem_ids = list(chain.from_iterable([arg.mem_ids for arg in fn_args])) + # check if any argument is a memory access + mem_ids = list(chain.from_iterable([arg.mem_ids for arg in fn_args])) - # update affected and dependent registers - regs_affected = set(chain.from_iterable([arg.regs_affected for arg in fn_args])) - context.dependent_regs.update(regs_affected) + # update affected and dependent registers + regs_affected = set(chain.from_iterable([arg.regs_affected for arg in fn_args])) + context.dependent_regs.update(regs_affected) - # add special behavior if this function is an exception entry point - exc_code = "" + # add special behavior if this function is an exception entry point + exc_code = "" - if arch.FunctionAttribute.ETISS_TRAP_TRANSLATE_FN in fn.attributes: - context.generates_exception = True + if arch.FunctionAttribute.ETISS_TRAP_TRANSLATE_FN in fn.attributes: + context.generates_exception = True - if arch.FunctionAttribute.ETISS_TRAP_ENTRY_FN in fn.attributes: - context.generates_exception = True + if arch.FunctionAttribute.ETISS_TRAP_ENTRY_FN in fn.attributes: + context.generates_exception = True - if fn.size is not None: - exc_code = "cpu->exception = " + if fn.size is not None: + exc_code = "cpu->exception = " - c = CodeString(f'{exc_code}{fn.name}({arg_str});', static, None, None, line_infos=[self.line_info] + [x.line_infos for x in fn_args]) - c.mem_ids = mem_ids - if fn.throws and not context.ignore_static: - c.check_trap = True + c = CodeString(f'{exc_code}{fn.name}({arg_str});', static, None, None, line_infos=[expr.line_info] + [x.line_infos for x in fn_args]) + c.mem_ids = mem_ids + if fn.throws and not context.ignore_static: + c.check_trap = True - cond = "if (cpu->return_pending) " if fn.throws == arch.FunctionThrows.MAYBE else "" - c2 = CodeString(cond + 'goto instr_exit_" + std::to_string(ic.current_address_) + ";', static, None, None) + cond = "if (cpu->return_pending) " if fn.throws == arch.FunctionThrows.MAYBE else "" + c2 = CodeString(cond + 'goto instr_exit_" + std::to_string(ic.current_address_) + ";', static, None, None) - pre = [CodeString("{ // procedure", StaticType.READ, None, None), CodeString("{ // procedure", StaticType.NONE, None, None)] - post = [CodeString("} // procedure", StaticType.NONE, None, None), CodeString("} // procedure", StaticType.READ, None, None)] + pre = [CodeString("{ // procedure", StaticType.READ, None, None), CodeString("{ // procedure", StaticType.NONE, None, None)] + post = [CodeString("} // procedure", StaticType.NONE, None, None), CodeString("} // procedure", StaticType.READ, None, None)] - return pre + [c, c2] + post + return pre + [c, c2] + post - return c + return c - raise M2NameError(f'Function {name} not recognized!') + raise M2NameError(f'Function {name} not recognized!') -def function_call(self: behav.FunctionCall, context: TransformerContext): - """Generate a regular function call (with further use of return value).""" + @generate.register + def _(self, expr: behav.FunctionCall, context: TransformerContext): + """Generate a regular function call (with further use of return value).""" + fn_args = [self.generate(arg, context) for arg in expr.args] - fn_args = [arg.generate(context) for arg in self.args] + # extract function object reference + ref = expr.ref_or_name if isinstance(expr.ref_or_name, arch.Function) else None + name = ref.name if isinstance(expr.ref_or_name, arch.Function) else expr.ref_or_name - # extract function object reference - ref = self.ref_or_name if isinstance(self.ref_or_name, arch.Function) else None - name = ref.name if isinstance(self.ref_or_name, arch.Function) else self.ref_or_name + if ref is not None: + # if there is a function object, use its information - if ref is not None: - # if there is a function object, use its information + fn = ref - fn = ref + # determine if function call is entirely static + static = StaticType.READ if fn.static and all(arg.static != StaticType.NONE for arg in fn_args) else StaticType.NONE - # determine if function call is entirely static - static = StaticType.READ if fn.static and all(arg.static != StaticType.NONE for arg in fn_args) else StaticType.NONE + # convert singular static arguments + if not static: + context.used_arch_data = True + for arg in fn_args: + if arg.static and not arg.is_literal: + arg.code = context.make_static(arg.code, arg.signed) - # convert singular static arguments - if not static: - context.used_arch_data = True - for arg in fn_args: - if arg.static and not arg.is_literal: - arg.code = context.make_static(arg.code, arg.signed) + # generate argument string, add ETISS arch data if required + arch_args = ['cpu', 'system', 'plugin_pointers'] if arch.FunctionAttribute.ETISS_NEEDS_ARCH in fn.attributes or (not fn.static and not fn.extern) else [] + arg_str = ', '.join(arch_args + [arg.code for arg in fn_args]) - # generate argument string, add ETISS arch data if required - arch_args = ['cpu', 'system', 'plugin_pointers'] if arch.FunctionAttribute.ETISS_NEEDS_ARCH in fn.attributes or (not fn.static and not fn.extern) else [] - arg_str = ', '.join(arch_args + [arg.code for arg in fn_args]) + # keep track of signedness of function return value + signed = fn.data_type == arch.DataType.S + # keep track of affected registers + regs_affected = set(chain.from_iterable([arg.regs_affected for arg in fn_args])) - # keep track of signedness of function return value - signed = fn.data_type == arch.DataType.S - # keep track of affected registers - regs_affected = set(chain.from_iterable([arg.regs_affected for arg in fn_args])) + c = CodeString(f'{fn.name}({arg_str})', static, fn.size, signed, regs_affected, [expr.line_info] + [x.line_infos for x in fn_args]) + c.mem_ids = list(chain.from_iterable([arg.mem_ids for arg in fn_args])) - #goto_code = "" + if fn.throws and not context.ignore_static: + fn_id = FnID(fn, context.fn_var_count, c) + repl_c = CodeString(f'{FN_VAL_REPL}{context.fn_var_count}', static, fn.size, signed, regs_affected) + repl_c.mem_ids = list(chain.from_iterable([arg.mem_ids for arg in fn_args])) + repl_c.function_calls.append(fn_id) + context.fn_var_count += 1 + return repl_c - #if fn.throws and not context.ignore_static: - # goto_code = '; goto instr_exit_" + std::to_string(ic.current_address_) + "' + return c - c = CodeString(f'{fn.name}({arg_str})', static, fn.size, signed, regs_affected, [self.line_info] + [x.line_infos for x in fn_args]) - c.mem_ids = list(chain.from_iterable([arg.mem_ids for arg in fn_args])) + raise M2NameError(f'Function {name} not recognized!') - if fn.throws and not context.ignore_static: - fn_id = FnID(fn, context.fn_var_count, c) - repl_c = CodeString(f'{FN_VAL_REPL}{context.fn_var_count}', static, fn.size, signed, regs_affected) - repl_c.mem_ids = list(chain.from_iterable([arg.mem_ids for arg in fn_args])) - repl_c.function_calls.append(fn_id) - context.fn_var_count += 1 - return repl_c + @generate.register + def _(self, expr: behav.Callable, context: TransformerContext): + # Callable is the base class; dispatch to specific subclass handler + if isinstance(expr, behav.FunctionCall): + return self.generate(expr, context) + elif isinstance(expr, behav.ProcedureCall): + return self.generate(expr, context) + raise M2SyntaxError(f"Unsupported callable node type {type(expr).__name__}") - return c + @generate.register + def _(self, expr: behav.Assignment, context: TransformerContext): + """Generate an assignment expression""" + + # generate target and value expressions + target: CodeString = self.generate(expr.target, context) + expr_str: CodeString = self.generate(expr.expr, context) - raise M2NameError(f'Function {name} not recognized!') + # check staticness + static = bool(target.static & StaticType.WRITE) and bool(expr_str.static) -def conditional(self: behav.Conditional, context: TransformerContext): - """Generate a conditional ('if' with optional 'else if' and 'else' blocks)""" + if not expr_str.static and bool(target.static & StaticType.WRITE) and not context.ignore_static: + raise M2ValueError('Static target cannot be assigned to non-static expression!') - # generate conditions and statement blocks - conds: "list[CodeString]" = [x.generate(context) for x in self.conds] - stmts: "list[list[CodeString]]" = [] #= [[y.generate(context) for y in x] for x in self.stmts] + # convert assignment value staticness + if expr_str.static and not expr_str.is_literal: + if bool(target.static & StaticType.WRITE): + if context.ignore_static: + expr_str.code = Template(f'{expr_str.code}').safe_substitute(**replacements.rename_dynamic) + else: + expr_str.code = Template(f'{expr_str.code}').safe_substitute(**replacements.rename_static) + else: + expr_str.code = context.make_static(expr_str.code, expr_str.signed) - for cond in conds[1:]: - conds[0].mem_ids.extend(cond.mem_ids) - cond.mem_ids.clear() + # convert target staticness + if bool(target.static & StaticType.READ): + target.code = Template(target.code).safe_substitute(replacements.rename_write) - for stmt in self.stmts: - ret = stmt.generate(context) + # keep track of affected and dependent registers + context.affected_regs.update(target.regs_affected) + context.dependent_regs.update(expr_str.regs_affected) - if isinstance(ret, list): - stmts.append(ret) + if not target.is_mem_access and not expr_str.is_mem_access: + if target.actual_size > target.size: + if target.signed: + shift = target.actual_size - target.size + expr_str.code = f'(((etiss_int{target.actual_size})({expr_str.code})) << {shift}) >> {shift}' + else: + mask = (1 << target.size) - 1 + mask_bits = log2(mask) + if mask_bits > 64: + mask64 = (1 << 64) - 1 + low = mask & mask64 + high = mask >> 64 + mask_code = f"(((etiss_int128){hex(high)}ULL << 64) | {hex(low)}ULL)" + else: + mask_code = f"{hex(mask)}ULL" + expr_str.code = f'({expr_str.code}) & {mask_code}' else: - stmts.append([ret]) + context.generates_exception = True - #for stmt_block in self.stmts: - # block_statements = [] - # for stmt in stmt_block: - # if isinstance(stmt, list): - # for stmt2 in stmt: - # block_statements.append(stmt2.generate(context)) - # else: - # block_statements.append(stmt.generate(context)) + for m_id in expr_str.mem_ids: + m_id.write = False - # stmts.append(block_statements) - # check if all conditions are static - static = all(x.static for x in conds) + if target.is_mem_access: + if len(target.mem_ids) != 1: + raise M2SyntaxError('Only one memory access is allowed as assignment target!') - outputs: "list[CodeString]" = [] + target.mem_ids[0].write = True - for cond in conds: - for m_id in cond.mem_ids: - m_id.write = False - if cond.static and not static: - cond.code = context.make_static(cond.code) - cond.static = False + c = CodeString(f"{target.code} = {expr_str.code};", static, None, None, line_infos=[expr.line_info] + target.line_infos + expr_str.line_infos) - # generate initial if - #c = conds[0] - conds[0].code = f'if ({conds[0].code}) {{ // conditional' - conds[0].line_infos.append(self.line_info) - outputs.append(conds[0]) - if not static: - context.dependent_regs.update(conds[0].regs_affected) + c.function_calls.extend(target.function_calls) + c.function_calls.extend(expr_str.function_calls) - # generate first statement block - outputs.extend(flatten(stmts[0])) + c.mem_ids.extend(target.mem_ids) + c.mem_ids.extend(expr_str.mem_ids) - # generate closing brace - outputs.append(CodeString("} // conditional", static, None, None)) + return c - for elif_cond, elif_stmts in zip(conds[1:], stmts[1:]): - elif_cond.code = f' else if ({elif_cond.code}) {{ // conditional' - outputs.append(elif_cond) - if not static: - context.dependent_regs.update(elif_cond.regs_affected) + @generate.register + def _(self, expr: behav.BinaryOperation, context: TransformerContext): + """Generate a binary expression""" - outputs.extend(flatten(elif_stmts)) + # generate LHS and RHS of the expression + left = self.generate(expr.left, context) + op = expr.op + right = self.generate(expr.right, context) - outputs.append(CodeString("} // conditional", static, None, None)) + # convert staticness if needed + if not left.static and right.static and not right.is_literal: + right.code = context.make_static(right.code, right.signed) + if not right.static and left.static and not left.is_literal: + left.code = context.make_static(left.code, left.signed) - if len(conds) < len(stmts): - outputs.append(CodeString("else { // conditional", static, None, None)) + c = CodeString( + f'{left.code} {op.value} {right.code}', + left.static and right.static, + left.size if left.size > right.size else right.size, + left.signed or right.signed, + set.union(left.regs_affected, right.regs_affected), + [expr.line_info] + left.line_infos + right.line_infos, + ) + # keep track of any memory accesses + c.mem_ids = left.mem_ids + right.mem_ids + return c - outputs.extend(flatten(stmts[-1])) + @generate.register + def _(self, expr: behav.UnaryOperation, context: TransformerContext): + op = expr.op + right = self.generate(expr.right, context) - outputs.append(CodeString("} // conditional", static, None, None)) + c = CodeString(f'{op.value}({right.code})', right.static, right.size, right.signed, right.regs_affected, [expr.line_info] + right.line_infos) + c.mem_ids = right.mem_ids + return c - return outputs + @generate.register + def _(self, expr: behav.Conditional, context: TransformerContext): + """Generate a conditional ('if' with optional 'else if' and 'else' blocks)""" -def loop(self: behav.Loop, context: TransformerContext): - """Generate 'while' and 'do .. while' loops.""" + # generate conditions and statement blocks + conds: "list[CodeString]" = [self.generate(x, context) for x in expr.conds] + stmts: "list[list[CodeString]]" = [] - # generate the loop condition and body - cond: CodeString = self.cond.generate(context) - stmts: "list[CodeString]" = [] #[stmt.generate(context) for stmt in self.stmts] + for cond in conds[1:]: + conds[0].mem_ids.extend(cond.mem_ids) + cond.mem_ids.clear() - for stmt in self.stmts: - if isinstance(stmt, list): - for stmt2 in stmt: - stmts.append(stmt2.generate(context)) - else: - stmts.append(stmt.generate(context)) + for stmt in expr.stmts: + if isinstance(stmt, list): + ret = [] + for stmt_ in stmt: + ret_ = self.generate(stmt_, context) + ret.append(ret_) + else: + ret = self.generate(stmt, context) - if not cond.static: - context.dependent_regs.update(cond.regs_affected) + if isinstance(ret, list): + stmts.append(ret) + else: + stmts.append([ret]) - outputs: "list[CodeString]" = [] + # check if all conditions are static + static = all(x.static for x in conds) + outputs: "list[CodeString]" = [] - if self.post_test: - start_c = CodeString("do", cond.static, None, None) - end_c = cond - end_c.code = f'while ({end_c.code})' - else: - start_c = cond - start_c.code = f'while ({start_c.code})' - end_c = CodeString("", cond.static, None, None) + for cond in conds: + for m_id in cond.mem_ids: + m_id.write = False - outputs.append(start_c) + if cond.static and not static: + cond.code = context.make_static(cond.code) + cond.static = False - outputs.extend(flatten(stmts)) + conds[0].code = f'if ({conds[0].code}) {{ // conditional' + conds[0].line_infos.append(expr.line_info) + outputs.append(conds[0]) + if not static: + context.dependent_regs.update(conds[0].regs_affected) - outputs.append(end_c) + # generate first statement block + outputs.extend(flatten(stmts[0])) - return outputs + # generate closing brace + outputs.append(CodeString("} // conditional", static, None, None)) -def ternary(self: behav.Ternary, context: TransformerContext): - """Generate a ternary expression.""" + for elif_cond, elif_stmts in zip(conds[1:], stmts[1:]): + elif_cond.code = f' else if ({elif_cond.code}) {{ // conditional' + outputs.append(elif_cond) + if not static: + context.dependent_regs.update(elif_cond.regs_affected) - # generate condition and 'then' and 'else' statements - cond = self.cond.generate(context) - then_expr = self.then_expr.generate(context) - else_expr = self.else_expr.generate(context) + outputs.extend(flatten(elif_stmts)) + outputs.append(CodeString("} // conditional", static, None, None)) - static = StaticType.NONE not in [x.static for x in (cond, then_expr, else_expr)] + if len(conds) < len(stmts): + outputs.append(CodeString("else { // conditional", static, None, None)) + outputs.extend(flatten(stmts[-1])) + outputs.append(CodeString("} // conditional", static, None, None)) - # convert singular static sub-components - if not static: - if cond.static and not cond.is_literal: - cond.code = context.make_static(cond.code, cond.signed) - if then_expr.static and not then_expr.is_literal: - then_expr.code = context.make_static(then_expr.code, then_expr.signed) - if else_expr.static and not else_expr.is_literal: - else_expr.code = context.make_static(else_expr.code, else_expr.signed) + return outputs - c = CodeString(f'({cond}) ? ({then_expr}) : ({else_expr})', static, then_expr.size if then_expr.size > else_expr.size else else_expr.size, - then_expr.signed or else_expr.signed, set.union(cond.regs_affected, then_expr.regs_affected, else_expr.regs_affected), [self.line_info] + cond.line_infos + then_expr.line_infos + else_expr.line_infos) - c.mem_ids = cond.mem_ids + then_expr.mem_ids + else_expr.mem_ids + @generate.register + def _(self, expr: behav.Loop, context: TransformerContext): + """Generate 'while' and 'do .. while' loops.""" - return c + cond: CodeString = self.generate(expr.cond, context) + stmts: "list[CodeString]" = [] -def assignment(self: behav.Assignment, context: TransformerContext): - """Generate an assignment expression""" + for stmt in expr.stmts: + if isinstance(stmt, list): + for stmt2 in stmt: + stmts.append(self.generate(stmt2, context)) + else: + stmts.append(self.generate(stmt, context)) - # generate target and value expressions - target: CodeString = self.target.generate(context) - expr: CodeString = self.expr.generate(context) + if not cond.static: + context.dependent_regs.update(cond.regs_affected) - # check staticness - static = bool(target.static & StaticType.WRITE) and bool(expr.static) + outputs: "list[CodeString]" = [] - # error out if a static target should be assigned a non-static value - if not expr.static and bool(target.static & StaticType.WRITE) and not context.ignore_static: - raise M2ValueError('Static target cannot be assigned to non-static expression!') + if expr.post_test: + start_c = CodeString("do", cond.static, None, None) + end_c = cond + end_c.code = f'while ({end_c.code})' + else: + start_c = cond + start_c.code = f'while ({start_c.code})' + end_c = CodeString("", cond.static, None, None) - # convert assignment value staticness - if expr.static and not expr.is_literal: - if bool(target.static & StaticType.WRITE): - if context.ignore_static: - expr.code = Template(f'{expr.code}').safe_substitute(**replacements.rename_dynamic) - else: - expr.code = Template(f'{expr.code}').safe_substitute(**replacements.rename_static) + outputs.append(start_c) + outputs.extend(flatten(stmts)) + outputs.append(end_c) - else: - expr.code = context.make_static(expr.code, expr.signed) + return outputs - # convert target staticness - if bool(target.static & StaticType.READ): - target.code = Template(target.code).safe_substitute(replacements.rename_write) + @generate.register + def _(self, expr: behav.Ternary, context: TransformerContext): + """Generate a ternary expression.""" - # keep track of affected and dependent registers - context.affected_regs.update(target.regs_affected) - context.dependent_regs.update(expr.regs_affected) + # generate condition and 'then' and 'else' statements + cond = self.generate(expr.cond, context) + then_expr = self.generate(expr.then_expr, context) + else_expr = self.generate(expr.else_expr, context) - if not target.is_mem_access and not expr.is_mem_access: - if target.actual_size > target.size: - if target.signed: - shift = target.actual_size - target.size - expr.code = f'(((etiss_int{target.actual_size})({expr.code})) << {shift}) >> {shift}' - else: - mask = (1 << target.size) - 1 - mask_bits = log2(mask) - if mask_bits > 64: - mask64 = (1 << 64) - 1 - low = mask & mask64 - high = mask >> 64 - mask_code = f"(((etiss_int128){hex(high)}ULL << 64) | {hex(low)}ULL)" - else: - mask_code = f"{hex(mask)}ULL" - expr.code = f'({expr.code}) & {mask_code}' + static = StaticType.NONE not in [x.static for x in (cond, then_expr, else_expr)] - else: - context.generates_exception = True + # convert singular static sub-components + if not static: + if cond.static and not cond.is_literal: + cond.code = context.make_static(cond.code, cond.signed) + if then_expr.static and not then_expr.is_literal: + then_expr.code = context.make_static(then_expr.code, then_expr.signed) + if else_expr.static and not else_expr.is_literal: + else_expr.code = context.make_static(else_expr.code, else_expr.signed) + + c = CodeString( + f'({cond}) ? ({then_expr}) : ({else_expr})', + static, + then_expr.size if then_expr.size > else_expr.size else else_expr.size, + then_expr.signed or else_expr.signed, + set.union(cond.regs_affected, then_expr.regs_affected, else_expr.regs_affected), + [expr.line_info] + cond.line_infos + then_expr.line_infos + else_expr.line_infos, + ) + c.mem_ids = cond.mem_ids + then_expr.mem_ids + else_expr.mem_ids - for m_id in expr.mem_ids: - m_id.write = False + return c - if not expr.mem_corrected: - logger.debug("assuming mem read size at %d", target.size) - m_id.access_size = target.size + @generate.register + def _(self, expr: behav.TypeConv, context: TransformerContext): + """Generate a type cast expression""" - if target.is_mem_access: - if len(target.mem_ids) != 1: - raise M2SyntaxError('Only one memory access is allowed as assignment target!') + # generate the expression to be type-casted + expr_str = self.generate(expr.expr, context) - target.mem_ids[0].write = True + # if only width should be changed assume data type remains unchanged + if expr.data_type is None: + expr.data_type = arch.DataType.S if expr_str.signed else arch.DataType.U - if not target.mem_corrected: - logger.debug("assuming mem write size at %d", expr.size) - target.mem_ids[0].access_size = expr.size + # if only data type should be changed assume width remains unchanged + if expr.size is None: + expr._size = expr_str.size + expr._actual_size = expr_str.actual_size - c = CodeString(f"{target.code} = {expr.code};", static, None, None, line_infos=[self.line_info] + target.line_infos + expr.line_infos) - c.function_calls.extend(target.function_calls) - c.function_calls.extend(expr.function_calls) + code_str = expr_str.code - c.mem_ids.extend(target.mem_ids) - c.mem_ids.extend(expr.mem_ids) + # sign extension for non-2^N datatypes + if expr.data_type == arch.DataType.S and expr_str.actual_size != expr_str.size: + target_size = expr.actual_size - return c + if isinstance(expr.size, int): + code_str = f'((etiss_int{target_size})(((etiss_int{target_size}){expr_str.code}) << ({target_size - expr.size})) >> ({target_size - expr.size}))' + else: + code_str = f'((etiss_int{target_size})(({expr_str.code}) << ({target_size} - {expr.size})) >> ({target_size} - {expr.size}))' + # normal type conversion + # TODO: check if behavior adheres to CoreDSL 2 spec + else: + code_str = f'({data_type_map[expr.data_type]}{expr.actual_size})({code_str})' -def binary_operation(self: behav.BinaryOperation, context: TransformerContext): - """Generate a binary expression""" + c = CodeString(code_str, expr_str.static, expr.size, expr.data_type == arch.DataType.S, expr_str.regs_affected, line_infos=[expr.line_info] + expr_str.line_infos) + c.mem_ids = expr_str.mem_ids - # generate LHS and RHS of the expression - left = self.left.generate(context) - op = self.op - right = self.right.generate(context) + return c - # convert staticness if needed - if not left.static and right.static and not right.is_literal: - right.code = context.make_static(right.code, right.signed) - if not right.static and left.static and not left.is_literal: - left.code = context.make_static(left.code, left.signed) + @generate.register + def _(self, expr: behav.NamedReference, context: TransformerContext): + """Generate a named reference""" - c = CodeString(f'{left.code} {op.value} {right.code}', left.static and right.static, left.size if left.size > right.size else right.size, - left.signed or right.signed, set.union(left.regs_affected, right.regs_affected), [self.line_info] + left.line_infos + right.line_infos) - # keep track of any memory accesses - c.mem_ids = left.mem_ids + right.mem_ids - return c + # extract referred object + referred_var = expr.reference -def unary_operation(self: behav.UnaryOperation, context: TransformerContext): - op = self.op - right = self.right.generate(context) + static = StaticType.NONE - c = CodeString(f'{op.value}({right.code})', right.static, right.size, right.signed, right.regs_affected, [self.line_info] + right.line_infos) - c.mem_ids = right.mem_ids - return c + name = referred_var.name + + # check if static name replacement is needed + if name in replacements.rename_static: + name = f'${{{name}}}' + static = StaticType.READ + + # check which type of reference has to be generated + if isinstance(referred_var, arch.Memory): + # architecture constant + if not static: + ref = "*" if len(referred_var.children) > 0 else "" + name = f"{ref}{replacements.default_prefix}{name}" + signed = False + size = referred_var.size + context.used_arch_data = True -def slice_operation(self: behav.SliceOperation, context: TransformerContext): - """Generate a slice expression""" + elif isinstance(referred_var, arch.BitFieldDescr): + # function argument + signed = referred_var.data_type == arch.DataType.S + size = referred_var.size + static = StaticType.READ + + elif isinstance(referred_var, arch.Scalar): + signed = referred_var.data_type == arch.DataType.S + size = referred_var.size + if context.static_scalars: + static = referred_var.static + + elif isinstance(referred_var, arch.Constant): + signed = referred_var.value < 0 + size = context.native_size + static = StaticType.READ + name = f'{referred_var.value}' + + elif isinstance(referred_var, arch.FnParam): + signed = referred_var.data_type == arch.DataType.S + size = referred_var.size + static = StaticType.RW - # generate expression to be sliced and lower and upper slice bound - expr = self.expr.generate(context) - left = self.left.generate(context) - right = self.right.generate(context) + elif isinstance(referred_var, arch.Intrinsic): + if context.ignore_static: + raise TypeError("intrinsic not allowed in function") - static = StaticType.NONE not in [x.static for x in (expr, left, right)] + signed = referred_var.data_type == arch.DataType.S + size = referred_var.size + static = StaticType.READ - if not static: - if expr.static and not expr.is_literal: - expr.code = context.make_static(expr.code, expr.signed) - if left.static and not left.is_literal: - left.code = context.make_static(left.code, left.signed) - if right.static and not right.is_literal: - right.code = context.make_static(right.code, right.signed) + if referred_var == context.intrinsics["__encoding_size"]: + name = str(context.instr_size // 8) - # slice with fixed integers if slice bounds are integers - try: - new_size = int(left.code.replace("U", "").replace("L", "")) - int(right.code.replace("U", "").replace("L", "")) + 1 - mask = (1 << (int(left.code.replace("U", "").replace("L", "")) - int(right.code.replace("U", "").replace("L", "")) + 1)) - 1 - mask_bits = log2(mask) - if mask_bits > 64: - mask64 = (1 << 64) - 1 - low = mask & mask64 - high = mask >> 64 - mask_code = f"((((etiss_int128)){hex(high)}ULL << 64) | {hex(low)}ULL)" else: - mask_code = f"{hex(mask)}ULL" - mask = f"{mask_code}" - simple_mask = True - - # slice with actual lower and upper bound code if not possible to slice with integers - except ValueError: - new_size = expr.size - mask = f"((1 << (({left.code}) - ({right.code}) + 1)) - 1)" - simple_mask = False - - if simple_mask and (int(right.code.replace("U", "").replace("L", "")) == 0): - # no need to shift zeros steps - code = f"(({expr.code}) & {mask})" - else: - code = f"((({expr.code}) >> ({right.code})) & {mask})" - c = CodeString(code, static, new_size, expr.signed, - set.union(expr.regs_affected, left.regs_affected, right.regs_affected), [self.line_info] + expr.line_infos + left.line_infos + right.line_infos) - c.mem_ids = expr.mem_ids + left.mem_ids + right.mem_ids - return c - -def concat_operation(self: behav.ConcatOperation, context: TransformerContext): - """Generate a concatenation expression""" - - # generate LHS and RHS operands - left: CodeString = self.left.generate(context) - right: CodeString = self.right.generate(context) - - if not left.static and right.static and not right.is_literal: - right.code = context.make_static(right.code, right.signed) - if not right.static and left.static and not left.is_literal: - left.code = context.make_static(left.code, left.signed) - - new_size = left.size + right.size - c = CodeString(f"((({left.code}) << {right.size}) | ({right.code}))", left.static and right.static, new_size, left.signed or right.signed, - set.union(left.regs_affected, right.regs_affected), [self.line_info] + left.line_infos + right.line_infos) - c.mem_ids = left.mem_ids + right.mem_ids - return c - -def named_reference(self: behav.NamedReference, context: TransformerContext): - """Generate a named reference""" - - # extract referred object - referred_var = self.reference - - static = StaticType.NONE - - name = referred_var.name - - # check if static name replacement is needed - if name in replacements.rename_static: - name = f'${{{name}}}' - static = StaticType.READ - - # check which type of reference has to be generated - if isinstance(referred_var, arch.Memory): - # architecture memory object (register, memory interface) - if not static: - ref = "*" if len(referred_var.children) > 0 else "" - name = f"{ref}{replacements.default_prefix}{name}" - signed = False - size = referred_var.size - context.used_arch_data = True - - elif isinstance(referred_var, arch.BitFieldDescr): - # instruction encoding operand - signed = referred_var.data_type == arch.DataType.S - size = referred_var.size - static = StaticType.READ - - elif isinstance(referred_var, arch.Scalar): - # scalar - signed = referred_var.data_type == arch.DataType.S - size = referred_var.size - if context.static_scalars: - static = referred_var.static - - elif isinstance(referred_var, arch.Constant): - # architecture constant - signed = referred_var.value < 0 - size = context.native_size - static = StaticType.READ - name = f'{referred_var.value}' - - elif isinstance(referred_var, arch.FnParam): - # function argument - signed = referred_var.data_type == arch.DataType.S - size = referred_var.size - static = StaticType.RW - - elif isinstance(referred_var, arch.Intrinsic): - if context.ignore_static: - raise TypeError("intrinsic not allowed in function") + raise TypeError("wrong type") - signed = referred_var.data_type == arch.DataType.S - size = referred_var.size - static = StaticType.READ + if context.ignore_static: + static = StaticType.RW - if referred_var == context.intrinsics["__encoding_size"]: - name = str(context.instr_size // 8) + return CodeString(name, static, size, signed, line_infos=expr.line_info) - else: - # should not happen - raise TypeError("wrong type") + @generate.register + def _(self, expr: behav.IndexedReference, context: TransformerContext): + """Generate an indexed reference expression (for register banks or memory).""" - if context.ignore_static: - static = StaticType.RW + name = expr.reference.name - c = CodeString(name, static, size, signed, line_infos=self.line_info) - #c.scalar = scalar - return c + # generate index expression + index = self.generate(expr.index, context) -def indexed_reference(self: behav.IndexedReference, context: TransformerContext): - """Generate an indexed reference expression (for register banks or memory).""" + referred_mem = expr.reference - name = self.reference.name + if isinstance(referred_mem, arch.Memory): + context.used_arch_data = True - # generate index expression - index = self.index.generate(context) + size = referred_mem.size - referred_mem = self.reference + # convert static index expression + index_code = index.code + if index.static and not context.ignore_static and not index.is_literal: + index.code = context.make_static(index.code, index.signed) - if isinstance(referred_mem, arch.Memory): - context.used_arch_data = True + if context.ignore_static: + static = StaticType.RW + else: + static = StaticType.NONE + + if arch.MemoryAttribute.IS_MAIN_MEM in referred_mem.attributes: + # generate memory access if main memory is accessed + size = expr.inferred_type._width + c = CodeString(f'{MEM_VAL_REPL}{context.mem_var_count}', static, size, False, line_infos=[expr.line_info] + index.line_infos) + if (expr.right != None): + # Use a simple base address on one site atleast for ranged_mem access. + # Index Codestring is forwarded into MemID class. + right = self.generate(expr.right, context) + if(type(expr.index) == behav.NamedReference): + c.mem_ids.append(MemID(referred_mem, context.mem_var_count, index, size)) + elif(type(expr.right) == behav.NamedReference): + c.mem_ids.append(MemID(referred_mem, context.mem_var_count, right, size)) + else: + raise(f"IndexedExpr ist needs to be static on one side: But Type is Left: {type(expr.index)} and Right: {type(expr.index)}") + else: + c.mem_ids.append(MemID(referred_mem, context.mem_var_count, index, size)) + context.mem_var_count += 1 + return c + + # generate normal indexed access if not + code_str = f'{replacements.prefixes.get(name, replacements.default_prefix)}{name}[{index.code}]' + if len(referred_mem.children) > 0: + code_str = '*' + code_str + c = CodeString(code_str, static, size, False, line_infos=[expr.line_info] + index.line_infos) + if arch.MemoryAttribute.IS_MAIN_REG in referred_mem.attributes: + c.regs_affected.add(index_code) + return c - size = referred_mem.size + @generate.register + def _(self, expr: behav.SliceOperation, context: TransformerContext): + """Generate a slice expression""" - # convert static index expression - index_code = index.code - if index.static and not context.ignore_static and not index.is_literal: - index.code = context.make_static(index.code, index.signed) + # generate expression to be sliced and lower and upper slice bound + expr_str = self.generate(expr.expr, context) + left = self.generate(expr.left, context) + right = self.generate(expr.right, context) - if context.ignore_static: - static = StaticType.RW - else: - static = StaticType.NONE + static = StaticType.NONE not in [x.static for x in (expr_str, left, right)] - if arch.MemoryAttribute.IS_MAIN_MEM in referred_mem.attributes: - # generate memory access if main memory is accessed - c = CodeString(f'{MEM_VAL_REPL}{context.mem_var_count}', static, size, False, line_infos=[self.line_info] + index.line_infos) - c.mem_ids.append(MemID(referred_mem, context.mem_var_count, index, size)) - context.mem_var_count += 1 - return c - - # generate normal indexed access if not - code_str = f'{replacements.prefixes.get(name, replacements.default_prefix)}{name}[{index.code}]' - if len(referred_mem.children) > 0: - code_str = '*' + code_str - if size != referred_mem.size: - code_str = f'(etiss_uint{size})' + code_str - c = CodeString(code_str, static, size, False, line_infos=[self.line_info] + index.line_infos) - if arch.MemoryAttribute.IS_MAIN_REG in referred_mem.attributes: - c.regs_affected.add(index_code) - return c - -def type_conv(self: behav.TypeConv, context: TransformerContext): - """Generate a type cast expression""" - - # generate the expression to be type-casted - expr = self.expr.generate(context) - - # if only width should be changed assume data type remains unchanged - if self.data_type is None: - self.data_type = arch.DataType.S if expr.signed else arch.DataType.U - - # if only data type should be changed assume width remains unchanged - if self.size is None: - self.size = expr.size - self.actual_size = expr.actual_size - - # save access size for memory access - if expr.is_mem_access: - if not expr.mem_corrected and expr.mem_ids[-1].access_size != self.size: - expr.mem_ids[-1].access_size = self.size - expr.size = self.size - expr.mem_corrected = True - elif expr.mem_ids[-1].access_size == self.size: - expr.mem_corrected = True - - code_str = expr.code - - # sign extension for non-2^N datatypes - if self.data_type == arch.DataType.S and expr.actual_size != expr.size: - target_size = self.actual_size - - if isinstance(self.size, int): - code_str = f'((etiss_int{target_size})(((etiss_int{target_size}){expr.code}) << ({target_size - expr.size})) >> ({target_size - expr.size}))' + if not static: + if expr_str.static and not expr_str.is_literal: + expr_str.code = context.make_static(expr_str.code, expr_str.signed) + if left.static and not left.is_literal: + left.code = context.make_static(left.code, left.signed) + if right.static and not right.is_literal: + right.code = context.make_static(right.code, right.signed) + + # slice with fixed integers if slice bounds are integers + try: + new_size = int(left.code.replace("U", "").replace("L", "")) - int(right.code.replace("U", "").replace("L", "")) + 1 + mask = (1 << (int(left.code.replace("U", "").replace("L", "")) - int(right.code.replace("U", "").replace("L", "")) + 1)) - 1 + mask_bits = log2(mask) + if mask_bits > 64: + mask64 = (1 << 64) - 1 + low = mask & mask64 + high = mask >> 64 + mask_code = f"((((etiss_int128)){hex(high)}ULL << 64) | {hex(low)}ULL)" + else: + mask_code = f"{hex(mask)}ULL" + mask = f"{mask_code}" + simple_mask = True + + # slice with actual lower and upper bound code if not possible to slice with integers + except ValueError: + new_size = expr_str.size + mask = f"((1 << (({left.code}) - ({right.code}) + 1)) - 1)" + simple_mask = False + + if simple_mask and (int(right.code.replace("U", "").replace("L", "")) == 0): + # no need to shift zeros steps + code = f"(({expr_str.code}) & {mask})" else: - code_str = f'((etiss_int{target_size})(({expr.code}) << ({target_size} - {expr.size})) >> ({target_size} - {expr.size}))' + code = f"((({expr_str.code}) >> ({right.code})) & {mask})" + c = CodeString(code, static, new_size, expr_str.signed, + set.union(expr_str.regs_affected, left.regs_affected, right.regs_affected), [expr.line_info] + expr_str.line_infos + left.line_infos + right.line_infos) + c.mem_ids = expr_str.mem_ids + left.mem_ids + right.mem_ids + return c - # normal type conversion - # TODO: check if behavior adheres to CoreDSL 2 spec - else: - code_str = f'({data_type_map[self.data_type]}{self.actual_size})({code_str})' + @generate.register + def _(self, expr: behav.ConcatOperation, context: TransformerContext): + """Generate a concatenation expression""" - c = CodeString(code_str, expr.static, self.size, self.data_type == arch.DataType.S, expr.regs_affected, line_infos=[self.line_info] + expr.line_infos) - c.mem_ids = expr.mem_ids - c.mem_corrected = expr.mem_corrected + # generate LHS and RHS operands + left: CodeString = self.generate(expr.left, context) + right: CodeString = self.generate(expr.right, context) - return c + if not left.static and right.static and not right.is_literal: + right.code = context.make_static(right.code, right.signed) + if not right.static and left.static and not left.is_literal: + left.code = context.make_static(left.code, left.signed) -def int_literal(self: behav.IntLiteral, context: TransformerContext): - """Generate an integer literal.""" + new_size = left.size + right.size + c = CodeString(f"((({left.code}) << {right.size}) | ({right.code}))", left.static and right.static, new_size, left.signed or right.signed, + set.union(left.regs_affected, right.regs_affected), [expr.line_info] + left.line_infos + right.line_infos) + c.mem_ids = left.mem_ids + right.mem_ids + return c - lit = int(self.value) - size = min(self.bit_size, 128) - sign = self.signed + @generate.register + def _(self, expr: behav.NumberLiteral, context: TransformerContext): + """Generate generic number literal. Currently unused.""" + lit = int(expr.value) + size = min(lit.bit_length(), 64) + sign = lit < 0 - minus = "" - if lit > 0 and sign and (lit >> (size - 1)) & 1: - minus = "-" + twocomp_lit = (lit + (1 << 64)) % (1 << 64) - twocomp_lit = (lit + (1 << size)) % (1 << size) + postfix = "U" if not sign else "" + postfix += "LL" - # add c postfix for large numbers - postfix = "U" if not sign else "" - postfix += "LL" - #postfix = "ULL" - #if size > 32: - # postfix += "L" - #if size > 64: - # postfix += "L" + return CodeString(str(twocomp_lit) + postfix, True, size, sign, line_infos=expr.line_info) - ret = CodeString(minus + str(lit) + postfix, True, size, sign, line_infos=self.line_info) - ret.is_literal = True - return ret + @generate.register + def _(self, expr: behav.IntLiteral, context: TransformerContext): + """Generate an integer literal.""" + lit = int(expr.value) + size = min(expr.bit_size, 128) + sign = expr.signed -def number_literal(self: behav.NumberLiteral, context: TransformerContext): - """Generate generic number literal. Currently unused.""" + minus = "" + if lit > 0 and sign and (lit >> (size - 1)) & 1: + minus = "-" - lit = int(self.value) - size = min(lit.bit_length(), 64) - sign = lit < 0 + _ = (lit + (1 << size)) % (1 << size) - twocomp_lit = (lit + (1 << 64)) % (1 << 64) + postfix = "U" if not sign else "" + postfix += "LL" - postfix = "U" if not sign else "" - postfix += "LL" - #postfix = "ULL" - #if size > 32: - # postfix += "L" - #if size > 64: - # postfix += "L" + ret = CodeString(minus + str(lit) + postfix, True, size, sign, line_infos=expr.line_info) + ret.is_literal = True + return ret - return CodeString(str(twocomp_lit) + postfix, True, size, sign, line_infos=self.line_info) + @generate.register + def _(self, expr: behav.StringLiteral, context: TransformerContext): + return CodeString(f'"{expr.value}"', StaticType.READ, None, False, line_infos=expr.line_info) -def group(self: behav.Group, context: TransformerContext): - """Generate a group of expressions.""" + @generate.register + def _(self, expr: behav.CodeLiteral, context: TransformerContext): + return CodeString(expr.val, False, context.native_size, False, line_infos=expr.line_info) - expr = self.expr.generate(context) - if isinstance(expr, CodeString): - expr.code = f'({expr.code})' - expr.line_infos.append(self.line_info) - else: - expr = f'({expr})' - return expr + @generate.register + def _(self, expr: behav.Operator, context: TransformerContext): + return expr.value -def operator(self: behav.Operator, context: TransformerContext): - return self.op + @generate.register + def _(self, expr: behav.Group, context: TransformerContext): + """Generate a group of expressions.""" -def code_literal(self: behav.CodeLiteral, context: TransformerContext): - return CodeString(self.val, False, context.native_size, False, line_infos=self.line_info) + expr_str = self.generate(expr.expr, context) + if isinstance(expr_str, CodeString): + expr_str.code = f'({expr_str.code})' + expr_str.line_infos.append(expr.line_info) + else: + expr_str = f'({expr_str})' + return expr_str diff --git a/m2isar/backends/etiss/instruction_utils.py b/m2isar/backends/etiss/instruction_utils.py index d8424f3d..e700483d 100644 --- a/m2isar/backends/etiss/instruction_utils.py +++ b/m2isar/backends/etiss/instruction_utils.py @@ -53,7 +53,6 @@ def __init__(self, code, static, size, signed, regs_affected=None, line_infos=[] self.signed = signed self.mem_ids = [] self.regs_affected = regs_affected if isinstance(regs_affected, set) else set() - self.mem_corrected = False self.is_literal = False self.function_calls = [] self.check_trap = False @@ -141,7 +140,7 @@ class TransformerContext: def __init__(self, constants: "dict[str, arch.Constant]", memories: "dict[str, arch.Memory]", memory_aliases: "dict[str, arch.Memory]", fields: "dict[str, arch.BitFieldDescr]", attributes: "list[arch.InstrAttribute]", functions: "dict[str, arch.Function]", - instr_size: int, native_size: int, arch_name: str, static_scalars: bool, intrinsics, generate_coverage: bool, ignore_static=False): + instr_size: int, native_size: int, arch_name: str, static_scalars: bool, intrinsics, generate_coverage: bool, ignore_static: bool = False): self.constants = constants self.memories = memories diff --git a/m2isar/backends/etiss/templates/etiss_instruction.mako b/m2isar/backends/etiss/templates/etiss_instruction.mako index c054e6ae..e7833f12 100644 --- a/m2isar/backends/etiss/templates/etiss_instruction.mako +++ b/m2isar/backends/etiss/templates/etiss_instruction.mako @@ -7,8 +7,8 @@ ## Technical University of Munich \ -${f'{"// "+instr_name+" ":-<80}'} -static InstructionDefinition ${instr_name.lower().replace('.', '_')}_${'_'.join(seen_fields)} ( +${f'{"// "+instr_name2+" ":-<80}'} +static InstructionDefinition ${instr_name2.lower().replace('.', '_')}_${'_'.join(seen_fields)} ( ISA${enc_idx}_${core_name}, "${instr_name.lower()}", (uint64_t) ${code_string}, diff --git a/m2isar/backends/etiss/writer.py b/m2isar/backends/etiss/writer.py index bd1b41ff..05074c86 100755 --- a/m2isar/backends/etiss/writer.py +++ b/m2isar/backends/etiss/writer.py @@ -28,6 +28,7 @@ from .instruction_writer import write_functions, write_instructions +# TODO: not required anymore for Python >= v3.9 class BooleanOptionalAction(argparse.Action): """A boolean optional action for argparse, supports automatic generation of --no-x flags.""" diff --git a/m2isar/backends/isa_manual/visitor.py b/m2isar/backends/isa_manual/visitor.py index 84c53e8a..d9bd3d84 100644 --- a/m2isar/backends/isa_manual/visitor.py +++ b/m2isar/backends/isa_manual/visitor.py @@ -9,161 +9,166 @@ """Visitor for traversing the behavior in the metamodel and generating the CoreDSL2 syntax.""" from m2isar.metamodel import arch, behav +from m2isar.metamodel.utils.ExprVisitor import ExprVisitor +from functools import singledispatchmethod # pylint: disable=unused-argument -def operation(self: behav.Operation, writer): - if len(self.statements) > 1: +class ISAmanualVisitor(ExprVisitor): + """Visitor for generating ISA manual CoreDSL2-like behavior text.""" + + @singledispatchmethod + def generate(self, expr: behav.BaseNode, context=None): + raise NotImplementedError(f"No visit method implemented for type {type(expr).__name__} in {type(self).__name__}") + + @generate.register + def _(self, expr: behav.Operation, writer): + if len(expr.statements) > 1: + writer.enter_block() + for stmt in expr.statements: + self.generate(stmt, writer) + if not isinstance(stmt, (behav.Conditional, behav.Operation)): + writer.write_line(";") + if len(expr.statements) > 1: + writer.leave_block() + + @generate.register + def _(self, expr: behav.BinaryOperation, writer): + self.generate(expr.left, writer) + writer.write(f" {expr.op.value} ") + self.generate(expr.right, writer) + + @generate.register + def _(self, expr: behav.SliceOperation, writer): + self.generate(expr.expr, writer) + writer.write("[") + self.generate(expr.left, writer) + writer.write(":") + self.generate(expr.right, writer) + writer.write("]") + + @generate.register + def _(self, expr: behav.ConcatOperation, writer): + self.generate(expr.left, writer) + writer.write(" :: ") + self.generate(expr.right, writer) + + @generate.register + def _(self, expr: behav.IntLiteral, writer): + writer.write(expr.value) + + @generate.register + def _(self, expr: behav.IntLiteral, writer): + writer.write(expr.value) + + @generate.register + def _(self, expr: behav.ScalarDefinition, writer): + writer.write_type(expr.scalar.data_type, expr.scalar.size) + writer.write(" ") + writer.write(expr.scalar.name) + if expr.scalar.value: + writer.write(" = ") + writer.write(expr.scalar.value) + + @generate.register + def _(self, expr: behav.Break, writer): + writer.write_line("break;") + + @generate.register + def _(self, expr: behav.Assignment, writer): + self.generate(expr.target, writer) + writer.write(" = ") + self.generate(expr.expr, writer) + + @generate.register + def _(self, expr: behav.Conditional, writer): + for i, stmt in enumerate(expr.stmts): + if i == 0: + writer.write("if (") + self.generate(expr.conds[i], writer) + writer.write(")") + elif 0 < i < len(expr.conds): + writer.write("else if(") + self.generate(expr.conds[i], writer) + writer.write(")") + else: + writer.write("else") + writer.enter_block() + self.generate(stmt, writer) + if not isinstance(stmt, (behav.Conditional, behav.Operation)): + writer.write_line(";") + nl = len(expr.stmts) > i + writer.leave_block(nl=nl) + + @generate.register + def _(self, expr: behav.Loop, writer): + writer.write("while (") + self.generate(expr.cond, writer) + writer.write(")") writer.enter_block() - for stmt in self.statements: - stmt.generate(writer) - if not isinstance(stmt, (behav.Conditional, behav.Operation)): - writer.write_line(";") - if len(self.statements) > 1: + for stmt in expr.stmts: + self.generate(stmt, writer) writer.leave_block() - -def binary_operation(self: behav.BinaryOperation, writer): - self.left = self.left.generate(writer) - writer.write(f" {self.op.value} ") - self.right = self.right.generate(writer) - - -def slice_operation(self: behav.SliceOperation, writer): - self.expr = self.expr.generate(writer) - writer.write("[") - self.left = self.left.generate(writer) - writer.write(":") - self.right = self.right.generate(writer) - writer.write("]") - - -def concat_operation(self: behav.ConcatOperation, writer): - self.left = self.left.generate(writer) - writer.write(" :: ") - self.right = self.right.generate(writer) - - -def number_literal(self: behav.IntLiteral, writer): - writer.write(self.value) - - -def int_literal(self: behav.IntLiteral, writer): - writer.write(self.value) - - -def scalar_definition(self: behav.ScalarDefinition, writer): - writer.write_type(self.scalar.data_type, self.scalar.size) - writer.write(" ") - writer.write(self.scalar.name) - if self.scalar.value: - writer.write(" = ") - writer.write(self.scalar.value) - - -def break_(self: behav.Break, writer): - writer.write_line("break;") - - -def assignment(self: behav.Assignment, writer): - self.target.generate(writer) - writer.write(" = ") - self.expr.generate(writer) - - -def conditional(self: behav.Conditional, writer): - for i, stmt in enumerate(self.stmts): - if i == 0: - writer.write("if (") - self.conds[i].generate(writer) - writer.write(")") - elif 0 < i < len(self.conds): - writer.write("else if(") - self.conds[i].generate(writer) - writer.write(")") + @generate.register + def _(self, expr: behav.Ternary, writer): + self.generate(expr.cond, writer) + writer.write(" ? ") + self.generate(expr.then_expr, writer) + writer.write(" : ") + self.generate(expr.else_expr, writer) + + @generate.register + def _(self, expr: behav.Return, writer): + writer.write("return") + if expr.expr is not None: + writer.write(" ") + self.generate(expr.expr, writer) + + @generate.register + def _(self, expr: behav.UnaryOperation, writer): + writer.write(expr.op.value) + self.generate(expr.right, writer) + + @generate.register + def _(self, expr: behav.NamedReference, writer): + writer.write(expr.reference.name) + if isinstance(expr.reference, (arch.Constant, arch.Memory, arch.Scalar)): + pass + + @generate.register + def _(self, expr: behav.IndexedReference, writer): + writer.write(expr.reference.name) + writer.write("[") + self.generate(expr.index, writer) + writer.write("]") + + @generate.register + def _(self, expr: behav.TypeConv, writer): + writer.write("(") + writer.write_type(expr.data_type, expr.size) + writer.write(")") + writer.write("(") + self.generate(expr.expr, writer) + writer.write(")") + + @generate.register + def _(self, expr: behav.Callable, writer): + ref = expr.ref_or_name + if isinstance(ref, arch.Function): + writer.write(ref.name) else: - writer.write("else") - writer.enter_block() - stmt.generate(writer) - if not isinstance(stmt, (behav.Conditional, behav.Operation)): - writer.write_line(";") - nl = len(self.stmts) > i - writer.leave_block(nl=nl) - - -def loop(self: behav.Loop, writer): - writer.write("while (") - self.cond.generate(writer) - writer.write(")") - writer.enter_block() - for stmt in self.stmts: - stmt.generate(writer) - writer.leave_block() - - -def ternary(self: behav.Ternary, writer): - self.cond.generate(writer) - writer.write(" ? ") - self.then_expr.generate(writer) - writer.write(" : ") - self.else_expr.generate(writer) - - -def return_(self: behav.Return, writer): - writer.write("return") - if self.expr is not None: - writer.write(" ") - self.expr = self.expr.generate(writer) - - -def unary_operation(self: behav.UnaryOperation, writer): - writer.write(self.op.value) - self.right.generate(writer) - - -def named_reference(self: behav.NamedReference, writer): - writer.write(self.reference.name) - if isinstance(self.reference, (arch.Constant, arch.Memory, arch.Scalar)): - # writer.track(self.reference.name) - pass - # if isinstance(self.reference, arch.Constant): - # return behav.IntLiteral(self.reference.value, self.reference.size, self.reference.signed) - - # if isinstance(self.reference, arch.Scalar) and self.reference.value is not None: - # return behav.IntLiteral(self.reference.value, self.reference.size, self.reference.data_type == arch.DataType.S) - - -def indexed_reference(self: behav.IndexedReference, writer): - writer.write(self.reference.name) - writer.write("[") - self.index.generate(writer) - writer.write("]") - - -def type_conv(self: behav.TypeConv, writer): - writer.write("(") - writer.write_type(self.data_type, self.size) - writer.write(")") - writer.write("(") - self.expr.generate(writer) - writer.write(")") - -def callable_(self: behav.Callable, writer): - ref = self.ref_or_name - if isinstance(ref, arch.Function): - writer.write(ref.name) - else: - raise NotImplementedError - writer.write("(") - for i, stmt in enumerate(self.args): - stmt.generate(writer) - if i < len(self.args) - 1: - writer.write(", ") - writer.write(")") - - -def group(self: behav.Group, writer): - writer.write("(") - self.expr.generate(writer) - writer.write(")") + raise NotImplementedError + writer.write("(") + for i, stmt in enumerate(expr.args): + self.generate(stmt, writer) + if i < len(expr.args) - 1: + writer.write(", ") + writer.write(")") + + @generate.register + def _(self, expr: behav.Group, writer): + writer.write("(") + self.generate(expr.expr, writer) + writer.write(")") diff --git a/m2isar/backends/isa_manual/writer.py b/m2isar/backends/isa_manual/writer.py index d9e4ca91..53c5e90f 100644 --- a/m2isar/backends/isa_manual/writer.py +++ b/m2isar/backends/isa_manual/writer.py @@ -18,9 +18,9 @@ from mako.template import Template from .utils import generate_encoding -from . import visitor +from .visitor import ISAmanualVisitor -from ...metamodel import M2_METAMODEL_VERSION, M2Model, arch, patch_model +from ...metamodel import M2_METAMODEL_VERSION, M2Model, arch from ...metamodel.utils.expr_preprocessor import (process_attributes, process_functions, process_instructions) @@ -158,9 +158,10 @@ def write_type(self, data_type, size): def write_behavior2(self, operation, drop_first=False): # Eliminate PC increment + visitor = ISAmanualVisitor() if drop_first: operation.statements = operation.statements[1:] - operation.generate(self) + visitor.generate(operation, self) def write_behavior(self, instruction): self.write("behavior: ") @@ -258,8 +259,8 @@ def main(): enc_str.append(f"{enc.name}[{enc.range.upper}:{enc.range.lower}]") writer = CoreDSL2Writer() - patch_model(visitor) - writer.write_behavior2(instr_def.operation, drop_first=True) + visitor = ISAmanualVisitor() + writer.write_behavior2(instr_def.operation, visitor, drop_first=True) behavior_text = writer.text asm_str = None if instr_def.assembly: diff --git a/m2isar/backends/viewer/treegen.py b/m2isar/backends/viewer/treegen.py index 83040a02..aef529a3 100644 --- a/m2isar/backends/viewer/treegen.py +++ b/m2isar/backends/viewer/treegen.py @@ -5,6 +5,9 @@ # Copyright (C) 2022 # Chair of Electrical Design Automation # Technical University of Munich +# +# Copyright (C) 2026 +# Modifed by JK TUW ECS """Generate a ttk.Treeview representation of a M2-ISA-R model structure.""" @@ -12,215 +15,268 @@ from ...metamodel import behav from .utils import TreeGenContext - +from ...metamodel.utils.ExprVisitor import ExprVisitor +from .utils import TreeGenContext +from functools import singledispatchmethod # pylint: disable=unused-argument -def operation(self: behav.Operation, context: "TreeGenContext"): - context.push(context.tree.insert(context.parent, tk.END, text="Operation")) - for stmt in self.statements: - stmt.generate(context) +class TreeGenVisitor(ExprVisitor): + """Visitor to generate a ttk.Treeview representation of a M2-ISA-R model structure.""" + @singledispatchmethod + def generate(self, expr : behav.BaseNode, context=None): + raise NotImplementedError(f"No visit method implemented for type {type(expr).__name__} in {type(expr).__name__}") - context.pop() + @generate.register + def visit_operation(self, expr: behav.Operation, context: "TreeGenContext"): + context.push(context.tree.insert(context.parent, tk.END, text="Operation")) -def block(self: behav.Block, context: "TreeGenContext"): - context.push(context.tree.insert(context.parent, tk.END, text="Block")) + for stmt in expr.statements: + self.generate(stmt, context) - for stmt in self.statements: - stmt.generate(context) + context.pop() - context.pop() + @generate.register + def visit_block(self, expr: behav.Block, context: "TreeGenContext"): + context.push(context.tree.insert(context.parent, tk.END, text="Block")) -def binary_operation(self: behav.BinaryOperation, context: "TreeGenContext"): - context.push(context.tree.insert(context.parent, tk.END, text="Binary Operation")) + for stmt in expr.statements: + self.generate(stmt, context) - context.push(context.tree.insert(context.parent, tk.END, text="Left")) - self.left.generate(context) - context.pop() + context.pop() - context.push(context.tree.insert(context.parent, tk.END, text="Right")) - self.right.generate(context) - context.pop() + @generate.register + def visit_binary_operation(self, expr: behav.BinaryOperation, context: "TreeGenContext"): + context.push(context.tree.insert(context.parent, tk.END, text="Binary Operation")) - context.tree.insert(context.parent, tk.END, text="Op", values=(self.op.value,)) + context.push(context.tree.insert(context.parent, tk.END, text="Left")) + self.generate(expr.left, context) + context.pop() - context.pop() + context.push(context.tree.insert(context.parent, tk.END, text="Right")) + self.generate(expr.right, context) + context.pop() -def slice_operation(self: behav.SliceOperation, context: "TreeGenContext"): - context.push(context.tree.insert(context.parent, tk.END, text="Slice Operation")) + context.tree.insert(context.parent, tk.END, text="Op", values=(expr.op.value,)) - context.push(context.tree.insert(context.parent, tk.END, text="Expr")) - self.expr.generate(context) - context.pop() + context.pop() - context.push(context.tree.insert(context.parent, tk.END, text="Left")) - self.left.generate(context) - context.pop() + @generate.register + def visit_slice_operation(self, expr: behav.SliceOperation, context: "TreeGenContext"): + context.push(context.tree.insert(context.parent, tk.END, text="Slice Operation")) - context.push(context.tree.insert(context.parent, tk.END, text="Right")) - self.right.generate(context) - context.pop() + context.push(context.tree.insert(context.parent, tk.END, text="Expr")) + self.generate(expr.expr, context) + context.pop() - context.pop() + context.push(context.tree.insert(context.parent, tk.END, text="Left")) + self.generate(expr.left, context) + context.pop() -def concat_operation(self: behav.ConcatOperation, context: "TreeGenContext"): - context.push(context.tree.insert(context.parent, tk.END, text="Concat Operation")) + context.push(context.tree.insert(context.parent, tk.END, text="Right")) + self.generate(expr.right, context) + context.pop() - context.push(context.tree.insert(context.parent, tk.END, text="Left")) - self.left.generate(context) - context.pop() + context.pop() + + + @generate.register + def concat_operation(self, expr: behav.ConcatOperation, context: "TreeGenContext"): + context.push(context.tree.insert(context.parent, tk.END, text="Concat Operation")) + + context.push(context.tree.insert(context.parent, tk.END, text="Left")) + self.generate(expr.left, context) + context.pop() - context.push(context.tree.insert(context.parent, tk.END, text="Right")) - self.right.generate(context) - context.pop() + context.push(context.tree.insert(context.parent, tk.END, text="Right")) + self.generate(expr.right, context) + context.pop() - context.pop() + context.pop() -def number_literal(self: behav.NumberLiteral, context: "TreeGenContext"): - context.tree.insert(context.parent, tk.END, text="Number Literal", values=(self.value,)) + @generate.register + def number_literal(self, expr: behav.NumberLiteral, context: "TreeGenContext"): + context.tree.insert(context.parent, tk.END, text="Number Literal", values=(expr.value,)) -def int_literal(self: behav.IntLiteral, context: "TreeGenContext"): - context.tree.insert(context.parent, tk.END, text="Int Literal", values=(self.value,)) + @generate.register + def int_literal(self, expr: behav.IntLiteral, context: "TreeGenContext"): + context.tree.insert(context.parent, tk.END, text="Int Literal", values=(expr.value,)) -def scalar_definition(self: behav.ScalarDefinition, context: "TreeGenContext"): - context.tree.insert(context.parent, tk.END, text="Scalar Definition", values=(self.scalar.name,)) + @generate.register + def scalar_definition(self, expr: behav.ScalarDefinition, context: "TreeGenContext"): + context.tree.insert(context.parent, tk.END, text="Scalar Definition", values=(expr.scalar.name,)) -def break_(self: behav.Break, context: "TreeGenContext"): - context.tree.insert(context.parent, tk.END, text="Break") + @generate.register + def break_(self, expr: behav.Break, context: "TreeGenContext"): + context.tree.insert(context.parent, tk.END, text="Break") -def assignment(self: behav.Assignment, context: "TreeGenContext"): - context.push(context.tree.insert(context.parent, tk.END, text="Assignment")) + @generate.register + def assignment(self, expr: behav.Assignment, context: "TreeGenContext"): + context.push(context.tree.insert(context.parent, tk.END, text="Assignment")) - context.push(context.tree.insert(context.parent, tk.END, text="Target")) - self.target.generate(context) - context.pop() + context.push(context.tree.insert(context.parent, tk.END, text="Target")) + self.generate(expr.target, context) + context.pop() + + context.push(context.tree.insert(context.parent, tk.END, text="Expr")) + self.generate(expr.expr, context) + context.pop() - context.push(context.tree.insert(context.parent, tk.END, text="Expr")) - self.expr.generate(context) - context.pop() + context.pop() - context.pop() + @generate.register + def conditional(self, expr: behav.Conditional, context: "TreeGenContext"): + context.push(context.tree.insert(context.parent, tk.END, text="Conditional")) -def conditional(self: behav.Conditional, context: "TreeGenContext"): - context.push(context.tree.insert(context.parent, tk.END, text="Conditional")) + context.push(context.tree.insert(context.parent, tk.END, text="Conditions")) + for cond in expr.conds: + self.generate(cond, context) + context.pop() - context.push(context.tree.insert(context.parent, tk.END, text="Conditions")) - for cond in self.conds: - cond.generate(context) - context.pop() + context.push(context.tree.insert(context.parent, tk.END, text="Statements")) + for stmt in expr.stmts: + self.generate(stmt, context) + context.pop() - context.push(context.tree.insert(context.parent, tk.END, text="Statements")) - for stmt in self.stmts: - stmt.generate(context) - context.pop() + context.pop() - context.pop() + @generate.register + def loop(self, expr: behav.Loop, context: "TreeGenContext"): + context.push(context.tree.insert(context.parent, tk.END, text="Loop")) -def loop(self: behav.Loop, context: "TreeGenContext"): - context.push(context.tree.insert(context.parent, tk.END, text="Loop")) + context.tree.insert(context.parent, tk.END, text="Post Test", values=(expr.post_test,)) - context.tree.insert(context.parent, tk.END, text="Post Test", values=(self.post_test,)) + context.push(context.tree.insert(context.parent, tk.END, text="Condition")) + self.generate(expr.cond, context) + context.pop() - context.push(context.tree.insert(context.parent, tk.END, text="Condition")) - self.cond.generate(context) - context.pop() + context.push(context.tree.insert(context.parent, tk.END, text="Statements")) + for stmt in expr.stmts: + self.generate(stmt, context) + context.pop() - context.push(context.tree.insert(context.parent, tk.END, text="Statements")) - for stmt in self.stmts: - stmt.generate(context) - context.pop() + context.pop() - context.pop() + @generate.register + def ternary(self, expr: behav.Ternary, context: "TreeGenContext"): + context.push(context.tree.insert(context.parent, tk.END, text="Ternary")) -def ternary(self: behav.Ternary, context: "TreeGenContext"): - context.push(context.tree.insert(context.parent, tk.END, text="Ternary")) + context.push(context.tree.insert(context.parent, tk.END, text="Cond")) + self.generate(expr.cond, context) + context.pop() - context.push(context.tree.insert(context.parent, tk.END, text="Cond")) - self.cond.generate(context) - context.pop() + context.push(context.tree.insert(context.parent, tk.END, text="Then Expression")) + self.generate(expr.then_expr, context) + context.pop() - context.push(context.tree.insert(context.parent, tk.END, text="Then Expression")) - self.then_expr.generate(context) - context.pop() + context.push(context.tree.insert(context.parent, tk.END, text="Else Expression")) + self.generate(expr.else_expr, context) + context.pop() - context.push(context.tree.insert(context.parent, tk.END, text="Else Expression")) - self.else_expr.generate(context) - context.pop() + context.pop() - context.pop() + @generate.register + def return_(self, expr: behav.Return, context: "TreeGenContext"): + context.push(context.tree.insert(context.parent, tk.END, text="Return")) -def return_(self: behav.Return, context: "TreeGenContext"): - context.push(context.tree.insert(context.parent, tk.END, text="Return")) + if expr.expr is not None: + context.push(context.tree.insert(context.parent, tk.END, text="Expression")) + self.generate(expr.expr, context) + context.pop() - if self.expr is not None: - context.push(context.tree.insert(context.parent, tk.END, text="Expression")) - self.expr.generate(context) context.pop() - context.pop() + @generate.register + def unary_operation(self, expr: behav.UnaryOperation, context: "TreeGenContext"): + context.push(context.tree.insert(context.parent, tk.END, text="Unary Operation")) -def unary_operation(self: behav.UnaryOperation, context: "TreeGenContext"): - context.push(context.tree.insert(context.parent, tk.END, text="Unary Operation")) + context.push(context.tree.insert(context.parent, tk.END, text="Right")) + self.generate(expr.right, context) + context.pop() - context.push(context.tree.insert(context.parent, tk.END, text="Right")) - self.right.generate(context) - context.pop() + context.tree.insert(context.parent, tk.END, text="Op", values=(expr.op.value,)) - context.tree.insert(context.parent, tk.END, text="Op", values=(self.op.value,)) + context.pop() - context.pop() + @generate.register + def named_reference(self, expr: behav.NamedReference, context: "TreeGenContext"): + context.tree.insert(context.parent, tk.END, text="Named Reference", values=(f"{expr.reference}",)) -def named_reference(self: behav.NamedReference, context: "TreeGenContext"): - context.tree.insert(context.parent, tk.END, text="Named Reference", values=(f"{self.reference}",)) + @generate.register + def indexed_reference(self, expr: behav.IndexedReference, context: "TreeGenContext"): + context.push(context.tree.insert(context.parent, tk.END, text="Indexed Reference")) -def indexed_reference(self: behav.IndexedReference, context: "TreeGenContext"): - context.push(context.tree.insert(context.parent, tk.END, text="Indexed Reference")) + context.tree.insert(context.parent, tk.END, text="Reference", values=(f"{expr.reference}",)) - context.tree.insert(context.parent, tk.END, text="Reference", values=(f"{self.reference}",)) + # If LHS is a complex expression => RHS also an expression + # TODO: Little Endian only so far supported + if (expr.reference.name == "MEM"): + if expr.right != None: + context.push(context.tree.insert(context.parent, tk.END, text="IndexRange")) - context.push(context.tree.insert(context.parent, tk.END, text="Index")) - self.index.generate(context) - context.pop() + context.push(context.tree.insert(context.parent, tk.END, text="Left")) + self.generate(expr.index, context) + context.pop() + assert(type(expr.right) == behav.NamedReference) + context.push(context.tree.insert(context.parent, tk.END, text="Right")) + self.generate(expr.right, context) + context.pop() - context.pop() + context.pop() -def type_conv(self: behav.TypeConv, context: "TreeGenContext"): - context.push(context.tree.insert(context.parent, tk.END, text="Type Conv")) + else: + context.push(context.tree.insert(context.parent, tk.END, text="Index")) + self.generate(expr.index, context) + context.pop() - context.tree.insert(context.parent, tk.END, text="Type", values=(self.data_type,)) - context.tree.insert(context.parent, tk.END, text="Size", values=(self.size,)) + else: + context.push(context.tree.insert(context.parent, tk.END, text="Index")) + self.generate(expr.index, context) + context.pop() - context.push(context.tree.insert(context.parent, tk.END, text="Expr")) - self.expr.generate(context) - context.pop() + context.pop() - context.pop() + @generate.register + def type_conv(self, expr: behav.TypeConv, context: "TreeGenContext"): + context.push(context.tree.insert(context.parent, tk.END, text="Type Conv")) -def callable_(self: behav.Callable, context: "TreeGenContext"): - context.push(context.tree.insert(context.parent, tk.END, text="Callable", values=(self.ref_or_name.name,))) + context.tree.insert(context.parent, tk.END, text="Type", values=(expr.data_type,)) + context.tree.insert(context.parent, tk.END, text="Size", values=(expr.size,)) + + context.push(context.tree.insert(context.parent, tk.END, text="Expr")) + self.generate(expr.expr, context) + context.pop() - for arg, arg_descr in zip(self.args, self.ref_or_name.args): - context.push(context.tree.insert(context.parent, tk.END, text="Arg", values=(arg_descr,))) - arg.generate(context) context.pop() - context.pop() + @generate.register + def callable_(self, expr: behav.Callable, context: "TreeGenContext"): + context.push(context.tree.insert(context.parent, tk.END, text="Callable", values=(expr.ref_or_name.name,))) -def procedure_call(self: behav.ProcedureCall, context: TransformerContext): - context.push(context.tree.insert(context.parent, tk.END, text="ProcedureCall", values=(self.ref_or_name.name,))) + for arg, arg_descr in zip(expr.args, expr.ref_or_name.args): + context.push(context.tree.insert(context.parent, tk.END, text="Arg", values=(arg_descr,))) + self.generate(arg, context) + context.pop() - for arg, arg_descr in zip(self.args, self.ref_or_name.args): - context.push(context.tree.insert(context.parent, tk.END, text="Arg", values=(arg_descr,))) - arg.generate(context) context.pop() - context.pop() + @generate.register + def procedure_call(self, expr: behav.ProcedureCall, context: "TreeGenContext"): + context.push(context.tree.insert(context.parent, tk.END, text="ProcedureCall", values=(expr.ref_or_name.name,))) -def group(self: behav.Group, context: "TreeGenContext"): - context.push(context.tree.insert(context.parent, tk.END, text="Group")) + for arg, arg_descr in zip(expr.args, expr.ref_or_name.args): + context.push(context.tree.insert(context.parent, tk.END, text="Arg", values=(arg_descr,))) + self.generate(arg, context) + context.pop() - context.push(context.tree.insert(context.parent, tk.END, text="Expr")) - self.expr.generate(context) - context.pop() + context.pop() + + @generate.register + def group(self, expr: behav.Group, context: "TreeGenContext"): + context.push(context.tree.insert(context.parent, tk.END, text="Group")) - context.pop() + context.push(context.tree.insert(context.parent, tk.END, text="Expr")) + self.generate(expr.expr, context) + context.pop() + + context.pop() diff --git a/m2isar/backends/viewer/viewer.py b/m2isar/backends/viewer/viewer.py index 4178d3c8..fb897fe8 100644 --- a/m2isar/backends/viewer/viewer.py +++ b/m2isar/backends/viewer/viewer.py @@ -5,6 +5,9 @@ # Copyright (C) 2022 # Chair of Electrical Design Automation # Technical University of Munich +# +# Copyright (C) 2026 +# Modifed by JK TUW ECS """Viewer tool to visualize an M2-ISA-R model hierarchy.""" @@ -18,11 +21,11 @@ from m2isar.backends.viewer.utils import TreeGenContext -from ...metamodel import M2_METAMODEL_VERSION, M2Model, arch, patch_model +from ...metamodel import M2_METAMODEL_VERSION, M2Model, arch from ...metamodel.utils.expr_preprocessor import (process_attributes, process_functions, process_instructions) -from . import treegen +from .treegen import TreeGenVisitor logger = logging.getLogger("viewer") @@ -83,7 +86,7 @@ def main(): process_attributes(core) # load Ttk TreeView transformer functions - patch_model(treegen) + visitor = TreeGenVisitor() # create main Tk window root = tk.Tk() @@ -141,7 +144,7 @@ def main(): attr_id = tree.insert(attrs_id, tk.END, text=attr) for op in ops: context = TreeGenContext(tree, attr_id) - op.generate(context) + visitor.generate(op, context) # generate and add parameters params_id = tree.insert(fn_id, tk.END, text="Parameters") @@ -151,7 +154,7 @@ def main(): # generate and add function behavior context = TreeGenContext(tree, fn_id) - fn_def.operation.generate(context) + visitor.generate(fn_def.operation, context) # group instructions by size instrs_by_size = defaultdict(dict) @@ -193,11 +196,13 @@ def main(): attr_id = tree.insert(attrs_id, tk.END, text=attr.name) for op in ops: context = TreeGenContext(tree, attr_id) - op.generate(context) + visitor.generate(op, context) # generate behavior context = TreeGenContext(tree, instr_id) - instr_def.operation.generate(context) + visitor.generate(instr_def.operation, context) + + #tree.tag_configure("mono", font=font.nametofont("TkFixedFont")) diff --git a/m2isar/frontends/coredsl2/architecture_model_builder.py b/m2isar/frontends/coredsl2/architecture_model_builder.py index 22bcfd6e..3739fd03 100644 --- a/m2isar/frontends/coredsl2/architecture_model_builder.py +++ b/m2isar/frontends/coredsl2/architecture_model_builder.py @@ -16,15 +16,16 @@ from ...metamodel.code_info import FunctionInfoFactory from .parser_gen import CoreDSL2Parser, CoreDSL2Visitor from .utils import RADIX, SHORTHANDS, SIGNEDNESS +from .expr_interpreter import ExprInterpreterVisitor logger = logging.getLogger("arch_builder") - +exprInterpretVisitor = ExprInterpreterVisitor() class ArchitectureModelBuilder(CoreDSL2Visitor): """ANTLR visitor to build an M2-ISA-R architecture model of a CoreDSL 2 specification.""" _constants: "dict[str, arch.Constant]" - _instructions: "dict[str, arch.Instruction]" + _instructions: "list[arch.Instruction]" _functions: "dict[str, arch.Function]" _always_blocks: "dict[str, arch.AlwaysBlock]" _instruction_sets: "dict[str, arch.InstructionSet]" @@ -38,7 +39,8 @@ class ArchitectureModelBuilder(CoreDSL2Visitor): def __init__(self): super().__init__() self._constants = {} - self._instructions = {} + # self._instructions = {} + self._instructions = [] self._functions = {} self._always_blocks = {} self._instruction_sets = {} @@ -84,7 +86,8 @@ def visitInstruction_set(self, ctx: CoreDSL2Parser.Instruction_setContext): constants = {} memories = {} functions = {} - instructions = {} + # instructions = {} + instructions = [] # group contents by type for item in contents: @@ -96,7 +99,8 @@ def visitInstruction_set(self, ctx: CoreDSL2Parser.Instruction_setContext): functions[item.name] = item item.ext_name = name elif isinstance(item, arch.Instruction): - instructions[(item.code, item.mask)] = item + # instructions[(item.code, item.mask)] = item + instructions.append(item) item.ext_name = name elif isinstance(item, arch.AlwaysBlock): pass @@ -172,17 +176,18 @@ def visitInstruction(self, ctx: CoreDSL2Parser.InstructionContext): i = arch.Instruction(ctx.name.text, attributes, encoding, mnemonic, assembly, ctx.behavior, None) self._instr_classes.add(i.size) - instr_id = (i.code, i.mask) + # instr_id = (i.code, i.mask) opcode_str = "{code:0{width}x}:{mask:0{width}x}".format(code=i.code, mask=i.mask, width=i.size//4) i.function_info = FunctionInfoFactory.make(ctx.start.source[1].fileName, ctx.start.start, ctx.stop.stop, ctx.start.line, ctx.stop.line, f"instr_{i.name}_{opcode_str}") # check for duplicate instructions - if instr_id in self._instructions: - self._overwritten_instrs.append((self._instructions[instr_id], i)) + # if instr_id in self._instructions: + # self._overwritten_instrs.append((self._instructions[instr_id], i)) # keep track of instruction - self._instructions[instr_id] = i + # self._instructions[instr_id] = i + self._instructions.append(i) return i @@ -379,7 +384,7 @@ def visitDeclaration(self, ctx: CoreDSL2Parser.DeclarationContext): # attach init value to memory object if init is not None: - m._initval[None] = init.generate(None) + m._initval[None] = exprInterpretVisitor.generate(init, None) if arch.MemoryAttribute.IS_MAIN_REG in attributes: self._main_reg_file = m @@ -490,13 +495,13 @@ def visitAssignment_expression(self, ctx: CoreDSL2Parser.Assignment_expressionCo # if LHS is a reference, assign RHS as its default value if isinstance(left, behav.NamedReference): if isinstance(left.reference, arch.Constant): - left.reference.value = right.generate(None) + left.reference.value = exprInterpretVisitor.generate(right, None) elif isinstance(left.reference, arch.Memory): - left.reference._initval[None] = right.generate(None) + left.reference._initval[None] = exprInterpretVisitor.generate(right, None) elif isinstance(left, behav.IndexedReference): - left.reference._initval[left.index.generate(None)] = right.generate(None) + left.reference._initval[exprInterpretVisitor.generate(left.index, None)] = exprInterpretVisitor.generate(right, None) def visitAttribute(self, ctx: CoreDSL2Parser.AttributeContext): """Generate an attribute.""" diff --git a/m2isar/frontends/coredsl2/behavior_model_builder.py b/m2isar/frontends/coredsl2/behavior_model_builder.py index 4cb6fb90..8e308a4d 100644 --- a/m2isar/frontends/coredsl2/behavior_model_builder.py +++ b/m2isar/frontends/coredsl2/behavior_model_builder.py @@ -18,6 +18,9 @@ from ...metamodel.utils import StaticType from .parser_gen import CoreDSL2Parser, CoreDSL2Visitor from .utils import BOOLCONST, RADIX, SHORTHANDS, SIGNEDNESS +from .expr_interpreter import ExprInterpreterVisitor + +exprInterpretVisitor = ExprInterpreterVisitor() if TYPE_CHECKING: from ...metamodel.code_info import LineInfo @@ -283,7 +286,11 @@ def visitSlice_expression(self, ctx: CoreDSL2Parser.Slice_expressionContext): right = self.visit(ctx.right) if ctx.right else left if isinstance(expr, behav.NamedReference) and isinstance(expr.reference, arch.Memory) and expr.reference.data_range.length > 1: - return behav.IndexedReference(expr.reference, left, right, LineInfoFactory.make(ctx.start.source[1].fileName, ctx.start.start, ctx.stop.stop, ctx.start.line, ctx.stop.line)) + #Dont duplicate index to differentiate between index and ranged access + if right == left: + return behav.IndexedReference(expr.reference, left, None, LineInfoFactory.make(ctx.start.source[1].fileName, ctx.start.start, ctx.stop.stop, ctx.start.line, ctx.stop.line)) + else: + return behav.IndexedReference(expr.reference, left, right, LineInfoFactory.make(ctx.start.source[1].fileName, ctx.start.start, ctx.stop.stop, ctx.start.line, ctx.stop.line)) else: return behav.SliceOperation(expr, left, right, LineInfoFactory.make(ctx.start.source[1].fileName, ctx.start.start, ctx.stop.stop, ctx.start.line, ctx.stop.line)) @@ -411,7 +418,7 @@ def visitInteger_type(self, ctx: CoreDSL2Parser.Integer_typeContext): width = self.visit(ctx.shorthand) if isinstance(width, behav.BaseNode): - width = width.generate(None) + width = exprInterpretVisitor.generate(width, None) else: raise M2TypeError("width has wrong type") diff --git a/m2isar/frontends/coredsl2/expr_interpreter.py b/m2isar/frontends/coredsl2/expr_interpreter.py index abee4a96..582b85bb 100644 --- a/m2isar/frontends/coredsl2/expr_interpreter.py +++ b/m2isar/frontends/coredsl2/expr_interpreter.py @@ -10,28 +10,47 @@ from ... import M2ValueError from ...metamodel import arch, behav - - -def group(self: behav.Group, context): - return self.expr.generate(context) - -def int_literal(self: behav.IntLiteral, context): - return self.value - -def named_reference(self: behav.NamedReference, context): - if isinstance(self.reference, arch.Constant) and self.reference.value is not None: - return self.reference.value - raise M2ValueError("non-interpretable value encountered") - -def indexed_reference(self: behav.IndexedReference, context): - idx = self.index.generate(context) - return self.reference._initval[idx] - -def binary_operation(self: behav.BinaryOperation, context): - left = self.left.generate(context) - right = self.right.generate(context) - return int(eval(f"{left}{self.op.value}{right}")) - -def unary_operation(self: behav.UnaryOperation, context): - right = self.right.generate(context) - return int(eval(f"{self.op.value}{right}")) +from ...metamodel.utils.ExprVisitor import ExprVisitor +from functools import singledispatchmethod + + +class ExprInterpreterVisitor(ExprVisitor): + """Visitor for evaluating parse-time constant expressions.""" + + @singledispatchmethod + def generate(self, expr: behav.BaseNode, context=None): + raise NotImplementedError(f"No visit method implemented for type {type(expr).__name__} in {type(self).__name__}") + + @generate.register + def _(self, expr: behav.Group, context): + return self.generate(expr.expr, context) + + @generate.register + def _(self, expr: behav.NumberLiteral, context): + return expr.value + + @generate.register + def _(self, expr: behav.IntLiteral, context): + return expr.value + + @generate.register + def _(self, expr: behav.NamedReference, context): + if isinstance(expr.reference, arch.Constant) and expr.reference.value is not None: + return expr.reference.value + raise M2ValueError("non-interpretable value encountered") + + @generate.register + def _(self, expr: behav.IndexedReference, context): + idx = self.generate(expr.index, context) + return expr.reference._initval[idx] + + @generate.register + def _(self, expr: behav.BinaryOperation, context): + left = self.generate(expr.left, context) + right = self.generate(expr.right, context) + return int(eval(f"{left}{expr.op.value}{right}")) + + @generate.register + def _(self, expr: behav.UnaryOperation, context): + right = self.generate(expr.right, context) + return int(eval(f"{expr.op.value}{right}")) diff --git a/m2isar/frontends/coredsl2/parser.py b/m2isar/frontends/coredsl2/parser.py index bcc88501..853cabbc 100644 --- a/m2isar/frontends/coredsl2/parser.py +++ b/m2isar/frontends/coredsl2/parser.py @@ -14,15 +14,27 @@ import sys from ... import M2Error, M2SyntaxError -from ...metamodel import (M2_METAMODEL_VERSION, M2Model, arch, behav, - patch_model) +from ...metamodel import M2_METAMODEL_VERSION, M2Model, arch, behav +from ...metamodel.utils.expr_simplifier import ExprSimplifierVisitor from ...metamodel.code_info import CodeInfoBase -from . import expr_interpreter from .architecture_model_builder import ArchitectureModelBuilder from .behavior_model_builder import BehaviorModelBuilder from .importer import recursive_import from .load_order import LoadOrder from .utils import make_parser +from ...backends.etiss.writer import BooleanOptionalAction # TODO: refactor +from ...transforms.infer_types.transform import infer_types +from ...transforms.validate_behav.validate import validate_behav +from ...warnings import add_warnings_flags, KNOWN_WARNINGS + +def try_eval_bool(operation, constants: "dict[str, arch.Constant]", memories: "dict[str, arch.Memory]", memory_aliases: "dict[str, arch.Memory]", + fields: "dict[str, arch.BitFieldDescr]", functions: "dict[str, arch.Function]", warned_fns: "set[str]"): + simplifier = ExprSimplifierVisitor() + # TODO: switch to ExprInterpreterVisitor? + op = simplifier.generate(operation, None) + if not isinstance(op, behav.IntLiteral): + return None + return op.value != 0 def main(): @@ -30,6 +42,9 @@ def main(): parser.add_argument("top_level", help="The top-level CoreDSL file.") parser.add_argument("--log", default="info", choices=["critical", "error", "warning", "info", "debug"]) parser.add_argument("-I", dest="includes", action="append", default=[], help="Extra include directories") + parser.add_argument('--infer-types', action=BooleanOptionalAction, default=True, help="Run type inference after parsing.") + parser.add_argument('--validate', action=BooleanOptionalAction, default=False, help="Run validator after parsing.") + add_warnings_flags(parser, KNOWN_WARNINGS, KNOWN_WARNINGS) # only if --validate args = parser.parse_args() @@ -67,8 +82,6 @@ def main(): temp_save = {} models: "dict[str, arch.CoreDef]" = {} - patch_model(expr_interpreter) - for core_name, core_def in cores.items(): logger.info('building architecture model for core %s', core_name) try: @@ -77,8 +90,8 @@ def main(): except M2Error as e: logger.critical("Error building architecture model of core %s: %s", core_name, e) - for orig, overwritten in arch_builder._overwritten_instrs: - logger.warning("instr %s from extension %s was overwritten by %s from %s", orig.name, orig.ext_name, overwritten.name, overwritten.ext_name) + # for orig, overwritten in arch_builder._overwritten_instrs: + # logger.warning("instr %s from extension %s was overwritten by %s from %s", orig.name, orig.ext_name, overwritten.name, overwritten.ext_name) temp_save[core_name] = (c, arch_builder) models[core_name] = c[-1] @@ -204,7 +217,11 @@ def main(): logger.debug("generating instruction behavior") - for instr_def in core_def.instructions.values(): + assert isinstance(core_def.instructions, list) + instructions_by_enc = {} + overwritten_instrs: "list[tuple[arch.Instruction, arch.Instruction]]" = [] + # for instr_def in core_def.instructions.values(): + for instr_def in core_def.instructions: logger.debug("generating instruction %s", instr_def.name) logger.debug("generating attributes") @@ -221,6 +238,17 @@ def main(): sys.exit(1) instr_def.attributes[attr_name] = ops + if arch.InstrAttribute.ENABLE in instr_def.attributes: + enable_attr = instr_def.attributes[arch.InstrAttribute.ENABLE] + assert isinstance(enable_attr, list) + assert len(enable_attr) == 1 + enable_attr = enable_attr[0] + enable = try_eval_bool(enable_attr, core_def.constants, core_def.memories, core_def.memory_aliases, instr_def.fields, core_def.functions, warned_fns) + if enable is not None: + assert isinstance(enable, bool) + instr_def.attributes.pop(arch.InstrAttribute.ENABLE) + if not enable: + continue behav_builder = BehaviorModelBuilder(core_def.constants, core_def.memories, core_def.memory_aliases, instr_def.fields, core_def.functions, warned_fns) @@ -250,15 +278,33 @@ def main(): #op.statements.insert(0, pc_inc) op.statements = always_block_statements + op.statements instr_def.operation = op + instr_id = (instr_def.code, instr_def.mask) + # check for duplicate instructions + if instr_id in instructions_by_enc: + overwritten_instrs.append((instructions_by_enc[instr_id], instr_def)) + instructions_by_enc[instr_id] = instr_def + core_def.instructions = instructions_by_enc + assert isinstance(core_def.instructions, dict) + for orig, overwritten in overwritten_instrs: + logger.warning("instr %s from extension %s was overwritten by %s from %s", orig.name, orig.ext_name, overwritten.name, overwritten.ext_name) + + model_obj = M2Model( + M2_METAMODEL_VERSION, + models, + {}, + CodeInfoBase.database + ) + + if args.infer_types or args.validate: + logger.info("Running type inference") + model_obj = infer_types(model_obj) + if args.validate: + logger.info("Running validator") + warnings_info = args.warnings + validate_behav(model_obj, warnings_info) logger.info("dumping model") with open(model_path / (abs_top_level.stem + '.m2isarmodel'), 'wb') as f: - model_obj = M2Model( - M2_METAMODEL_VERSION, - models, - {}, - CodeInfoBase.database - ) pickle.dump(model_obj, f) diff --git a/m2isar/metamodel/__init__.py b/m2isar/metamodel/__init__.py index 0a304296..97522984 100644 --- a/m2isar/metamodel/__init__.py +++ b/m2isar/metamodel/__init__.py @@ -13,23 +13,28 @@ Also included are preprocessing functions, mostly to simplify a model and to extract information about scalar and function staticness as well as exceptions. -Any model traversal should use the :func:`patch_model` function and a module including the needed -transformations. :func:`patch_model` monkey patches transformation functions into the classes of the -behavior model, therefore separating model code from transformation code. For examples on how -these transformation functions look like, see either the modules in :mod:`m2isar.metamodel.utils` -or the main code generation module :mod:`m2isar.backends.etiss.instruction_transform`. For a description -of the monkey patching, see :func:`patch_model`. +Any model traversal should use a :subclass`` of the ExprVisitor MetaClass including the needed +transformations/ recursive calls. For examples on how the recursive generate functions look like, +see the ExprVisitor MetaClass with its default generate function in :mod:`m2isar.metamodel.utils.ExprVisitor` +Multiple examples of such visitors can be found in the :mod:`m2isar.backends` submodules, +e.g. :mod:`m2isar.backends.etiss.instruction_transform` or :mod:`m2isar.backends.isa_manual.visitor`. +But also the metamodel builders in :mod:`m2isar.frontends.coredsl2` use this approach to build +the model from the parse tree. As well as the Frontend. Usually a M2-ISA-R behavioral model is traversed from top to bottom. Necessary contextual -information is passed to lower levels by a user-defined `context` object. Each object should then +information is either stored globally within the self object of the ExprVisitor or passed to lower levels +by a user-defined `context` object for local stack-based information. Each object should then generate a piece of output (e.g. c-code for ETISS) and return it to its parent. Value passing between generation functions is completely user-defined, :mod:`m2isar.backends.etiss.instruction_transform` uses complex objects in lower levels of translation and switches to strings for the two highest levels of the hierarchy. """ +import pickle import inspect import logging +from typing import Union +from pathlib import Path from dataclasses import dataclass from . import arch, behav, code_info @@ -91,3 +96,36 @@ def __post_init__(self): self.line_infos[idx] = c elif isinstance(c, code_info.FunctionInfo): self.function_infos[idx] = c + + +def load_model( + model_path: Union[str, Path], allow_missmatch: bool = False +) -> M2Model: + logger = logging.getLogger("load_model") + logger.debug("loading model: %s", str(model_path)) + with open(model_path, "rb") as f: + model_obj: M2Model = pickle.load(f) + assert isinstance(model_obj, M2Model), "Expected M2Model" + required_version = M2_METAMODEL_VERSION + if model_obj.model_version != required_version: + err_handler = logger.warning if allow_missmatch else RuntimeError + err_handler("Loaded model version mismatch") + return model_obj + + +def dump_model( + model_obj: M2Model, out_path: Union[str, Path], ignore_suffix: bool = False +): + logger = logging.getLogger("dump_model") + if not ignore_suffix: + out_path = Path(out_path) + suffix = out_path.suffix + required_suffix = ".m2isarmodel" + if suffix not in [required_suffix]: + assert len(suffix) == 0, f"Invalid suffix: {suffix}" + out_path = out_path.parent / f"{out_path.stem}{required_suffix}" + else: + assert suffix == required_suffix, f"Invalid suffix: {suffix}, Expected: {required_suffix}" + logger.debug("dumping model: %s", out_path) + with open(out_path, "wb") as f: + pickle.dump(model_obj, f) diff --git a/m2isar/metamodel/arch.py b/m2isar/metamodel/arch.py index adcd7e9f..6761b164 100644 --- a/m2isar/metamodel/arch.py +++ b/m2isar/metamodel/arch.py @@ -16,20 +16,24 @@ from collections import defaultdict from enum import Enum, IntEnum, auto from typing import TYPE_CHECKING, Any, Union +from m2isar.frontends.coredsl2.expr_interpreter import ExprInterpreterVisitor from .. import M2TypeError -from .behav import BaseNode, Operation +from .behav import BaseNode, Operation, NumberLiteral if TYPE_CHECKING: from .code_info import FunctionInfo - +exprInterpretVisitor = ExprInterpreterVisitor() def get_const_or_val(arg) -> int: if isinstance(arg, Constant): return arg.value + if isinstance(arg, NumberLiteral): + arg = arg.value + if isinstance(arg, BaseNode): - return arg.generate(None) + arg = exprInterpretVisitor.generate(arg, None) return arg @@ -67,7 +71,10 @@ def __init__(self, name, size: ValOrConst): def size(self) -> int: """Returns the resolved size, by calling get_const_or_val on _size.""" - return get_const_or_val(self._size) + ret = get_const_or_val(self._size) + if ret is None: + return None + return int(ret) @property def actual_size(self): @@ -266,6 +273,13 @@ def width(self): return get_const_or_val(self._width) + def __str__(self) -> str: + return f'{super().__repr__()}, width={self.width}, signed={self.signed}' + + def __repr__(self): + return self.__str__() + + @property def actual_width(self): """Returns the resolved width value rounded to the nearest multiple of 8.""" @@ -560,7 +574,7 @@ class CoreDef(Named): """A class representing an entire CPU core. Contains the collected attributes of multiple InstructionSets.""" def __init__(self, name, contributing_types: "list[str]", template: str, constants: "dict[str, Constant]", memories: "dict[str, Memory]", - memory_aliases: "dict[str, Memory]", functions: "dict[str, Function]", instructions: "dict[tuple[int, int], Instruction]", + memory_aliases: "dict[str, Memory]", functions: "dict[str, Function]", instructions: "dict[tuple[int, int], Instruction] | list[Instruction]", instr_classes: "set[int]", intrinsics: "dict[str, Intrinsic]"): self.contributing_types = contributing_types @@ -581,13 +595,9 @@ def __init__(self, name, contributing_types: "list[str]", template: str, constan self.irq_pending_memory = None self.intrinsics = intrinsics - self.instructions_by_ext = defaultdict(dict) + self._instructions_by_ext = None self.functions_by_ext = defaultdict(dict) - self.instructions_by_class = defaultdict(dict) - - for (code, mask), instr_def in self.instructions.items(): - self.instructions_by_ext[instr_def.ext_name][(code, mask)] = instr_def - self.instructions_by_class[instr_def.size][(code, mask)] = instr_def + self._instructions_by_class = None for fn_name, fn_def in self.functions.items(): self.functions_by_ext[fn_def.ext_name][fn_name] = fn_def @@ -609,3 +619,23 @@ def __init__(self, name, contributing_types: "list[str]", template: str, constan self.irq_pending_memory = mem super().__init__(name) + + @property + def instructions_by_ext(self): + if self._instructions_by_ext is not None: + return self._instructions_by_ext + assert isinstance(self.instructions, dict) + self._instructions_by_ext = defaultdict(dict) + for (code, mask), instr_def in self.instructions.items(): + self._instructions_by_ext[instr_def.ext_name][(code, mask)] = instr_def + return self._instructions_by_ext + + @property + def instructions_by_class(self): + if self._instructions_by_class is not None: + return self._instructions_by_class + assert isinstance(self.instructions, dict) + self._instructions_by_class = defaultdict(dict) + for (code, mask), instr_def in self.instructions.items(): + self._instructions_by_class[instr_def.size][(code, mask)] = instr_def + return self._instructions_by_class diff --git a/m2isar/metamodel/behav.py b/m2isar/metamodel/behav.py index 0d43a901..85889b80 100644 --- a/m2isar/metamodel/behav.py +++ b/m2isar/metamodel/behav.py @@ -6,7 +6,10 @@ # Chair of Electrical Design Automation # Technical University of Munich -"""This module contains classes for modeling the behavioral part +""" Deprecated module, only used for legacy reasons. Monkey patching can be avoided +by a custom ExprVisitor subclass and a custom generate function. +See :mod:`m2isar.metamodel.utils.ExprVisitor` for more details on how to do this. +This module contains classes for modeling the behavioral part of an M2-ISA-R model, this means the functional behavior of functions and instructions. Behavior is modeled as a tree of instances of the classes in this module. This object tree can then be traversed with transformation @@ -93,11 +96,18 @@ class NumberLiteral(BaseNode): def __init__(self, value, line_info=None): super().__init__(line_info) - self.value = value + self._value = value def __repr__(self): return f"NumberLiteral({self.value})" + @property + def value(self) -> int: + """Returns the resolved value.""" + if isinstance(self, IntLiteral): + return int(self._value) + return self._value + class IntLiteral(NumberLiteral): """A more precise class holding only integer literals.""" @@ -110,7 +120,11 @@ def __init__(self, value: int, bit_size: int=None, signed: bool=None, line_info= else: self.bit_size = bit_size + if isinstance(self.bit_size, IntLiteral): + self.bit_size = self.bit_size.value + self.bit_size = max(1, self.bit_size) + assert self.bit_size is not None if signed is None: self.signed = value <= 0 @@ -120,6 +134,9 @@ def __init__(self, value: int, bit_size: int=None, signed: bool=None, line_info= def __repr__(self): return f"IntLiteral({self.value}, {self.bit_size}, {self.signed})" + def __int__(self): + return self.value + class StringLiteral(BaseNode): """A string constant""" @@ -222,15 +239,24 @@ class TypeConv(BaseNode): def __init__(self, data_type, size, expr: BaseNode, line_info=None): super().__init__(line_info) self.data_type = data_type - self.size = size + self._size = size self.expr = expr - if self.size is not None: - self.actual_size = 1 << (self.size - 1).bit_length() - self.actual_size = max(self.actual_size, 8) + @property + def size(self) -> int: + """Returns the resolved size.""" + if isinstance(self._size, IntLiteral): + return int(self._size) + return self._size - else: - self.actual_size = None + @property + def actual_size(self) -> int: + """Returns the actual size.""" + if self.size is not None: + actual_size = 1 << (int(self.size) - 1).bit_length() + actual_size = max(actual_size, 8) + return actual_size + return None class Callable(BaseNode): """A generic invocation of a callable.""" diff --git a/m2isar/metamodel/utils/ExprMutator.py b/m2isar/metamodel/utils/ExprMutator.py new file mode 100644 index 00000000..0f787071 --- /dev/null +++ b/m2isar/metamodel/utils/ExprMutator.py @@ -0,0 +1,183 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# This file is part of the M2-ISA-R project: https://github.com/tum-ei-eda/M2-ISA-R +# +# Copyright (C) 2026 +# Chair of Embedded Computing Systems +# Technical University of Wien + +"""A helper module for applying all expression visitor functions in this package to +functions and instructions to get rid of monkey patching and use instead polymorphism. +""" + +from ...metamodel import behav +from abc import ABC, abstractmethod +from functools import singledispatchmethod +# pylint: disable=unused-argument + + +class ExprMutator(ABC): + """Base class for recursive metamodel traversal with 2 modes: + - Generating text by appending context while traversing AST. + - Analyzing/Mutating the AST (sometimes with the help of a context) and returning a modified AST. + To implement a new mutator, overload the 'generate' method of nodes that need altered visitation behavior. + Use self for additonal global state information + Use context for stack-based information that is only relevant for the current branch of the AST. + """ + @abstractmethod + def generate(self, expr : behav.BaseNode, context=None): + raise NotImplementedError(f"No visit method implemented for type {type(expr).__name__} in {type(expr).__name__}") + + @singledispatchmethod + def default_visit(self, expr: behav.BaseNode, context): + raise NotImplementedError(f"No visit method implemented for type {type(expr).__name__} in {type(expr).__name__}") + + @default_visit.register + def visit_codeliteral(self, expr: behav.CodeLiteral, context): + return expr + + @default_visit.register + def visit_operator(self, expr: behav.Operator, context): + return expr + + @default_visit.register + def visit_operation(self, expr: behav.Operation, context): + statements = [] + for stmt in expr.statements: + stmt = self.generate(stmt, context) + statements.append(stmt) + expr.statements = statements + return expr + + @default_visit.register + def visit_block(self, expr: behav.Block, context): + statements = [] + for stmt in expr.statements: + stmt = self.generate(stmt, context) + statements.append(stmt) + expr.statements = statements + return expr + + @default_visit.register + def visit_binary_operation(self, expr: behav.BinaryOperation, context): + expr.left = self.generate(expr.left, context) + expr.right = self.generate(expr.right, context) + return expr + + @default_visit.register + def visit_slice_operation(self, expr: behav.SliceOperation, context): + expr.expr = self.generate(expr.expr, context) + expr.left = self.generate(expr.left, context) + expr.right = self.generate(expr.right, context) + return expr + + @default_visit.register + def visit_concat_operation(self, expr: behav.ConcatOperation, context): + expr.left = self.generate(expr.left, context) + expr.right = self.generate(expr.right, context) + return expr + + @default_visit.register + def visit_number_literal(self, expr: behav.NumberLiteral, context): + return expr + + @default_visit.register + def visit_int_literal(self, expr: behav.IntLiteral, context): + return expr + + @default_visit.register + def visit_string_literal(self, expr: behav.StringLiteral, context): + return expr + + @default_visit.register + def visit_assignment(self, expr: behav.Assignment, context): + expr_target = self.generate(expr.target, context) + expr.expr = self.generate(expr.expr, context) + return expr + + @default_visit.register + def visit_conditional(self, expr: behav.Conditional, context): + conds = [] + for cond in expr.conds: + cond = self.generate(cond, context) + conds.append(cond) + expr.conds = conds + stmts = [] + for stmt in expr.stmts: + smts = self.generate(stmt, context) + stmts.append(stmt) + expr.stmts = stmts + return expr + + @default_visit.register + def visit_loop(self, expr: behav.Loop, context): + expr.cond = self.generate(expr.cond, context) + stmts = [] + for stmt in expr.stmts: + stmt = self.generate(stmt, context) + stmts.append(stmt) + expr.stmts = stmts + return expr + + @default_visit.register + def visit_ternary_operation(self, expr: behav.Ternary, context): + expr.cond = self.generate(expr.cond, context) + expr.then_expr = self.generate(expr.then_expr, context) + expr.else_expr = self.generate(expr.else_expr, context) + return expr + + @default_visit.register + def visit_return_operation(self, expr: behav.Return, context): + if expr.expr is not None: + expr.expr = self.generate(expr.expr, context) + return expr + + @default_visit.register + def visit_unary_operation(self, expr: behav.UnaryOperation, context): + expr.right = self.generate(expr.right, context) + return expr + + @default_visit.register + def visit_scalar_definition(self, expr: behav.ScalarDefinition, context): + return expr + + @default_visit.register + def visit_break(self, expr: behav.Break, context): + return expr + + @default_visit.register + def visit_named_reference(self, expr: behav.NamedReference, context): + return expr + + @default_visit.register + def visit_indexed_reference(self, expr: behav.IndexedReference, context): + expr.index = self.generate(expr.index, context) + return expr + + @default_visit.register + def visit_type_conv(self, expr: behav.TypeConv, context): + expr.expr = expr.expr.generate(context) + return expr + + @default_visit.register + def visit_callable(self, expr: behav.Callable, context): + args = [] + for arg in expr.args: + arg = self.generate(arg, context) + args.append(arg) + epxr.args = args + return expr + + @default_visit.register + def visit_procedure_call(self, expr: behav.Callable, context): + args = [] + for arg in expr.args: + arg = self.generate(arg, context) + args.append(arg) + epxr.args = args + return expr + + @default_visit.register + def visit_group(self, expr: behav.Group, context): + expr.expr = self.generate(expr.expr, context) + return expr diff --git a/m2isar/metamodel/utils/ExprVisitor.py b/m2isar/metamodel/utils/ExprVisitor.py new file mode 100644 index 00000000..3c2071a3 --- /dev/null +++ b/m2isar/metamodel/utils/ExprVisitor.py @@ -0,0 +1,146 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# This file is part of the M2-ISA-R project: https://github.com/tum-ei-eda/M2-ISA-R +# +# Copyright (C) 2026 +# Chair of Embedded Computing Systems +# Technical University of Wien + +"""A helper module for applying all expression visitor functions in this package to +functions and instructions to get rid of monkey patching and use instead polymorphism. +""" + +from ...metamodel import behav +from abc import ABC, abstractmethod +from functools import singledispatchmethod +# pylint: disable=unused-argument + + +class ExprVisitor(ABC): + """Base class for recursive metamodel traversal with 2 modes: + - Generating text by appending context while traversing AST. + - Analyzing/Mutating the AST (sometimes with the help of a context) and returning a modified AST. + To implement a new visitor, overload the 'generate' method of nodes that need altered visitation behavior. + Use self for additonal global state information + Use context for stack-based information that is only relevant for the current branch of the AST. + """ + @abstractmethod + def generate(self, expr : behav.BaseNode, context=None): + raise NotImplementedError(f"No visit method implemented for type {type(expr).__name__} in {type(expr).__name__}") + + @singledispatchmethod + def default_visit(self, expr: behav.BaseNode, context): + raise NotImplementedError(f"No visit method implemented for type {type(expr).__name__} in {type(expr).__name__}") + + @default_visit.register + def visit_codeliteral(self, expr: behav.CodeLiteral, context): + pass + + @default_visit.register + def visit_operator(self, expr: behav.Operator, context): + pass + + @default_visit.register + def visit_operation(self, expr: behav.Operation, context): + for stmt in expr.statements: + self.generate(stmt, context) + + @default_visit.register + def visit_block(self, expr: behav.Block, context): + for stmt in expr.statements: + self.generate(stmt, context) + + @default_visit.register + def visit_binary_operation(self, expr: behav.BinaryOperation, context): + self.generate(expr.left, context) + self.generate(expr.right, context) + + @default_visit.register + def visit_slice_operation(self, expr: behav.SliceOperation, context): + self.generate(expr.expr, context) + self.generate(expr.left, context) + self.generate(expr.right, context) + + @default_visit.register + def visit_concat_operation(self, expr: behav.ConcatOperation, context): + self.generate(expr.left, context) + self.generate(expr.right, context) + + @default_visit.register + def visit_number_literal(self, expr: behav.NumberLiteral, context): + pass + + @default_visit.register + def visit_int_literal(self, expr: behav.IntLiteral, context): + pass + + @default_visit.register + def visit_string_literal(self, expr: behav.StringLiteral, context): + pass + + @default_visit.register + def visit_assignment(self, expr: behav.Assignment, context): + self.generate(expr.target, context) + self.generate(expr.expr, context) + + @default_visit.register + def visit_conditional(self, expr: behav.Conditional, context): + for cond in expr.conds: + self.generate(cond, context) + for stmt in expr.stmts: + self.generate(stmt, context) + + @default_visit.register + def visit_loop(self, expr: behav.Loop, context): + self.generate(expr.cond, context) + for stmt in expr.stmts: + self.generate(stmt, context) + + @default_visit.register + def visit_ternary_operation(self, expr: behav.Ternary, context): + self.generate(expr.cond, context) + self.generate(expr.then_expr, context) + self.generate(expr.else_expr, context) + + @default_visit.register + def visit_return_operation(self, expr: behav.Return, context): + if expr.expr is not None: + self.generate(expr.expr, context) + + @default_visit.register + def visit_unary_operation(self, expr: behav.UnaryOperation, context): + self.generate(expr.right, context) + + @default_visit.register + def visit_scalar_definition(self, expr: behav.ScalarDefinition, context): + pass + + @default_visit.register + def visit_break(self, expr: behav.Break, context): + pass + + @default_visit.register + def visit_named_reference(self, expr: behav.NamedReference, context): + pass + + @default_visit.register + def visit_indexed_reference(self, expr: behav.IndexedReference, context): + self.generate(expr.index, context) + + @default_visit.register + def visit_type_conv(self, expr: behav.TypeConv, context): + self.generate(expr.expr, context) + + @default_visit.register + def visit_callable(self, expr: behav.Callable, context): + for arg in expr.args: + self.generate(arg, context) + + @default_visit.register + def visit_procedure_call(self, expr: behav.Callable, context): + for arg in expr.args: + self.generate(arg, context) + + @default_visit.register + def visit_group(self, expr: behav.Group, context): + self.generate(expr.expr, context) diff --git a/m2isar/metamodel/utils/expr_preprocessor.py b/m2isar/metamodel/utils/expr_preprocessor.py index 9d6c9d9a..c0541c9b 100644 --- a/m2isar/metamodel/utils/expr_preprocessor.py +++ b/m2isar/metamodel/utils/expr_preprocessor.py @@ -14,42 +14,46 @@ from itertools import chain from ... import M2ValueError -from .. import arch, patch_model -from . import (ScalarStaticnessContext, expr_simplifier, function_staticness, - function_throws, scalar_staticness) +from .. import arch +from . import ScalarStaticnessContext +from .expr_simplifier import ExprSimplifierVisitor +from .function_staticness import FunctionStaticnessVisitor +from .function_throws import FunctionThrowsVisitor +from .scalar_staticness import ScalarStaticnessVisitor logger = logging.getLogger("preprocessor") def process_attributes(core: arch.CoreDef): """Apply all preprocessing to memory, function and instruction attributes in `core`.""" - patch_model(expr_simplifier) + simplifier = ExprSimplifierVisitor() for _, obj_def in chain(core.functions.items(), core.instructions.items(), core.memories.items(), core.memory_aliases.items()): for attr_name, attr_defs in obj_def.attributes.items(): logger.debug("simplifying expressions for attr %s of %s", attr_name, obj_def.name) for attr_def in attr_defs: - attr_def.generate(None) + simplifier.generate(attr_def, None) def process_functions(core: arch.CoreDef): """Apply all preprocessing to all functions in `core`.""" + simplifier = ExprSimplifierVisitor() + function_throws_visitor = FunctionThrowsVisitor() + scalar_staticness_visitor = ScalarStaticnessVisitor() + function_staticness_visitor = FunctionStaticnessVisitor() + for fn_name, fn_def in core.functions.items(): - patch_model(expr_simplifier) logger.debug("simplifying expressions for fn %s", fn_name) - fn_def.operation.generate(None) + simplifier.generate(fn_def.operation, None) - patch_model(function_throws) logger.debug("checking throws for fn %s", fn_name) - throws = fn_def.operation.generate(None) + throws = function_throws_visitor.generate(fn_def.operation, None) fn_def.throws = throws or arch.FunctionAttribute.ETISS_TRAP_ENTRY_FN in fn_def.attributes context = ScalarStaticnessContext() - patch_model(scalar_staticness) logger.debug("examining scalar staticness for fn %s", fn_name) - fn_def.operation.generate(context) + scalar_staticness_visitor.generate(fn_def.operation, context) - patch_model(function_staticness) logger.debug("examining function staticness for fn %s", fn_name) if arch.FunctionAttribute.ETISS_NEEDS_ARCH in fn_def.attributes and arch.FunctionAttribute.ETISS_STATICFN in fn_def.attributes: @@ -63,23 +67,24 @@ def process_functions(core: arch.CoreDef): fn_def.static = True else: - ret = fn_def.operation.generate(None) + ret = function_staticness_visitor.generate(fn_def.operation, None) fn_def.static = ret def process_instructions(core: arch.CoreDef): """Apply all preprocessing to all instructions in `core`.""" + simplifier = ExprSimplifierVisitor() + function_throws_visitor = FunctionThrowsVisitor() + scalar_staticness_visitor = ScalarStaticnessVisitor() + for _, instr_def in core.instructions.items(): - patch_model(expr_simplifier) logger.debug("simplifying expressions for instr %s", instr_def.name) - instr_def.operation.generate(None) + simplifier.generate(instr_def.operation, None) - patch_model(function_throws) logger.debug("checking throws for instr %s", instr_def.name) - throws = instr_def.operation.generate(None) + throws = function_throws_visitor.generate(instr_def.operation, None) instr_def.throws = throws context = ScalarStaticnessContext() - patch_model(scalar_staticness) logger.debug("examining staticness for instr %s", instr_def.name) - instr_def.operation.generate(context) + scalar_staticness_visitor.generate(instr_def.operation, context) diff --git a/m2isar/metamodel/utils/expr_simplifier.py b/m2isar/metamodel/utils/expr_simplifier.py index 71459bd8..155ffcf6 100644 --- a/m2isar/metamodel/utils/expr_simplifier.py +++ b/m2isar/metamodel/utils/expr_simplifier.py @@ -21,214 +21,242 @@ """ from ...metamodel import arch, behav +from .ExprVisitor import ExprVisitor +from functools import singledispatchmethod # pylint: disable=unused-argument -def operation(self: behav.Operation, context): - statements = [] - for stmt in self.statements: - try: - temp = stmt.generate(context) - if isinstance(temp, list): - statements.extend(temp) +class ExprSimplifierVisitor(ExprVisitor): + """Visitor that simplifies behavior expression trees.""" + + @singledispatchmethod + def generate(self, expr : behav.BaseNode, context=None): + raise NotImplementedError(f"No visit method implemented for type {type(expr).__name__} in {type(expr).__name__}") + + + @generate.register + def _(self, expr: behav.Operation, context): + statements = [] + for stmt in expr.statements: + try: + temp = self.generate(stmt, context) + if isinstance(temp, list): + statements.extend(temp) + else: + statements.append(temp) + except (NotImplementedError, ValueError): + print(f"cant simplify {stmt}") + + expr.statements = statements + return expr + + @generate.register + def _(self, expr: behav.Block, context): + expr.statements = [self.generate(x, context) for x in expr.statements] + return expr + + @generate.register + def _(self, expr: behav.BinaryOperation, context): + expr.left = self.generate(expr.left, context) + expr.right = self.generate(expr.right, context) + + if isinstance(expr.left, behav.IntLiteral) and isinstance(expr.right, (behav.NamedReference, behav.IndexedReference)): + if expr.left.bit_size < expr.right.reference.size: + expr.left.bit_size = expr.right.reference.size + + if isinstance(expr.right, behav.IntLiteral) and isinstance(expr.left, (behav.NamedReference, behav.IndexedReference)): + if expr.right.bit_size < expr.left.reference.size: + expr.right.bit_size = expr.left.reference.size + + if isinstance(expr.left, behav.IntLiteral) and isinstance(expr.right, behav.IntLiteral): + # pylint: disable=eval-used + res: int = int(eval(f"{expr.left.value}{expr.op.value}{expr.right.value}")) + return behav.IntLiteral(res, max(expr.left.bit_size, expr.right.bit_size, res.bit_length())) + + if expr.op.value == "&&": + if isinstance(expr.left, behav.IntLiteral): + if expr.left.value: + return expr.right + return expr.left + + if isinstance(expr.right, behav.IntLiteral): + if expr.right.value: + return expr.left + return expr.right + + if expr.op.value == "||": + if isinstance(expr.left, behav.IntLiteral): + if expr.left.value: + return expr.left + return expr.right + + if isinstance(expr.right, behav.IntLiteral): + if expr.right.value: + return expr.right + return expr.left + + return expr + + @generate.register + def _(self, expr: behav.SliceOperation, context): + expr.expr = self.generate(expr.expr, context) + expr.left = self.generate(expr.left, context) + expr.right = self.generate(expr.right, context) + + return expr + + @generate.register + def _(self, expr: behav.ConcatOperation, context): + expr.left = self.generate(expr.left, context) + expr.right = self.generate(expr.right, context) + + return expr + + @generate.register + def _(self, expr: behav.NumberLiteral, context): + return expr + + @generate.register + def _(self, expr: behav.IntLiteral, context): + return expr + + @generate.register + def _(self, expr: behav.StringLiteral, context): + return expr + + @generate.register + def _(self, expr: behav.ScalarDefinition, context): + return expr + + @generate.register + def _(self, expr: behav.Break, context): + return expr + + @generate.register + def _(self, expr: behav.Assignment, context): + expr.target = self.generate(expr.target, context) + expr.expr = self.generate(expr.expr, context) + + if isinstance(expr.expr, behav.IntLiteral) and isinstance(expr.target, (behav.NamedReference, behav.IndexedReference)): + if expr.expr.bit_size < expr.target.reference.size: + expr.expr.bit_size = expr.target.reference.size + + return expr + + @generate.register + def _(self, expr: behav.Conditional, context): + expr.conds = [self.generate(x, context) for x in expr.conds] + expr.stmts = [self.generate(x, context) for x in expr.stmts] + + eval_false = True + + conds = [] + stmts = [] + + for cond, stmt in zip(expr.conds, expr.stmts): + if isinstance(cond, behav.IntLiteral): + if cond.value: + return stmt else: - statements.append(temp) - except (NotImplementedError, ValueError): - print(f"cant simplify {stmt}") - - self.statements = statements - return self - -def block(self: behav.Block, context): - self.statements = [x.generate(context) for x in self.statements] - return self - -def binary_operation(self: behav.BinaryOperation, context): - self.left = self.left.generate(context) - self.right = self.right.generate(context) - - if isinstance(self.left, behav.IntLiteral) and isinstance(self.right, (behav.NamedReference, behav.IndexedReference)): - if self.left.bit_size < self.right.reference.size: - self.left.bit_size = self.right.reference.size - - if isinstance(self.right, behav.IntLiteral) and isinstance(self.left, (behav.NamedReference, behav.IndexedReference)): - if self.right.bit_size < self.left.reference.size: - self.right.bit_size = self.left.reference.size - - if isinstance(self.left, behav.IntLiteral) and isinstance(self.right, behav.IntLiteral): - # pylint: disable=eval-used - res: int = int(eval(f"{self.left.value}{self.op.value}{self.right.value}")) - return behav.IntLiteral(res, max(self.left.bit_size, self.right.bit_size, res.bit_length())) - - if self.op.value == "&&": - if isinstance(self.left, behav.IntLiteral): - if self.left.value: - return self.right - else: - return self.left - - if isinstance(self.right, behav.IntLiteral): - if self.right.value: - return self.left - else: - return self.right - - if self.op.value == "||": - if isinstance(self.left, behav.IntLiteral): - if self.left.value: - return self.left - else: - return self.right - - if isinstance(self.right, behav.IntLiteral): - if self.right.value: - return self.right - else: - return self.left - - return self - -def slice_operation(self: behav.SliceOperation, context): - self.expr = self.expr.generate(context) - self.left = self.left.generate(context) - self.right = self.right.generate(context) - - return self - -def concat_operation(self: behav.ConcatOperation, context): - self.left = self.left.generate(context) - self.right = self.right.generate(context) - - return self - -def number_literal(self: behav.NumberLiteral, context): - return self - -def int_literal(self: behav.IntLiteral, context): - return self - -def string_literal(self: behav.StringLiteral, context): - return self - -def scalar_definition(self: behav.ScalarDefinition, context): - return self - -def break_(self: behav.Break, context): - return self - -def assignment(self: behav.Assignment, context): - self.target = self.target.generate(context) - self.expr = self.expr.generate(context) - - if isinstance(self.expr, behav.IntLiteral) and isinstance(self.target, (behav.NamedReference, behav.IndexedReference)): - if self.expr.bit_size < self.target.reference.size: - self.expr.bit_size = self.target.reference.size - - #if isinstance(self.expr, behav.IntLiteral) and isinstance(self.target, behav.ScalarDefinition): -# self.target.scalar.value = self.expr.value - - return self - -def conditional(self: behav.Conditional, context): - self.conds = [x.generate(context) for x in self.conds] - self.stmts = [x.generate(context) for x in self.stmts] - - eval_false = True - - conds = [] - stmts = [] - - for cond, stmt in zip(self.conds, self.stmts): - if isinstance(cond, behav.IntLiteral): - if cond.value: - return stmt - else: - conds.append(cond) - stmts.append(stmt) - eval_false = False - - if len(self.conds) < len(self.stmts): - if eval_false and isinstance(self.conds[-1], behav.IntLiteral): - if not cond.value: # pylint: disable=undefined-loop-variable - return self.stmts[-1] - stmts.append(self.stmts[-1]) + conds.append(cond) + stmts.append(stmt) + eval_false = False - self.conds = conds - self.stmts = stmts + if len(expr.conds) < len(expr.stmts): + if eval_false and isinstance(expr.conds[-1], behav.IntLiteral): + if not cond.value: # pylint: disable=undefined-loop-variable + return expr.stmts[-1] + stmts.append(expr.stmts[-1]) - return self + expr.conds = conds + expr.stmts = stmts -def loop(self: behav.Loop, context): - self.cond = self.cond.generate(context) - self.stmts = [x.generate(context) for x in self.stmts] + return expr - return self + @generate.register + def _(self, expr: behav.Loop, context): + expr.cond = self.generate(expr.cond, context) + expr.stmts = [self.generate(x, context) for x in expr.stmts] -def ternary(self: behav.Ternary, context): - self.cond = self.cond.generate(context) - self.then_expr = self.then_expr.generate(context) - self.else_expr = self.else_expr.generate(context) + return expr - if isinstance(self.cond, behav.IntLiteral): - if self.cond.value: - return self.then_expr + @generate.register + def _(self, expr: behav.Ternary, context): + expr.cond = self.generate(expr.cond, context) + expr.then_expr = self.generate(expr.then_expr, context) + expr.else_expr = self.generate(expr.else_expr, context) - return self.else_expr + if isinstance(expr.cond, behav.IntLiteral): + if expr.cond.value: + return expr.then_expr - return self + return expr.else_expr -def return_(self: behav.Return, context): - if self.expr is not None: - self.expr = self.expr.generate(context) + return expr - return self + @generate.register + def _(self, expr: behav.Return, context): + if expr.expr is not None: + expr.expr = self.generate(expr.expr, context) -def unary_operation(self: behav.UnaryOperation, context): - self.right = self.right.generate(context) - if isinstance(self.right, behav.IntLiteral): - # pylint: disable=eval-used - res: int = eval(f"{self.op.value}{self.right.value}") - return behav.IntLiteral(res, max(self.right.bit_size, res.bit_length())) + return expr - return self + @generate.register + def _(self, expr: behav.UnaryOperation, context): + expr.right = self.generate(expr.right, context) + if isinstance(expr.right, behav.IntLiteral): + # pylint: disable=eval-used + res: int = eval(f"{expr.op.value}{expr.right.value}") + return behav.IntLiteral(res, max(expr.right.bit_size, res.bit_length())) -def named_reference(self: behav.NamedReference, context): - if isinstance(self.reference, arch.Constant): - return behav.IntLiteral(self.reference.value, self.reference.size, self.reference.signed) + return expr - #if isinstance(self.reference, arch.Scalar) and self.reference.value is not None: -# return behav.IntLiteral(self.reference.value, self.reference.size, self.reference.data_type == arch.DataType.S) + @generate.register + def _(self, expr: behav.NamedReference, context): + if isinstance(expr.reference, arch.Constant): + return behav.IntLiteral(expr.reference.value, expr.reference.size, expr.reference.signed) - return self + return expr -def indexed_reference(self: behav.IndexedReference, context): - self.index = self.index.generate(context) + @generate.register + def _(self, expr: behav.IndexedReference, context): + expr.index = self.generate(expr.index, context) - return self + return expr -def type_conv(self: behav.TypeConv, context): - self.expr = self.expr.generate(context) - if isinstance(self.expr, behav.IntLiteral): - self.expr.bit_size = self.size - self.expr.signed = self.data_type == arch.DataType.S - return self.expr + @generate.register + def _(self, expr: behav.TypeConv, context): + expr.expr = self.generate(expr.expr, context) + if isinstance(expr.expr, behav.IntLiteral): + size = expr.size + if size is None: + assert expr.inferred_type is not None + size = expr.inferred_type.width + assert size is not None + expr.expr.bit_size = size + assert expr.expr.bit_size is not None + expr.expr.signed = expr.data_type == arch.DataType.S + return expr.expr - return self + return expr -def callable_(self: behav.Callable, context): - self.args = [stmt.generate(context) for stmt in self.args] + @generate.register + def _(self, expr: behav.Callable, context): + expr.args = [self.generate(stmt, context) for stmt in expr.args] - return self + return expr -def procedure_call(self: behav.ProcedureCall, context): - self.args = [stmt.generate(context) for stmt in self.args] + @generate.register + def _(self, expr: behav.ProcedureCall, context): + expr.args = [self.generate(stmt, context) for stmt in expr.args] - return self + return expr -def group(self: behav.Group, context): - self.expr = self.expr.generate(context) + @generate.register + def _(self, expr: behav.Group, context): + expr.expr = self.generate(expr.expr, context) - if isinstance(self.expr, behav.IntLiteral): - return self.expr + if isinstance(expr.expr, behav.IntLiteral): + return expr.expr - return self + return expr diff --git a/m2isar/metamodel/utils/function_staticness.py b/m2isar/metamodel/utils/function_staticness.py index 3e575aa9..e8d25d91 100644 --- a/m2isar/metamodel/utils/function_staticness.py +++ b/m2isar/metamodel/utils/function_staticness.py @@ -8,132 +8,168 @@ """Transformation functions to determine whether a function is considered to be static.""" +from functools import singledispatchmethod + from ...metamodel import arch, behav +from .ExprVisitor import ExprVisitor # pylint: disable=unused-argument -def operation(self: behav.Operation, context): - statements = [] - for stmt in self.statements: - temp = stmt.generate(context) - if isinstance(temp, list): - statements.extend(temp) - else: - statements.append(temp) +class FunctionStaticnessVisitor(ExprVisitor): + """Visitor that determines whether behavior expression trees are static.""" + + @singledispatchmethod + def generate(self, expr: behav.BaseNode, context=None): + raise NotImplementedError(f"No visit method implemented for type {type(expr).__name__} in {type(expr).__name__}") + + @generate.register + def _(self, expr: behav.Operation, context): + statements = [] + for stmt in expr.statements: + temp = self.generate(stmt, context) + if isinstance(temp, list): + statements.extend(temp) + else: + statements.append(temp) - return all(statements) + return all(statements) + @generate.register + def _(self, expr: behav.Block, context): + stmts = [self.generate(x, context) for x in expr.statements] + return all(stmts) -def block(self: behav.Block, context): - stmts = [x.generate(context) for x in self.statements] - return all(stmts) + @generate.register + def _(self, expr: behav.BinaryOperation, context): + left = self.generate(expr.left, context) + right = self.generate(expr.right, context) + return all([left, right]) -def binary_operation(self: behav.BinaryOperation, context): - left = self.left.generate(context) - right = self.right.generate(context) + @generate.register + def _(self, expr: behav.SliceOperation, context): + expr_result = self.generate(expr.expr, context) + left = self.generate(expr.left, context) + right = self.generate(expr.right, context) - return all([left, right]) + return all([expr_result, left, right]) -def slice_operation(self: behav.SliceOperation, context): - expr = self.expr.generate(context) - left = self.left.generate(context) - right = self.right.generate(context) + @generate.register + def _(self, expr: behav.ConcatOperation, context): + left = self.generate(expr.left, context) + right = self.generate(expr.right, context) - return all([expr, left, right]) + return all([left, right]) -def concat_operation(self: behav.ConcatOperation, context): - left = self.left.generate(context) - right = self.right.generate(context) + @generate.register + def _(self, expr: behav.NumberLiteral, context): + return True - return all([left, right]) + @generate.register + def _(self, expr: behav.IntLiteral, context): + return True -def number_literal(self: behav.NumberLiteral, context): - return True + @generate.register + def _(self, expr: behav.StringLiteral, context): + return True -def int_literal(self: behav.IntLiteral, context): - return True + @generate.register + def _(self, expr: behav.ScalarDefinition, context): + return True -def string_literal(self: behav.StringLiteral, context): - return True + @generate.register + def _(self, expr: behav.Break, context): + return True -def scalar_definition(self: behav.ScalarDefinition, context): - return True + @generate.register + def _(self, expr: behav.Assignment, context): + target = self.generate(expr.target, context) + expr_result = self.generate(expr.expr, context) -def break_(self: behav.Break, context): - return True + return all([target, expr_result]) -def assignment(self: behav.Assignment, context): - target = self.target.generate(context) - expr = self.expr.generate(context) + @generate.register + def _(self, expr: behav.Conditional, context): + conds = [self.generate(x, context) for x in expr.conds] + stmts = [self.generate(x, context) for x in expr.stmts] - return all([target, expr]) + conds.extend(stmts) -def conditional(self: behav.Conditional, context): - conds = [x.generate(context) for x in self.conds] - stmts = [x.generate(context) for x in self.stmts] + return all(conds) - conds.extend(stmts) + @generate.register + def _(self, expr: behav.Loop, context): + cond = self.generate(expr.cond, context) + stmts = [self.generate(x, context) for x in expr.stmts] + stmts.append(cond) - return all(conds) + return all(stmts) -def loop(self: behav.Loop, context): - cond = self.cond.generate(context) - stmts = [x.generate(context) for x in self.stmts] - stmts.append(cond) + @generate.register + def _(self, expr: behav.Ternary, context): + cond = self.generate(expr.cond, context) + then_expr = self.generate(expr.then_expr, context) + else_expr = self.generate(expr.else_expr, context) - return all(stmts) + return all([cond, then_expr, else_expr]) -def ternary(self: behav.Ternary, context): - cond = self.cond.generate(context) - then_expr = self.then_expr.generate(context) - else_expr = self.else_expr.generate(context) + @generate.register + def _(self, expr: behav.Return, context): + if expr.expr is not None: + return self.generate(expr.expr, context) - return all([cond, then_expr, else_expr]) + return True -def return_(self: behav.Return, context): - if self.expr is not None: - return self.expr.generate(context) + @generate.register + def _(self, expr: behav.UnaryOperation, context): + right = self.generate(expr.right, context) - return True + return right -def unary_operation(self: behav.UnaryOperation, context): - right = self.right.generate(context) + @generate.register + def _(self, expr: behav.NamedReference, context): + if isinstance(expr.reference, arch.Scalar): + return expr.reference.static - return right + static_map = { + arch.Memory: False, + arch.BitFieldDescr: True, + arch.Constant: True, + arch.FnParam: True, + arch.Scalar: True, + arch.Intrinsic: False + } -def named_reference(self: behav.NamedReference, context): - if isinstance(self.reference, arch.Scalar): - return self.reference.static + return static_map.get(type(expr.reference), False) - static_map = { - arch.Memory: False, - arch.BitFieldDescr: True, - arch.Constant: True, - arch.FnParam: True, - arch.Scalar: True, - arch.Intrinsic: False - } + @generate.register + def _(self, expr: behav.IndexedReference, context): + self.generate(expr.index, context) - return static_map.get(type(self.reference), False) + return False -def indexed_reference(self: behav.IndexedReference, context): - self.index.generate(context) + @generate.register + def _(self, expr: behav.TypeConv, context): + expr_result = self.generate(expr.expr, context) - return False + return expr_result -def type_conv(self: behav.TypeConv, context): - expr = self.expr.generate(context) + @generate.register + def _(self, expr: behav.Callable, context): + args = [self.generate(arg, context) for arg in expr.args] + args.append(bool(getattr(expr.ref_or_name, "static", False))) - return expr + return all(args) -def callable_(self: behav.Callable, context): - args = [arg.generate(context) for arg in self.args] - args.append(self.ref_or_name.static) + @generate.register + def _(self, expr: behav.ProcedureCall, context): + args = [self.generate(arg, context) for arg in expr.args] + args.append(bool(getattr(expr.ref_or_name, "static", False))) - return all(args) + return all(args) -def group(self: behav.Group, context): - expr = self.expr.generate(context) + @generate.register + def _(self, expr: behav.Group, context): + expr_result = self.generate(expr.expr, context) - return expr + return expr_result diff --git a/m2isar/metamodel/utils/function_throws.py b/m2isar/metamodel/utils/function_throws.py index 9a558ec7..3489fbc9 100644 --- a/m2isar/metamodel/utils/function_throws.py +++ b/m2isar/metamodel/utils/function_throws.py @@ -9,133 +9,171 @@ """Tranformation functions to determine whether a function throws an exception.""" from functools import reduce +from functools import singledispatchmethod from operator import or_ +from typing import Any from ...metamodel import arch, behav +from .ExprVisitor import ExprVisitor # pylint: disable=unused-argument -def operation(self: behav.Operation, context): - statements = [] - for stmt in self.statements: - temp = stmt.generate(context) - if isinstance(temp, list): - statements.extend(temp) - else: - statements.append(temp) +class FunctionThrowsVisitor(ExprVisitor): + """Visitor that determines whether behavior expression trees can throw exceptions.""" - return reduce(or_, statements, arch.FunctionThrows.NO) + @singledispatchmethod + def generate(self, expr: behav.BaseNode, context=None): + raise NotImplementedError(f"No visit method implemented for type {type(expr).__name__} in {type(expr).__name__}") + @generate.register + def _(self, expr: behav.Operation, context): + statements = [] + for stmt in expr.statements: + temp = self.generate(stmt, context) + if isinstance(temp, list): + statements.extend(temp) + else: + statements.append(temp) -def block(self: behav.Block, context): - stmts = [x.generate(context) for x in self.statements] - return reduce(or_, stmts, arch.FunctionThrows.NO) + return reduce(or_, statements, arch.FunctionThrows.NO) + @generate.register + def _(self, expr: behav.Block, context): + stmts = [self.generate(x, context) for x in expr.statements] + return reduce(or_, stmts, arch.FunctionThrows.NO) -def binary_operation(self: behav.BinaryOperation, context): - left = self.left.generate(context) - right = self.right.generate(context) + @generate.register + def _(self, expr: behav.BinaryOperation, context): + left = self.generate(expr.left, context) + right = self.generate(expr.right, context) - return reduce(or_, [left, right]) + return reduce(or_, [left, right]) -def slice_operation(self: behav.SliceOperation, context): - expr = self.expr.generate(context) - left = self.left.generate(context) - right = self.right.generate(context) + @generate.register + def _(self, expr: behav.SliceOperation, context): + expr_result = self.generate(expr.expr, context) + left = self.generate(expr.left, context) + right = self.generate(expr.right, context) - return reduce(or_, [expr, left, right]) + return reduce(or_, [expr_result, left, right]) -def concat_operation(self: behav.ConcatOperation, context): - left = self.left.generate(context) - right = self.right.generate(context) + @generate.register + def _(self, expr: behav.ConcatOperation, context): + left = self.generate(expr.left, context) + right = self.generate(expr.right, context) - return reduce(or_, [left, right]) + return reduce(or_, [left, right]) -def number_literal(self: behav.IntLiteral, context): - return arch.FunctionThrows.NO + @generate.register + def _(self, expr: behav.NumberLiteral, context): + return arch.FunctionThrows.NO -def int_literal(self: behav.IntLiteral, context): - return arch.FunctionThrows.NO + @generate.register + def _(self, expr: behav.IntLiteral, context): + return arch.FunctionThrows.NO -def string_literal(self: behav.StringLiteral, context): - return arch.FunctionThrows.NO + @generate.register + def _(self, expr: behav.StringLiteral, context): + return arch.FunctionThrows.NO -def scalar_definition(self: behav.ScalarDefinition, context): - return arch.FunctionThrows.NO + @generate.register + def _(self, expr: behav.ScalarDefinition, context): + return arch.FunctionThrows.NO -def break_(self: behav.Break, context): - return arch.FunctionThrows.NO + @generate.register + def _(self, expr: behav.Break, context): + return arch.FunctionThrows.NO -def assignment(self: behav.Assignment, context): - target = self.target.generate(context) - expr = self.expr.generate(context) + @generate.register + def _(self, expr: behav.Assignment, context): + target = self.generate(expr.target, context) + expr_result = self.generate(expr.expr, context) - return reduce(or_, [target, expr]) + return reduce(or_, [target, expr_result]) -def conditional(self: behav.Conditional, context): - conds = [x.generate(context) for x in self.conds] - stmts = [x.generate(context) for x in self.stmts] + @generate.register + def _(self, expr: behav.Conditional, context): + conds = [self.generate(x, context) for x in expr.conds] + stmts = [self.generate(x, context) for x in expr.stmts] - conds.extend(stmts) + conds.extend(stmts) - return arch.FunctionThrows.MAYBE if reduce(or_, conds) else arch.FunctionThrows.NO + return arch.FunctionThrows.MAYBE if reduce(or_, conds) else arch.FunctionThrows.NO -def loop(self: behav.Loop, context): - cond = self.cond.generate(context) - stmts = [x.generate(context) for x in self.stmts] - stmts.append(cond) + @generate.register + def _(self, expr: behav.Loop, context): + cond = self.generate(expr.cond, context) + stmts = [self.generate(x, context) for x in expr.stmts] + stmts.append(cond) - return reduce(or_, stmts) + return reduce(or_, stmts) -def ternary(self: behav.Ternary, context): - cond = self.cond.generate(context) - then_expr = self.then_expr.generate(context) - else_expr = self.else_expr.generate(context) + @generate.register + def _(self, expr: behav.Ternary, context): + cond = self.generate(expr.cond, context) + then_expr = self.generate(expr.then_expr, context) + else_expr = self.generate(expr.else_expr, context) - return reduce(or_, [cond, then_expr, else_expr]) + return reduce(or_, [cond, then_expr, else_expr]) -def return_(self: behav.Return, context): - if self.expr is not None: - return self.expr.generate(context) + @generate.register + def _(self, expr: behav.Return, context): + if expr.expr is not None: + return self.generate(expr.expr, context) - return arch.FunctionThrows.NO + return arch.FunctionThrows.NO -def unary_operation(self: behav.UnaryOperation, context): - right = self.right.generate(context) + @generate.register + def _(self, expr: behav.UnaryOperation, context): + right = self.generate(expr.right, context) - return right + return right -def named_reference(self: behav.NamedReference, context): - if isinstance(self.reference, arch.Memory) and arch.MemoryAttribute.ETISS_CAN_FAIL in self.reference.attributes: - return arch.FunctionThrows.YES + @generate.register + def _(self, expr: behav.NamedReference, context): + if isinstance(expr.reference, arch.Memory) and arch.MemoryAttribute.ETISS_CAN_FAIL in expr.reference.attributes: + return arch.FunctionThrows.YES - return arch.FunctionThrows.NO + return arch.FunctionThrows.NO -def indexed_reference(self: behav.IndexedReference, context): - if isinstance(self.reference, arch.Memory) and arch.MemoryAttribute.ETISS_CAN_FAIL in self.reference.attributes: - return arch.FunctionThrows.YES + @generate.register + def _(self, expr: behav.IndexedReference, context): + if isinstance(expr.reference, arch.Memory) and arch.MemoryAttribute.ETISS_CAN_FAIL in expr.reference.attributes: + return arch.FunctionThrows.YES - return self.index.generate(context) + return self.generate(expr.index, context) -def type_conv(self: behav.TypeConv, context): - expr = self.expr.generate(context) + @generate.register + def _(self, expr: behav.TypeConv, context): + expr_result = self.generate(expr.expr, context) - return expr + return expr_result -def callable_(self: behav.Callable, context): - args = [arg.generate(context) for arg in self.args] - args.append(self.ref_or_name.throws) + @generate.register + def _(self, expr: behav.Callable, context): + args = [self.generate(arg, context) for arg in expr.args] + throws = getattr(expr.ref_or_name, "throws", arch.FunctionThrows.NO) + args.append(throws if isinstance(throws, arch.FunctionThrows) else cast_to_throws(throws)) - return reduce(or_, args) + return reduce(or_, args) + @generate.register + def _(self, expr: behav.ProcedureCall, context): + args = [self.generate(arg, context) for arg in expr.args] + throws = getattr(expr.ref_or_name, "throws", arch.FunctionThrows.NO) + args.append(throws if isinstance(throws, arch.FunctionThrows) else cast_to_throws(throws)) -def procedure_call(self: behav.ProcedureCall, context): - args = [arg.generate(context) for arg in self.args] - args.append(self.ref_or_name.throws) + return reduce(or_, args) - return reduce(or_, args) + @generate.register + def _(self, expr: behav.Group, context): + expr_result = self.generate(expr.expr, context) -def group(self: behav.Group, context): - expr = self.expr.generate(context) + return expr_result - return expr + +def cast_to_throws(throws: Any) -> arch.FunctionThrows: + """Cast unknown throws values into FunctionThrows for robust visitor dispatch.""" + if isinstance(throws, bool): + return arch.FunctionThrows.YES if throws else arch.FunctionThrows.NO + return arch.FunctionThrows(throws) diff --git a/m2isar/metamodel/utils/scalar_staticness.py b/m2isar/metamodel/utils/scalar_staticness.py index b0611c8a..fd172ae3 100644 --- a/m2isar/metamodel/utils/scalar_staticness.py +++ b/m2isar/metamodel/utils/scalar_staticness.py @@ -11,141 +11,183 @@ """ import dataclasses +from functools import singledispatchmethod +from typing import Any, cast from ...metamodel import arch, behav from ...metamodel.utils import ScalarStaticnessContext, StaticType +from .ExprVisitor import ExprVisitor # pylint: disable=unused-argument -def operation(self: behav.Operation, context: ScalarStaticnessContext): - statements = [] - for stmt in self.statements: - temp = stmt.generate(context) - if isinstance(temp, list): - statements.extend(temp) +class ScalarStaticnessVisitor(ExprVisitor): + """Visitor that determines scalar staticness for behavior expression trees.""" + + @singledispatchmethod + def generate(self, expr: behav.BaseNode, context=None): + raise NotImplementedError(f"No visit method implemented for type {type(expr).__name__} in {type(expr).__name__}") + + @generate.register + def _(self, expr: behav.Operation, context: ScalarStaticnessContext): + statements = [] + for stmt in expr.statements: + temp = self.generate(stmt, context) + if isinstance(temp, list): + statements.extend(temp) + else: + statements.append(temp) + + return expr + + @generate.register + def _(self, expr: behav.Block, context): + stmts = [self.generate(x, context) for x in expr.statements] + valid = [s for s in stmts if s is not None] + if not valid: + return StaticType.NONE + return min(valid) + + @generate.register + def _(self, expr: behav.BinaryOperation, context: ScalarStaticnessContext): + left = self.generate(expr.left, context) + right = self.generate(expr.right, context) + + return min(left, right) + + @generate.register + def _(self, expr: behav.SliceOperation, context: ScalarStaticnessContext): + expr_result = self.generate(expr.expr, context) + left = self.generate(expr.left, context) + right = self.generate(expr.right, context) + + return min(expr_result, left, right) + + @generate.register + def _(self, expr: behav.ConcatOperation, context: ScalarStaticnessContext): + left = self.generate(expr.left, context) + right = self.generate(expr.right, context) + + return min(left, right) + + @generate.register + def _(self, expr: behav.NumberLiteral, context: ScalarStaticnessContext): + return StaticType.READ + + @generate.register + def _(self, expr: behav.IntLiteral, context: ScalarStaticnessContext): + return StaticType.READ + + @generate.register + def _(self, expr: behav.StringLiteral, context: ScalarStaticnessContext): + return StaticType.READ + + @generate.register + def _(self, expr: behav.ScalarDefinition, context: ScalarStaticnessContext): + scalar = cast(Any, expr.scalar) + scalar.static = StaticType.RW + return StaticType.RW + + @generate.register + def _(self, expr: behav.Break, context): + return StaticType.READ + + @generate.register + def _(self, expr: behav.Assignment, context: ScalarStaticnessContext): + self.generate(expr.target, context) + + if context.context_is_static != StaticType.NONE or isinstance(expr.target, behav.ScalarDefinition): + expr_static = self.generate(expr.expr, context) + + if expr_static != StaticType.NONE: + expr_static = StaticType.RW else: - statements.append(temp) + expr_static = StaticType.NONE - return self + if isinstance(expr.target, behav.NamedReference) and isinstance(expr.target.reference, arch.Scalar): + target_ref = cast(Any, expr.target.reference) + target_ref.static &= expr_static -def block(self: behav.Block, context): - stmts = [x.generate(context) for x in self.statements] - valid = [s for s in stmts if s is not None] - if not valid: - return StaticType.NONE - return min(valid) - -def binary_operation(self: behav.BinaryOperation, context: ScalarStaticnessContext): - left = self.left.generate(context) - right = self.right.generate(context) - - return min(left, right) - -def slice_operation(self: behav.SliceOperation, context: ScalarStaticnessContext): - expr = self.expr.generate(context) - left = self.left.generate(context) - right = self.right.generate(context) - - return min(expr, left, right) - -def concat_operation(self: behav.ConcatOperation, context: ScalarStaticnessContext): - left = self.left.generate(context) - right = self.right.generate(context) - - return min(left, right) - -def number_literal(self: behav.NumberLiteral, context: ScalarStaticnessContext): - return StaticType.READ - -def int_literal(self: behav.IntLiteral, context: ScalarStaticnessContext): - return StaticType.READ + if isinstance(expr.target, behav.ScalarDefinition): + target_scalar = cast(Any, expr.target.scalar) + target_scalar.static &= expr_static -def string_literal(self: behav.StringLiteral, context: ScalarStaticnessContext): - return StaticType.READ + @generate.register + def _(self, expr: behav.Conditional, context: ScalarStaticnessContext): + conds = [self.generate(x, context) for x in expr.conds] + stmt_context = dataclasses.replace(context, context_is_static=min(conds)) + _ = [self.generate(x, stmt_context) for x in expr.stmts] -def scalar_definition(self: behav.ScalarDefinition, context: ScalarStaticnessContext): - self.scalar.static = StaticType.RW - return StaticType.RW + @generate.register + def _(self, expr: behav.Loop, context: ScalarStaticnessContext): + cond = self.generate(expr.cond, context) + stmt_context = dataclasses.replace(context, context_is_static=cond) + _ = [self.generate(x, stmt_context) for x in expr.stmts] -def break_(self: behav.Break, context): - return StaticType.READ + @generate.register + def _(self, expr: behav.Ternary, context: ScalarStaticnessContext): + cond = self.generate(expr.cond, context) + then_expr = self.generate(expr.then_expr, context) + else_expr = self.generate(expr.else_expr, context) -def assignment(self: behav.Assignment, context: ScalarStaticnessContext): - self.target.generate(context) + return min(cond, then_expr, else_expr) - if context.context_is_static != StaticType.NONE or isinstance(self.target, behav.ScalarDefinition): - expr = self.expr.generate(context) + @generate.register + def _(self, expr: behav.Return, context: ScalarStaticnessContext): + if expr.expr is not None: + return self.generate(expr.expr, context) - if expr != StaticType.NONE: - expr = StaticType.RW - else: - expr = StaticType.NONE + return StaticType.RW - if isinstance(self.target, behav.NamedReference) and isinstance(self.target.reference, arch.Scalar): - self.target.reference.static &= expr + @generate.register + def _(self, expr: behav.UnaryOperation, context: ScalarStaticnessContext): + right = self.generate(expr.right, context) - if isinstance(self.target, behav.ScalarDefinition): - self.target.scalar.static &= expr + return right + @generate.register + def _(self, expr: behav.NamedReference, context: ScalarStaticnessContext): + if isinstance(expr.reference, arch.Scalar): + return expr.reference.static -def conditional(self: behav.Conditional, context: ScalarStaticnessContext): - conds = [x.generate(context) for x in self.conds] - stmt_context = dataclasses.replace(context, context_is_static=min(conds)) - _ = [x.generate(stmt_context) for x in self.stmts] + static_map = { + arch.Memory: StaticType.NONE, + arch.BitFieldDescr: StaticType.READ, + arch.Constant: StaticType.READ, + arch.FnParam: StaticType.READ + } -def loop(self: behav.Loop, context: ScalarStaticnessContext): - cond = self.cond.generate(context) - stmt_context = dataclasses.replace(context, context_is_static=cond) - _ = [x.generate(stmt_context) for x in self.stmts] + return static_map.get(type(expr.reference), StaticType.NONE) -def ternary(self: behav.Ternary, context: ScalarStaticnessContext): - cond = self.cond.generate(context) - then_expr = self.then_expr.generate(context) - else_expr = self.else_expr.generate(context) + @generate.register + def _(self, expr: behav.IndexedReference, context: ScalarStaticnessContext): + self.generate(expr.index, context) - return min(cond, then_expr, else_expr) - -def return_(self: behav.Return, context: ScalarStaticnessContext): - if self.expr is not None: - return self.expr.generate(context) - - return StaticType.RW - -def unary_operation(self: behav.UnaryOperation, context: ScalarStaticnessContext): - right = self.right.generate(context) - - return right - -def named_reference(self: behav.NamedReference, context: ScalarStaticnessContext): - if isinstance(self.reference, arch.Scalar): - return self.reference.static - - static_map = { - arch.Memory: StaticType.NONE, - arch.BitFieldDescr: StaticType.READ, - arch.Constant: StaticType.READ, - arch.FnParam: StaticType.READ - } - - return static_map.get(type(self.reference), StaticType.NONE) + return StaticType.NONE -def indexed_reference(self: behav.IndexedReference, context: ScalarStaticnessContext): - self.index.generate(context) + @generate.register + def _(self, expr: behav.TypeConv, context: ScalarStaticnessContext): + expr_result = self.generate(expr.expr, context) - return StaticType.NONE + return expr_result -def type_conv(self: behav.TypeConv, context: ScalarStaticnessContext): - expr = self.expr.generate(context) + @generate.register + def _(self, expr: behav.Callable, context: ScalarStaticnessContext): + args = [self.generate(arg, context) for arg in expr.args] + is_static = bool(getattr(expr.ref_or_name, "static", False)) + args.append(StaticType.READ if is_static else StaticType.NONE) - return expr + return min(args) -def callable_(self: behav.Callable, context: ScalarStaticnessContext): - args = [arg.generate(context) for arg in self.args] - args.append(StaticType.READ if self.ref_or_name.static else StaticType.NONE) + @generate.register + def _(self, expr: behav.ProcedureCall, context: ScalarStaticnessContext): + args = [self.generate(arg, context) for arg in expr.args] + is_static = bool(getattr(expr.ref_or_name, "static", False)) + args.append(StaticType.READ if is_static else StaticType.NONE) - return min(args) + return min(args) -def group(self: behav.Group, context: ScalarStaticnessContext): - expr = self.expr.generate(context) + @generate.register + def _(self, expr: behav.Group, context: ScalarStaticnessContext): + expr_result = self.generate(expr.expr, context) - return expr + return expr_result diff --git a/m2isar/transforms/__init__.py b/m2isar/transforms/__init__.py new file mode 100644 index 00000000..044d6037 --- /dev/null +++ b/m2isar/transforms/__init__.py @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# This file is part of the M2-ISA-R project: https://github.com/tum-ei-eda/M2-ISA-R +# +# Copyright (C) 2024 +# Chair of Electrical Design Automation +# Technical University of Munich + +"""This module contains various transforms for M2-ISA-R models.""" diff --git a/m2isar/transforms/infer_types/__init__.py b/m2isar/transforms/infer_types/__init__.py new file mode 100644 index 00000000..9e0ff52b --- /dev/null +++ b/m2isar/transforms/infer_types/__init__.py @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# This file is part of the M2-ISA-R project: https://github.com/tum-ei-eda/M2-ISA-R +# +# Copyright (C) 2024 +# Chair of Electrical Design Automation +# Technical University of Munich + +"""This module contains a type inference pass for M2-ISA-R models.""" diff --git a/m2isar/transforms/infer_types/transform.py b/m2isar/transforms/infer_types/transform.py new file mode 100644 index 00000000..92b5034e --- /dev/null +++ b/m2isar/transforms/infer_types/transform.py @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# This file is part of the M2-ISA-R project: https://github.com/tum-ei-eda/M2-ISA-R +# +# Copyright (C) 2022 +# Chair of Electrical Design Automation +# Technical University of Munich + +"""Type inference for M2-ISA-R metamodel.""" + +import sys +import argparse +import logging +import pathlib + +from m2isar.metamodel import load_model, dump_model + +from .visitor import InferTypesMutator + + +def get_parser(): + # read command line args + parser = argparse.ArgumentParser() + parser.add_argument("top_level", help="A .m2isarmodel file.") + parser.add_argument("--log", default="info", choices=["critical", "error", "warning", "info", "debug"]) + parser.add_argument("--output", "-o", type=str, default=None) + return parser + + +def infer_types(model_obj): + logger = logging.getLogger("infer_types") + for _, core_def in model_obj.cores.items(): + logger.debug("inferring types for core %s", core_def.name) + mutator = InferTypesMutator() + for _, instr_def in core_def.instructions.items(): + logger.debug("inferring types for instr %s", instr_def.name) + mutator.generate(instr_def.operation, None) + for _, set_def in model_obj.sets.items(): + logger.debug("inferring types for set %s", set_def.name) + for _, instr_def in set_def.instructions.items(): + logger.debug("inferring types for instr %s", instr_def.name) + mutator.generate(instr_def.operation, None) + return model_obj + + +def run(args): + # initialize logging + logging.basicConfig(level=getattr(logging, args.log.upper())) + + # resolve model paths + top_level = pathlib.Path(args.top_level) + + out_path = (top_level.parent / top_level.stem) if args.output is None else args.output + print("out_path", out_path) + + model_obj = load_model(top_level) + model_obj = infer_types(model_obj) + + dump_model(model_obj, out_path) + + +def main(argv): + parser = get_parser() + args = parser.parse_args(argv) + run(args) + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/m2isar/transforms/infer_types/visitor.py b/m2isar/transforms/infer_types/visitor.py new file mode 100644 index 00000000..b3504cf6 --- /dev/null +++ b/m2isar/transforms/infer_types/visitor.py @@ -0,0 +1,423 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# This file is part of the M2-ISA-R project: https://github.com/tum-ei-eda/M2-ISA-R +# +# Copyright (C) 2022 +# Chair of Electrical Design Automation +# Technical University of Munich + +"""A transformation module for simplifying M2-ISA-R behavior expressions. The following +simplifications are done: + +* Resolvable :class:`m2isar.metamodel.arch.Constant` s are replaced by + `m2isar.metamodel.arch.IntLiteral` s representing their value +* Fully resolvable arithmetic operations are carried out and their results + represented as a matching :class:`m2isar.metamodel.arch.IntLiteral` +* Conditions and loops with fully resolvable conditions are either discarded entirely + or transformed into code blocks without any conditions +* Ternaries with fully resolvable conditions are transformed into only the matching part +* Type conversions of :class:`m2isar.metamodel.arch.IntLiteral` s apply the desired + type directly to the :class:`IntLiteral` and discard the type conversion +""" + +import logging +from copy import copy +from functools import singledispatchmethod +from copy import deepcopy + +from m2isar.metamodel import arch, behav +from ...metamodel.utils.ExprMutator import ExprMutator + +logger = logging.getLogger("infer_types") + +# pylint: disable=unused-argument + +class InferTypesMutator(ExprMutator): + """Mutator to annote inferred types to a metamodel.""" + + @singledispatchmethod + def generate(self, expr: behav.BaseNode, context): + raise NotImplementedError(f"No visit method implemented for type {type(expr).__name__} in {type(self).__name__}") + + @generate.register + def _(self, expr: behav.Operation, context): + statements = [] + for stmt in expr.statements: + temp = self.generate(stmt, context) + if isinstance(temp, list): + statements.extend(temp) + else: + statements.append(temp) + + expr.statements = statements + return expr + + + @generate.register + def _(self, expr: behav.BinaryOperation, context): + expr.left = self.generate(expr.left, context) + expr.right = self.generate(expr.right, context) + + # see: https://github.com/Minres/CoreDSL/wiki/Expressions#arithmetic-type-rules + if expr.op.value in ["+", "-", "*", "/", "%", "|", "&", "^", "<<", ">>"]: + if expr.left.inferred_type is None or expr.right.inferred_type is None: + logger.warning("Slice Operation needs inferred type. Skipping...") + expr.inferred_type = None + return expr + assert isinstance(expr.left.inferred_type, arch.IntegerType) + assert isinstance(expr.right.inferred_type, arch.IntegerType) + w1 = expr.left.inferred_type._width + w2 = expr.right.inferred_type._width + s1 = expr.left.inferred_type.signed + s2 = expr.right.inferred_type.signed + if expr.op.value == "+": + if not s1 and not s2: + wr = max(w1, w2) + 1 + sr = False + elif s1 and s2: + wr = max(w1, w2) + 1 + sr = True + elif s1 and not s2: + wr = max(w1, w2 + 1) + 1 + sr = True + elif not s1 and s2: + wr = max(w1 + 1, w2) + 1 + sr = True + elif expr.op.value == "-": + sr = True + if not s1 and not s2: + wr = max(w1 + 1, w2 + 1) + elif s1 and s2: + wr = max(w1 + 1, w2 + 1) + elif s1 and not s2: + wr = max(w1, w2 + 1) + 1 + elif not s1 and s2: + wr = max(w1 + 1, w2) + 1 + elif expr.op.value == "*": + wr = w1 + w2 + sr = s1 or s2 + elif expr.op.value == "/": + wr = w1 if not s2 else (w1 + 1) + sr = s1 or s2 + elif expr.op.value == "%": + if not s1 and not s2: + wr = min(w1, w2) + sr = False + elif s1 and s2: + wr = min(w1, w2) + sr = True + elif s1 and not s2: + wr = min(w1, w2 + 1) + sr = True + elif not s1 and s2: + wr = min(w1, max(1, w2 - 1)) + sr = False + elif expr.op.value in ["|", "&", "^"]: + wr = max(w1, w2) + sr = s1 or s2 + elif expr.op.value in [">>", "<<"]: + wr = w1 + sr = s1 + expr.inferred_type = arch.IntegerType(wr, sr, None) + else: + if expr.op.value in ["||", "&&"]: + expr.inferred_type = arch.IntegerType(1, False, None) # unsigned<1> / bool + elif expr.op.value in ["<", ">", "==", "!=", ">=", "<="]: + expr.inferred_type = arch.IntegerType(1, False, None) # unsigned<1> / bool + assert expr.inferred_type is not None + + return expr + + + @generate.register + def _(self, expr: behav.SliceOperation, context): + expr.expr = self.generate(expr.expr, context) + expr.left = self.generate(expr.left, context) + expr.right = self.generate(expr.right, context) + + # type inference + if expr.expr.inferred_type is None: + logger.warning("Slice Operation needs inferred type. Skipping...") + return expr + assert isinstance(expr.expr.inferred_type, arch.IntegerType) + ty = expr.expr.inferred_type + # For non-static slices, we cann not infer the type! + if not isinstance(expr.left, behav.IntLiteral): + logger.warning("Can not infer type of non-static slice operation. Skipping...") + return expr + lval = expr.left.value + if not isinstance(expr.right, behav.IntLiteral): + logger.warning("Can not infer type of non-static slice operation. Skipping...") + return expr + rval = expr.right.value + width = lval - rval + 1 if lval > rval else rval - lval + 1 + ty_ = copy(ty) + ty_._width = width + expr.inferred_type = ty_ + + return expr + + + @generate.register + def _(self, expr: behav.ConcatOperation, context): + expr.left = self.generate(expr.left, context) + expr.right = self.generate(expr.right, context) + if expr.left.inferred_type is None: + logger.warning("Concat Operation needs inferred type. Skipping...") + return expr + if expr.right.inferred_type is None: + logger.warning("Concat Operation needs inferred type. Skipping...") + return expr + width = expr.left.inferred_type.width + expr.right.inferred_type.width + ty = arch.IntegerType(width, False, None) + expr.inferred_type = ty + + return expr + + + @generate.register + def _(self, expr: behav.IntLiteral, context): + if isinstance(expr, behav.IntLiteral): + bit_size = expr.bit_size + signed = expr.signed + + expr.inferred_type = arch.IntegerType(bit_size, signed, None) + return expr + + + @generate.register + def _(self, expr: behav.IntLiteral, context): + # type inference + bit_size = expr.bit_size + signed = expr.signed + + expr.inferred_type = arch.IntegerType(bit_size, signed, None) + + return expr + + @generate.register + def _(self, expr: behav.ScalarDefinition, context): + # type inference + signed = expr.scalar.data_type == arch.DataType.S + width = expr.scalar.size + expr.inferred_type = arch.IntegerType(width, signed, None) + return expr + + @generate.register + def _(self, expr: behav.Assignment, context): + expr.target = self.generate(expr.target, context) + expr.expr = self.generate(expr.expr, context) + + # if isinstance(expr.expr, behav.IntLiteral) and isinstance(expr.target, behav.ScalarDefinition): + # expr.target.scalar.value = expr.expr.value + + # type inference + expr.inferred_type = None + + return expr + + @generate.register + def _(self, expr: behav.Conditional, context): + expr.conds = [self.generate(x, context) for x in expr.conds] + # expr.stmts = [[self.generate(y, context) for y in x] for x in expr.stmts] + stmts = [] + for stmt in expr.stmts: + if isinstance(stmt, list): # TODO: legacy? + new = [seld.generate(y, context) for y in stmt] + else: + new = self.generate(stmt, context) + stmts.append(new) + expr.stmts = stmts + + return expr + + @generate.register + def _(self, expr: behav.Loop, context): + expr.cond = self.generate(expr.cond, context) + expr.stmts = [self.generate(x, context) for x in expr.stmts] + + return expr + + @generate.register + def _(self, expr: behav.Ternary, context): + + expr.cond = self.generate(expr.cond, context) + expr.then_expr = self.generate(expr.then_expr, context) + expr.else_expr = self.generate(expr.else_expr, context) + + # TODO + then_ty = expr.then_expr.inferred_type + else_ty = expr.else_expr.inferred_type + if then_ty and else_ty: + # assert then_ty.signed == else_ty.signed + wt = then_ty.width + we = else_ty.width + wr = max(wt, we) + expr.inferred_type = arch.IntegerType(wr, True, None) + + return expr + + @generate.register + def _(self, expr: behav.Return, context): + if expr.expr is not None: + expr.expr = self.generate(expr.expr, context) + + return expr + + @generate.register + def _(self, expr: behav.UnaryOperation, context): + expr.right = self.generate(expr.right, context) + if expr.right.inferred_type: + w1 = expr.right.inferred_type.width + if expr.op.value == "-": + inferred_type = arch.IntegerType(w1 + 1, True, None) + elif expr.op.value == "~": + inferred_type = arch.IntegerType(w1, True, None) + elif expr.op.value == "!": + inferred_type = arch.IntegerType(1, False, None) + else: + inferred_type = None + expr.inferred_type = inferred_type + + return expr + + @generate.register + def _(self, expr: behav.NamedReference, context): + reference = expr.reference + + # type inference + # expr.infered_type = ? + if isinstance(reference, arch.BitFieldDescr): + assert expr.reference.data_type in [arch.DataType.U, arch.DataType.S] + ty = arch.IntegerType(reference.size, reference.data_type == arch.DataType.S, None) + expr.inferred_type = ty + + elif isinstance(reference, arch.Scalar): + dt = reference.data_type + sz = reference.size + assert dt in [arch.DataType.U, arch.DataType.S] + signed = dt == arch.DataType.S + ty = arch.IntegerType(sz, signed, None) + expr.inferred_type = ty + elif isinstance(reference, arch.Memory): + expr.inferred_type = arch.IntegerType(reference.size, False, None) + elif isinstance(reference, arch.Intrinsic): + assert expr.reference.data_type in [arch.DataType.U, arch.DataType.S] + expr.inferred_type = arch.IntegerType(reference.size, reference.data_type == arch.DataType.S, None) + elif isinstance(reference, arch.Constant): + expr.inferred_type = arch.IntegerType(reference.size, reference.signed, None) + else: + assert False, "Unhandled reference" + + return expr + + @generate.register + def _(self, expr: behav.IndexedReference, context): + expr.index = self.generate(expr.index, context) + + # type inference + assert isinstance(expr.reference, arch.Memory) + ty = arch.DataType.U # TODO: Memory class should keep track of dtype, not only size? + assert ty in [arch.DataType.U, arch.DataType.S] + single_mem_acc_size = expr.reference.size + + ## Simple eval check for ranged access. + # Little-endian interpretation: + # - lhs > rhs → width = lhs - rhs + 1 + # - lhs == rhs → width = 8 bits + + if expr.right == None: + size = single_mem_acc_size + if expr.right != None: + lhs_offset = helper_expr_size(expr.index) + rhs_offset = helper_expr_size(expr.right) + assert(lhs_offset >= rhs_offset) + size = (lhs_offset - rhs_offset + 1)*single_mem_acc_size + + ty_ = arch.IntegerType(size, ty == arch.DataType.S, None) + + expr.inferred_type = ty_ + + return expr + + @generate.register + def _(self, expr: behav.TypeConv, context): + expr.expr = self.generate(expr.expr, context) + + ty = deepcopy(expr.expr.inferred_type) + if ty is None: + logger.warning("Type conv needs inferred type. Skipping...") + return expr + assert isinstance(ty, arch.IntegerType) + assert expr.data_type in [arch.DataType.U, arch.DataType.S] + ty.signed = expr.data_type == arch.DataType.S + if expr.size is not None: + ty._width = expr.size + + # type inference + expr.inferred_type = ty + + return expr + + @generate.register + def _(self, expr: behav.Callable, context): + if isinstance(expr.ref_or_name, arch.Function): + if expr.ref_or_name.data_type == arch.DataType.NONE: + signed = None + else: + assert expr.ref_or_name.data_type in [arch.DataType.U, arch.DataType.S] + signed = expr.ref_or_name.data_type == arch.DataType.S + width = expr.ref_or_name.size + expr.inferred_type = arch.IntegerType(width, signed, None) + expr.args = [self.generate(stmt, context) for stmt in expr.args] + + return expr + + # Just use Callable super class + # @generate.register + # def _(self, expr: behav.ProcedureCall, context): + # expr.args = [self.generate(stmt, context) for stmt in expr.args] + + # return expr + + @generate.register + def _(self, expr: behav.Group, context): + expr.expr = self.generate(expr.expr, context) + + if isinstance(expr.expr, behav.IntLiteral): + return expr.expr + + # type inference + expr.inferred_type = expr.expr.inferred_type + + return expr + + @generate.register + def _(self, expr: behav.Break, context): + return expr + + +# Simple expression evaluation for ranged mem indexes +def helper_expr_size(sub_expr: behav.BaseNode): + expr = None + if type(sub_expr) == behav.Group: + expr = sub_expr.expr + return helper_expr_size(expr) + elif type(sub_expr) == behav.BinaryOperation: + expr = sub_expr + assert isinstance(expr.left, behav.NamedReference) + + if expr.op.value == "+": + if type(expr.right) == behav.IntLiteral: + return int(expr.right.value) + + elif expr.op.value == "-": + if type(expr.right) == behav.IntLiteral: + return (-1 * int(expr.right.value)) + else: + raise(f"Not supported Operation value Type {expr.op.value} within mem access range") + + elif type(sub_expr) == behav.NamedReference: + return 0 + else: + raise(f"Not supported expr Type {type(sub_expr)} within mem access range") diff --git a/m2isar/transforms/validate_behav/__init__.py b/m2isar/transforms/validate_behav/__init__.py new file mode 100644 index 00000000..71ee24de --- /dev/null +++ b/m2isar/transforms/validate_behav/__init__.py @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# This file is part of the M2-ISA-R project: https://github.com/tum-ei-eda/M2-ISA-R +# +# Copyright (C) 2024 +# Chair of Electrical Design Automation +# Technical University of Munich + +"""This module contains a behavior validator pass for M2-ISA-R models. +Type inference has to be run first! +""" diff --git a/m2isar/transforms/validate_behav/validate.py b/m2isar/transforms/validate_behav/validate.py new file mode 100644 index 00000000..041b491d --- /dev/null +++ b/m2isar/transforms/validate_behav/validate.py @@ -0,0 +1,75 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# This file is part of the M2-ISA-R project: https://github.com/tum-ei-eda/M2-ISA-R +# +# Copyright (C) 2022 +# Chair of Electrical Design Automation +# Technical University of Munich + +"""validate behavior in M2-ISA-R metamodel.""" + +import sys +import argparse +import logging +import pathlib + +from ...metamodel import patch_model, load_model, dump_model +from ...warnings import WarningsManager, WarningsInfo, add_warnings_flags, KNOWN_WARNINGS +from .visitor import ValidateBehavVisitor + +from . import visitor + +class ValidatorContext(WarningsManager): + """Track miscellaneous information throughout the validation process.""" + def __init__(self, warnings_info: WarningsInfo = None): + super().__init__(warnings_info) + + +def get_parser(): + # read command line args + parser = argparse.ArgumentParser() + parser.add_argument("top_level", help="A .m2isarmodel file.") + parser.add_argument("--log", default="info", choices=["critical", "error", "warning", "info", "debug"]) + add_warnings_flags(parser, KNOWN_WARNINGS, KNOWN_WARNINGS) + return parser + + +def validate_behav(model_obj, warnings_info): + logger = logging.getLogger("validate_behav") + for _, core_def in model_obj.cores.items(): + logger.debug("validating behavior for core %s", core_def.name) + context = ValidatorContext(warnings_info) + visitor = ValidateBehavVisitor() + for _, instr_def in core_def.instructions.items(): + logger.debug("validating behavior for instr %s", instr_def.name) + visitor.generate(instr_def.operation, context) + for _, set_def in model_obj.sets.items(): + logger.debug("validating behavior for set %s", set_def.name) + context = ValidatorContext(warnings_info) + visitor = ValidateBehavVisitor() + for _, instr_def in set_def.instructions.items(): + logger.debug("validating behavior for instr %s", instr_def.name) + visitor.generate(instr_def.operation, context) + # return model_obj + + +def run(args): + # initialize logging + logging.basicConfig(level=getattr(logging, args.log.upper())) + + # resolve model paths + top_level = pathlib.Path(args.top_level) + + model_obj = load_model(top_level) + warnings_info = args.warnings + alidate_behav(model_obj, warnings_info) + + +def main(argv): + parser = get_parser() + args = parser.parse_args(argv) + run(args) + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/m2isar/transforms/validate_behav/visitor.py b/m2isar/transforms/validate_behav/visitor.py new file mode 100644 index 00000000..b6b59c44 --- /dev/null +++ b/m2isar/transforms/validate_behav/visitor.py @@ -0,0 +1,160 @@ + + +# SPDX-License-Identifier: Apache-2.0 +# +# This file is part of the M2-ISA-R project: https://github.com/tum-ei-eda/M2-ISA-R +# +# Copyright (C) 2022 +# Chair of Electrical Design Automation +# Technical University of Munich + + +import logging +from copy import copy +from functools import singledispatchmethod + +from m2isar.metamodel import arch, behav +from ...metamodel.utils.ExprVisitor import ExprVisitor + +logger = logging.getLogger("validate_behav") + +# pylint: disable=unused-argument + +class ValidateBehavVisitor(ExprVisitor): + """Visitor to validate a metamodel.""" + + @singledispatchmethod + def generate(self, expr: behav.BaseNode, context): + raise NotImplementedError(f"No visit method implemented for type {type(expr).__name__} in {type(self).__name__}") + + @generate.register + def _(self, expr: behav.Operation, context): + for stmt in expr.statements: + self.generate(stmt, context) + + @generate.register + def _(self, expr: behav.Block, context): + for stmt in expr.statements: + self.generate(stmt, context) + + @generate.register + def _(self, expr: behav.BinaryOperation, context): + self.generate(expr.left, context) + self.generate(expr.right, context) + op = expr.op + + assert expr.left.inferred_type is not None + assert expr.right.inferred_type is not None + if op.value in ["|", "&", "^"] and expr.left.inferred_type.width != expr.right.inferred_type.width: + context.emit_warning(f"Bitwise operations with differently size operands are discouraged.", "bit-op-missmatch", logger=logger, line_info=expr.line_info) + if op.value in ["<<", ">>", ">>>"] and expr.right.inferred_type.signed: + context.emit_warning(f"Shift by signed amount", "shift-signed", logger=logger, line_info=expr.line_info) + if op.value in ["<", "<=", ">", ">=", "==", "!="] and expr.left.inferred_type.signed != expr.right.inferred_type.signed: + if isinstance(expr.left, behav.IntLiteral) and expr.left.value == 0: + pass + if isinstance(expr.right, behav.IntLiteral) and expr.right.value == 0: + pass + else: + context.emit_warning(f"Signed vs. unsigned comparison", "sign-compare", logger=logger, line_info=expr.line_info) + # TODO: also check possible range of non-literal rhs? + if op.value == "<<" and isinstance(expr.right, behav.IntLiteral) and expr.left.inferred_type.width <= expr.right.value: + context.emit_warning(f"Shift count overflow for << operation ({expr.left.inferred_type.width} vs. {expr.right.value})", "shift-overflow", logger=logger, line_info=expr.line_info) + + + @generate.register + def _(self, expr: behav.SliceOperation, context): + self.generate(expr.expr, context) + self.generate(expr.left, context) + self.generate(expr.right, context) + + @generate.register + def _(self, expr: behav.ConcatOperation, context): + self.generate(expr.left, context) + self.generate(expr.right, context) + + @generate.register + def _(self, expr: behav.NumberLiteral, context): + pass + + @generate.register + def _(self, expr: behav.IntLiteral, context): + pass + + @generate.register + def _(self, expr: behav.StringLiteral, context): + pass + + @generate.register + def _(self, expr: behav.Assignment, context): + self.generate(expr.target, context) + self.generate(expr.expr, context) + assert expr.target.inferred_type is not None + assert expr.expr.inferred_type is not None + if expr.target.inferred_type.width < expr.expr.inferred_type.width: + context.emit_warning(f"Implicit truncation {expr.expr.inferred_type.width} -> {expr.target.inferred_type.width} found", "implicit-trunc", logger=logger, line_info=expr.line_info) + if expr.target.inferred_type.width > expr.expr.inferred_type.width: + context.emit_warning(f"Implicit extend {expr.expr.inferred_type.width} -> {expr.target.inferred_type.width} found", "implicit-extend", logger=logger, line_info=expr.line_info) + + @generate.register + def _(self, expr: behav.Conditional, context): + for cond in expr.conds: + self.generate(cond, context) + for stmt in expr.stmts: + self.generate(stmt, context) + + @generate.register + def _(self, expr: behav.Loop, context): + self.generate(expr.cond, context) + for stmt in expr.stmts: + self.generate(stmt, context) + + @generate.register + def _(self, expr: behav.Ternary, context): + self.generate(expr.cond, context) + self.generate(expr.then_expr, context) + self.generate(expr.else_expr, context) + + @generate.register + def _(self, expr: behav.Return, context): + if expr.expr is not None: + self.generate(expr.expr, context) + return expr + + @generate.register + def _(self, expr: behav.UnaryOperation, context): + self.generate(expr.right, context) + return expr + + @generate.register + def _(self, expr: behav.ScalarDefinition, context): + pass + + @generate.register + def _(self, expr: behav.Break, context): + pass + + @generate.register + def _(self, expr: behav.NamedReference, context): + pass + + @generate.register + def _(self, expr: behav.IndexedReference, context): + self.generate(expr.index, context) + + @generate.register + def _(self, expr: behav.TypeConv, context): + self.generate(expr.expr, context) + + @generate.register + def _(self, expr: behav.Callable, context): + for arg in expr.args: + self.generate(arg, context) + + @generate.register + def _(self, expr: behav.Callable, context): + for arg in expr.args: + self.generate(arg, context) + + @generate.register + def _(self, expr: behav.Group, context): + self.generate(expr.expr, context) diff --git a/m2isar/warnings.py b/m2isar/warnings.py new file mode 100644 index 00000000..68b1a81e --- /dev/null +++ b/m2isar/warnings.py @@ -0,0 +1,108 @@ +import argparse +from dataclasses import dataclass, field +from typing import Set + +KNOWN_WARNINGS = { + 'implicit-trunc', + 'shift-overflow', + 'shift-signed', + 'implicit-extend', + 'sign-compare', + 'unused-value', + 'bit-op-missmatch', +} + + +@dataclass +class WarningsInfo: + # known: Set[str] = field(default_factory=set) + known: Set[str] = field(default_factory=lambda: set(KNOWN_WARNINGS)) + # defaults: Set[str] = field(default_factory=set) + defaults: Set[str] = field(default_factory=lambda: set(KNOWN_WARNINGS)) + enabled: Set[str] = field(default_factory=set) + disabled: Set[str] = field(default_factory=set) + as_error: Set[str] = field(default_factory=set) + all_as_error: bool = False + + @property + def warnings(self): + return (self.defaults - self.disabled) | self.enabled + + @property + def errors(self): + return self.as_error if not self.all_as_error else self.warnings + + +class WarningFlagAction(argparse.Action): + def __call__(self, parser, namespace, values, option_string=None): + warnings_info = getattr(namespace, 'warnings', None) + if warnings_info is None: + warnings_info = WarningsInfo() + + for val in values: + if val == 'no-error': + warnings_info.all_as_error = False + elif val.startswith('no-'): + warn = val[3:] + assert warn in warnings_info.known, f"Unknown warning: {warn}" + warnings_info.disabled.add(warn) + elif val.startswith('error='): + warn = val[6:] + assert warn in warnings_info.known, f"Unknown warning: {warn}" + warnings_info.as_error.add(warn) + elif val == 'error': + warnings_info.all_as_error = True + elif val == 'all': + warnings_info.enabled.update(warnings_info.known) + else: + warn = val[3:] + assert warn in warnings_info.known, f"Unknown warning: {val}" + warnings_info.enabled.add(val) + # No need for -Wall as all warnings are enabled by default + + setattr(namespace, 'warnings', warnings_info) + +def add_warnings_flags(parser, known_warnings: Set[str], default_warnings: Set[str]): + parser.add_argument( + '-W', + dest='warnings', + metavar='warning', + action=WarningFlagAction, + nargs='+', + help=( + "Enable/disable warnings like -Wfoo or -Wno-foo; " + "Make fatal with -Werror or -Werror=foo" + ), + ) + + # Defaults + warnings_info = WarningsInfo(known=known_warnings, defaults=default_warnings) + parser.set_defaults(warnings=warnings_info) + + +class WarningsManager: + def __init__(self, warnings_info: WarningsInfo): + self.warnings_info = warnings_info + + # TODO: warnings as errors? + def emit_warning(self, msg, name=None, logger=None, line_info=None): + log_warn_f = logging.warning if logger is None else logger.warning + log_err_f = logging.error if logger is None else logger.error + if self.warnings_info is None: + return # ignore + assert name in self.warnings_info.known, f"Unknown warning: {name}" + is_err = name in self.warnings_info.errors + if name not in self.warnings_info.warnings: + # do nothing + return + log_f = log_err_f if is_err else log_warn_f + if name is not None: + msg += f" [-W{name}]" + if line_info is not None: + line_info_str = f"{line_info.file_path}:{line_info.start_line_no}" + msg += f" @ {line_info_str}" + log_f(msg) + if is_err: + raise RuntimeError(msg) + + diff --git a/pyproject.toml b/pyproject.toml index f71860d4..7772c995 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ maintainers = [ ] dynamic = ["version"] requires-python = ">=3.8" -license = {text = "Apache-2.0"} +license = "Apache-2.0" readme = "README.md" dependencies = [ "mako",