From 56191d2cb25137bbef9493aeb52824a3b8f98393 Mon Sep 17 00:00:00 2001 From: jokap11 Date: Fri, 20 Mar 2026 22:45:53 +0100 Subject: [PATCH 01/62] ExprVisitor MetaClass instead of MonkeyPatch --- m2isar/metamodel/utils/ExprVisitor.py | 129 ++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 m2isar/metamodel/utils/ExprVisitor.py diff --git a/m2isar/metamodel/utils/ExprVisitor.py b/m2isar/metamodel/utils/ExprVisitor.py new file mode 100644 index 00000000..d264cce7 --- /dev/null +++ b/m2isar/metamodel/utils/ExprVisitor.py @@ -0,0 +1,129 @@ +# 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. + """ + @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 visit_codeliteral(self, expr: behav.CodeLiteral, context): + pass + + @generate.register + def visit_operator(self, expr: behav.Operator, context): + pass + + @generate.register + def visit_operation(self, expr: behav.Operation, context): + for stmt in expr.statements: + stmt.generate(context) + + @generate.register + def visit_block(self, expr: behav.Block, context): + for stmt in expr.statements: + stmt.generate(context) + + @generate.register + def visit_binary_operation(self, expr: behav.BinaryOperation, context): + expr.left.generate(context) + expr.right.generate(context) + + @generate.register + def visit_slice_operation(self, expr: behav.SliceOperation, context): + expr.expr.generate(context) + expr.left.generate(context) + expr.right.generate(context) + + @generate.register + def visit_concat_operation(self, expr: behav.ConcatOperation, context): + expr.left.generate(context) + expr.right.generate(context) + + @generate.register + def visit_number_literal(self, expr: behav.NumberLiteral, context): + pass + + @generate.register + def visit_int_literal(self, expr: behav.IntLiteral, context): + pass + + @generate.register + def visit_string_literal(self, expr: behav.StringLiteral, context): + pass + + @generate.register + def visit_assignment(self, expr: behav.Assignment, context): + expr.target.generate(context) + expr.expr.generate(context) + + @generate.register + def visit_conditional(self, expr: behav.Conditional, context): + for cond in expr.conds: + cond.generate(context) + for stmt in expr.stmts: + stmt.generate(context) + + @generate.register + def visit_loop(self, expr: behav.Loop, context): + expr.cond.generate(context) + for stmt in expr.stmts: + stmt.generate(context) + + # TODO: Add more visit methods for other node types as needed, e.g., Ternary, etc. + @generate.register + def visit_ternary_operation(self, expr: behav.Ternary, context): + expr.cond.generate(context) + expr.then_expr.generate(context) + expr.else_expr.generate(context) + + @generate.register + def visit_scalar_definition(self, expr: behav.ScalarDefinition, context): + pass + + @generate.register + def visit_break(self, expr: behav.Break, context): + pass + + @generate.register + def visit_named_reference(self, expr: behav.NamedReference, context): + pass + + @generate.register + def visit_type_conv(self, expr: behav.TypeConv, context): + expr.expr.generate(context) + + @generate.register + def visit_callable(self, expr: behav.Callable, context): + for arg in expr.args: + arg.generate(context) + + @generate.register + def visit_procedure_call(self, expr: behav.Callable, context): + for arg in expr.args: + arg.generate(context) + + @generate.register + def group(self, expr: behav.Group, context): + expr.expr.generate(context) From 7e1f9eb5d67e8010d8041217e9006f75b6d64e67 Mon Sep 17 00:00:00 2001 From: jokap11 Date: Fri, 20 Mar 2026 22:52:53 +0100 Subject: [PATCH 02/62] [Backend] Use ExprVisitor for Modelviewer --- m2isar/backends/viewer/treegen.py | 313 +++++++++++++++++------------- m2isar/backends/viewer/viewer.py | 17 +- 2 files changed, 184 insertions(+), 146 deletions(-) diff --git a/m2isar/backends/viewer/treegen.py b/m2isar/backends/viewer/treegen.py index 83040a02..4ec71d30 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,245 @@ from ...metamodel import behav from .utils import TreeGenContext +from ...metamodel.utils.ExprVisitor import ExprVisitor +from .utils import TreeGenContext # 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.""" + + @ExprVisitor.generate.register + def visit_operation(self, expr: behav.Operation, context: "TreeGenContext"): + context.push(context.tree.insert(context.parent, tk.END, text="Operation")) - context.pop() + for stmt in expr.statements: + stmt.generate(context) + + context.pop() -def block(self: behav.Block, context: "TreeGenContext"): - context.push(context.tree.insert(context.parent, tk.END, text="Block")) + @ExprVisitor.generate.register + def visit_block(self, expr: behav.Block, context: "TreeGenContext"): + context.push(context.tree.insert(context.parent, tk.END, text="Block")) - for stmt in self.statements: - stmt.generate(context) + for stmt in expr.statements: + stmt.generate(context) - context.pop() + context.pop() -def binary_operation(self: behav.BinaryOperation, context: "TreeGenContext"): - context.push(context.tree.insert(context.parent, tk.END, text="Binary Operation")) + @ExprVisitor.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.push(context.tree.insert(context.parent, tk.END, text="Left")) - self.left.generate(context) - context.pop() + context.push(context.tree.insert(context.parent, tk.END, text="Left")) + expr.left.generate(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")) + expr.right.generate(context) + context.pop() - context.tree.insert(context.parent, tk.END, text="Op", values=(self.op.value,)) + context.tree.insert(context.parent, tk.END, text="Op", values=(expr.op.value,)) - context.pop() + context.pop() -def slice_operation(self: behav.SliceOperation, context: "TreeGenContext"): - context.push(context.tree.insert(context.parent, tk.END, text="Slice Operation")) + @ExprVisitor.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="Expr")) - self.expr.generate(context) - context.pop() + context.push(context.tree.insert(context.parent, tk.END, text="Expr")) + expr.expr.generate(context) + context.pop() - context.push(context.tree.insert(context.parent, tk.END, text="Left")) - self.left.generate(context) - context.pop() + context.push(context.tree.insert(context.parent, tk.END, text="Left")) + expr.left.generate(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")) + expr.right.generate(context) + context.pop() - context.pop() + 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="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() + @ExprVisitor.generate.register + def concat_operation(self, expr: behav.ConcatOperation, context: "TreeGenContext"): + context.push(context.tree.insert(context.parent, tk.END, text="Concat Operation")) - context.pop() + context.push(context.tree.insert(context.parent, tk.END, text="Left")) + expr.left.generate(context) + context.pop() -def number_literal(self: behav.NumberLiteral, context: "TreeGenContext"): - context.tree.insert(context.parent, tk.END, text="Number Literal", values=(self.value,)) + context.push(context.tree.insert(context.parent, tk.END, text="Right")) + expr.right.generate(context) + context.pop() -def int_literal(self: behav.IntLiteral, context: "TreeGenContext"): - context.tree.insert(context.parent, tk.END, text="Int Literal", values=(self.value,)) + context.pop() -def scalar_definition(self: behav.ScalarDefinition, context: "TreeGenContext"): - context.tree.insert(context.parent, tk.END, text="Scalar Definition", values=(self.scalar.name,)) + @ExprVisitor.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 break_(self: behav.Break, context: "TreeGenContext"): - context.tree.insert(context.parent, tk.END, text="Break") + @ExprVisitor.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 assignment(self: behav.Assignment, context: "TreeGenContext"): - context.push(context.tree.insert(context.parent, tk.END, text="Assignment")) + @ExprVisitor.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,)) - context.push(context.tree.insert(context.parent, tk.END, text="Target")) - self.target.generate(context) - context.pop() + @ExprVisitor.generate.register + def break_(self, expr: behav.Break, context: "TreeGenContext"): + context.tree.insert(context.parent, tk.END, text="Break") - context.push(context.tree.insert(context.parent, tk.END, text="Expr")) - self.expr.generate(context) - context.pop() + @ExprVisitor.generate.register + def assignment(self, expr: behav.Assignment, context: "TreeGenContext"): + context.push(context.tree.insert(context.parent, tk.END, text="Assignment")) - context.pop() + context.push(context.tree.insert(context.parent, tk.END, text="Target")) + expr.target.generate(context) + context.pop() -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="Expr")) + expr.expr.generate(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.pop() - context.push(context.tree.insert(context.parent, tk.END, text="Statements")) - for stmt in self.stmts: - stmt.generate(context) - context.pop() + @ExprVisitor.generate.register + def conditional(self, expr: behav.Conditional, context: "TreeGenContext"): + context.push(context.tree.insert(context.parent, tk.END, text="Conditional")) - context.pop() + context.push(context.tree.insert(context.parent, tk.END, text="Conditions")) + for cond in expr.conds: + cond.generate(context) + context.pop() -def loop(self: behav.Loop, context: "TreeGenContext"): - context.push(context.tree.insert(context.parent, tk.END, text="Loop")) + context.push(context.tree.insert(context.parent, tk.END, text="Statements")) + for stmt in expr.stmts: + stmt.generate(context) + context.pop() - context.tree.insert(context.parent, tk.END, text="Post Test", values=(self.post_test,)) + context.pop() - context.push(context.tree.insert(context.parent, tk.END, text="Condition")) - self.cond.generate(context) - context.pop() + @ExprVisitor.generate.register + def loop(self, expr: behav.Loop, context: "TreeGenContext"): + context.push(context.tree.insert(context.parent, tk.END, text="Loop")) - context.push(context.tree.insert(context.parent, tk.END, text="Statements")) - for stmt in self.stmts: - stmt.generate(context) - context.pop() + context.tree.insert(context.parent, tk.END, text="Post Test", values=(expr.post_test,)) - context.pop() + context.push(context.tree.insert(context.parent, tk.END, text="Condition")) + expr.cond.generate(context) + context.pop() -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="Statements")) + for stmt in expr.stmts: + stmt.generate(context) + context.pop() - context.push(context.tree.insert(context.parent, tk.END, text="Cond")) - self.cond.generate(context) - context.pop() + context.pop() - context.push(context.tree.insert(context.parent, tk.END, text="Then Expression")) - self.then_expr.generate(context) - context.pop() + @ExprVisitor.generate.register + def ternary(self, expr: 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="Else Expression")) - self.else_expr.generate(context) - context.pop() + context.push(context.tree.insert(context.parent, tk.END, text="Cond")) + expr.cond.generate(context) + context.pop() - context.pop() + context.push(context.tree.insert(context.parent, tk.END, text="Then Expression")) + expr.then_expr.generate(context) + context.pop() -def return_(self: behav.Return, context: "TreeGenContext"): - context.push(context.tree.insert(context.parent, tk.END, text="Return")) + context.push(context.tree.insert(context.parent, tk.END, text="Else Expression")) + expr.else_expr.generate(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() + @ExprVisitor.generate.register + def return_(self, expr: behav.Return, context: "TreeGenContext"): + context.push(context.tree.insert(context.parent, tk.END, text="Return")) -def unary_operation(self: behav.UnaryOperation, context: "TreeGenContext"): - context.push(context.tree.insert(context.parent, tk.END, text="Unary Operation")) + if expr.expr is not None: + context.push(context.tree.insert(context.parent, tk.END, text="Expression")) + expr.expr.generate(context) + context.pop() - context.push(context.tree.insert(context.parent, tk.END, text="Right")) - self.right.generate(context) - context.pop() + context.pop() - context.tree.insert(context.parent, tk.END, text="Op", values=(self.op.value,)) + @ExprVisitor.generate.register + def unary_operation(self, expr: behav.UnaryOperation, context: "TreeGenContext"): + context.push(context.tree.insert(context.parent, tk.END, text="Unary Operation")) - context.pop() + context.push(context.tree.insert(context.parent, tk.END, text="Right")) + expr.right.generate(context) + context.pop() -def named_reference(self: behav.NamedReference, context: "TreeGenContext"): - context.tree.insert(context.parent, tk.END, text="Named Reference", values=(f"{self.reference}",)) + context.tree.insert(context.parent, tk.END, text="Op", values=(expr.op.value,)) -def indexed_reference(self: behav.IndexedReference, context: "TreeGenContext"): - context.push(context.tree.insert(context.parent, tk.END, text="Indexed Reference")) + context.pop() - context.tree.insert(context.parent, tk.END, text="Reference", values=(f"{self.reference}",)) + @ExprVisitor.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}",)) - context.push(context.tree.insert(context.parent, tk.END, text="Index")) - self.index.generate(context) - context.pop() + @ExprVisitor.generate.register + def indexed_reference(self, expr: behav.IndexedReference, context: "TreeGenContext"): + context.push(context.tree.insert(context.parent, tk.END, text="Indexed Reference")) - context.pop() + context.tree.insert(context.parent, tk.END, text="Reference", values=(f"{expr.reference}",)) -def type_conv(self: behav.TypeConv, context: "TreeGenContext"): - context.push(context.tree.insert(context.parent, tk.END, text="Type Conv")) + context.push(context.tree.insert(context.parent, tk.END, text="Index")) + expr.index.generate(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,)) + context.pop() - context.push(context.tree.insert(context.parent, tk.END, text="Expr")) - self.expr.generate(context) - context.pop() + @ExprVisitor.generate.register + def type_conv(self, expr: behav.TypeConv, context: "TreeGenContext"): + context.push(context.tree.insert(context.parent, tk.END, text="Type Conv")) - context.pop() + 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,)) -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.push(context.tree.insert(context.parent, tk.END, text="Expr")) + expr.expr.generate(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() + @ExprVisitor.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,))) + arg.generate(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() + @ExprVisitor.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,))) + arg.generate(context) + context.pop() - context.push(context.tree.insert(context.parent, tk.END, text="Expr")) - self.expr.generate(context) - context.pop() + context.pop() + + @ExprVisitor.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")) + expr.expr.generate(context) + context.pop() + + context.pop() diff --git a/m2isar/backends/viewer/viewer.py b/m2isar/backends/viewer/viewer.py index 4178d3c8..9663ee0c 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.""" @@ -22,7 +25,7 @@ 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")) From 2fd123e4afd2633f01a90f3de819f8471d793073 Mon Sep 17 00:00:00 2001 From: jokap11 Date: Sat, 21 Mar 2026 11:27:38 +0100 Subject: [PATCH 03/62] ExprVisitor calling convention plus bugfix in treeview --- m2isar/backends/viewer/treegen.py | 52 +++++++++++++-------------- m2isar/metamodel/utils/ExprVisitor.py | 2 ++ 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/m2isar/backends/viewer/treegen.py b/m2isar/backends/viewer/treegen.py index 4ec71d30..186c3bc1 100644 --- a/m2isar/backends/viewer/treegen.py +++ b/m2isar/backends/viewer/treegen.py @@ -29,7 +29,7 @@ def visit_operation(self, expr: behav.Operation, context: "TreeGenContext"): context.push(context.tree.insert(context.parent, tk.END, text="Operation")) for stmt in expr.statements: - stmt.generate(context) + self.generate(stmt, context) context.pop() @@ -38,7 +38,7 @@ def visit_block(self, expr: behav.Block, context: "TreeGenContext"): context.push(context.tree.insert(context.parent, tk.END, text="Block")) for stmt in expr.statements: - stmt.generate(context) + self.generate(stmt, context) context.pop() @@ -47,11 +47,11 @@ def visit_binary_operation(self, expr: behav.BinaryOperation, context: "TreeGenC context.push(context.tree.insert(context.parent, tk.END, text="Binary Operation")) context.push(context.tree.insert(context.parent, tk.END, text="Left")) - expr.left.generate(context) + self.generate(expr.left, context) context.pop() context.push(context.tree.insert(context.parent, tk.END, text="Right")) - expr.right.generate(context) + self.generate(expr.right, context) context.pop() context.tree.insert(context.parent, tk.END, text="Op", values=(expr.op.value,)) @@ -63,32 +63,30 @@ def visit_slice_operation(self, expr: behav.SliceOperation, context: "TreeGenCon context.push(context.tree.insert(context.parent, tk.END, text="Slice Operation")) context.push(context.tree.insert(context.parent, tk.END, text="Expr")) - expr.expr.generate(context) + self.generate(expr.expr, context) context.pop() context.push(context.tree.insert(context.parent, tk.END, text="Left")) - expr.left.generate(context) + self.generate(expr.left, context) context.pop() context.push(context.tree.insert(context.parent, tk.END, text="Right")) - expr.right.generate(context) + self.generate(expr.right, context) context.pop() context.pop() - context.pop() - @ExprVisitor.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")) - expr.left.generate(context) + self.generate(expr.left, context) context.pop() context.push(context.tree.insert(context.parent, tk.END, text="Right")) - expr.right.generate(context) + self.generate(expr.right, context) context.pop() context.pop() @@ -114,11 +112,11 @@ 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")) - expr.target.generate(context) + self.generate(expr.target, context) context.pop() context.push(context.tree.insert(context.parent, tk.END, text="Expr")) - expr.expr.generate(context) + self.generate(expr.expr, context) context.pop() context.pop() @@ -129,12 +127,12 @@ def conditional(self, expr: behav.Conditional, context: "TreeGenContext"): context.push(context.tree.insert(context.parent, tk.END, text="Conditions")) for cond in expr.conds: - cond.generate(context) + self.generate(cond, context) context.pop() context.push(context.tree.insert(context.parent, tk.END, text="Statements")) for stmt in expr.stmts: - stmt.generate(context) + self.generate(stmt, context) context.pop() context.pop() @@ -146,12 +144,12 @@ def loop(self, expr: behav.Loop, context: "TreeGenContext"): context.tree.insert(context.parent, tk.END, text="Post Test", values=(expr.post_test,)) context.push(context.tree.insert(context.parent, tk.END, text="Condition")) - expr.cond.generate(context) + self.generate(expr.cond, context) context.pop() context.push(context.tree.insert(context.parent, tk.END, text="Statements")) for stmt in expr.stmts: - stmt.generate(context) + self.generate(stmt, context) context.pop() context.pop() @@ -161,15 +159,15 @@ def ternary(self, expr: 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")) - expr.cond.generate(context) + self.generate(expr.cond, context) context.pop() context.push(context.tree.insert(context.parent, tk.END, text="Then Expression")) - expr.then_expr.generate(context) + self.generate(expr.then_expr, context) context.pop() context.push(context.tree.insert(context.parent, tk.END, text="Else Expression")) - expr.else_expr.generate(context) + self.generate(expr.else_expr, context) context.pop() context.pop() @@ -180,7 +178,7 @@ def return_(self, expr: behav.Return, context: "TreeGenContext"): if expr.expr is not None: context.push(context.tree.insert(context.parent, tk.END, text="Expression")) - expr.expr.generate(context) + self.generate(expr.expr, context) context.pop() context.pop() @@ -190,7 +188,7 @@ def unary_operation(self, expr: 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")) - expr.right.generate(context) + self.generate(expr.right, context) context.pop() context.tree.insert(context.parent, tk.END, text="Op", values=(expr.op.value,)) @@ -208,7 +206,7 @@ def indexed_reference(self, expr: behav.IndexedReference, context: "TreeGenConte context.tree.insert(context.parent, tk.END, text="Reference", values=(f"{expr.reference}",)) context.push(context.tree.insert(context.parent, tk.END, text="Index")) - expr.index.generate(context) + self.generate(expr.index, context) context.pop() context.pop() @@ -221,7 +219,7 @@ def type_conv(self, expr: behav.TypeConv, context: "TreeGenContext"): context.tree.insert(context.parent, tk.END, text="Size", values=(expr.size,)) context.push(context.tree.insert(context.parent, tk.END, text="Expr")) - expr.expr.generate(context) + self.generate(expr.expr, context) context.pop() context.pop() @@ -232,7 +230,7 @@ def callable_(self, expr: behav.Callable, context: "TreeGenContext"): 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,))) - arg.generate(context) + self.generate(arg, context) context.pop() context.pop() @@ -243,7 +241,7 @@ def procedure_call(self, expr: behav.ProcedureCall, context: "TreeGenContext"): 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,))) - arg.generate(context) + self.generate(arg, context) context.pop() context.pop() @@ -253,7 +251,7 @@ def group(self, expr: behav.Group, context: "TreeGenContext"): context.push(context.tree.insert(context.parent, tk.END, text="Group")) context.push(context.tree.insert(context.parent, tk.END, text="Expr")) - expr.expr.generate(context) + self.generate(expr.expr, context) context.pop() context.pop() diff --git a/m2isar/metamodel/utils/ExprVisitor.py b/m2isar/metamodel/utils/ExprVisitor.py index d264cce7..3fe458e1 100644 --- a/m2isar/metamodel/utils/ExprVisitor.py +++ b/m2isar/metamodel/utils/ExprVisitor.py @@ -22,6 +22,8 @@ class ExprVisitor(ABC): - 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. """ @singledispatchmethod def generate(self, expr : behav.BaseNode, context=None): From 7633fb654174d5e0b7cd2b58507405ca414e7f2a Mon Sep 17 00:00:00 2001 From: jokap11 Date: Sat, 21 Mar 2026 14:12:09 +0100 Subject: [PATCH 04/62] ExprVisitor get rid of global dispatch table --- m2isar/backends/viewer/treegen.py | 47 ++++++++++++------------ m2isar/metamodel/utils/ExprVisitor.py | 51 ++++++++++++++------------- 2 files changed, 52 insertions(+), 46 deletions(-) diff --git a/m2isar/backends/viewer/treegen.py b/m2isar/backends/viewer/treegen.py index 186c3bc1..f6605f01 100644 --- a/m2isar/backends/viewer/treegen.py +++ b/m2isar/backends/viewer/treegen.py @@ -17,14 +17,17 @@ from .utils import TreeGenContext from ...metamodel.utils.ExprVisitor import ExprVisitor from .utils import TreeGenContext - +from functools import singledispatchmethod # pylint: disable=unused-argument 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__}") - @ExprVisitor.generate.register + @generate.register def visit_operation(self, expr: behav.Operation, context: "TreeGenContext"): context.push(context.tree.insert(context.parent, tk.END, text="Operation")) @@ -33,7 +36,7 @@ def visit_operation(self, expr: behav.Operation, context: "TreeGenContext"): context.pop() - @ExprVisitor.generate.register + @generate.register def visit_block(self, expr: behav.Block, context: "TreeGenContext"): context.push(context.tree.insert(context.parent, tk.END, text="Block")) @@ -42,7 +45,7 @@ def visit_block(self, expr: behav.Block, context: "TreeGenContext"): context.pop() - @ExprVisitor.generate.register + @generate.register def visit_binary_operation(self, expr: behav.BinaryOperation, context: "TreeGenContext"): context.push(context.tree.insert(context.parent, tk.END, text="Binary Operation")) @@ -58,7 +61,7 @@ def visit_binary_operation(self, expr: behav.BinaryOperation, context: "TreeGenC context.pop() - @ExprVisitor.generate.register + @generate.register def visit_slice_operation(self, expr: behav.SliceOperation, context: "TreeGenContext"): context.push(context.tree.insert(context.parent, tk.END, text="Slice Operation")) @@ -77,7 +80,7 @@ def visit_slice_operation(self, expr: behav.SliceOperation, context: "TreeGenCon context.pop() - @ExprVisitor.generate.register + @generate.register def concat_operation(self, expr: behav.ConcatOperation, context: "TreeGenContext"): context.push(context.tree.insert(context.parent, tk.END, text="Concat Operation")) @@ -91,23 +94,23 @@ def concat_operation(self, expr: behav.ConcatOperation, context: "TreeGenContext context.pop() - @ExprVisitor.generate.register + @generate.register def number_literal(self, expr: behav.NumberLiteral, context: "TreeGenContext"): context.tree.insert(context.parent, tk.END, text="Number Literal", values=(expr.value,)) - @ExprVisitor.generate.register + @generate.register def int_literal(self, expr: behav.IntLiteral, context: "TreeGenContext"): context.tree.insert(context.parent, tk.END, text="Int Literal", values=(expr.value,)) - @ExprVisitor.generate.register + @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,)) - @ExprVisitor.generate.register + @generate.register def break_(self, expr: behav.Break, context: "TreeGenContext"): context.tree.insert(context.parent, tk.END, text="Break") - @ExprVisitor.generate.register + @generate.register def assignment(self, expr: behav.Assignment, context: "TreeGenContext"): context.push(context.tree.insert(context.parent, tk.END, text="Assignment")) @@ -121,7 +124,7 @@ def assignment(self, expr: behav.Assignment, context: "TreeGenContext"): context.pop() - @ExprVisitor.generate.register + @generate.register def conditional(self, expr: behav.Conditional, context: "TreeGenContext"): context.push(context.tree.insert(context.parent, tk.END, text="Conditional")) @@ -137,7 +140,7 @@ def conditional(self, expr: behav.Conditional, context: "TreeGenContext"): context.pop() - @ExprVisitor.generate.register + @generate.register def loop(self, expr: behav.Loop, context: "TreeGenContext"): context.push(context.tree.insert(context.parent, tk.END, text="Loop")) @@ -154,7 +157,7 @@ def loop(self, expr: behav.Loop, context: "TreeGenContext"): context.pop() - @ExprVisitor.generate.register + @generate.register def ternary(self, expr: behav.Ternary, context: "TreeGenContext"): context.push(context.tree.insert(context.parent, tk.END, text="Ternary")) @@ -172,7 +175,7 @@ def ternary(self, expr: behav.Ternary, context: "TreeGenContext"): context.pop() - @ExprVisitor.generate.register + @generate.register def return_(self, expr: behav.Return, context: "TreeGenContext"): context.push(context.tree.insert(context.parent, tk.END, text="Return")) @@ -183,7 +186,7 @@ def return_(self, expr: behav.Return, context: "TreeGenContext"): context.pop() - @ExprVisitor.generate.register + @generate.register def unary_operation(self, expr: behav.UnaryOperation, context: "TreeGenContext"): context.push(context.tree.insert(context.parent, tk.END, text="Unary Operation")) @@ -195,11 +198,11 @@ def unary_operation(self, expr: behav.UnaryOperation, context: "TreeGenContext") context.pop() - @ExprVisitor.generate.register + @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}",)) - @ExprVisitor.generate.register + @generate.register def indexed_reference(self, expr: behav.IndexedReference, context: "TreeGenContext"): context.push(context.tree.insert(context.parent, tk.END, text="Indexed Reference")) @@ -211,7 +214,7 @@ def indexed_reference(self, expr: behav.IndexedReference, context: "TreeGenConte context.pop() - @ExprVisitor.generate.register + @generate.register def type_conv(self, expr: behav.TypeConv, context: "TreeGenContext"): context.push(context.tree.insert(context.parent, tk.END, text="Type Conv")) @@ -224,7 +227,7 @@ def type_conv(self, expr: behav.TypeConv, context: "TreeGenContext"): context.pop() - @ExprVisitor.generate.register + @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,))) @@ -235,7 +238,7 @@ def callable_(self, expr: behav.Callable, context: "TreeGenContext"): context.pop() - @ExprVisitor.generate.register + @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,))) @@ -246,7 +249,7 @@ def procedure_call(self, expr: behav.ProcedureCall, context: "TreeGenContext"): context.pop() - @ExprVisitor.generate.register + @generate.register def group(self, expr: behav.Group, context: "TreeGenContext"): context.push(context.tree.insert(context.parent, tk.END, text="Group")) diff --git a/m2isar/metamodel/utils/ExprVisitor.py b/m2isar/metamodel/utils/ExprVisitor.py index 3fe458e1..1256c809 100644 --- a/m2isar/metamodel/utils/ExprVisitor.py +++ b/m2isar/metamodel/utils/ExprVisitor.py @@ -12,7 +12,6 @@ from ...metamodel import behav from abc import ABC, abstractmethod - from functools import singledispatchmethod # pylint: disable=unused-argument @@ -25,107 +24,111 @@ class ExprVisitor(ABC): Use self for additonal global state information Use context for stack-based information that is only relevant for the current branch of the AST. """ - @singledispatchmethod + @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__}") - @generate.register + @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 - @generate.register + @default_visit.register def visit_operator(self, expr: behav.Operator, context): pass - @generate.register + @default_visit.register def visit_operation(self, expr: behav.Operation, context): for stmt in expr.statements: stmt.generate(context) - @generate.register + @default_visit.register def visit_block(self, expr: behav.Block, context): for stmt in expr.statements: stmt.generate(context) - @generate.register + @default_visit.register def visit_binary_operation(self, expr: behav.BinaryOperation, context): expr.left.generate(context) expr.right.generate(context) - @generate.register + @default_visit.register def visit_slice_operation(self, expr: behav.SliceOperation, context): expr.expr.generate(context) expr.left.generate(context) expr.right.generate(context) - @generate.register + @default_visit.register def visit_concat_operation(self, expr: behav.ConcatOperation, context): expr.left.generate(context) expr.right.generate(context) - @generate.register + @default_visit.register def visit_number_literal(self, expr: behav.NumberLiteral, context): pass - @generate.register + @default_visit.register def visit_int_literal(self, expr: behav.IntLiteral, context): pass - @generate.register + @default_visit.register def visit_string_literal(self, expr: behav.StringLiteral, context): pass - @generate.register + @default_visit.register def visit_assignment(self, expr: behav.Assignment, context): expr.target.generate(context) expr.expr.generate(context) - @generate.register + @default_visit.register def visit_conditional(self, expr: behav.Conditional, context): for cond in expr.conds: cond.generate(context) for stmt in expr.stmts: stmt.generate(context) - @generate.register + @default_visit.register def visit_loop(self, expr: behav.Loop, context): expr.cond.generate(context) for stmt in expr.stmts: stmt.generate(context) # TODO: Add more visit methods for other node types as needed, e.g., Ternary, etc. - @generate.register + @default_visit.register def visit_ternary_operation(self, expr: behav.Ternary, context): expr.cond.generate(context) expr.then_expr.generate(context) expr.else_expr.generate(context) - @generate.register + @default_visit.register def visit_scalar_definition(self, expr: behav.ScalarDefinition, context): pass - @generate.register + @default_visit.register def visit_break(self, expr: behav.Break, context): pass - @generate.register + @default_visit.register def visit_named_reference(self, expr: behav.NamedReference, context): pass - @generate.register + @default_visit.register def visit_type_conv(self, expr: behav.TypeConv, context): expr.expr.generate(context) - @generate.register + @default_visit.register def visit_callable(self, expr: behav.Callable, context): for arg in expr.args: arg.generate(context) - @generate.register + @default_visit.register def visit_procedure_call(self, expr: behav.Callable, context): for arg in expr.args: arg.generate(context) - @generate.register - def group(self, expr: behav.Group, context): + @default_visit.register + def visit_group(self, expr: behav.Group, context): expr.expr.generate(context) From c786b9bd37cbf1b9295391259d1aaee15b9e89cf Mon Sep 17 00:00:00 2001 From: jokap11 Date: Sat, 21 Mar 2026 14:12:36 +0100 Subject: [PATCH 05/62] Bugfix FunctionThrow --- m2isar/metamodel/utils/function_throws.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/m2isar/metamodel/utils/function_throws.py b/m2isar/metamodel/utils/function_throws.py index 9a558ec7..1af940f3 100644 --- a/m2isar/metamodel/utils/function_throws.py +++ b/m2isar/metamodel/utils/function_throws.py @@ -51,7 +51,7 @@ def concat_operation(self: behav.ConcatOperation, context): return reduce(or_, [left, right]) -def number_literal(self: behav.IntLiteral, context): +def number_literal(self: behav.NumberLiteral, context): return arch.FunctionThrows.NO def int_literal(self: behav.IntLiteral, context): From 8beba4dada491aa522443054bb35ef9da8c00a0d Mon Sep 17 00:00:00 2001 From: jokap11 Date: Sat, 21 Mar 2026 14:26:18 +0100 Subject: [PATCH 06/62] [METAMODEL] Used ExprVisitor for preprocessing Utils Removed patch_model for ExprVisitor 1to1 mapping from xmnn-isax-flow/cdsl/top.core_desc to etiss for pre patch_model to current flow --- m2isar/metamodel/utils/expr_preprocessor.py | 43 +- m2isar/metamodel/utils/expr_simplifier.py | 384 +++++++++--------- m2isar/metamodel/utils/function_staticness.py | 208 ++++++---- m2isar/metamodel/utils/function_throws.py | 204 ++++++---- m2isar/metamodel/utils/scalar_staticness.py | 258 +++++++----- 5 files changed, 620 insertions(+), 477 deletions(-) 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..f2d8d0ba 100644 --- a/m2isar/metamodel/utils/expr_simplifier.py +++ b/m2isar/metamodel/utils/expr_simplifier.py @@ -21,214 +21,236 @@ """ 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): + expr.expr.bit_size = expr.size + 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 1af940f3..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.NumberLiteral, 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 From ddf509e0252a368cd5857d05052e92d5349e2bbd Mon Sep 17 00:00:00 2001 From: jokap11 Date: Sat, 21 Mar 2026 15:27:41 +0100 Subject: [PATCH 07/62] [Backend] Use ExprVisitor for Coverage --- m2isar/backends/coverage/coverage_dbg.py | 10 +- m2isar/backends/coverage/coverage_lcov.py | 10 +- m2isar/backends/coverage/id_transform.py | 264 +++++++++++----------- 3 files changed, 142 insertions(+), 142 deletions(-) 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) From 168f46f1923c187905095bc425daafeefb3a127f Mon Sep 17 00:00:00 2001 From: jokap11 Date: Sat, 21 Mar 2026 15:32:26 +0100 Subject: [PATCH 08/62] [Backend] Switch ETISS gen to ExprVisitor Caution: Most of the transform is generated by Copilot 1to1 mapping from xmnn-isax-flow/cdsl/top.core_desc to etiss for pre patch_model to current flow --- .../backends/etiss/instruction_generator.py | 13 +- .../backends/etiss/instruction_transform.py | 1221 ++++++++--------- 2 files changed, 576 insertions(+), 658 deletions(-) diff --git a/m2isar/backends/etiss/instruction_generator.py b/m2isar/backends/etiss/instruction_generator.py index 49acb45b..ec3fadae 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 @@ -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) diff --git a/m2isar/backends/etiss/instruction_transform.py b/m2isar/backends/etiss/instruction_transform.py index 0e0d62c5..e2342046 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,729 @@ 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) - - 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});")) - - 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) + @generate.register + def _(self, expr: behav.Operation, context: TransformerContext): + """Generate an `Operation` model object.""" - raise_fn_str = [context.wrap_codestring(c.code, c.static) for c in raise_fn_call] + args: "list[CodeString]" = [] + code_lines = [] - before_line_infos = [] - after_line_infos = [] + for stmt in expr.statements: + c = self.generate(stmt, context) - if context.generate_coverage: - for l in flatten(arg.line_infos): - if l is not None: - CodeInfoTracker.insert(context.arch_name, l) - - if l.placement == LineInfoPlacement.BEFORE: - before_line_infos.append(str(l.id)) + if isinstance(c, list): + args.extend(flatten(c)) + else: + args.append(c) - elif l.placement == LineInfoPlacement.AFTER: - after_line_infos.append(str(l.id)) + 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 len(before_line_infos) > 0: - code_lines.append(context.wrap_codestring(f"etiss_coverage_count({len(before_line_infos)}, {', '.join(before_line_infos)});")) + 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) - 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)) + raise_fn_str = [context.wrap_codestring(c.code, c.static) for c in raise_fn_call] - 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) + before_line_infos = [] + after_line_infos = [] - 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 context.generate_coverage: + for l in flatten(arg.line_infos): + if l is not None: + CodeInfoTracker.insert(context.arch_name, l) - code_lines.append(context.wrap_codestring(f'{arg.code}', arg.static)) + if l.placement == LineInfoPlacement.BEFORE: + before_line_infos.append(str(l.id)) - if len(after_line_infos) > 0: - code_lines.append(context.wrap_codestring(f"etiss_coverage_count({len(after_line_infos)}, {', '.join(after_line_infos)});")) + elif l.placement == LineInfoPlacement.AFTER: + after_line_infos.append(str(l.id)) - #if arg.check_trap: - # code_lines.append(context.wrap_codestring('goto instr_exit_" + std::to_string(ic.current_address_) + ";')) + if len(before_line_infos) > 0: + code_lines.append(context.wrap_codestring(f"etiss_coverage_count({len(before_line_infos)}, {', '.join(before_line_infos)});")) - 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) + 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)) - container = CodePartsContainer() + 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) - container.initial_required = '\n'.join(code_lines) + 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};')) - # 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 - )) + code_lines.append(context.wrap_codestring(f'{arg.code}', arg.static)) - if context.generates_exception: - return_conditions.append("cpu->return_pending") - return_conditions.append("cpu->exception") + if len(after_line_infos) > 0: + code_lines.append(context.wrap_codestring(f"etiss_coverage_count({len(after_line_infos)}, {', '.join(after_line_infos)});")) - 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') + 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) - elif arch.InstrAttribute.NO_CONT in context.attributes: - return_conditions.clear() + container = CodePartsContainer() + container.initial_required = '\n'.join(code_lines) - 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() + 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 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 context.generates_exception: + return_conditions.append("cpu->return_pending") + return_conditions.append("cpu->exception") - 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 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') - return container + elif arch.InstrAttribute.NO_CONT in context.attributes: + return_conditions.clear() -def block(self: behav.Block, context: TransformerContext): - stmts = [stmt.generate(context) for stmt in self.statements] + 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() - pre = [CodeString("{ // block", StaticType.READ, None, None, line_infos=self.line_info)] - post = [CodeString("} // block", StaticType.READ, None, None)] + 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 not context.ignore_static: - pre.append(CodeString("{ // block", StaticType.NONE, None, None)) - post.insert(0, CodeString("} // block", StaticType.NONE, None, None)) + elif arch.FunctionAttribute.ETISS_TRAP_ENTRY_FN in context.attributes: + container.initial_required = "cpu->return_pending = 1;\ncpu->exception = 0;\n" + container.initial_required - return pre + stmts + post + return container -def return_(self: behav.Return, context: TransformerContext): - if context.instr_size != 0: - raise M2SyntaxError('Return statements are not allowed in instruction behavior!') + @generate.register + def _(self, expr: behav.Block, context: TransformerContext): + stmts = [self.generate(stmt, context) for stmt in expr.statements] - 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) + pre = [CodeString("{ // block", StaticType.READ, None, None, line_infos=expr.line_info)] + post = [CodeString("} // block", StaticType.READ, None, None)] - return c + if not context.ignore_static: + pre.append(CodeString("{ // block", StaticType.NONE, None, None)) + post.insert(0, CodeString("} // block", StaticType.NONE, None, None)) -def break_(self: behav.Break, context: TransformerContext): - return CodeString("break;", StaticType.RW, None, None, line_infos=self.line_info) + return pre + stmts + post -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.Return, context: TransformerContext): + if context.instr_size != 0: + raise M2SyntaxError('Return statements are not allowed in instruction behavior!') - if context.static_scalars: - if context.ignore_static: - static = StaticType.RW + 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: - static = self.scalar.static - else: - static = StaticType.NONE - - actual_size = 1 << (self.scalar.size - 1).bit_length() - actual_size = max(actual_size, 8) + c = CodeString("return;", StaticType.RW, None, None, line_infos=expr.line_info) - 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 c -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.Break, context: TransformerContext): + return CodeString("break;", StaticType.RW, None, None, line_infos=expr.line_info) - fn_args = [arg.generate(context) for arg in self.args] + @generate.register + def _(self, expr: behav.ScalarDefinition, context: TransformerContext): + if context.static_scalars: + if context.ignore_static: + static = StaticType.RW + else: + static = expr.scalar.static + else: + static = StaticType.NONE - # 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 + actual_size = 1 << (expr.scalar.size - 1).bit_length() + actual_size = max(actual_size, 8) - if ref is not None: - # if there is a function object, use its information - fn = ref + 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, + ) - # 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 + @generate.register + def _(self, expr: behav.ProcedureCall, context: TransformerContext): + fn_args = [self.generate(arg, context) for arg in expr.args] - # 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) + 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 - # 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]) + if ref is not None: + fn = ref + static = StaticType.READ if fn.static and all(arg.static != StaticType.NONE for arg in fn_args) else StaticType.NONE - # check if any argument is a memory access - mem_ids = list(chain.from_iterable([arg.mem_ids for arg in fn_args])) + 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) - # 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) + 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]) - # add special behavior if this function is an exception entry point - exc_code = "" + mem_ids = list(chain.from_iterable([arg.mem_ids for arg in fn_args])) - if arch.FunctionAttribute.ETISS_TRAP_TRANSLATE_FN in fn.attributes: - context.generates_exception = True + regs_affected = set(chain.from_iterable([arg.regs_affected for arg in fn_args])) + context.dependent_regs.update(regs_affected) - if arch.FunctionAttribute.ETISS_TRAP_ENTRY_FN in fn.attributes: - context.generates_exception = True + exc_code = "" - if fn.size is not None: - exc_code = "cpu->exception = " + if arch.FunctionAttribute.ETISS_TRAP_TRANSLATE_FN in fn.attributes: + context.generates_exception = True - 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 + if arch.FunctionAttribute.ETISS_TRAP_ENTRY_FN in fn.attributes: + context.generates_exception = 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) + if fn.size is not None: + exc_code = "cpu->exception = " - 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)] + 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 - return pre + [c, c2] + post + 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) - return c + 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)] - raise M2NameError(f'Function {name} not recognized!') + return pre + [c, c2] + post -def function_call(self: behav.FunctionCall, context: TransformerContext): - """Generate a regular function call (with further use of return value).""" + return c - fn_args = [arg.generate(context) for arg in self.args] + raise M2NameError(f'Function {name} not recognized!') - # 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 + @generate.register + def _(self, expr: behav.FunctionCall, context: TransformerContext): + fn_args = [self.generate(arg, context) for arg in expr.args] - if ref is not None: - # if there is a function object, use its information + 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 - fn = ref + if ref is not None: + fn = ref + 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 + 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) + 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]) + signed = fn.data_type == arch.DataType.S + 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): + target: CodeString = self.generate(expr.target, context) + expr_str: CodeString = self.generate(expr.expr, context) - raise M2NameError(f'Function {name} not recognized!') + 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] + 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() + 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) + 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]) - - #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)) - - # stmts.append(block_statements) - - # check if all conditions are static - static = all(x.static for x in conds) - - outputs: "list[CodeString]" = [] - - 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 - - # 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) - - # generate first statement block - outputs.extend(flatten(stmts[0])) + context.generates_exception = True - # generate closing brace - outputs.append(CodeString("} // conditional", static, None, None)) + for m_id in expr_str.mem_ids: + m_id.write = False - 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) + if not expr_str.mem_corrected: + logger.debug("assuming mem read size at %d", target.size) + m_id.access_size = target.size - outputs.extend(flatten(elif_stmts)) + if target.is_mem_access: + if len(target.mem_ids) != 1: + raise M2SyntaxError('Only one memory access is allowed as assignment target!') - outputs.append(CodeString("} // conditional", static, None, None)) + target.mem_ids[0].write = True - if len(conds) < len(stmts): - outputs.append(CodeString("else { // conditional", static, None, None)) + if not target.mem_corrected: + logger.debug("assuming mem write size at %d", expr_str.size) + target.mem_ids[0].access_size = expr_str.size - outputs.extend(flatten(stmts[-1])) + c = CodeString(f"{target.code} = {expr_str.code};", static, None, None, line_infos=[expr.line_info] + target.line_infos + expr_str.line_infos) - outputs.append(CodeString("} // conditional", static, None, None)) + c.function_calls.extend(target.function_calls) + c.function_calls.extend(expr_str.function_calls) - return outputs + c.mem_ids.extend(target.mem_ids) + c.mem_ids.extend(expr_str.mem_ids) -def loop(self: behav.Loop, context: TransformerContext): - """Generate 'while' and 'do .. while' loops.""" + return c - # generate the loop condition and body - cond: CodeString = self.cond.generate(context) - stmts: "list[CodeString]" = [] #[stmt.generate(context) for stmt in self.stmts] + @generate.register + def _(self, expr: behav.BinaryOperation, context: TransformerContext): + left = self.generate(expr.left, context) + op = expr.op + right = self.generate(expr.right, context) - for stmt in self.stmts: - if isinstance(stmt, list): - for stmt2 in stmt: - stmts.append(stmt2.generate(context)) - else: - stmts.append(stmt.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) - if not cond.static: - context.dependent_regs.update(cond.regs_affected) + 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, + ) + c.mem_ids = left.mem_ids + right.mem_ids + return c - outputs: "list[CodeString]" = [] + @generate.register + def _(self, expr: behav.UnaryOperation, context: TransformerContext): + op = expr.op + right = self.generate(expr.right, context) - 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) + 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 - outputs.append(start_c) + @generate.register + def _(self, expr: behav.Conditional, context: TransformerContext): + conds: "list[CodeString]" = [self.generate(x, context) for x in expr.conds] + stmts: "list[list[CodeString]]" = [] - outputs.extend(flatten(stmts)) + for cond in conds[1:]: + conds[0].mem_ids.extend(cond.mem_ids) + cond.mem_ids.clear() - outputs.append(end_c) + for stmt in expr.stmts: + ret = self.generate(stmt, context) - return outputs + if isinstance(ret, list): + stmts.append(ret) + else: + stmts.append([ret]) -def ternary(self: behav.Ternary, context: TransformerContext): - """Generate a ternary expression.""" + static = all(x.static for x in conds) + outputs: "list[CodeString]" = [] - # 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) + for cond in conds: + for m_id in cond.mem_ids: + m_id.write = False - static = StaticType.NONE not in [x.static for x in (cond, then_expr, else_expr)] + if cond.static and not static: + cond.code = context.make_static(cond.code) + cond.static = False - # 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) + 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) - 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 + outputs.extend(flatten(stmts[0])) + 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) -def assignment(self: behav.Assignment, context: TransformerContext): - """Generate an assignment expression""" + outputs.extend(flatten(elif_stmts)) + outputs.append(CodeString("} // conditional", static, None, None)) - # generate target and value expressions - target: CodeString = self.target.generate(context) - expr: CodeString = self.expr.generate(context) + 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)) - # check staticness - static = bool(target.static & StaticType.WRITE) and bool(expr.static) + return outputs - # 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!') + @generate.register + def _(self, expr: behav.Loop, context: TransformerContext): + cond: CodeString = self.generate(expr.cond, context) + stmts: "list[CodeString]" = [] - # 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) + for stmt in expr.stmts: + if isinstance(stmt, list): + for stmt2 in stmt: + stmts.append(self.generate(stmt2, context)) else: - expr.code = Template(f'{expr.code}').safe_substitute(**replacements.rename_static) - - else: - expr.code = context.make_static(expr.code, expr.signed) + stmts.append(self.generate(stmt, context)) - # convert target staticness - if bool(target.static & StaticType.READ): - target.code = Template(target.code).safe_substitute(replacements.rename_write) + if not cond.static: + context.dependent_regs.update(cond.regs_affected) - # keep track of affected and dependent registers - context.affected_regs.update(target.regs_affected) - context.dependent_regs.update(expr.regs_affected) + outputs: "list[CodeString]" = [] - 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}' + 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) - else: - context.generates_exception = True + outputs.append(start_c) + outputs.extend(flatten(stmts)) + outputs.append(end_c) - for m_id in expr.mem_ids: - m_id.write = False + return outputs - 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.Ternary, context: TransformerContext): + cond = self.generate(expr.cond, context) + then_expr = self.generate(expr.then_expr, context) + else_expr = self.generate(expr.else_expr, context) - if target.is_mem_access: - if len(target.mem_ids) != 1: - raise M2SyntaxError('Only one memory access is allowed as assignment target!') + static = StaticType.NONE not in [x.static for x in (cond, then_expr, else_expr)] - target.mem_ids[0].write = True + 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 - if not target.mem_corrected: - logger.debug("assuming mem write size at %d", expr.size) - target.mem_ids[0].access_size = expr.size + return c - c = CodeString(f"{target.code} = {expr.code};", static, None, None, line_infos=[self.line_info] + target.line_infos + expr.line_infos) + @generate.register + def _(self, expr: behav.TypeConv, context: TransformerContext): + expr_str = self.generate(expr.expr, context) - c.function_calls.extend(target.function_calls) - c.function_calls.extend(expr.function_calls) + if expr.data_type is None: + expr.data_type = arch.DataType.S if expr_str.signed else arch.DataType.U - c.mem_ids.extend(target.mem_ids) - c.mem_ids.extend(expr.mem_ids) + if expr.size is None: + expr.size = expr_str.size + expr.actual_size = expr_str.actual_size - return c + if expr_str.is_mem_access: + if not expr_str.mem_corrected and expr_str.mem_ids[-1].access_size != expr.size: + expr_str.mem_ids[-1].access_size = expr.size + expr_str.size = expr.size + expr_str.mem_corrected = True + elif expr_str.mem_ids[-1].access_size == expr.size: + expr_str.mem_corrected = True -def binary_operation(self: behav.BinaryOperation, context: TransformerContext): - """Generate a binary expression""" + code_str = expr_str.code - # generate LHS and RHS of the expression - left = self.left.generate(context) - op = self.op - right = self.right.generate(context) + if expr.data_type == arch.DataType.S and expr_str.actual_size != expr_str.size: + target_size = expr.actual_size - # 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 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}))' + else: + code_str = f'({data_type_map[expr.data_type]}{expr.actual_size})({code_str})' - 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 + 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 + c.mem_corrected = expr_str.mem_corrected -def unary_operation(self: behav.UnaryOperation, context: TransformerContext): - op = self.op - right = self.right.generate(context) + return c - 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 + @generate.register + def _(self, expr: behav.NamedReference, context: TransformerContext): + referred_var = expr.reference + static = StaticType.NONE + name = referred_var.name + + if name in replacements.rename_static: + name = f'${{{name}}}' + static = StaticType.READ + + if isinstance(referred_var, arch.Memory): + 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): + 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") - - signed = referred_var.data_type == arch.DataType.S - size = referred_var.size - static = StaticType.READ + raise TypeError("wrong type") - if referred_var == context.intrinsics["__encoding_size"]: - name = str(context.instr_size // 8) - - else: - # should not happen - raise TypeError("wrong type") - - if context.ignore_static: - static = StaticType.RW - - c = CodeString(name, static, size, signed, line_infos=self.line_info) - #c.scalar = scalar - return c - -def indexed_reference(self: behav.IndexedReference, context: TransformerContext): - """Generate an indexed reference expression (for register banks or memory).""" - - name = self.reference.name - - # generate index expression - index = self.index.generate(context) + if context.ignore_static: + static = StaticType.RW - referred_mem = self.reference + return CodeString(name, static, size, signed, line_infos=expr.line_info) - if isinstance(referred_mem, arch.Memory): - context.used_arch_data = True + @generate.register + def _(self, expr: behav.IndexedReference, context: TransformerContext): + name = expr.reference.name + index = self.generate(expr.index, context) + referred_mem = expr.reference - size = referred_mem.size + if isinstance(referred_mem, arch.Memory): + context.used_arch_data = True - # 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) + size = referred_mem.size - if context.ignore_static: - static = StaticType.RW - else: - static = StaticType.NONE + 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 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 + if context.ignore_static: + static = StaticType.RW + else: + static = StaticType.NONE + + if arch.MemoryAttribute.IS_MAIN_MEM in referred_mem.attributes: + c = CodeString(f'{MEM_VAL_REPL}{context.mem_var_count}', static, size, False, line_infos=[expr.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 + + 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=[expr.line_info] + index.line_infos) + if arch.MemoryAttribute.IS_MAIN_REG in referred_mem.attributes: + c.regs_affected.add(index_code) 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}))' - else: - code_str = f'((etiss_int{target_size})(({expr.code}) << ({target_size} - {expr.size})) >> ({target_size} - {expr.size}))' + @generate.register + def _(self, expr: behav.SliceOperation, context: TransformerContext): + expr_str = self.generate(expr.expr, context) + left = self.generate(expr.left, context) + right = self.generate(expr.right, context) - # 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})' + static = StaticType.NONE not in [x.static for x in (expr_str, left, right)] - 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 + 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) + + 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 + 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): + code = f"(({expr_str.code}) & {mask})" + else: + 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 - return c + @generate.register + def _(self, expr: behav.ConcatOperation, context: TransformerContext): + left: CodeString = self.generate(expr.left, context) + right: CodeString = self.generate(expr.right, context) -def int_literal(self: behav.IntLiteral, context: TransformerContext): - """Generate an integer literal.""" + 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) - lit = int(self.value) - size = min(self.bit_size, 128) - sign = self.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), [expr.line_info] + left.line_infos + right.line_infos) + c.mem_ids = left.mem_ids + right.mem_ids + return c - minus = "" - if lit > 0 and sign and (lit >> (size - 1)) & 1: - minus = "-" + @generate.register + def _(self, expr: behav.NumberLiteral, context: TransformerContext): + lit = int(expr.value) + size = min(lit.bit_length(), 64) + sign = lit < 0 - twocomp_lit = (lit + (1 << size)) % (1 << size) + twocomp_lit = (lit + (1 << 64)) % (1 << 64) - # 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" + postfix = "U" if not sign else "" + postfix += "LL" - ret = CodeString(minus + str(lit) + postfix, True, size, sign, line_infos=self.line_info) - ret.is_literal = True - return ret + return CodeString(str(twocomp_lit) + postfix, True, size, sign, line_infos=expr.line_info) -def number_literal(self: behav.NumberLiteral, context: TransformerContext): - """Generate generic number literal. Currently unused.""" + @generate.register + def _(self, expr: behav.IntLiteral, context: TransformerContext): + lit = int(expr.value) + size = min(expr.bit_size, 128) + sign = expr.signed - lit = int(self.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) + _ = (lit + (1 << size)) % (1 << size) - postfix = "U" if not sign else "" - postfix += "LL" - #postfix = "ULL" - #if size > 32: - # postfix += "L" - #if size > 64: - # postfix += "L" + postfix = "U" if not sign else "" + postfix += "LL" - return CodeString(str(twocomp_lit) + postfix, True, size, sign, line_infos=self.line_info) + ret = CodeString(minus + str(lit) + postfix, True, size, sign, line_infos=expr.line_info) + ret.is_literal = True + return ret -def group(self: behav.Group, context: TransformerContext): - """Generate a group of expressions.""" + @generate.register + def _(self, expr: behav.StringLiteral, context: TransformerContext): + return CodeString(f'"{expr.value}"', StaticType.READ, None, 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.CodeLiteral, context: TransformerContext): + return CodeString(expr.val, False, context.native_size, False, line_infos=expr.line_info) -def operator(self: behav.Operator, context: TransformerContext): - return self.op + @generate.register + def _(self, expr: behav.Operator, context: TransformerContext): + return expr.value -def code_literal(self: behav.CodeLiteral, context: TransformerContext): - return CodeString(self.val, False, context.native_size, False, line_infos=self.line_info) + @generate.register + def _(self, expr: behav.Group, context: TransformerContext): + 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 From 6625f601aef639f0e9228ed562d194c02bf92c3f Mon Sep 17 00:00:00 2001 From: jokap11 Date: Sat, 21 Mar 2026 21:06:04 +0100 Subject: [PATCH 09/62] [BackEnd] Isa Manual ExprVisitor --- m2isar/backends/isa_manual/visitor.py | 305 +++++++++++++------------- m2isar/backends/isa_manual/writer.py | 11 +- 2 files changed, 161 insertions(+), 155 deletions(-) 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: From 6f042923b2015fc5a39d7443ae776fb10d4346c6 Mon Sep 17 00:00:00 2001 From: jokap11 Date: Sat, 21 Mar 2026 21:14:05 +0100 Subject: [PATCH 10/62] [FrontEnd] Remove monkey patching from FrontEnd --- .../coredsl2/architecture_model_builder.py | 11 +-- .../coredsl2/behavior_model_builder.py | 5 +- m2isar/frontends/coredsl2/expr_interpreter.py | 69 ++++++++++++------- m2isar/frontends/coredsl2/parser.py | 6 +- m2isar/metamodel/arch.py | 5 +- 5 files changed, 58 insertions(+), 38 deletions(-) diff --git a/m2isar/frontends/coredsl2/architecture_model_builder.py b/m2isar/frontends/coredsl2/architecture_model_builder.py index 22bcfd6e..9028d606 100644 --- a/m2isar/frontends/coredsl2/architecture_model_builder.py +++ b/m2isar/frontends/coredsl2/architecture_model_builder.py @@ -16,9 +16,10 @@ 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.""" @@ -379,7 +380,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 +491,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..0c31d6db 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 @@ -411,7 +414,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..a26f575a 100644 --- a/m2isar/frontends/coredsl2/parser.py +++ b/m2isar/frontends/coredsl2/parser.py @@ -14,10 +14,8 @@ 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.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 @@ -67,8 +65,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: diff --git a/m2isar/metamodel/arch.py b/m2isar/metamodel/arch.py index adcd7e9f..844cc9d3 100644 --- a/m2isar/metamodel/arch.py +++ b/m2isar/metamodel/arch.py @@ -16,20 +16,21 @@ 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 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, BaseNode): - return arg.generate(None) + return exprInterpretVisitor.generate(arg, None) return arg From adb06c34f36f3e99004f1d857fd42270940d364e Mon Sep 17 00:00:00 2001 From: jokap11 Date: Sat, 21 Mar 2026 21:22:18 +0100 Subject: [PATCH 11/62] Change Description to use ExprVisitor --- m2isar/backends/viewer/viewer.py | 2 +- m2isar/metamodel/__init__.py | 16 +++++++++------- m2isar/metamodel/behav.py | 5 ++++- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/m2isar/backends/viewer/viewer.py b/m2isar/backends/viewer/viewer.py index 9663ee0c..fb897fe8 100644 --- a/m2isar/backends/viewer/viewer.py +++ b/m2isar/backends/viewer/viewer.py @@ -21,7 +21,7 @@ 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) diff --git a/m2isar/metamodel/__init__.py b/m2isar/metamodel/__init__.py index e6268df2..dbf9197d 100644 --- a/m2isar/metamodel/__init__.py +++ b/m2isar/metamodel/__init__.py @@ -13,15 +13,17 @@ 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 diff --git a/m2isar/metamodel/behav.py b/m2isar/metamodel/behav.py index 0d43a901..4f745786 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 From f3c1eb0836f6e44ca75277bd0052735f3e723338 Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Sun, 15 Jun 2025 02:05:06 +0200 Subject: [PATCH 12/62] print warning for implicit truncations (asignment only) (TODO: cli) --- m2isar/backends/etiss/instruction_generator.py | 3 ++- m2isar/backends/etiss/instruction_transform.py | 5 +++++ m2isar/backends/etiss/instruction_utils.py | 4 +++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/m2isar/backends/etiss/instruction_generator.py b/m2isar/backends/etiss/instruction_generator.py index 49acb45b..c0de4940 100644 --- a/m2isar/backends/etiss/instruction_generator.py +++ b/m2isar/backends/etiss/instruction_generator.py @@ -46,8 +46,9 @@ def generate_functions(core: arch.CoreDef, static_scalars: bool, decls_only: boo return_type += f'{fn_def.actual_size}' # set up a transformer context and generate code + ignore_trunc_warnings = False context = instruction_utils.TransformerContext(core.constants, core.memories, core.memory_aliases, fn_def.args, fn_def.attributes, - core.functions, 0, core_default_width, core_name, static_scalars, core.intrinsics, generate_coverage, True) + core.functions, 0, core_default_width, core_name, static_scalars, core.intrinsics, generate_coverage, True, ignore_trunc_warnings) logger.debug("generating code for %s", fn_name) diff --git a/m2isar/backends/etiss/instruction_transform.py b/m2isar/backends/etiss/instruction_transform.py index 0e0d62c5..e420eb79 100644 --- a/m2isar/backends/etiss/instruction_transform.py +++ b/m2isar/backends/etiss/instruction_transform.py @@ -471,6 +471,11 @@ def assignment(self: behav.Assignment, context: TransformerContext): context.affected_regs.update(target.regs_affected) context.dependent_regs.update(expr.regs_affected) + # TODO: move to parser! + if target.size < expr.size and not context.ignore_trunc_warnings: + # TODO: add position? + logger.warning("Implicit truncation %d -> %d found", expr.size, target.size) + if not target.is_mem_access and not expr.is_mem_access: if target.actual_size > target.size: if target.signed: diff --git a/m2isar/backends/etiss/instruction_utils.py b/m2isar/backends/etiss/instruction_utils.py index d8424f3d..e20359a4 100644 --- a/m2isar/backends/etiss/instruction_utils.py +++ b/m2isar/backends/etiss/instruction_utils.py @@ -141,7 +141,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=False, ignore_trunc_warnings=False): self.constants = constants self.memories = memories @@ -155,6 +155,8 @@ def __init__(self, constants: "dict[str, arch.Constant]", memories: "dict[str, a self.intrinsics = intrinsics self.static_scalars = static_scalars self.generate_coverage = generate_coverage + self.ignore_trunc_warnings = ignore_trunc_warnings + # TODO: warnings as errors? self.ignore_static = ignore_static From fb2473543a6baf546f0c3088123b003d328748a2 Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Sun, 15 Jun 2025 02:06:17 +0200 Subject: [PATCH 13/62] print warning for shift width overflows (TODO: cli & LOC) --- m2isar/backends/etiss/instruction_transform.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/m2isar/backends/etiss/instruction_transform.py b/m2isar/backends/etiss/instruction_transform.py index e420eb79..aaab3fcf 100644 --- a/m2isar/backends/etiss/instruction_transform.py +++ b/m2isar/backends/etiss/instruction_transform.py @@ -537,6 +537,9 @@ def binary_operation(self: behav.BinaryOperation, context: TransformerContext): if not right.static and left.static and not left.is_literal: left.code = context.make_static(left.code, left.signed) + if op.value == "<<" and left.size <= right.size: + # TODO: add LOC + logger.warning("Shift count overflow for << operation (%d vs. %d)", left.size, right.size) 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 From e495b5805190e10b0661832a3125682ac633783b Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Fri, 4 Jul 2025 18:23:26 +0200 Subject: [PATCH 14/62] add warnings manager --- .../backends/etiss/instruction_generator.py | 12 +-- .../backends/etiss/instruction_transform.py | 8 +- m2isar/backends/etiss/instruction_utils.py | 12 ++- m2isar/backends/etiss/instruction_writer.py | 5 +- m2isar/backends/etiss/warnings.py | 97 +++++++++++++++++++ m2isar/backends/etiss/writer.py | 4 +- 6 files changed, 121 insertions(+), 17 deletions(-) create mode 100644 m2isar/backends/etiss/warnings.py diff --git a/m2isar/backends/etiss/instruction_generator.py b/m2isar/backends/etiss/instruction_generator.py index c0de4940..f981624a 100644 --- a/m2isar/backends/etiss/instruction_generator.py +++ b/m2isar/backends/etiss/instruction_generator.py @@ -15,6 +15,7 @@ from ...metamodel import arch, behav, patch_model from . import BlockEndType, instruction_transform, instruction_utils from .templates import template_dir +from .warnings import WarningsInfo logger = logging.getLogger("instruction_generator") @@ -46,9 +47,8 @@ def generate_functions(core: arch.CoreDef, static_scalars: bool, decls_only: boo return_type += f'{fn_def.actual_size}' # set up a transformer context and generate code - ignore_trunc_warnings = False context = instruction_utils.TransformerContext(core.constants, core.memories, core.memory_aliases, fn_def.args, fn_def.attributes, - core.functions, 0, core_default_width, core_name, static_scalars, core.intrinsics, generate_coverage, True, ignore_trunc_warnings) + core.functions, 0, core_default_width, core_name, static_scalars, core.intrinsics, generate_coverage, True, warnings_info) logger.debug("generating code for %s", fn_name) @@ -140,7 +140,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): +def generate_instruction_callback(core: arch.CoreDef, instr_def: arch.Instruction, fields, static_scalars: bool, block_end_on: BlockEndType, generate_coverage: bool, warnings_info: WarningsInfo): patch_model(instruction_transform) instr_name = instr_def.name @@ -152,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, warnings_info) # force a block end if necessary if ((arch.InstrAttribute.NO_CONT in instr_def.attributes @@ -187,7 +187,7 @@ def generate_instruction_callback(core: arch.CoreDef, instr_def: arch.Instructio return callback_str -def generate_instructions(core: arch.CoreDef, static_scalars: bool, block_end_on: BlockEndType, generate_coverage: bool): +def generate_instructions(core: arch.CoreDef, static_scalars: bool, block_end_on: BlockEndType, generate_coverage: bool, warnings_info: WarningsInfo): """Return a generator object to generate instruction behavior code. Uses instruction definitions in the core object. """ @@ -231,7 +231,7 @@ def generate_instructions(core: arch.CoreDef, static_scalars: bool, block_end_on instr_def.operation = new_op instr_def.throws = True - callback_str = generate_instruction_callback(core, instr_def, fields, static_scalars, block_end_on, generate_coverage) + callback_str = generate_instruction_callback(core, instr_def, fields, static_scalars, block_end_on, generate_coverage, warnings_info) # render code for whole instruction templ_str = instr_template.render( diff --git a/m2isar/backends/etiss/instruction_transform.py b/m2isar/backends/etiss/instruction_transform.py index aaab3fcf..4fad6e93 100644 --- a/m2isar/backends/etiss/instruction_transform.py +++ b/m2isar/backends/etiss/instruction_transform.py @@ -472,9 +472,9 @@ def assignment(self: behav.Assignment, context: TransformerContext): context.dependent_regs.update(expr.regs_affected) # TODO: move to parser! - if target.size < expr.size and not context.ignore_trunc_warnings: - # TODO: add position? - logger.warning("Implicit truncation %d -> %d found", expr.size, target.size) + # if target.size < expr.size and not context.ignore_trunc_warnings: + if target.size < expr.size: + context.emit_warning(f"Implicit truncation {expr.size} -> {target.size} found", "implicit-trunc", logger=logger, line_info=self.line_info) if not target.is_mem_access and not expr.is_mem_access: if target.actual_size > target.size: @@ -539,7 +539,7 @@ def binary_operation(self: behav.BinaryOperation, context: TransformerContext): if op.value == "<<" and left.size <= right.size: # TODO: add LOC - logger.warning("Shift count overflow for << operation (%d vs. %d)", left.size, right.size) + context.emit_warning(f"Shift count overflow for << operation ({left.size} vs. {right.size})", "shift-overflow", logger=logger, line_info=self.line_info) 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 diff --git a/m2isar/backends/etiss/instruction_utils.py b/m2isar/backends/etiss/instruction_utils.py index e20359a4..2f13b86b 100644 --- a/m2isar/backends/etiss/instruction_utils.py +++ b/m2isar/backends/etiss/instruction_utils.py @@ -17,6 +17,7 @@ from ...metamodel.code_info import LineInfo from ...metamodel.utils import StaticType from . import replacements +from .warnings import WarningsManager data_type_map = { arch.DataType.S: 'etiss_int', @@ -134,14 +135,19 @@ def format(self, mapping={}, **kwargs): setattr(self, name, formatted) -class TransformerContext: +class EtissWriterWarningsManager(WarningsManager): + pass + + +class TransformerContext(EtissWriterWarningsManager): """Track miscellaneous information throughout the code generation process. Also provides helper functions for staticness conversion etc. """ 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, ignore_trunc_warnings=False): + instr_size: int, native_size: int, arch_name: str, static_scalars: bool, intrinsics, generate_coverage: bool, ignore_static: bool = False, warnings_info: WarningsInfo = None): + super().__init__(warnings_info) self.constants = constants self.memories = memories @@ -155,8 +161,6 @@ def __init__(self, constants: "dict[str, arch.Constant]", memories: "dict[str, a self.intrinsics = intrinsics self.static_scalars = static_scalars self.generate_coverage = generate_coverage - self.ignore_trunc_warnings = ignore_trunc_warnings - # TODO: warnings as errors? self.ignore_static = ignore_static diff --git a/m2isar/backends/etiss/instruction_writer.py b/m2isar/backends/etiss/instruction_writer.py index d852f062..b5bdd061 100644 --- a/m2isar/backends/etiss/instruction_writer.py +++ b/m2isar/backends/etiss/instruction_writer.py @@ -18,6 +18,7 @@ from . import BlockEndType from .instruction_generator import generate_functions, generate_instructions from .templates import template_dir +from .warnings import WarningsInfo logger = logging.getLogger("instruction_writer") @@ -69,7 +70,7 @@ def write_functions(core: arch.CoreDef, start_time: str, output_path: pathlib.Pa funcs_f.write("// clang-format on\n") def write_instructions(core: arch.CoreDef, start_time: str, output_path: pathlib.Path, separate: bool, static_scalars: bool, - block_end_on: BlockEndType, generate_coverage: bool): + block_end_on: BlockEndType, generate_coverage: bool, warnings_info: WarningsInfo): """Generate and write the instruction model C++ files for ETISS.""" instr_set_template = Template(filename=str(template_dir/'etiss_instruction_set.mako')) @@ -103,7 +104,7 @@ def write_instructions(core: arch.CoreDef, start_time: str, output_path: pathlib out_f.write(instr_set_str) # generate instruction behavior models - for instr_name, _, ext_name, templ_str in generate_instructions(core, static_scalars, block_end_on, generate_coverage): + for instr_name, _, ext_name, templ_str in generate_instructions(core, static_scalars, block_end_on, generate_coverage, warnings_info): logger.debug("writing instruction %s", instr_name) outfiles.get(ext_name, outfiles['default']).write(templ_str) for outfile in outfiles.values(): diff --git a/m2isar/backends/etiss/warnings.py b/m2isar/backends/etiss/warnings.py new file mode 100644 index 00000000..0902876b --- /dev/null +++ b/m2isar/backends/etiss/warnings.py @@ -0,0 +1,97 @@ +import argparse +from dataclasses import dataclass +from typing import Set + +KNOWN_WARNINGS = { + 'implicit-trunc', + 'shift-outofrange', + 'implicit-extend', + 'signed-compare', + 'unused-value', +} + + +@dataclass +class WarningsInfo: + known: Set[str] = default + defaults: Set[str] = {} + enabled: Set[str] = {} + disabled: Set[str] = {} + as_error: Set[str] = {} + 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_info', WarningsInfo()) + + for val in values: + if val.startswith('no-'): + warn = val[3:] + assert warn in warnings_info.known, f"Unknown warning: {warn}" + disabled.add(warn) + elif val.startswith('error='): + warn = val[6:] + assert warn in warnings_info.known, f"Unknown warning: {warn}" + error_set.add(warn) + elif val == 'error': + all_as_error = True + elif val == 'all': + warnings_info.enabled.update(warnings_info.known) + else: + assert warn in warnings_info.known, f"Unknown warning: {val}" + enabled.add(val) + # No need for -Wall as all warnings are enabled by default + + setattr(namespace, 'warnings_info', 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, default=default_warnings) + parser.set_defaults(enabled_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 + assert name in self.warnings_info.known, f"Unknown warning: {name}" + is_err = name in self.warnings_info.errors + if name not in self.wwarnings_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/m2isar/backends/etiss/writer.py b/m2isar/backends/etiss/writer.py index bd1b41ff..e232ffea 100755 --- a/m2isar/backends/etiss/writer.py +++ b/m2isar/backends/etiss/writer.py @@ -26,6 +26,7 @@ write_arch_specific_header, write_arch_struct) from .instruction_writer import write_functions, write_instructions +from .warnings import add_warnings_flags class BooleanOptionalAction(argparse.Action): @@ -85,6 +86,7 @@ def setup(): help="Force end translation blocks on no instructions, uncoditional jumps or all jumps.") parser.add_argument("--coverage", action=BooleanOptionalAction, default=False, help="Generate coverage tracking code into model.") parser.add_argument("--log", default="info", choices=["critical", "error", "warning", "info", "debug"]) + add_warnings_flags(parser) args = parser.parse_args() # configure logging @@ -172,7 +174,7 @@ def main(): write_arch_cmake(core, start_time, output_path, args.separate) write_arch_gdbcore(core, start_time, output_path) write_functions(core, start_time, output_path, args.static_scalars, args.coverage) - write_instructions(core, start_time, output_path, args.separate, args.static_scalars, BlockEndType[args.block_end_on.upper()], args.coverage) + write_instructions(core, start_time, output_path, args.separate, args.static_scalars, BlockEndType[args.block_end_on.upper()], args.coverage, args.warnings_info) with open(output_path / "coverage.csv", "w") as f: for c_id, c_info in sorted(CodeInfoTracker.tracker[core_name].items()): From 234df0c08041207019acdfbeecf196d0345370d5 Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Mon, 13 Apr 2026 08:54:40 +0200 Subject: [PATCH 15/62] warnings: fixes --- m2isar/backends/etiss/architecture_writer.py | 2 +- .../backends/etiss/instruction_generator.py | 4 +-- m2isar/backends/etiss/instruction_utils.py | 2 +- m2isar/backends/etiss/instruction_writer.py | 6 ++-- m2isar/backends/etiss/warnings.py | 33 +++++++++++-------- m2isar/backends/etiss/writer.py | 8 ++--- 6 files changed, 30 insertions(+), 25 deletions(-) diff --git a/m2isar/backends/etiss/architecture_writer.py b/m2isar/backends/etiss/architecture_writer.py index 6bb671d3..bc69f8f7 100644 --- a/m2isar/backends/etiss/architecture_writer.py +++ b/m2isar/backends/etiss/architecture_writer.py @@ -201,7 +201,7 @@ def write_arch_specific_cpp(core: arch.CoreDef, start_time: str, output_path: pa error_instr._size = bitsize # pylint: disable=protected-access error_fields = generate_fields(32, error_instr) - error_callbacks[bitsize] = generate_instruction_callback(core, error_instr, error_fields, True, BlockEndType.NONE, False) + error_callbacks[bitsize] = generate_instruction_callback(core, error_instr, error_fields, True, BlockEndType.NONE, False, None) logger.info("writing architecture specific file") diff --git a/m2isar/backends/etiss/instruction_generator.py b/m2isar/backends/etiss/instruction_generator.py index f981624a..005081fe 100644 --- a/m2isar/backends/etiss/instruction_generator.py +++ b/m2isar/backends/etiss/instruction_generator.py @@ -23,7 +23,7 @@ def generate_arg_str(arg: arch.FnParam): arg_name = f" {arg.name}" if arg.name is not None else "" return f'{instruction_utils.data_type_map[arg.data_type]}{arg.actual_size}{arg_name}' -def generate_functions(core: arch.CoreDef, static_scalars: bool, decls_only: bool, generate_coverage: bool): +def generate_functions(core: arch.CoreDef, static_scalars: bool, decls_only: bool, generate_coverage: bool, warnings_info: WarningsInfo): """Return a generator object to generate function behavior code. Uses function definitions in the core object. """ @@ -152,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, warnings_info) + core.functions, enc_idx, core_default_width, core_name, static_scalars, core.intrinsics, generate_coverage, False, warnings_info) # force a block end if necessary if ((arch.InstrAttribute.NO_CONT in instr_def.attributes diff --git a/m2isar/backends/etiss/instruction_utils.py b/m2isar/backends/etiss/instruction_utils.py index 2f13b86b..9df52d8a 100644 --- a/m2isar/backends/etiss/instruction_utils.py +++ b/m2isar/backends/etiss/instruction_utils.py @@ -17,7 +17,7 @@ from ...metamodel.code_info import LineInfo from ...metamodel.utils import StaticType from . import replacements -from .warnings import WarningsManager +from .warnings import WarningsManager, WarningsInfo data_type_map = { arch.DataType.S: 'etiss_int', diff --git a/m2isar/backends/etiss/instruction_writer.py b/m2isar/backends/etiss/instruction_writer.py index b5bdd061..2eff89f2 100644 --- a/m2isar/backends/etiss/instruction_writer.py +++ b/m2isar/backends/etiss/instruction_writer.py @@ -22,7 +22,7 @@ logger = logging.getLogger("instruction_writer") -def write_functions(core: arch.CoreDef, start_time: str, output_path: pathlib.Path, static_scalars: bool, generate_coverage: bool): +def write_functions(core: arch.CoreDef, start_time: str, output_path: pathlib.Path, static_scalars: bool, generate_coverage: bool, warnings_info: WarningsInfo): """Generate and write the {CoreName}Funcs.h file for ETISS.""" fn_set_header_template = Template(filename=str(template_dir/'etiss_function_set_header.mako')) @@ -45,7 +45,7 @@ def write_functions(core: arch.CoreDef, start_time: str, output_path: pathlib.Pa funcs_f.write(" // clang-format off\n") # generate and write function declarations - for fn_name, templ_str in generate_functions(core, static_scalars, True, generate_coverage): + for fn_name, templ_str in generate_functions(core, static_scalars, True, generate_coverage, warnings_info): logger.debug("writing function decl %s", fn_name) funcs_f.write(templ_str) @@ -64,7 +64,7 @@ def write_functions(core: arch.CoreDef, start_time: str, output_path: pathlib.Pa funcs_f.write("// clang-format off\n") # generate and write function definitions - for fn_name, templ_str in generate_functions(core, static_scalars, False, generate_coverage): + for fn_name, templ_str in generate_functions(core, static_scalars, False, generate_coverage, warnings_info): logger.debug("writing function def %s", fn_name) funcs_f.write(templ_str) funcs_f.write("// clang-format on\n") diff --git a/m2isar/backends/etiss/warnings.py b/m2isar/backends/etiss/warnings.py index 0902876b..c1ccdab0 100644 --- a/m2isar/backends/etiss/warnings.py +++ b/m2isar/backends/etiss/warnings.py @@ -1,5 +1,5 @@ import argparse -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import Set KNOWN_WARNINGS = { @@ -13,16 +13,18 @@ @dataclass class WarningsInfo: - known: Set[str] = default - defaults: Set[str] = {} - enabled: Set[str] = {} - disabled: Set[str] = {} - as_error: Set[str] = {} + # 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 + return (self.defaults - self.disabled) | self.enabled @property def errors(self): @@ -37,21 +39,22 @@ def __call__(self, parser, namespace, values, option_string=None): if val.startswith('no-'): warn = val[3:] assert warn in warnings_info.known, f"Unknown warning: {warn}" - disabled.add(warn) + warnings_info.disabled.add(warn) elif val.startswith('error='): warn = val[6:] assert warn in warnings_info.known, f"Unknown warning: {warn}" - error_set.add(warn) + warnings_info.error_set.add(warn) elif val == 'error': - all_as_error = True + 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}" - enabled.add(val) + warnings_info.enabled.add(val) # No need for -Wall as all warnings are enabled by default - setattr(namespace, 'warnings_info', warnings_info) + setattr(namespace, 'warnings', warnings_info) def add_warnings_flags(parser, known_warnings: Set[str], default_warnings: Set[str]): parser.add_argument( @@ -67,7 +70,7 @@ def add_warnings_flags(parser, known_warnings: Set[str], default_warnings: Set[s ) # Defaults - warnings_info = WarningsInfo(known=known_warnings, default=default_warnings) + warnings_info = WarningsInfo(known=known_warnings, defaults=default_warnings) parser.set_defaults(enabled_warnings=warnings_info) @@ -79,9 +82,11 @@ def __init__(self, warnings_info: WarningsInfo): 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.wwarnings_info.warnings: + if name not in self.warnings_info.warnings: # do nothing return log_f = log_err_f if is_err else log_warn_f diff --git a/m2isar/backends/etiss/writer.py b/m2isar/backends/etiss/writer.py index e232ffea..8906fc5a 100755 --- a/m2isar/backends/etiss/writer.py +++ b/m2isar/backends/etiss/writer.py @@ -26,7 +26,7 @@ write_arch_specific_header, write_arch_struct) from .instruction_writer import write_functions, write_instructions -from .warnings import add_warnings_flags +from .warnings import add_warnings_flags, KNOWN_WARNINGS class BooleanOptionalAction(argparse.Action): @@ -86,7 +86,7 @@ def setup(): help="Force end translation blocks on no instructions, uncoditional jumps or all jumps.") parser.add_argument("--coverage", action=BooleanOptionalAction, default=False, help="Generate coverage tracking code into model.") parser.add_argument("--log", default="info", choices=["critical", "error", "warning", "info", "debug"]) - add_warnings_flags(parser) + add_warnings_flags(parser, KNOWN_WARNINGS, KNOWN_WARNINGS) args = parser.parse_args() # configure logging @@ -173,8 +173,8 @@ def main(): write_arch_lib(core, start_time, output_path) write_arch_cmake(core, start_time, output_path, args.separate) write_arch_gdbcore(core, start_time, output_path) - write_functions(core, start_time, output_path, args.static_scalars, args.coverage) - write_instructions(core, start_time, output_path, args.separate, args.static_scalars, BlockEndType[args.block_end_on.upper()], args.coverage, args.warnings_info) + write_functions(core, start_time, output_path, args.static_scalars, args.coverage, args.warnings) + write_instructions(core, start_time, output_path, args.separate, args.static_scalars, BlockEndType[args.block_end_on.upper()], args.coverage, args.warnings) with open(output_path / "coverage.csv", "w") as f: for c_id, c_info in sorted(CodeInfoTracker.tracker[core_name].items()): From b284f1cab96677846e253d2883debb8df102bcff Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Mon, 13 Apr 2026 08:54:54 +0200 Subject: [PATCH 16/62] warnings: drop comment --- m2isar/backends/etiss/instruction_transform.py | 1 - 1 file changed, 1 deletion(-) diff --git a/m2isar/backends/etiss/instruction_transform.py b/m2isar/backends/etiss/instruction_transform.py index 4fad6e93..9a76c1c1 100644 --- a/m2isar/backends/etiss/instruction_transform.py +++ b/m2isar/backends/etiss/instruction_transform.py @@ -538,7 +538,6 @@ def binary_operation(self: behav.BinaryOperation, context: TransformerContext): left.code = context.make_static(left.code, left.signed) if op.value == "<<" and left.size <= right.size: - # TODO: add LOC context.emit_warning(f"Shift count overflow for << operation ({left.size} vs. {right.size})", "shift-overflow", logger=logger, line_info=self.line_info) 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) From 790a44dd5945528de74fb8075166070794f78cef Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Mon, 13 Apr 2026 08:55:20 +0200 Subject: [PATCH 17/62] warnings: add shift-signed & sign-compare --- m2isar/backends/etiss/instruction_transform.py | 10 ++++++++++ m2isar/backends/etiss/warnings.py | 5 +++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/m2isar/backends/etiss/instruction_transform.py b/m2isar/backends/etiss/instruction_transform.py index 9a76c1c1..20b0c7a2 100644 --- a/m2isar/backends/etiss/instruction_transform.py +++ b/m2isar/backends/etiss/instruction_transform.py @@ -530,6 +530,16 @@ def binary_operation(self: behav.BinaryOperation, context: TransformerContext): left = self.left.generate(context) op = self.op right = self.right.generate(context) + if op.value in ["<<", ">>", ">>>"] and right.signed: + context.emit_warning(f"Shift by signed amount", "shift-signed", logger=logger, line_info=self.line_info) + if op.value in ["<", "<=", ">", ">=", "==", "!="] and left.signed != right.signed: + # TODO: handle unsigned < 0 + if left.static and left.is_literal and left.code == "0": + pass + elif right.static and right.is_literal and right.code == "0": + pass + else: + context.emit_warning(f"Signed vs. unsigned comparison", "sign-compare", logger=logger, line_info=self.line_info) # convert staticness if needed if not left.static and right.static and not right.is_literal: diff --git a/m2isar/backends/etiss/warnings.py b/m2isar/backends/etiss/warnings.py index c1ccdab0..968e3ff4 100644 --- a/m2isar/backends/etiss/warnings.py +++ b/m2isar/backends/etiss/warnings.py @@ -4,9 +4,10 @@ KNOWN_WARNINGS = { 'implicit-trunc', - 'shift-outofrange', + 'shift-overflow', + 'shift-signed', 'implicit-extend', - 'signed-compare', + 'sign-compare', 'unused-value', } From ba433c03cf32aac30b77545e1440b1e18f430e24 Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Wed, 15 Apr 2026 17:00:04 +0200 Subject: [PATCH 18/62] fix warnings manager --- m2isar/backends/etiss/warnings.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/m2isar/backends/etiss/warnings.py b/m2isar/backends/etiss/warnings.py index 968e3ff4..ee5499fc 100644 --- a/m2isar/backends/etiss/warnings.py +++ b/m2isar/backends/etiss/warnings.py @@ -37,7 +37,9 @@ def __call__(self, parser, namespace, values, option_string=None): warnings_info = getattr(namespace, 'warnings_info', WarningsInfo()) for val in values: - if val.startswith('no-'): + 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) @@ -72,7 +74,7 @@ def add_warnings_flags(parser, known_warnings: Set[str], default_warnings: Set[s # Defaults warnings_info = WarningsInfo(known=known_warnings, defaults=default_warnings) - parser.set_defaults(enabled_warnings=warnings_info) + parser.set_defaults(warnings=warnings_info) class WarningsManager: From 5d0f39c3f4123cdb0b12dc10c944afd7779e703f Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Wed, 15 Apr 2026 17:00:29 +0200 Subject: [PATCH 19/62] m2isar/metamodel/__init__.py: add helpers load_model & dump_model --- m2isar/metamodel/__init__.py | 38 ++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/m2isar/metamodel/__init__.py b/m2isar/metamodel/__init__.py index e6268df2..55c0b155 100644 --- a/m2isar/metamodel/__init__.py +++ b/m2isar/metamodel/__init__.py @@ -28,8 +28,11 @@ 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 +94,38 @@ 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: + # models: "dict[str, arch.CoreDef]" = pickle.load(f) + # sets: "dict[str, arch.InstructionSet]" = pickle.load(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) From 9b511fb72d8041acb336637f151c46849894544a Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Wed, 15 Apr 2026 17:00:53 +0200 Subject: [PATCH 20/62] m2isar/metamodel/arch.py: add __repr__ method for DataType --- m2isar/metamodel/arch.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/m2isar/metamodel/arch.py b/m2isar/metamodel/arch.py index adcd7e9f..88b6ed8e 100644 --- a/m2isar/metamodel/arch.py +++ b/m2isar/metamodel/arch.py @@ -266,6 +266,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.""" From d65c3359d4fbdceeee6b5931224cf829464edd74 Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Wed, 15 Apr 2026 17:01:11 +0200 Subject: [PATCH 21/62] add m2isar/transforms/__init__.py --- m2isar/transforms/__init__.py | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 m2isar/transforms/__init__.py 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.""" From 963db922d415df88ac08a1e1f37d286e6c9ccbca Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Wed, 15 Apr 2026 17:01:22 +0200 Subject: [PATCH 22/62] add m2isar/transforms/infer_types --- m2isar/transforms/infer_types/__init__.py | 9 + m2isar/transforms/infer_types/transform.py | 69 +++ m2isar/transforms/infer_types/visitor.py | 470 +++++++++++++++++++++ 3 files changed, 548 insertions(+) create mode 100644 m2isar/transforms/infer_types/__init__.py create mode 100644 m2isar/transforms/infer_types/transform.py create mode 100644 m2isar/transforms/infer_types/visitor.py 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..149e3056 --- /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 patch_model, load_model, dump_model + +from . import visitor + + +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 run(args): + # initialize logging + logging.basicConfig(level=getattr(logging, args.log.upper())) + logger = logging.getLogger("infer_types") + logger.setLevel(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) + + for _, core_def in model_obj.cores.items(): + logger.debug("inferring types for core %s", core_def.name) + patch_model(visitor) + for _, instr_def in core_def.instructions.items(): + logger.debug("inferring types for instr %s", instr_def.name) + instr_def.operation.generate(None) + # input("!") + for _, set_def in model_obj.sets.items(): + logger.debug("inferring types for set %s", set_def.name) + patch_model(visitor) + for _, instr_def in set_def.instructions.items(): + logger.debug("inferring types for instr %s", instr_def.name) + instr_def.operation.generate(None) + # input("!") + + 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..5d8297f0 --- /dev/null +++ b/m2isar/transforms/infer_types/visitor.py @@ -0,0 +1,470 @@ +# 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 m2isar.metamodel import arch, behav + +logger = logging.getLogger("infer_types") + +# pylint: disable=unused-argument + + +def operation(self: behav.Operation, context): + print("operation", operation) + statements = [] + for stmt in self.statements: + # try: + temp = stmt.generate(context) + if isinstance(temp, list): + statements.extend(temp) + else: + statements.append(temp) + # except (NotImplementedError, ValueError): + # print(f"cant simplify {stmt}") + + self.statements = statements + return self + + +def binary_operation(self: behav.BinaryOperation, context): + print("binary_operation") + print("self.op.value", self.op.value) + + self.left = self.left.generate(context) + self.right = self.right.generate(context) + print("self.left", self.left) + print("self.right", self.right) + + print("self.left.inferred_type", self.left.inferred_type) + print("self.right.inferred_type", self.right.inferred_type) + print("self.inferred_type_", self.inferred_type) + + # see: https://github.com/Minres/CoreDSL/wiki/Expressions#arithmetic-type-rules + if self.op.value in ["+", "-", "*", "/", "%", "|", "&", "^", "<<", ">>"]: + if self.left.inferred_type is None or self.right.inferred_type is None: + logger.warning("Slice Operation needs inferred type. Skipping...") + self.inferred_type = None + return self + assert isinstance(self.left.inferred_type, arch.IntegerType) + assert isinstance(self.right.inferred_type, arch.IntegerType) + w1 = self.left.inferred_type._width + w2 = self.right.inferred_type._width + s1 = self.left.inferred_type.signed + s2 = self.right.inferred_type.signed + if self.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 self.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 self.op.value == "*": + wr = w1 + w2 + sr = s1 or s2 + elif self.op.value == "/": + wr = w1 if not s2 else (w1 + 1) + sr = s1 or s2 + elif self.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 self.op.value in ["|", "&", "^"]: + wr = max(w1, w2) + sr = s1 or s2 + elif self.op.value in [">>", "<<"]: + wr = w1 + sr = s1 + self.inferred_type = arch.IntegerType(wr, sr, None) + else: + if self.op.value in ["||", "&&"]: + self.inferred_type = arch.IntegerType(1, False, None) # unsigned<1> / bool + elif self.op.value in ["<", ">", "==", "!=", ">=", "<="]: + self.inferred_type = arch.IntegerType(1, False, None) # unsigned<1> / bool + # print("sit", self.inferred_type.width) + print("self.inferred_type", self.inferred_type) + assert self.inferred_type is not None + # input("!x!") + + return self + + +def slice_operation(self: behav.SliceOperation, context): + print("slice_operation") + self.expr = self.expr.generate(context) + self.left = self.left.generate(context) + self.right = self.right.generate(context) + # print("self.expr", self.expr) + # print("self.left", self.left) + # print("self.right", self.right) + # input("slice") + + # type inference + if self.expr.inferred_type is None: + logger.warning("Slice Operation needs inferred type. Skipping...") + return self + assert isinstance(self.expr.inferred_type, arch.IntegerType) + ty = self.expr.inferred_type + # For non-static slices, we cann not infer the type! + if not isinstance(self.left, behav.IntLiteral): + logger.warning("Can not infer type of non-static slice operation. Skipping...") + return self + lval = self.left.value + if not isinstance(self.right, behav.IntLiteral): + logger.warning("Can not infer type of non-static slice operation. Skipping...") + return self + rval = self.right.value + width = lval - rval + 1 if lval > rval else rval - lval + 1 + ty_ = copy(ty) + ty_._width = width + self.inferred_type = ty_ + # print("sit", ty) + # input("*") + + return self + + +def concat_operation(self: behav.ConcatOperation, context): + print("concat_coperation") + self.left = self.left.generate(context) + self.right = self.right.generate(context) + # print("self", self) + # print("dir(self)", dir(self)) + # print("self.left", self.left) + # print("dir(self.left)", dir(self.left)) + # print("self.right", self.right) + # print("dir(self.right)", dir(self.right)) + # input("!") + if self.left.inferred_type is None: + logger.warning("Concat Operation needs inferred type. Skipping...") + return self + if self.right.inferred_type is None: + logger.warning("Concat Operation needs inferred type. Skipping...") + return self + width = self.left.inferred_type.width + self.right.inferred_type.width + ty = arch.IntegerType(width, False, None) + self.inferred_type = ty + + return self + + +def number_literal(self: behav.IntLiteral, context): + print("number_literal") + if isinstance(self, behav.IntLiteral): + bit_size = self.bit_size + signed = self.signed + + self.inferred_type = arch.IntegerType(bit_size, signed, None) + return self + + +def int_literal(self: behav.IntLiteral, context): + print("int_literal") + + # type inference + bit_size = self.bit_size + signed = self.signed + + self.inferred_type = arch.IntegerType(bit_size, signed, None) + + return self + + +def scalar_definition(self: behav.ScalarDefinition, context): + print("scalar_definition") + # type inference + # print("scalar_definition", self, dir(self), self.scalar, self.scalar.size, self.scalar.data_type) + signed = self.scalar.data_type == arch.DataType.S + width = self.scalar.size + self.inferred_type = arch.IntegerType(width, signed, None) + return self + + +def assignment(self: behav.Assignment, context): + print("assignment", self) + # print("at_", self.target) + # print("ae_", self.expr) + self.target = self.target.generate(context) + self.expr = self.expr.generate(context) + + # if isinstance(self.expr, behav.IntLiteral) and isinstance(self.target, behav.ScalarDefinition): + # self.target.scalar.value = self.expr.value + + # type inference + # print("at", self.target) + # print("ae", self.expr) + # print("at1", self.target.inferred_type) + # print("ae1", self.expr.inferred_type) + # print("at2", self.target.inferred_type.width) + # print("ae2", self.expr.inferred_type.width) + # input("ccc") + self.inferred_type = None + + return self + + +def conditional(self: behav.Conditional, context): + print("conditional") + self.conds = [x.generate(context) for x in self.conds] + # self.stmts = [[y.generate(context) for y in x] for x in self.stmts] + stmts = [] + for stmt in self.stmts: + if isinstance(stmt, list): # TODO: legacy? + new = [y.generate(context) for y in stmt] + else: + new = stmt.generate(context) + stmts.append(new) + self.stmts = stmts + + return self + + +def loop(self: behav.Loop, context): + print("loop") + self.cond = self.cond.generate(context) + self.stmts = [x.generate(context) for x in self.stmts] + + return self + + +def ternary(self: behav.Ternary, context): + print("ternary") + + self.cond = self.cond.generate(context) + self.then_expr = self.then_expr.generate(context) + self.else_expr = self.else_expr.generate(context) + + # print("ste", self.then_expr) + # print("see", self.else_expr) + # print("ste1", self.then_expr.inferred_type) + # print("see1", self.else_expr.inferred_type) + # print("ste2", self.then_expr.inferred_type.width) + # print("see2", self.else_expr.inferred_type.width) + # TODO + then_ty = self.then_expr.inferred_type + else_ty = self.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) + # print("wr", wr) + # input("o") + self.inferred_type = arch.IntegerType(wr, True, None) + # input("ppp") + + return self + + +def return_(self: behav.Return, context): + print("return_") + if self.expr is not None: + self.expr = self.expr.generate(context) + + return self + + +def unary_operation(self: behav.UnaryOperation, context): + print("unary_operation") + + self.right = self.right.generate(context) + + # print("sr", self.right) + # print("sr1", self.right.inferred_type) + # print("sr2", self.right.inferred_type.width) + # input("!") + if self.right.inferred_type: + w1 = self.right.inferred_type.width + if self.op.value == "-": + inferred_type = arch.IntegerType(w1 + 1, True, None) + elif self.op.value == "~": + inferred_type = arch.IntegerType(w1, True, None) + elif self.op.value == "!": + inferred_type = arch.IntegerType(1, False, None) + else: + inferred_type = None + self.inferred_type = inferred_type + + return self + + +def named_reference(self: behav.NamedReference, context): + print("named_reference", self) + # print("dir", dir(self)) + # print("self.reference", self.reference) + reference = self.reference + + # type inference + # self.infered_type = ? + if isinstance(reference, arch.BitFieldDescr): + # print("BITFIELD", reference) + # print("self.reference", self.reference) + # print("self.reference.data_type", self.reference.data_type) + assert self.reference.data_type in [arch.DataType.U, arch.DataType.S] + ty = arch.IntegerType(reference.size, reference.data_type == arch.DataType.S, None) + # print("ty", ty) + self.inferred_type = ty + + elif isinstance(reference, arch.Scalar): + # print("SCALAR", reference) + 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) + self.inferred_type = ty + elif isinstance(reference, arch.Memory): + # print("MEMORY", reference) + self.inferred_type = arch.IntegerType(reference.size, False, None) + # print("dir(reference)", dir(reference)) + # input("%%%%%") + elif isinstance(reference, arch.Intrinsic): + # print("INTRIN", reference) + # print("dir(reference)", dir(reference)) + # print("reference.size", reference.size) + # print("reference.data_type", reference.data_type) + assert self.reference.data_type in [arch.DataType.U, arch.DataType.S] + self.inferred_type = arch.IntegerType(reference.size, reference.data_type == arch.DataType.S, None) + elif isinstance(reference, arch.Constant): + # print("CONST", reference) + # print("dir(reference)", dir(reference)) + # print("reference.size", reference.size) + # print("reference.signed", reference.signed) + self.inferred_type = arch.IntegerType(reference.size, reference.signed, None) + else: + # print("ELSE", reference) + # print("reference.size", reference.size) + # print("reference.data_type", reference.data_type) + assert False, "Unhandled reference" + + # print("self.inferred_type", self.inferred_type) + # input("222") + + return self + + +def indexed_reference(self: behav.IndexedReference, context): + print("indexed_reference") + self.index = self.index.generate(context) + + # type inference + assert isinstance(self.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] + size = self.reference.size + ty_ = arch.IntegerType(size, ty == arch.DataType.S, None) + + self.inferred_type = ty_ + # print("self.inferred_type", self.inferred_type) + # input("111") + + return self + + +def type_conv(self: behav.TypeConv, context): + print("type_conv") + self.expr = self.expr.generate(context) + print("self.expr", self.expr) + + ty = self.expr.inferred_type + if ty is None: + logger.warning("Type conv needs inferred type. Skipping...") + input("!!@@") + return self + assert isinstance(ty, arch.IntegerType) + assert self.data_type in [arch.DataType.U, arch.DataType.S] + ty.signed = self.data_type == arch.DataType.S + if self.size is not None: + ty._width = self.size + + # type inference + self.inferred_type = ty + + return self + + +def callable_(self: behav.Callable, context): + print("callable_") + print("self", self) + print("dir(self)", dir(self)) + print("self.ref_or_name", self.ref_or_name) + if isinstance(self.ref_or_name, arch.Function): + print("dir(self.ref_or_name)", dir(self.ref_or_name)) + assert self.ref_or_name.data_type in [arch.DataType.U, arch.DataType.S] + signed = self.ref_or_name.data_type == arch.DataType.S + width = self.ref_or_name.size + self.inferred_type = arch.IntegerType(width, signed, None) + # print("self.inferred_type", self.inferred_type) + self.args = [stmt.generate(context) for stmt in self.args] + + return self + + +def procedure_call(self: behav.ProcedureCall, context): + print("procedure_call") + self.args = [stmt.generate(context) for stmt in self.args] + + return self + + +def group(self: behav.Group, context): + print("group") + self.expr = self.expr.generate(context) + + if isinstance(self.expr, behav.IntLiteral): + return self.expr + + # type inference + self.inferred_type = self.expr.inferred_type + + return self + + +def break_(self: behav.Break, context): + print("break_") + return self From fd497e2171cea068adf133b5a5480ad60962e6cd Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Wed, 15 Apr 2026 17:01:40 +0200 Subject: [PATCH 23/62] add m2isar/transforms/validate_behav --- m2isar/transforms/validate_behav/__init__.py | 11 + m2isar/transforms/validate_behav/validate.py | 77 +++++++ m2isar/transforms/validate_behav/visitor.py | 217 +++++++++++++++++++ 3 files changed, 305 insertions(+) create mode 100644 m2isar/transforms/validate_behav/__init__.py create mode 100644 m2isar/transforms/validate_behav/validate.py create mode 100644 m2isar/transforms/validate_behav/visitor.py 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..36d1604b --- /dev/null +++ b/m2isar/transforms/validate_behav/validate.py @@ -0,0 +1,77 @@ +# 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 m2isar.metamodel import patch_model, load_model, dump_model +from m2isar.backends.etiss.warnings import WarningsManager, WarningsInfo, add_warnings_flags, KNOWN_WARNINGS + +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"]) + parser.add_argument("--output", "-o", type=str, default=None) + add_warnings_flags(parser, KNOWN_WARNINGS, KNOWN_WARNINGS) + return parser + + +def run(args): + # initialize logging + logging.basicConfig(level=getattr(logging, args.log.upper())) + logger = logging.getLogger("validate_behav") + logger.setLevel(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) + warnings_info = args.warnings + + for _, core_def in model_obj.cores.items(): + logger.debug("validating behavior for core %s", core_def.name) + context = ValidatorContext(warnings_info) + patch_model(visitor) + for _, instr_def in core_def.instructions.items(): + logger.debug("validating behavior for instr %s", instr_def.name) + instr_def.operation.generate(context) + for _, set_def in model_obj.sets.items(): + logger.debug("validating behavior for set %s", set_def.name) + context = ValidatorContext(warnings_info) + patch_model(visitor) + for _, instr_def in set_def.instructions.items(): + logger.debug("validating behavior for instr %s", instr_def.name) + instr_def.operation.generate(context) + + 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/validate_behav/visitor.py b/m2isar/transforms/validate_behav/visitor.py new file mode 100644 index 00000000..3293e066 --- /dev/null +++ b/m2isar/transforms/validate_behav/visitor.py @@ -0,0 +1,217 @@ +# 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 m2isar.metamodel import arch, behav + +logger = logging.getLogger("validate_behav") + +# pylint: disable=unused-argument + + +def operation(self: behav.Operation, context): + print("operation", operation) + statements = [] + for stmt in self.statements: + # try: + temp = stmt.generate(context) + if isinstance(temp, list): + statements.extend(temp) + else: + statements.append(temp) + # except (NotImplementedError, ValueError): + # print(f"cant simplify {stmt}") + + self.statements = statements + return self + + +def binary_operation(self: behav.BinaryOperation, context): + print("binary_operation") + self.left = self.left.generate(context) + op = self.op + self.right = self.right.generate(context) + + print("self.left", self.left) + print("op", op) + print("self.right", self.right) + if isinstance(self.right, behav.NamedReference): + print("self.right.reference", self.right.reference) + assert self.left.inferred_type is not None + assert self.right.inferred_type is not None + if op.value in ["|", "&", "^"] and self.left.inferred_type.width != self.right.inferred_type.width: + context.emit_warning(f"Bitwise operations with differently size operands are discouraged.", "bit-op-missmatch", logger=logger, line_info=self.line_info) + # input("!!") + # print("self.left.inferred_type", self.left.inferred_type) + # print("self.right.inferred_type", self.right.inferred_type) + if op.value in ["<<", ">>", ">>>"] and self.right.inferred_type.signed: + context.emit_warning(f"Shift by signed amount", "shift-signed", logger=logger, line_info=self.line_info) + # input("!!5") + if op.value in ["<", "<=", ">", ">=", "==", "!="] and self.left.inferred_type.signed != self.right.inferred_type.signed: + # TODO: handle unsigned < 0 + print("self.left", self.left, dir(self.left), self.left.inferred_type) + print("self.right", self.right, dir(self.right), self.right.inferred_type) + if isinstance(self.left, behav.IntLiteral) and self.left.value == 0: + pass + if isinstance(self.right, behav.IntLiteral) and self.right.value == 0: + pass + else: + context.emit_warning(f"Signed vs. unsigned comparison", "sign-compare", logger=logger, line_info=self.line_info) + # input("!!4") + if op.value == "<<" and self.left.inferred_type.width <= self.right.inferred_type.width: + context.emit_warning(f"Shift count overflow for << operation ({self.left.inferred_type.width} vs. {self.right.inferred_type.width})", "shift-overflow", logger=logger, line_info=self.line_info) + input("!!6") + return self + + +def slice_operation(self: behav.SliceOperation, context): + print("slice_operation") + 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): + print("concat_coperation") + self.left = self.left.generate(context) + self.right = self.right.generate(context) + return self + + +def number_literal(self: behav.IntLiteral, context): + print("number_literal") + return self + + +def int_literal(self: behav.IntLiteral, context): + print("int_literal") + return self + + +def scalar_definition(self: behav.ScalarDefinition, context): + print("scalar_definition") + return self + + +def assignment(self: behav.Assignment, context): + print("assignment", self) + self.target = self.target.generate(context) + self.expr = self.expr.generate(context) + print("self.target", self.target) + print("self.expr", self.expr) + assert self.target.inferred_type is not None + assert self.expr.inferred_type is not None + if self.target.inferred_type.width < self.expr.inferred_type.width: + context.emit_warning(f"Implicit truncation {self.expr.inferred_type.width} -> {self.target.inferred_type.width} found", "implicit-trunc", logger=logger, line_info=self.line_info) + # input("!!2") + if self.target.inferred_type.width > self.expr.inferred_type.width: + context.emit_warning(f"Implicit extend {self.expr.inferred_type.width} -> {self.target.inferred_type.width} found", "implicit-extend", logger=logger, line_info=self.line_info) + # input("!!3") + return self + + +def conditional(self: behav.Conditional, context): + print("conditional") + self.conds = [x.generate(context) for x in self.conds] + stmts = [] + for stmt in self.stmts: + if isinstance(stmt, list): # TODO: legacy? + new = [y.generate(context) for y in stmt] + else: + new = stmt.generate(context) + stmts.append(new) + self.stmts = stmts + return self + + +def loop(self: behav.Loop, context): + print("loop") + self.cond = self.cond.generate(context) + self.stmts = [x.generate(context) for x in self.stmts] + return self + + +def ternary(self: behav.Ternary, context): + print("ternary") + self.cond = self.cond.generate(context) + self.then_expr = self.then_expr.generate(context) + self.else_expr = self.else_expr.generate(context) + + return self + + +def return_(self: behav.Return, context): + print("return_") + if self.expr is not None: + self.expr = self.expr.generate(context) + return self + + +def unary_operation(self: behav.UnaryOperation, context): + print("unary_operation") + self.right = self.right.generate(context) + return self + + +def named_reference(self: behav.NamedReference, context): + print("named_reference", self) + return self + + +def indexed_reference(self: behav.IndexedReference, context): + print("indexed_reference") + self.index = self.index.generate(context) + return self + + +def type_conv(self: behav.TypeConv, context): + print("type_conv") + self.expr = self.expr.generate(context) + return self + + +def callable_(self: behav.Callable, context): + print("callable_") + self.args = [stmt.generate(context) for stmt in self.args] + return self + + +def procedure_call(self: behav.ProcedureCall, context): + print("procedure_call") + self.args = [stmt.generate(context) for stmt in self.args] + return self + + +def group(self: behav.Group, context): + print("group") + self.expr = self.expr.generate(context) + if isinstance(self.expr, behav.IntLiteral): + return self.expr + return self + + +def break_(self: behav.Break, context): + print("break_") + return self From 37e874c960e1017348a3e2083a23d2d0cf65b615 Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Wed, 15 Apr 2026 17:01:54 +0200 Subject: [PATCH 24/62] m2isar/backends/etiss/warnings.py: add bit-op-missmatch --- m2isar/backends/etiss/warnings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/m2isar/backends/etiss/warnings.py b/m2isar/backends/etiss/warnings.py index ee5499fc..20df07fc 100644 --- a/m2isar/backends/etiss/warnings.py +++ b/m2isar/backends/etiss/warnings.py @@ -9,6 +9,7 @@ 'implicit-extend', 'sign-compare', 'unused-value', + 'bit-op-missmatch', } From 94642be97bde11040293b77e3a0786c29185bd53 Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Wed, 15 Apr 2026 17:06:15 +0200 Subject: [PATCH 25/62] move common warnings code to toplevel --- m2isar/backends/etiss/instruction_generator.py | 2 +- m2isar/backends/etiss/instruction_utils.py | 2 +- m2isar/backends/etiss/instruction_writer.py | 2 +- m2isar/backends/etiss/writer.py | 13 +++++++++++-- m2isar/{backends/etiss => }/warnings.py | 0 5 files changed, 14 insertions(+), 5 deletions(-) rename m2isar/{backends/etiss => }/warnings.py (100%) diff --git a/m2isar/backends/etiss/instruction_generator.py b/m2isar/backends/etiss/instruction_generator.py index 005081fe..0700bb15 100644 --- a/m2isar/backends/etiss/instruction_generator.py +++ b/m2isar/backends/etiss/instruction_generator.py @@ -15,7 +15,7 @@ from ...metamodel import arch, behav, patch_model from . import BlockEndType, instruction_transform, instruction_utils from .templates import template_dir -from .warnings import WarningsInfo +from ...warnings import WarningsInfo logger = logging.getLogger("instruction_generator") diff --git a/m2isar/backends/etiss/instruction_utils.py b/m2isar/backends/etiss/instruction_utils.py index 9df52d8a..8829452e 100644 --- a/m2isar/backends/etiss/instruction_utils.py +++ b/m2isar/backends/etiss/instruction_utils.py @@ -17,7 +17,7 @@ from ...metamodel.code_info import LineInfo from ...metamodel.utils import StaticType from . import replacements -from .warnings import WarningsManager, WarningsInfo +from ...warnings import WarningsManager, WarningsInfo data_type_map = { arch.DataType.S: 'etiss_int', diff --git a/m2isar/backends/etiss/instruction_writer.py b/m2isar/backends/etiss/instruction_writer.py index 2eff89f2..0a9326fa 100644 --- a/m2isar/backends/etiss/instruction_writer.py +++ b/m2isar/backends/etiss/instruction_writer.py @@ -18,7 +18,7 @@ from . import BlockEndType from .instruction_generator import generate_functions, generate_instructions from .templates import template_dir -from .warnings import WarningsInfo +from ...warnings import WarningsInfo logger = logging.getLogger("instruction_writer") diff --git a/m2isar/backends/etiss/writer.py b/m2isar/backends/etiss/writer.py index 8906fc5a..591b7588 100755 --- a/m2isar/backends/etiss/writer.py +++ b/m2isar/backends/etiss/writer.py @@ -26,7 +26,16 @@ write_arch_specific_header, write_arch_struct) from .instruction_writer import write_functions, write_instructions -from .warnings import add_warnings_flags, KNOWN_WARNINGS +from ...warnings import add_warnings_flags + +ETISS_WRITER_WARNINGS = { + 'implicit-trunc', + 'shift-overflow', + 'shift-signed', + 'implicit-extend', + 'sign-compare', + 'unused-value', +} class BooleanOptionalAction(argparse.Action): @@ -86,7 +95,7 @@ def setup(): help="Force end translation blocks on no instructions, uncoditional jumps or all jumps.") parser.add_argument("--coverage", action=BooleanOptionalAction, default=False, help="Generate coverage tracking code into model.") parser.add_argument("--log", default="info", choices=["critical", "error", "warning", "info", "debug"]) - add_warnings_flags(parser, KNOWN_WARNINGS, KNOWN_WARNINGS) + add_warnings_flags(parser, ETISS_WRITER_WARNINGS, ETISS_WRITER_WARNINGS) args = parser.parse_args() # configure logging diff --git a/m2isar/backends/etiss/warnings.py b/m2isar/warnings.py similarity index 100% rename from m2isar/backends/etiss/warnings.py rename to m2isar/warnings.py From 857f25fb5d5263ea069980c97b992621a2718fdc Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Wed, 15 Apr 2026 17:24:54 +0200 Subject: [PATCH 26/62] m2isar/backends/etiss/writer.py: add comment --- m2isar/backends/etiss/writer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/m2isar/backends/etiss/writer.py b/m2isar/backends/etiss/writer.py index 591b7588..2ed6723d 100755 --- a/m2isar/backends/etiss/writer.py +++ b/m2isar/backends/etiss/writer.py @@ -38,6 +38,7 @@ } +# 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.""" From 8ffa0ede21fed706b6f3d4d511615f22d34f41ad Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Wed, 15 Apr 2026 17:25:22 +0200 Subject: [PATCH 27/62] add --validate flag for parser --- m2isar/frontends/coredsl2/parser.py | 27 ++++++++++++---- m2isar/transforms/infer_types/transform.py | 31 +++++++++--------- m2isar/transforms/validate_behav/validate.py | 33 +++++++++----------- 3 files changed, 52 insertions(+), 39 deletions(-) diff --git a/m2isar/frontends/coredsl2/parser.py b/m2isar/frontends/coredsl2/parser.py index bcc88501..f3bda456 100644 --- a/m2isar/frontends/coredsl2/parser.py +++ b/m2isar/frontends/coredsl2/parser.py @@ -23,6 +23,10 @@ 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 main(): @@ -30,6 +34,8 @@ 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('--validate', action=BooleanOptionalAction, default=False, help="Run type inference and validator after parsing.") + add_warnings_flags(parser, KNOWN_WARNINGS, KNOWN_WARNINGS) # only if --validate args = parser.parse_args() @@ -251,14 +257,23 @@ def main(): op.statements = always_block_statements + op.statements instr_def.operation = op + model_obj = M2Model( + M2_METAMODEL_VERSION, + models, + {}, + CodeInfoBase.database + ) + + print("args.validate", args.validate) + if args.validate: + logger.info("Running type inference") + model_obj = infer_types(model_obj) + 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/transforms/infer_types/transform.py b/m2isar/transforms/infer_types/transform.py index 149e3056..7707bb8c 100644 --- a/m2isar/transforms/infer_types/transform.py +++ b/m2isar/transforms/infer_types/transform.py @@ -27,34 +27,35 @@ def get_parser(): return parser -def run(args): - # initialize logging - logging.basicConfig(level=getattr(logging, args.log.upper())) +def infer_types(model_obj): logger = logging.getLogger("infer_types") - logger.setLevel(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) - for _, core_def in model_obj.cores.items(): logger.debug("inferring types for core %s", core_def.name) patch_model(visitor) for _, instr_def in core_def.instructions.items(): logger.debug("inferring types for instr %s", instr_def.name) instr_def.operation.generate(None) - # input("!") for _, set_def in model_obj.sets.items(): logger.debug("inferring types for set %s", set_def.name) patch_model(visitor) for _, instr_def in set_def.instructions.items(): logger.debug("inferring types for instr %s", instr_def.name) instr_def.operation.generate(None) - # input("!") + 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) diff --git a/m2isar/transforms/validate_behav/validate.py b/m2isar/transforms/validate_behav/validate.py index 36d1604b..c9a931d4 100644 --- a/m2isar/transforms/validate_behav/validate.py +++ b/m2isar/transforms/validate_behav/validate.py @@ -13,8 +13,8 @@ import logging import pathlib -from m2isar.metamodel import patch_model, load_model, dump_model -from m2isar.backends.etiss.warnings import WarningsManager, WarningsInfo, add_warnings_flags, KNOWN_WARNINGS +from ...metamodel import patch_model, load_model, dump_model +from ...warnings import WarningsManager, WarningsInfo, add_warnings_flags, KNOWN_WARNINGS from . import visitor @@ -29,26 +29,12 @@ def get_parser(): 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) add_warnings_flags(parser, KNOWN_WARNINGS, KNOWN_WARNINGS) return parser -def run(args): - # initialize logging - logging.basicConfig(level=getattr(logging, args.log.upper())) +def validate_behav(model_obj, warnings_info): logger = logging.getLogger("validate_behav") - logger.setLevel(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) - warnings_info = args.warnings - for _, core_def in model_obj.cores.items(): logger.debug("validating behavior for core %s", core_def.name) context = ValidatorContext(warnings_info) @@ -63,8 +49,19 @@ def run(args): for _, instr_def in set_def.instructions.items(): logger.debug("validating behavior for instr %s", instr_def.name) instr_def.operation.generate(context) + # return model_obj - dump_model(model_obj, out_path) + +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): From a83a3817dfbf6a2c174565386ce98bce3b1f65fb Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Wed, 15 Apr 2026 17:25:48 +0200 Subject: [PATCH 28/62] comment out prints in transforms --- m2isar/transforms/validate_behav/visitor.py | 58 ++++++++++----------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/m2isar/transforms/validate_behav/visitor.py b/m2isar/transforms/validate_behav/visitor.py index 3293e066..3f939c35 100644 --- a/m2isar/transforms/validate_behav/visitor.py +++ b/m2isar/transforms/validate_behav/visitor.py @@ -31,7 +31,7 @@ def operation(self: behav.Operation, context): - print("operation", operation) + # print("operation", operation) statements = [] for stmt in self.statements: # try: @@ -48,16 +48,14 @@ def operation(self: behav.Operation, context): def binary_operation(self: behav.BinaryOperation, context): - print("binary_operation") + # print("binary_operation") self.left = self.left.generate(context) op = self.op self.right = self.right.generate(context) - print("self.left", self.left) - print("op", op) - print("self.right", self.right) - if isinstance(self.right, behav.NamedReference): - print("self.right.reference", self.right.reference) + # print("self.left", self.left) + # print("op", op) + # print("self.right", self.right) assert self.left.inferred_type is not None assert self.right.inferred_type is not None if op.value in ["|", "&", "^"] and self.left.inferred_type.width != self.right.inferred_type.width: @@ -70,8 +68,8 @@ def binary_operation(self: behav.BinaryOperation, context): # input("!!5") if op.value in ["<", "<=", ">", ">=", "==", "!="] and self.left.inferred_type.signed != self.right.inferred_type.signed: # TODO: handle unsigned < 0 - print("self.left", self.left, dir(self.left), self.left.inferred_type) - print("self.right", self.right, dir(self.right), self.right.inferred_type) + # print("self.left", self.left, dir(self.left), self.left.inferred_type) + # print("self.right", self.right, dir(self.right), self.right.inferred_type) if isinstance(self.left, behav.IntLiteral) and self.left.value == 0: pass if isinstance(self.right, behav.IntLiteral) and self.right.value == 0: @@ -81,12 +79,12 @@ def binary_operation(self: behav.BinaryOperation, context): # input("!!4") if op.value == "<<" and self.left.inferred_type.width <= self.right.inferred_type.width: context.emit_warning(f"Shift count overflow for << operation ({self.left.inferred_type.width} vs. {self.right.inferred_type.width})", "shift-overflow", logger=logger, line_info=self.line_info) - input("!!6") + # input("!!6") return self def slice_operation(self: behav.SliceOperation, context): - print("slice_operation") + # print("slice_operation") self.expr = self.expr.generate(context) self.left = self.left.generate(context) self.right = self.right.generate(context) @@ -94,33 +92,33 @@ def slice_operation(self: behav.SliceOperation, context): def concat_operation(self: behav.ConcatOperation, context): - print("concat_coperation") + # print("concat_coperation") self.left = self.left.generate(context) self.right = self.right.generate(context) return self def number_literal(self: behav.IntLiteral, context): - print("number_literal") + # print("number_literal") return self def int_literal(self: behav.IntLiteral, context): - print("int_literal") + # print("int_literal") return self def scalar_definition(self: behav.ScalarDefinition, context): - print("scalar_definition") + # print("scalar_definition") return self def assignment(self: behav.Assignment, context): - print("assignment", self) + # print("assignment", self) self.target = self.target.generate(context) self.expr = self.expr.generate(context) - print("self.target", self.target) - print("self.expr", self.expr) + # print("self.target", self.target) + # print("self.expr", self.expr) assert self.target.inferred_type is not None assert self.expr.inferred_type is not None if self.target.inferred_type.width < self.expr.inferred_type.width: @@ -133,7 +131,7 @@ def assignment(self: behav.Assignment, context): def conditional(self: behav.Conditional, context): - print("conditional") + # print("conditional") self.conds = [x.generate(context) for x in self.conds] stmts = [] for stmt in self.stmts: @@ -147,14 +145,14 @@ def conditional(self: behav.Conditional, context): def loop(self: behav.Loop, context): - print("loop") + # print("loop") self.cond = self.cond.generate(context) self.stmts = [x.generate(context) for x in self.stmts] return self def ternary(self: behav.Ternary, context): - print("ternary") + # print("ternary") self.cond = self.cond.generate(context) self.then_expr = self.then_expr.generate(context) self.else_expr = self.else_expr.generate(context) @@ -163,49 +161,49 @@ def ternary(self: behav.Ternary, context): def return_(self: behav.Return, context): - print("return_") + # print("return_") if self.expr is not None: self.expr = self.expr.generate(context) return self def unary_operation(self: behav.UnaryOperation, context): - print("unary_operation") + # print("unary_operation") self.right = self.right.generate(context) return self def named_reference(self: behav.NamedReference, context): - print("named_reference", self) + # print("named_reference", self) return self def indexed_reference(self: behav.IndexedReference, context): - print("indexed_reference") + # print("indexed_reference") self.index = self.index.generate(context) return self def type_conv(self: behav.TypeConv, context): - print("type_conv") + # print("type_conv") self.expr = self.expr.generate(context) return self def callable_(self: behav.Callable, context): - print("callable_") + # print("callable_") self.args = [stmt.generate(context) for stmt in self.args] return self def procedure_call(self: behav.ProcedureCall, context): - print("procedure_call") + # print("procedure_call") self.args = [stmt.generate(context) for stmt in self.args] return self def group(self: behav.Group, context): - print("group") + # print("group") self.expr = self.expr.generate(context) if isinstance(self.expr, behav.IntLiteral): return self.expr @@ -213,5 +211,5 @@ def group(self: behav.Group, context): def break_(self: behav.Break, context): - print("break_") + # print("break_") return self From 64fa03d85c8b8e5d751512870151633ce26d8c27 Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Wed, 15 Apr 2026 17:30:39 +0200 Subject: [PATCH 29/62] remove redundant warnings code from etiss writer --- m2isar/backends/etiss/architecture_writer.py | 2 +- m2isar/backends/etiss/instruction_generator.py | 13 ++++++------- m2isar/backends/etiss/instruction_transform.py | 17 ----------------- m2isar/backends/etiss/instruction_utils.py | 10 ++-------- m2isar/backends/etiss/instruction_writer.py | 11 +++++------ m2isar/backends/etiss/writer.py | 15 ++------------- 6 files changed, 16 insertions(+), 52 deletions(-) diff --git a/m2isar/backends/etiss/architecture_writer.py b/m2isar/backends/etiss/architecture_writer.py index bc69f8f7..6bb671d3 100644 --- a/m2isar/backends/etiss/architecture_writer.py +++ b/m2isar/backends/etiss/architecture_writer.py @@ -201,7 +201,7 @@ def write_arch_specific_cpp(core: arch.CoreDef, start_time: str, output_path: pa error_instr._size = bitsize # pylint: disable=protected-access error_fields = generate_fields(32, error_instr) - error_callbacks[bitsize] = generate_instruction_callback(core, error_instr, error_fields, True, BlockEndType.NONE, False, None) + error_callbacks[bitsize] = generate_instruction_callback(core, error_instr, error_fields, True, BlockEndType.NONE, False) logger.info("writing architecture specific file") diff --git a/m2isar/backends/etiss/instruction_generator.py b/m2isar/backends/etiss/instruction_generator.py index 0700bb15..69a8a268 100644 --- a/m2isar/backends/etiss/instruction_generator.py +++ b/m2isar/backends/etiss/instruction_generator.py @@ -15,7 +15,6 @@ from ...metamodel import arch, behav, patch_model from . import BlockEndType, instruction_transform, instruction_utils from .templates import template_dir -from ...warnings import WarningsInfo logger = logging.getLogger("instruction_generator") @@ -23,7 +22,7 @@ def generate_arg_str(arg: arch.FnParam): arg_name = f" {arg.name}" if arg.name is not None else "" return f'{instruction_utils.data_type_map[arg.data_type]}{arg.actual_size}{arg_name}' -def generate_functions(core: arch.CoreDef, static_scalars: bool, decls_only: bool, generate_coverage: bool, warnings_info: WarningsInfo): +def generate_functions(core: arch.CoreDef, static_scalars: bool, decls_only: bool, generate_coverage: bool): """Return a generator object to generate function behavior code. Uses function definitions in the core object. """ @@ -48,7 +47,7 @@ def generate_functions(core: arch.CoreDef, static_scalars: bool, decls_only: boo # set up a transformer context and generate code context = instruction_utils.TransformerContext(core.constants, core.memories, core.memory_aliases, fn_def.args, fn_def.attributes, - core.functions, 0, core_default_width, core_name, static_scalars, core.intrinsics, generate_coverage, True, warnings_info) + core.functions, 0, core_default_width, core_name, static_scalars, core.intrinsics, generate_coverage, True) logger.debug("generating code for %s", fn_name) @@ -140,7 +139,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, warnings_info: WarningsInfo): +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) instr_name = instr_def.name @@ -152,7 +151,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, False, warnings_info) + 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 @@ -187,7 +186,7 @@ def generate_instruction_callback(core: arch.CoreDef, instr_def: arch.Instructio return callback_str -def generate_instructions(core: arch.CoreDef, static_scalars: bool, block_end_on: BlockEndType, generate_coverage: bool, warnings_info: WarningsInfo): +def generate_instructions(core: arch.CoreDef, static_scalars: bool, block_end_on: BlockEndType, generate_coverage: bool): """Return a generator object to generate instruction behavior code. Uses instruction definitions in the core object. """ @@ -231,7 +230,7 @@ def generate_instructions(core: arch.CoreDef, static_scalars: bool, block_end_on instr_def.operation = new_op instr_def.throws = True - callback_str = generate_instruction_callback(core, instr_def, fields, static_scalars, block_end_on, generate_coverage, warnings_info) + callback_str = generate_instruction_callback(core, instr_def, fields, static_scalars, block_end_on, generate_coverage) # render code for whole instruction templ_str = instr_template.render( diff --git a/m2isar/backends/etiss/instruction_transform.py b/m2isar/backends/etiss/instruction_transform.py index 20b0c7a2..0e0d62c5 100644 --- a/m2isar/backends/etiss/instruction_transform.py +++ b/m2isar/backends/etiss/instruction_transform.py @@ -471,11 +471,6 @@ def assignment(self: behav.Assignment, context: TransformerContext): context.affected_regs.update(target.regs_affected) context.dependent_regs.update(expr.regs_affected) - # TODO: move to parser! - # if target.size < expr.size and not context.ignore_trunc_warnings: - if target.size < expr.size: - context.emit_warning(f"Implicit truncation {expr.size} -> {target.size} found", "implicit-trunc", logger=logger, line_info=self.line_info) - if not target.is_mem_access and not expr.is_mem_access: if target.actual_size > target.size: if target.signed: @@ -530,16 +525,6 @@ def binary_operation(self: behav.BinaryOperation, context: TransformerContext): left = self.left.generate(context) op = self.op right = self.right.generate(context) - if op.value in ["<<", ">>", ">>>"] and right.signed: - context.emit_warning(f"Shift by signed amount", "shift-signed", logger=logger, line_info=self.line_info) - if op.value in ["<", "<=", ">", ">=", "==", "!="] and left.signed != right.signed: - # TODO: handle unsigned < 0 - if left.static and left.is_literal and left.code == "0": - pass - elif right.static and right.is_literal and right.code == "0": - pass - else: - context.emit_warning(f"Signed vs. unsigned comparison", "sign-compare", logger=logger, line_info=self.line_info) # convert staticness if needed if not left.static and right.static and not right.is_literal: @@ -547,8 +532,6 @@ def binary_operation(self: behav.BinaryOperation, context: TransformerContext): if not right.static and left.static and not left.is_literal: left.code = context.make_static(left.code, left.signed) - if op.value == "<<" and left.size <= right.size: - context.emit_warning(f"Shift count overflow for << operation ({left.size} vs. {right.size})", "shift-overflow", logger=logger, line_info=self.line_info) 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 diff --git a/m2isar/backends/etiss/instruction_utils.py b/m2isar/backends/etiss/instruction_utils.py index 8829452e..db57ec06 100644 --- a/m2isar/backends/etiss/instruction_utils.py +++ b/m2isar/backends/etiss/instruction_utils.py @@ -17,7 +17,6 @@ from ...metamodel.code_info import LineInfo from ...metamodel.utils import StaticType from . import replacements -from ...warnings import WarningsManager, WarningsInfo data_type_map = { arch.DataType.S: 'etiss_int', @@ -135,19 +134,14 @@ def format(self, mapping={}, **kwargs): setattr(self, name, formatted) -class EtissWriterWarningsManager(WarningsManager): - pass - - -class TransformerContext(EtissWriterWarningsManager): +class TransformerContext: """Track miscellaneous information throughout the code generation process. Also provides helper functions for staticness conversion etc. """ 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: bool = False, warnings_info: WarningsInfo = None): - super().__init__(warnings_info) + 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/instruction_writer.py b/m2isar/backends/etiss/instruction_writer.py index 0a9326fa..d852f062 100644 --- a/m2isar/backends/etiss/instruction_writer.py +++ b/m2isar/backends/etiss/instruction_writer.py @@ -18,11 +18,10 @@ from . import BlockEndType from .instruction_generator import generate_functions, generate_instructions from .templates import template_dir -from ...warnings import WarningsInfo logger = logging.getLogger("instruction_writer") -def write_functions(core: arch.CoreDef, start_time: str, output_path: pathlib.Path, static_scalars: bool, generate_coverage: bool, warnings_info: WarningsInfo): +def write_functions(core: arch.CoreDef, start_time: str, output_path: pathlib.Path, static_scalars: bool, generate_coverage: bool): """Generate and write the {CoreName}Funcs.h file for ETISS.""" fn_set_header_template = Template(filename=str(template_dir/'etiss_function_set_header.mako')) @@ -45,7 +44,7 @@ def write_functions(core: arch.CoreDef, start_time: str, output_path: pathlib.Pa funcs_f.write(" // clang-format off\n") # generate and write function declarations - for fn_name, templ_str in generate_functions(core, static_scalars, True, generate_coverage, warnings_info): + for fn_name, templ_str in generate_functions(core, static_scalars, True, generate_coverage): logger.debug("writing function decl %s", fn_name) funcs_f.write(templ_str) @@ -64,13 +63,13 @@ def write_functions(core: arch.CoreDef, start_time: str, output_path: pathlib.Pa funcs_f.write("// clang-format off\n") # generate and write function definitions - for fn_name, templ_str in generate_functions(core, static_scalars, False, generate_coverage, warnings_info): + for fn_name, templ_str in generate_functions(core, static_scalars, False, generate_coverage): logger.debug("writing function def %s", fn_name) funcs_f.write(templ_str) funcs_f.write("// clang-format on\n") def write_instructions(core: arch.CoreDef, start_time: str, output_path: pathlib.Path, separate: bool, static_scalars: bool, - block_end_on: BlockEndType, generate_coverage: bool, warnings_info: WarningsInfo): + block_end_on: BlockEndType, generate_coverage: bool): """Generate and write the instruction model C++ files for ETISS.""" instr_set_template = Template(filename=str(template_dir/'etiss_instruction_set.mako')) @@ -104,7 +103,7 @@ def write_instructions(core: arch.CoreDef, start_time: str, output_path: pathlib out_f.write(instr_set_str) # generate instruction behavior models - for instr_name, _, ext_name, templ_str in generate_instructions(core, static_scalars, block_end_on, generate_coverage, warnings_info): + for instr_name, _, ext_name, templ_str in generate_instructions(core, static_scalars, block_end_on, generate_coverage): logger.debug("writing instruction %s", instr_name) outfiles.get(ext_name, outfiles['default']).write(templ_str) for outfile in outfiles.values(): diff --git a/m2isar/backends/etiss/writer.py b/m2isar/backends/etiss/writer.py index 2ed6723d..05074c86 100755 --- a/m2isar/backends/etiss/writer.py +++ b/m2isar/backends/etiss/writer.py @@ -26,16 +26,6 @@ write_arch_specific_header, write_arch_struct) from .instruction_writer import write_functions, write_instructions -from ...warnings import add_warnings_flags - -ETISS_WRITER_WARNINGS = { - 'implicit-trunc', - 'shift-overflow', - 'shift-signed', - 'implicit-extend', - 'sign-compare', - 'unused-value', -} # TODO: not required anymore for Python >= v3.9 @@ -96,7 +86,6 @@ def setup(): help="Force end translation blocks on no instructions, uncoditional jumps or all jumps.") parser.add_argument("--coverage", action=BooleanOptionalAction, default=False, help="Generate coverage tracking code into model.") parser.add_argument("--log", default="info", choices=["critical", "error", "warning", "info", "debug"]) - add_warnings_flags(parser, ETISS_WRITER_WARNINGS, ETISS_WRITER_WARNINGS) args = parser.parse_args() # configure logging @@ -183,8 +172,8 @@ def main(): write_arch_lib(core, start_time, output_path) write_arch_cmake(core, start_time, output_path, args.separate) write_arch_gdbcore(core, start_time, output_path) - write_functions(core, start_time, output_path, args.static_scalars, args.coverage, args.warnings) - write_instructions(core, start_time, output_path, args.separate, args.static_scalars, BlockEndType[args.block_end_on.upper()], args.coverage, args.warnings) + write_functions(core, start_time, output_path, args.static_scalars, args.coverage) + write_instructions(core, start_time, output_path, args.separate, args.static_scalars, BlockEndType[args.block_end_on.upper()], args.coverage) with open(output_path / "coverage.csv", "w") as f: for c_id, c_info in sorted(CodeInfoTracker.tracker[core_name].items()): From 93834489ef72d4008a020e1a6104e3ccbbec5021 Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Wed, 15 Apr 2026 17:31:29 +0200 Subject: [PATCH 30/62] remove print --- m2isar/frontends/coredsl2/parser.py | 1 - 1 file changed, 1 deletion(-) diff --git a/m2isar/frontends/coredsl2/parser.py b/m2isar/frontends/coredsl2/parser.py index f3bda456..9e2a4ac7 100644 --- a/m2isar/frontends/coredsl2/parser.py +++ b/m2isar/frontends/coredsl2/parser.py @@ -264,7 +264,6 @@ def main(): CodeInfoBase.database ) - print("args.validate", args.validate) if args.validate: logger.info("Running type inference") model_obj = infer_types(model_obj) From aa4a9b90ba5cd158467a8afa2b048307a339be9b Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Wed, 15 Apr 2026 17:32:07 +0200 Subject: [PATCH 31/62] load_model cleanup --- m2isar/metamodel/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/m2isar/metamodel/__init__.py b/m2isar/metamodel/__init__.py index 55c0b155..0396add7 100644 --- a/m2isar/metamodel/__init__.py +++ b/m2isar/metamodel/__init__.py @@ -102,8 +102,6 @@ def load_model( logger = logging.getLogger("load_model") logger.debug("loading model: %s", str(model_path)) with open(model_path, "rb") as f: - # models: "dict[str, arch.CoreDef]" = pickle.load(f) - # sets: "dict[str, arch.InstructionSet]" = pickle.load(f) model_obj: M2Model = pickle.load(f) assert isinstance(model_obj, M2Model), "Expected M2Model" required_version = M2_METAMODEL_VERSION From ff3b86707b299e68b80d859cb4c9c5d7d6b0be5c Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Wed, 15 Apr 2026 17:33:12 +0200 Subject: [PATCH 32/62] validate_behav: cleanup visitor --- m2isar/transforms/validate_behav/visitor.py | 39 --------------------- 1 file changed, 39 deletions(-) diff --git a/m2isar/transforms/validate_behav/visitor.py b/m2isar/transforms/validate_behav/visitor.py index 3f939c35..8591d3fa 100644 --- a/m2isar/transforms/validate_behav/visitor.py +++ b/m2isar/transforms/validate_behav/visitor.py @@ -31,60 +31,42 @@ def operation(self: behav.Operation, context): - # print("operation", operation) statements = [] for stmt in self.statements: - # try: temp = stmt.generate(context) if isinstance(temp, list): statements.extend(temp) else: statements.append(temp) - # except (NotImplementedError, ValueError): - # print(f"cant simplify {stmt}") self.statements = statements return self def binary_operation(self: behav.BinaryOperation, context): - # print("binary_operation") self.left = self.left.generate(context) op = self.op self.right = self.right.generate(context) - # print("self.left", self.left) - # print("op", op) - # print("self.right", self.right) assert self.left.inferred_type is not None assert self.right.inferred_type is not None if op.value in ["|", "&", "^"] and self.left.inferred_type.width != self.right.inferred_type.width: context.emit_warning(f"Bitwise operations with differently size operands are discouraged.", "bit-op-missmatch", logger=logger, line_info=self.line_info) - # input("!!") - # print("self.left.inferred_type", self.left.inferred_type) - # print("self.right.inferred_type", self.right.inferred_type) if op.value in ["<<", ">>", ">>>"] and self.right.inferred_type.signed: context.emit_warning(f"Shift by signed amount", "shift-signed", logger=logger, line_info=self.line_info) - # input("!!5") if op.value in ["<", "<=", ">", ">=", "==", "!="] and self.left.inferred_type.signed != self.right.inferred_type.signed: - # TODO: handle unsigned < 0 - # print("self.left", self.left, dir(self.left), self.left.inferred_type) - # print("self.right", self.right, dir(self.right), self.right.inferred_type) if isinstance(self.left, behav.IntLiteral) and self.left.value == 0: pass if isinstance(self.right, behav.IntLiteral) and self.right.value == 0: pass else: context.emit_warning(f"Signed vs. unsigned comparison", "sign-compare", logger=logger, line_info=self.line_info) - # input("!!4") if op.value == "<<" and self.left.inferred_type.width <= self.right.inferred_type.width: context.emit_warning(f"Shift count overflow for << operation ({self.left.inferred_type.width} vs. {self.right.inferred_type.width})", "shift-overflow", logger=logger, line_info=self.line_info) - # input("!!6") return self def slice_operation(self: behav.SliceOperation, context): - # print("slice_operation") self.expr = self.expr.generate(context) self.left = self.left.generate(context) self.right = self.right.generate(context) @@ -92,46 +74,36 @@ def slice_operation(self: behav.SliceOperation, context): def concat_operation(self: behav.ConcatOperation, context): - # print("concat_coperation") self.left = self.left.generate(context) self.right = self.right.generate(context) return self def number_literal(self: behav.IntLiteral, context): - # print("number_literal") return self def int_literal(self: behav.IntLiteral, context): - # print("int_literal") return self def scalar_definition(self: behav.ScalarDefinition, context): - # print("scalar_definition") return self def assignment(self: behav.Assignment, context): - # print("assignment", self) self.target = self.target.generate(context) self.expr = self.expr.generate(context) - # print("self.target", self.target) - # print("self.expr", self.expr) assert self.target.inferred_type is not None assert self.expr.inferred_type is not None if self.target.inferred_type.width < self.expr.inferred_type.width: context.emit_warning(f"Implicit truncation {self.expr.inferred_type.width} -> {self.target.inferred_type.width} found", "implicit-trunc", logger=logger, line_info=self.line_info) - # input("!!2") if self.target.inferred_type.width > self.expr.inferred_type.width: context.emit_warning(f"Implicit extend {self.expr.inferred_type.width} -> {self.target.inferred_type.width} found", "implicit-extend", logger=logger, line_info=self.line_info) - # input("!!3") return self def conditional(self: behav.Conditional, context): - # print("conditional") self.conds = [x.generate(context) for x in self.conds] stmts = [] for stmt in self.stmts: @@ -145,14 +117,12 @@ def conditional(self: behav.Conditional, context): def loop(self: behav.Loop, context): - # print("loop") self.cond = self.cond.generate(context) self.stmts = [x.generate(context) for x in self.stmts] return self def ternary(self: behav.Ternary, context): - # print("ternary") self.cond = self.cond.generate(context) self.then_expr = self.then_expr.generate(context) self.else_expr = self.else_expr.generate(context) @@ -161,49 +131,41 @@ def ternary(self: behav.Ternary, context): def return_(self: behav.Return, context): - # print("return_") if self.expr is not None: self.expr = self.expr.generate(context) return self def unary_operation(self: behav.UnaryOperation, context): - # print("unary_operation") self.right = self.right.generate(context) return self def named_reference(self: behav.NamedReference, context): - # print("named_reference", self) return self def indexed_reference(self: behav.IndexedReference, context): - # print("indexed_reference") self.index = self.index.generate(context) return self def type_conv(self: behav.TypeConv, context): - # print("type_conv") self.expr = self.expr.generate(context) return self def callable_(self: behav.Callable, context): - # print("callable_") self.args = [stmt.generate(context) for stmt in self.args] return self def procedure_call(self: behav.ProcedureCall, context): - # print("procedure_call") self.args = [stmt.generate(context) for stmt in self.args] return self def group(self: behav.Group, context): - # print("group") self.expr = self.expr.generate(context) if isinstance(self.expr, behav.IntLiteral): return self.expr @@ -211,5 +173,4 @@ def group(self: behav.Group, context): def break_(self: behav.Break, context): - # print("break_") return self From da1b076384cd409357df29dbf2feb9f5e66870f5 Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Wed, 15 Apr 2026 17:35:50 +0200 Subject: [PATCH 33/62] coredsl parser: expose --infer-types (on by default) --- m2isar/frontends/coredsl2/parser.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/m2isar/frontends/coredsl2/parser.py b/m2isar/frontends/coredsl2/parser.py index 9e2a4ac7..1052d525 100644 --- a/m2isar/frontends/coredsl2/parser.py +++ b/m2isar/frontends/coredsl2/parser.py @@ -34,7 +34,8 @@ 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('--validate', action=BooleanOptionalAction, default=False, help="Run type inference and validator after parsing.") + 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() @@ -264,9 +265,10 @@ def main(): CodeInfoBase.database ) - if args.validate: + 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) From 611fea16d4c5274de44cbef28f3ca1d803526ea3 Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Wed, 15 Apr 2026 17:36:11 +0200 Subject: [PATCH 34/62] infer_types: cleanup --- m2isar/transforms/infer_types/visitor.py | 64 ++++++++++++------------ 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/m2isar/transforms/infer_types/visitor.py b/m2isar/transforms/infer_types/visitor.py index 5d8297f0..e2aee579 100644 --- a/m2isar/transforms/infer_types/visitor.py +++ b/m2isar/transforms/infer_types/visitor.py @@ -31,7 +31,7 @@ def operation(self: behav.Operation, context): - print("operation", operation) + # print("operation", operation) statements = [] for stmt in self.statements: # try: @@ -48,17 +48,17 @@ def operation(self: behav.Operation, context): def binary_operation(self: behav.BinaryOperation, context): - print("binary_operation") - print("self.op.value", self.op.value) + # print("binary_operation") + # print("self.op.value", self.op.value) self.left = self.left.generate(context) self.right = self.right.generate(context) - print("self.left", self.left) - print("self.right", self.right) + # print("self.left", self.left) + # print("self.right", self.right) - print("self.left.inferred_type", self.left.inferred_type) - print("self.right.inferred_type", self.right.inferred_type) - print("self.inferred_type_", self.inferred_type) + # print("self.left.inferred_type", self.left.inferred_type) + # print("self.right.inferred_type", self.right.inferred_type) + # print("self.inferred_type_", self.inferred_type) # see: https://github.com/Minres/CoreDSL/wiki/Expressions#arithmetic-type-rules if self.op.value in ["+", "-", "*", "/", "%", "|", "&", "^", "<<", ">>"]: @@ -127,7 +127,7 @@ def binary_operation(self: behav.BinaryOperation, context): elif self.op.value in ["<", ">", "==", "!=", ">=", "<="]: self.inferred_type = arch.IntegerType(1, False, None) # unsigned<1> / bool # print("sit", self.inferred_type.width) - print("self.inferred_type", self.inferred_type) + # print("self.inferred_type", self.inferred_type) assert self.inferred_type is not None # input("!x!") @@ -135,7 +135,7 @@ def binary_operation(self: behav.BinaryOperation, context): def slice_operation(self: behav.SliceOperation, context): - print("slice_operation") + # print("slice_operation") self.expr = self.expr.generate(context) self.left = self.left.generate(context) self.right = self.right.generate(context) @@ -170,7 +170,7 @@ def slice_operation(self: behav.SliceOperation, context): def concat_operation(self: behav.ConcatOperation, context): - print("concat_coperation") + # print("concat_coperation") self.left = self.left.generate(context) self.right = self.right.generate(context) # print("self", self) @@ -194,7 +194,7 @@ def concat_operation(self: behav.ConcatOperation, context): def number_literal(self: behav.IntLiteral, context): - print("number_literal") + # print("number_literal") if isinstance(self, behav.IntLiteral): bit_size = self.bit_size signed = self.signed @@ -204,7 +204,7 @@ def number_literal(self: behav.IntLiteral, context): def int_literal(self: behav.IntLiteral, context): - print("int_literal") + # print("int_literal") # type inference bit_size = self.bit_size @@ -216,7 +216,7 @@ def int_literal(self: behav.IntLiteral, context): def scalar_definition(self: behav.ScalarDefinition, context): - print("scalar_definition") + # print("scalar_definition") # type inference # print("scalar_definition", self, dir(self), self.scalar, self.scalar.size, self.scalar.data_type) signed = self.scalar.data_type == arch.DataType.S @@ -226,7 +226,7 @@ def scalar_definition(self: behav.ScalarDefinition, context): def assignment(self: behav.Assignment, context): - print("assignment", self) + # print("assignment", self) # print("at_", self.target) # print("ae_", self.expr) self.target = self.target.generate(context) @@ -249,7 +249,7 @@ def assignment(self: behav.Assignment, context): def conditional(self: behav.Conditional, context): - print("conditional") + # print("conditional") self.conds = [x.generate(context) for x in self.conds] # self.stmts = [[y.generate(context) for y in x] for x in self.stmts] stmts = [] @@ -265,7 +265,7 @@ def conditional(self: behav.Conditional, context): def loop(self: behav.Loop, context): - print("loop") + # print("loop") self.cond = self.cond.generate(context) self.stmts = [x.generate(context) for x in self.stmts] @@ -273,7 +273,7 @@ def loop(self: behav.Loop, context): def ternary(self: behav.Ternary, context): - print("ternary") + # print("ternary") self.cond = self.cond.generate(context) self.then_expr = self.then_expr.generate(context) @@ -302,7 +302,7 @@ def ternary(self: behav.Ternary, context): def return_(self: behav.Return, context): - print("return_") + # print("return_") if self.expr is not None: self.expr = self.expr.generate(context) @@ -310,7 +310,7 @@ def return_(self: behav.Return, context): def unary_operation(self: behav.UnaryOperation, context): - print("unary_operation") + # print("unary_operation") self.right = self.right.generate(context) @@ -334,7 +334,7 @@ def unary_operation(self: behav.UnaryOperation, context): def named_reference(self: behav.NamedReference, context): - print("named_reference", self) + # print("named_reference", self) # print("dir", dir(self)) # print("self.reference", self.reference) reference = self.reference @@ -389,7 +389,7 @@ def named_reference(self: behav.NamedReference, context): def indexed_reference(self: behav.IndexedReference, context): - print("indexed_reference") + # print("indexed_reference") self.index = self.index.generate(context) # type inference @@ -407,9 +407,9 @@ def indexed_reference(self: behav.IndexedReference, context): def type_conv(self: behav.TypeConv, context): - print("type_conv") + # print("type_conv") self.expr = self.expr.generate(context) - print("self.expr", self.expr) + # print("self.expr", self.expr) ty = self.expr.inferred_type if ty is None: @@ -429,12 +429,12 @@ def type_conv(self: behav.TypeConv, context): def callable_(self: behav.Callable, context): - print("callable_") - print("self", self) - print("dir(self)", dir(self)) - print("self.ref_or_name", self.ref_or_name) + # print("callable_") + # print("self", self) + # print("dir(self)", dir(self)) + # print("self.ref_or_name", self.ref_or_name) if isinstance(self.ref_or_name, arch.Function): - print("dir(self.ref_or_name)", dir(self.ref_or_name)) + # print("dir(self.ref_or_name)", dir(self.ref_or_name)) assert self.ref_or_name.data_type in [arch.DataType.U, arch.DataType.S] signed = self.ref_or_name.data_type == arch.DataType.S width = self.ref_or_name.size @@ -446,14 +446,14 @@ def callable_(self: behav.Callable, context): def procedure_call(self: behav.ProcedureCall, context): - print("procedure_call") + # print("procedure_call") self.args = [stmt.generate(context) for stmt in self.args] return self def group(self: behav.Group, context): - print("group") + # print("group") self.expr = self.expr.generate(context) if isinstance(self.expr, behav.IntLiteral): @@ -466,5 +466,5 @@ def group(self: behav.Group, context): def break_(self: behav.Break, context): - print("break_") + # print("break_") return self From 0a15f88abc093de45816e686a1f313393e24420a Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Wed, 15 Apr 2026 17:38:00 +0200 Subject: [PATCH 35/62] infer_types: cleanup prints --- m2isar/transforms/infer_types/visitor.py | 103 ----------------------- 1 file changed, 103 deletions(-) diff --git a/m2isar/transforms/infer_types/visitor.py b/m2isar/transforms/infer_types/visitor.py index e2aee579..948c380e 100644 --- a/m2isar/transforms/infer_types/visitor.py +++ b/m2isar/transforms/infer_types/visitor.py @@ -31,34 +31,21 @@ def operation(self: behav.Operation, context): - # print("operation", operation) statements = [] for stmt in self.statements: - # try: temp = stmt.generate(context) if isinstance(temp, list): statements.extend(temp) else: statements.append(temp) - # except (NotImplementedError, ValueError): - # print(f"cant simplify {stmt}") self.statements = statements return self def binary_operation(self: behav.BinaryOperation, context): - # print("binary_operation") - # print("self.op.value", self.op.value) - self.left = self.left.generate(context) self.right = self.right.generate(context) - # print("self.left", self.left) - # print("self.right", self.right) - - # print("self.left.inferred_type", self.left.inferred_type) - # print("self.right.inferred_type", self.right.inferred_type) - # print("self.inferred_type_", self.inferred_type) # see: https://github.com/Minres/CoreDSL/wiki/Expressions#arithmetic-type-rules if self.op.value in ["+", "-", "*", "/", "%", "|", "&", "^", "<<", ">>"]: @@ -126,23 +113,15 @@ def binary_operation(self: behav.BinaryOperation, context): self.inferred_type = arch.IntegerType(1, False, None) # unsigned<1> / bool elif self.op.value in ["<", ">", "==", "!=", ">=", "<="]: self.inferred_type = arch.IntegerType(1, False, None) # unsigned<1> / bool - # print("sit", self.inferred_type.width) - # print("self.inferred_type", self.inferred_type) assert self.inferred_type is not None - # input("!x!") return self def slice_operation(self: behav.SliceOperation, context): - # print("slice_operation") self.expr = self.expr.generate(context) self.left = self.left.generate(context) self.right = self.right.generate(context) - # print("self.expr", self.expr) - # print("self.left", self.left) - # print("self.right", self.right) - # input("slice") # type inference if self.expr.inferred_type is None: @@ -163,23 +142,13 @@ def slice_operation(self: behav.SliceOperation, context): ty_ = copy(ty) ty_._width = width self.inferred_type = ty_ - # print("sit", ty) - # input("*") return self def concat_operation(self: behav.ConcatOperation, context): - # print("concat_coperation") self.left = self.left.generate(context) self.right = self.right.generate(context) - # print("self", self) - # print("dir(self)", dir(self)) - # print("self.left", self.left) - # print("dir(self.left)", dir(self.left)) - # print("self.right", self.right) - # print("dir(self.right)", dir(self.right)) - # input("!") if self.left.inferred_type is None: logger.warning("Concat Operation needs inferred type. Skipping...") return self @@ -194,7 +163,6 @@ def concat_operation(self: behav.ConcatOperation, context): def number_literal(self: behav.IntLiteral, context): - # print("number_literal") if isinstance(self, behav.IntLiteral): bit_size = self.bit_size signed = self.signed @@ -204,7 +172,6 @@ def number_literal(self: behav.IntLiteral, context): def int_literal(self: behav.IntLiteral, context): - # print("int_literal") # type inference bit_size = self.bit_size @@ -216,9 +183,7 @@ def int_literal(self: behav.IntLiteral, context): def scalar_definition(self: behav.ScalarDefinition, context): - # print("scalar_definition") # type inference - # print("scalar_definition", self, dir(self), self.scalar, self.scalar.size, self.scalar.data_type) signed = self.scalar.data_type == arch.DataType.S width = self.scalar.size self.inferred_type = arch.IntegerType(width, signed, None) @@ -226,9 +191,6 @@ def scalar_definition(self: behav.ScalarDefinition, context): def assignment(self: behav.Assignment, context): - # print("assignment", self) - # print("at_", self.target) - # print("ae_", self.expr) self.target = self.target.generate(context) self.expr = self.expr.generate(context) @@ -236,20 +198,12 @@ def assignment(self: behav.Assignment, context): # self.target.scalar.value = self.expr.value # type inference - # print("at", self.target) - # print("ae", self.expr) - # print("at1", self.target.inferred_type) - # print("ae1", self.expr.inferred_type) - # print("at2", self.target.inferred_type.width) - # print("ae2", self.expr.inferred_type.width) - # input("ccc") self.inferred_type = None return self def conditional(self: behav.Conditional, context): - # print("conditional") self.conds = [x.generate(context) for x in self.conds] # self.stmts = [[y.generate(context) for y in x] for x in self.stmts] stmts = [] @@ -265,7 +219,6 @@ def conditional(self: behav.Conditional, context): def loop(self: behav.Loop, context): - # print("loop") self.cond = self.cond.generate(context) self.stmts = [x.generate(context) for x in self.stmts] @@ -273,18 +226,11 @@ def loop(self: behav.Loop, context): def ternary(self: behav.Ternary, context): - # print("ternary") self.cond = self.cond.generate(context) self.then_expr = self.then_expr.generate(context) self.else_expr = self.else_expr.generate(context) - # print("ste", self.then_expr) - # print("see", self.else_expr) - # print("ste1", self.then_expr.inferred_type) - # print("see1", self.else_expr.inferred_type) - # print("ste2", self.then_expr.inferred_type.width) - # print("see2", self.else_expr.inferred_type.width) # TODO then_ty = self.then_expr.inferred_type else_ty = self.else_expr.inferred_type @@ -293,16 +239,12 @@ def ternary(self: behav.Ternary, context): wt = then_ty.width we = else_ty.width wr = max(wt, we) - # print("wr", wr) - # input("o") self.inferred_type = arch.IntegerType(wr, True, None) - # input("ppp") return self def return_(self: behav.Return, context): - # print("return_") if self.expr is not None: self.expr = self.expr.generate(context) @@ -310,14 +252,9 @@ def return_(self: behav.Return, context): def unary_operation(self: behav.UnaryOperation, context): - # print("unary_operation") self.right = self.right.generate(context) - # print("sr", self.right) - # print("sr1", self.right.inferred_type) - # print("sr2", self.right.inferred_type.width) - # input("!") if self.right.inferred_type: w1 = self.right.inferred_type.width if self.op.value == "-": @@ -334,24 +271,16 @@ def unary_operation(self: behav.UnaryOperation, context): def named_reference(self: behav.NamedReference, context): - # print("named_reference", self) - # print("dir", dir(self)) - # print("self.reference", self.reference) reference = self.reference # type inference # self.infered_type = ? if isinstance(reference, arch.BitFieldDescr): - # print("BITFIELD", reference) - # print("self.reference", self.reference) - # print("self.reference.data_type", self.reference.data_type) assert self.reference.data_type in [arch.DataType.U, arch.DataType.S] ty = arch.IntegerType(reference.size, reference.data_type == arch.DataType.S, None) - # print("ty", ty) self.inferred_type = ty elif isinstance(reference, arch.Scalar): - # print("SCALAR", reference) dt = reference.data_type sz = reference.size assert dt in [arch.DataType.U, arch.DataType.S] @@ -359,37 +288,19 @@ def named_reference(self: behav.NamedReference, context): ty = arch.IntegerType(sz, signed, None) self.inferred_type = ty elif isinstance(reference, arch.Memory): - # print("MEMORY", reference) self.inferred_type = arch.IntegerType(reference.size, False, None) - # print("dir(reference)", dir(reference)) - # input("%%%%%") elif isinstance(reference, arch.Intrinsic): - # print("INTRIN", reference) - # print("dir(reference)", dir(reference)) - # print("reference.size", reference.size) - # print("reference.data_type", reference.data_type) assert self.reference.data_type in [arch.DataType.U, arch.DataType.S] self.inferred_type = arch.IntegerType(reference.size, reference.data_type == arch.DataType.S, None) elif isinstance(reference, arch.Constant): - # print("CONST", reference) - # print("dir(reference)", dir(reference)) - # print("reference.size", reference.size) - # print("reference.signed", reference.signed) self.inferred_type = arch.IntegerType(reference.size, reference.signed, None) else: - # print("ELSE", reference) - # print("reference.size", reference.size) - # print("reference.data_type", reference.data_type) assert False, "Unhandled reference" - # print("self.inferred_type", self.inferred_type) - # input("222") - return self def indexed_reference(self: behav.IndexedReference, context): - # print("indexed_reference") self.index = self.index.generate(context) # type inference @@ -400,21 +311,16 @@ def indexed_reference(self: behav.IndexedReference, context): ty_ = arch.IntegerType(size, ty == arch.DataType.S, None) self.inferred_type = ty_ - # print("self.inferred_type", self.inferred_type) - # input("111") return self def type_conv(self: behav.TypeConv, context): - # print("type_conv") self.expr = self.expr.generate(context) - # print("self.expr", self.expr) ty = self.expr.inferred_type if ty is None: logger.warning("Type conv needs inferred type. Skipping...") - input("!!@@") return self assert isinstance(ty, arch.IntegerType) assert self.data_type in [arch.DataType.U, arch.DataType.S] @@ -429,31 +335,23 @@ def type_conv(self: behav.TypeConv, context): def callable_(self: behav.Callable, context): - # print("callable_") - # print("self", self) - # print("dir(self)", dir(self)) - # print("self.ref_or_name", self.ref_or_name) if isinstance(self.ref_or_name, arch.Function): - # print("dir(self.ref_or_name)", dir(self.ref_or_name)) assert self.ref_or_name.data_type in [arch.DataType.U, arch.DataType.S] signed = self.ref_or_name.data_type == arch.DataType.S width = self.ref_or_name.size self.inferred_type = arch.IntegerType(width, signed, None) - # print("self.inferred_type", self.inferred_type) self.args = [stmt.generate(context) for stmt in self.args] return self def procedure_call(self: behav.ProcedureCall, context): - # print("procedure_call") self.args = [stmt.generate(context) for stmt in self.args] return self def group(self: behav.Group, context): - # print("group") self.expr = self.expr.generate(context) if isinstance(self.expr, behav.IntLiteral): @@ -466,5 +364,4 @@ def group(self: behav.Group, context): def break_(self: behav.Break, context): - # print("break_") return self From 72bbcf0eccd5c18d2101e911b81dbdc6b3eab036 Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Mon, 13 Apr 2026 08:57:16 +0200 Subject: [PATCH 36/62] handle ENABLE attribute in parser (before duplicate detection) --- .../coredsl2/architecture_model_builder.py | 20 ++++++---- m2isar/frontends/coredsl2/parser.py | 39 +++++++++++++++++-- m2isar/metamodel/arch.py | 30 ++++++++++---- 3 files changed, 71 insertions(+), 18 deletions(-) diff --git a/m2isar/frontends/coredsl2/architecture_model_builder.py b/m2isar/frontends/coredsl2/architecture_model_builder.py index 9028d606..3739fd03 100644 --- a/m2isar/frontends/coredsl2/architecture_model_builder.py +++ b/m2isar/frontends/coredsl2/architecture_model_builder.py @@ -25,7 +25,7 @@ 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]" @@ -39,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 = {} @@ -85,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: @@ -97,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 @@ -173,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 diff --git a/m2isar/frontends/coredsl2/parser.py b/m2isar/frontends/coredsl2/parser.py index d9f79054..f18b5b0c 100644 --- a/m2isar/frontends/coredsl2/parser.py +++ b/m2isar/frontends/coredsl2/parser.py @@ -15,6 +15,7 @@ from ... import M2Error, M2SyntaxError from ...metamodel import M2_METAMODEL_VERSION, M2Model, arch, behav +from ...metamodel.utils import expr_simplifier from ...metamodel.code_info import CodeInfoBase from .architecture_model_builder import ArchitectureModelBuilder from .behavior_model_builder import BehaviorModelBuilder @@ -26,6 +27,14 @@ 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]"): + patch_model(expr_simplifier) + op = operation.generate(None) + if not isinstance(op, behav.IntLiteral): + return None + return op.value != 0 + def main(): parser = argparse.ArgumentParser() @@ -80,8 +89,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] @@ -207,7 +216,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") @@ -224,6 +237,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) @@ -253,6 +277,15 @@ 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, diff --git a/m2isar/metamodel/arch.py b/m2isar/metamodel/arch.py index cb2cb9fd..8a3246dd 100644 --- a/m2isar/metamodel/arch.py +++ b/m2isar/metamodel/arch.py @@ -568,7 +568,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 @@ -589,13 +589,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 @@ -617,3 +613,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 From 30eb9053acf033c7959d2dfdf7a2696714ce4782 Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Mon, 13 Apr 2026 08:58:19 +0200 Subject: [PATCH 37/62] resolve IntLiteral to int in metamodel --- .../backends/etiss/instruction_transform.py | 4 +-- m2isar/metamodel/arch.py | 12 +++++-- m2isar/metamodel/behav.py | 36 +++++++++++++++---- 3 files changed, 40 insertions(+), 12 deletions(-) diff --git a/m2isar/backends/etiss/instruction_transform.py b/m2isar/backends/etiss/instruction_transform.py index e2342046..7656bb2d 100644 --- a/m2isar/backends/etiss/instruction_transform.py +++ b/m2isar/backends/etiss/instruction_transform.py @@ -517,8 +517,8 @@ def _(self, expr: behav.TypeConv, context: TransformerContext): expr.data_type = arch.DataType.S if expr_str.signed else arch.DataType.U if expr.size is None: - expr.size = expr_str.size - expr.actual_size = expr_str.actual_size + expr._size = expr_str.size + expr._actual_size = expr_str.actual_size if expr_str.is_mem_access: if not expr_str.mem_corrected and expr_str.mem_ids[-1].access_size != expr.size: diff --git a/m2isar/metamodel/arch.py b/m2isar/metamodel/arch.py index 8a3246dd..6761b164 100644 --- a/m2isar/metamodel/arch.py +++ b/m2isar/metamodel/arch.py @@ -19,7 +19,7 @@ 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 @@ -29,8 +29,11 @@ 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 exprInterpretVisitor.generate(arg, None) + arg = exprInterpretVisitor.generate(arg, None) return arg @@ -68,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): diff --git a/m2isar/metamodel/behav.py b/m2isar/metamodel/behav.py index 4f745786..f8c392db 100644 --- a/m2isar/metamodel/behav.py +++ b/m2isar/metamodel/behav.py @@ -96,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.""" @@ -113,6 +120,9 @@ 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) if signed is None: @@ -123,6 +133,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""" @@ -225,15 +238,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.""" From 8177e2b8aeaf7bbfc4a73c87d2e8c396b392ad10 Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Mon, 13 Apr 2026 08:58:56 +0200 Subject: [PATCH 38/62] pyproject.toml: fix for new setuptools --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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", From 36bedfdc242c8ea59cf4c697db25b7b2a7ce7157 Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Mon, 13 Apr 2026 09:41:47 +0200 Subject: [PATCH 39/62] m2isar/backends/etiss/instruction_transform.py: handle list of stmts in etiss instruction_transform conditional --- m2isar/backends/etiss/instruction_transform.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/m2isar/backends/etiss/instruction_transform.py b/m2isar/backends/etiss/instruction_transform.py index 7656bb2d..7c6d428e 100644 --- a/m2isar/backends/etiss/instruction_transform.py +++ b/m2isar/backends/etiss/instruction_transform.py @@ -406,7 +406,13 @@ def _(self, expr: behav.Conditional, context: TransformerContext): cond.mem_ids.clear() for stmt in expr.stmts: - ret = self.generate(stmt, context) + if isinstance(stmt, list): + ret = [] + for stmt_ in stmt: + ret_ = self.generate(stmt_, context) + ret.append(ret_) + else: + ret = self.generate(stmt, context) if isinstance(ret, list): stmts.append(ret) From 0b71172856c4e345f43c3e08f39f549f29031383 Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Thu, 16 Apr 2026 15:01:45 +0200 Subject: [PATCH 40/62] m2isar/frontends/coredsl2/parser.py: fixup try_eval_bool --- m2isar/frontends/coredsl2/parser.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/m2isar/frontends/coredsl2/parser.py b/m2isar/frontends/coredsl2/parser.py index f18b5b0c..853cabbc 100644 --- a/m2isar/frontends/coredsl2/parser.py +++ b/m2isar/frontends/coredsl2/parser.py @@ -15,7 +15,7 @@ from ... import M2Error, M2SyntaxError from ...metamodel import M2_METAMODEL_VERSION, M2Model, arch, behav -from ...metamodel.utils import expr_simplifier +from ...metamodel.utils.expr_simplifier import ExprSimplifierVisitor from ...metamodel.code_info import CodeInfoBase from .architecture_model_builder import ArchitectureModelBuilder from .behavior_model_builder import BehaviorModelBuilder @@ -29,8 +29,9 @@ 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]"): - patch_model(expr_simplifier) - op = operation.generate(None) + simplifier = ExprSimplifierVisitor() + # TODO: switch to ExprInterpreterVisitor? + op = simplifier.generate(operation, None) if not isinstance(op, behav.IntLiteral): return None return op.value != 0 From ab300ec7fde3f3653ad8de9dc84861daaeaf65e2 Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Thu, 16 Apr 2026 13:04:44 +0200 Subject: [PATCH 41/62] m2isar/metamodel/utils/ExprVisitor.py: change default implementation to return mutated expr --- m2isar/metamodel/utils/ExprVisitor.py | 95 ++++++++++++++++++--------- 1 file changed, 64 insertions(+), 31 deletions(-) diff --git a/m2isar/metamodel/utils/ExprVisitor.py b/m2isar/metamodel/utils/ExprVisitor.py index 1256c809..a6a66375 100644 --- a/m2isar/metamodel/utils/ExprVisitor.py +++ b/m2isar/metamodel/utils/ExprVisitor.py @@ -34,101 +34,134 @@ def default_visit(self, expr: behav.BaseNode, context): @default_visit.register def visit_codeliteral(self, expr: behav.CodeLiteral, context): - pass + return expr @default_visit.register def visit_operator(self, expr: behav.Operator, context): - pass + return expr @default_visit.register def visit_operation(self, expr: behav.Operation, context): + statements = [] for stmt in expr.statements: - stmt.generate(context) + stmt = stmt.generate(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.generate(context) + stmt = stmt.generate(context) + statements.append(stmt) + expr.statements = statements + return expr @default_visit.register def visit_binary_operation(self, expr: behav.BinaryOperation, context): - expr.left.generate(context) - expr.right.generate(context) + expr.left = expr.left.generate(context) + expr.right = expr.right.generate(context) + return expr @default_visit.register def visit_slice_operation(self, expr: behav.SliceOperation, context): - expr.expr.generate(context) - expr.left.generate(context) - expr.right.generate(context) + expr.expr = expr.expr.generate(context) + expr.left = expr.left.generate(context) + expr.right = expr.right.generate(context) + return expr @default_visit.register def visit_concat_operation(self, expr: behav.ConcatOperation, context): - expr.left.generate(context) - expr.right.generate(context) + expr.left = expr.left.generate(context) + expr.right = expr.right.generate(context) + return expr @default_visit.register def visit_number_literal(self, expr: behav.NumberLiteral, context): - pass + return expr @default_visit.register def visit_int_literal(self, expr: behav.IntLiteral, context): - pass + return expr @default_visit.register def visit_string_literal(self, expr: behav.StringLiteral, context): - pass + return expr @default_visit.register def visit_assignment(self, expr: behav.Assignment, context): - expr.target.generate(context) - expr.expr.generate(context) + expr_target = expr.target.generate(context) + expr.expr = expr.expr.generate(context) + return expr @default_visit.register def visit_conditional(self, expr: behav.Conditional, context): + conds = [] for cond in expr.conds: - cond.generate(context) + cond = cond.generate(context) + conds.append(cond) + expr.conds = conds + stmts = [] for stmt in expr.stmts: - stmt.generate(context) + smts = stmt.generate(context) + stmts.append(stmt) + expr.stmts = stmts + return expr @default_visit.register def visit_loop(self, expr: behav.Loop, context): - expr.cond.generate(context) + expr.cond = expr.cond.generate(context) + stmts = [] for stmt in expr.stmts: - stmt.generate(context) + stmt = stmt.generate(context) + stmts.append(stmt) + expr.stmts = stmts + return expr - # TODO: Add more visit methods for other node types as needed, e.g., Ternary, etc. @default_visit.register def visit_ternary_operation(self, expr: behav.Ternary, context): - expr.cond.generate(context) - expr.then_expr.generate(context) - expr.else_expr.generate(context) + expr.cond = expr.cond.generate(context) + expr.then_expr = expr.then_expr.generate(context) + expr.else_expr = expr.else_expr.generate(context) + return expr @default_visit.register def visit_scalar_definition(self, expr: behav.ScalarDefinition, context): - pass + return expr @default_visit.register def visit_break(self, expr: behav.Break, context): - pass + return expr @default_visit.register def visit_named_reference(self, expr: behav.NamedReference, context): - pass + return expr @default_visit.register def visit_type_conv(self, expr: behav.TypeConv, context): - expr.expr.generate(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.generate(context) + arg = arg.generate(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.generate(context) + arg = arg.generate(context) + args.append(arg) + epxr.args = args + return expr @default_visit.register def visit_group(self, expr: behav.Group, context): - expr.expr.generate(context) + expr.expr = expr.expr.generate(context) + return expr From f5c212f253e7d0bb0c8670d7ca3992c7be8a79d1 Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Thu, 16 Apr 2026 13:05:13 +0200 Subject: [PATCH 42/62] infer_types: port to new visitor --- m2isar/transforms/infer_types/transform.py | 11 +- m2isar/transforms/infer_types/visitor.py | 637 +++++++++++---------- 2 files changed, 331 insertions(+), 317 deletions(-) diff --git a/m2isar/transforms/infer_types/transform.py b/m2isar/transforms/infer_types/transform.py index 7707bb8c..1fc7066d 100644 --- a/m2isar/transforms/infer_types/transform.py +++ b/m2isar/transforms/infer_types/transform.py @@ -13,9 +13,9 @@ import logging import pathlib -from m2isar.metamodel import patch_model, load_model, dump_model +from m2isar.metamodel import load_model, dump_model -from . import visitor +from .visitor import InferTypesVisitor def get_parser(): @@ -31,16 +31,15 @@ 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) - patch_model(visitor) + visitor = InferTypesVisitor() for _, instr_def in core_def.instructions.items(): logger.debug("inferring types for instr %s", instr_def.name) - instr_def.operation.generate(None) + visitor.generate(instr_def.operation, None) for _, set_def in model_obj.sets.items(): logger.debug("inferring types for set %s", set_def.name) - patch_model(visitor) for _, instr_def in set_def.instructions.items(): logger.debug("inferring types for instr %s", instr_def.name) - instr_def.operation.generate(None) + visitor.generate(instr_def.operation, None) return model_obj diff --git a/m2isar/transforms/infer_types/visitor.py b/m2isar/transforms/infer_types/visitor.py index 948c380e..e068a968 100644 --- a/m2isar/transforms/infer_types/visitor.py +++ b/m2isar/transforms/infer_types/visitor.py @@ -22,346 +22,361 @@ 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("infer_types") # 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) - - self.statements = statements - return self - - -def binary_operation(self: behav.BinaryOperation, context): - self.left = self.left.generate(context) - self.right = self.right.generate(context) - - # see: https://github.com/Minres/CoreDSL/wiki/Expressions#arithmetic-type-rules - if self.op.value in ["+", "-", "*", "/", "%", "|", "&", "^", "<<", ">>"]: - if self.left.inferred_type is None or self.right.inferred_type is None: - logger.warning("Slice Operation needs inferred type. Skipping...") - self.inferred_type = None - return self - assert isinstance(self.left.inferred_type, arch.IntegerType) - assert isinstance(self.right.inferred_type, arch.IntegerType) - w1 = self.left.inferred_type._width - w2 = self.right.inferred_type._width - s1 = self.left.inferred_type.signed - s2 = self.right.inferred_type.signed - if self.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 +class InferTypesVisitor(ExprVisitor): + """Visitor 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 - elif self.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 self.op.value == "*": - wr = w1 + w2 - sr = s1 or s2 - elif self.op.value == "/": - wr = w1 if not s2 else (w1 + 1) - sr = s1 or s2 - elif self.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 self.op.value in ["|", "&", "^"]: - wr = max(w1, w2) - sr = s1 or s2 - elif self.op.value in [">>", "<<"]: - wr = w1 - sr = s1 - self.inferred_type = arch.IntegerType(wr, sr, None) - else: - if self.op.value in ["||", "&&"]: - self.inferred_type = arch.IntegerType(1, False, None) # unsigned<1> / bool - elif self.op.value in ["<", ">", "==", "!=", ">=", "<="]: - self.inferred_type = arch.IntegerType(1, False, None) # unsigned<1> / bool - assert self.inferred_type is not None - - 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) - - # type inference - if self.expr.inferred_type is None: - logger.warning("Slice Operation needs inferred type. Skipping...") - return self - assert isinstance(self.expr.inferred_type, arch.IntegerType) - ty = self.expr.inferred_type - # For non-static slices, we cann not infer the type! - if not isinstance(self.left, behav.IntLiteral): - logger.warning("Can not infer type of non-static slice operation. Skipping...") - return self - lval = self.left.value - if not isinstance(self.right, behav.IntLiteral): - logger.warning("Can not infer type of non-static slice operation. Skipping...") - return self - rval = self.right.value - width = lval - rval + 1 if lval > rval else rval - lval + 1 - ty_ = copy(ty) - ty_._width = width - self.inferred_type = ty_ - - return self - - -def concat_operation(self: behav.ConcatOperation, context): - self.left = self.left.generate(context) - self.right = self.right.generate(context) - if self.left.inferred_type is None: - logger.warning("Concat Operation needs inferred type. Skipping...") - return self - if self.right.inferred_type is None: - logger.warning("Concat Operation needs inferred type. Skipping...") - return self - width = self.left.inferred_type.width + self.right.inferred_type.width - ty = arch.IntegerType(width, False, None) - self.inferred_type = ty - - return self - - -def number_literal(self: behav.IntLiteral, context): - if isinstance(self, behav.IntLiteral): - bit_size = self.bit_size - signed = self.signed - - self.inferred_type = arch.IntegerType(bit_size, signed, None) - return self - - -def int_literal(self: behav.IntLiteral, context): - - # type inference - bit_size = self.bit_size - signed = self.signed - - self.inferred_type = arch.IntegerType(bit_size, signed, None) - - return self - - -def scalar_definition(self: behav.ScalarDefinition, context): - # type inference - signed = self.scalar.data_type == arch.DataType.S - width = self.scalar.size - self.inferred_type = arch.IntegerType(width, signed, None) - 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.ScalarDefinition): - # self.target.scalar.value = self.expr.value - - # type inference - self.inferred_type = None - - return self - - -def conditional(self: behav.Conditional, context): - self.conds = [x.generate(context) for x in self.conds] - # self.stmts = [[y.generate(context) for y in x] for x in self.stmts] - stmts = [] - for stmt in self.stmts: - if isinstance(stmt, list): # TODO: legacy? - new = [y.generate(context) for y in stmt] + 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: - new = stmt.generate(context) - stmts.append(new) - self.stmts = stmts - - return self - - -def loop(self: behav.Loop, context): - self.cond = self.cond.generate(context) - self.stmts = [x.generate(context) for x in self.stmts] - - return self - - -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) + 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 - # TODO - then_ty = self.then_expr.inferred_type - else_ty = self.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) - self.inferred_type = arch.IntegerType(wr, True, None) + return expr - return self + @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) -def return_(self: behav.Return, context): - if self.expr is not None: - self.expr = self.expr.generate(context) - - return self - - -def unary_operation(self: behav.UnaryOperation, context): - - self.right = self.right.generate(context) - - if self.right.inferred_type: - w1 = self.right.inferred_type.width - if self.op.value == "-": - inferred_type = arch.IntegerType(w1 + 1, True, None) - elif self.op.value == "~": - inferred_type = arch.IntegerType(w1, True, None) - elif self.op.value == "!": - inferred_type = arch.IntegerType(1, False, None) + # 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: - inferred_type = None - self.inferred_type = inferred_type - - return self - - -def named_reference(self: behav.NamedReference, context): - reference = self.reference - - # type inference - # self.infered_type = ? - if isinstance(reference, arch.BitFieldDescr): - assert self.reference.data_type in [arch.DataType.U, arch.DataType.S] - ty = arch.IntegerType(reference.size, reference.data_type == arch.DataType.S, None) - self.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) - self.inferred_type = ty - elif isinstance(reference, arch.Memory): - self.inferred_type = arch.IntegerType(reference.size, False, None) - elif isinstance(reference, arch.Intrinsic): - assert self.reference.data_type in [arch.DataType.U, arch.DataType.S] - self.inferred_type = arch.IntegerType(reference.size, reference.data_type == arch.DataType.S, None) - elif isinstance(reference, arch.Constant): - self.inferred_type = arch.IntegerType(reference.size, reference.signed, None) - else: - assert False, "Unhandled reference" - - return self - - -def indexed_reference(self: behav.IndexedReference, context): - self.index = self.index.generate(context) - - # type inference - assert isinstance(self.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] - size = self.reference.size - ty_ = arch.IntegerType(size, ty == arch.DataType.S, None) - - self.inferred_type = ty_ - - return self + assert False, "Unhandled reference" + return expr -def type_conv(self: behav.TypeConv, context): - self.expr = self.expr.generate(context) + @generate.register + def _(self, expr: behav.IndexedReference, context): + expr.index = self.generate(expr.index, context) - ty = self.expr.inferred_type - if ty is None: - logger.warning("Type conv needs inferred type. Skipping...") - return self - assert isinstance(ty, arch.IntegerType) - assert self.data_type in [arch.DataType.U, arch.DataType.S] - ty.signed = self.data_type == arch.DataType.S - if self.size is not None: - ty._width = self.size + # 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] + size = expr.reference.size + ty_ = arch.IntegerType(size, ty == arch.DataType.S, None) - # type inference - self.inferred_type = ty + expr.inferred_type = ty_ - return self + return expr + @generate.register + def _(self, expr: behav.TypeConv, context): + expr.expr = self.generate(expr.expr, context) -def callable_(self: behav.Callable, context): - if isinstance(self.ref_or_name, arch.Function): - assert self.ref_or_name.data_type in [arch.DataType.U, arch.DataType.S] - signed = self.ref_or_name.data_type == arch.DataType.S - width = self.ref_or_name.size - self.inferred_type = arch.IntegerType(width, signed, None) - self.args = [stmt.generate(context) for stmt in self.args] + ty = 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 - return self + # type inference + expr.inferred_type = ty + 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.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 self + 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] -def group(self: behav.Group, context): - self.expr = self.expr.generate(context) + # return expr - if isinstance(self.expr, behav.IntLiteral): - return self.expr + @generate.register + def _(self, expr: behav.Group, context): + expr.expr = self.generate(expr.expr, context) - # type inference - self.inferred_type = self.expr.inferred_type + if isinstance(expr.expr, behav.IntLiteral): + return expr.expr - return self + # type inference + expr.inferred_type = expr.expr.inferred_type + return expr -def break_(self: behav.Break, context): - return self + @generate.register + def _(self, expr: behav.Break, context): + return expr From af30cf303313e81fe632ee26389b84819e7c8b7e Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Thu, 16 Apr 2026 13:21:09 +0200 Subject: [PATCH 43/62] restore ExprVisitor and add ExprMutator --- m2isar/metamodel/utils/ExprMutator.py | 167 +++++++++++++++++++++ m2isar/metamodel/utils/ExprVisitor.py | 94 ++++-------- m2isar/transforms/infer_types/transform.py | 8 +- m2isar/transforms/infer_types/visitor.py | 6 +- 4 files changed, 204 insertions(+), 71 deletions(-) create mode 100644 m2isar/metamodel/utils/ExprMutator.py diff --git a/m2isar/metamodel/utils/ExprMutator.py b/m2isar/metamodel/utils/ExprMutator.py new file mode 100644 index 00000000..4249d51e --- /dev/null +++ b/m2isar/metamodel/utils/ExprMutator.py @@ -0,0 +1,167 @@ +# 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 = stmt.generate(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 = stmt.generate(context) + statements.append(stmt) + expr.statements = statements + return expr + + @default_visit.register + def visit_binary_operation(self, expr: behav.BinaryOperation, context): + expr.left = expr.left.generate(context) + expr.right = expr.right.generate(context) + return expr + + @default_visit.register + def visit_slice_operation(self, expr: behav.SliceOperation, context): + expr.expr = expr.expr.generate(context) + expr.left = expr.left.generate(context) + expr.right = expr.right.generate(context) + return expr + + @default_visit.register + def visit_concat_operation(self, expr: behav.ConcatOperation, context): + expr.left = expr.left.generate(context) + expr.right = expr.right.generate(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 = expr.target.generate(context) + expr.expr = expr.expr.generate(context) + return expr + + @default_visit.register + def visit_conditional(self, expr: behav.Conditional, context): + conds = [] + for cond in expr.conds: + cond = cond.generate(context) + conds.append(cond) + expr.conds = conds + stmts = [] + for stmt in expr.stmts: + smts = stmt.generate(context) + stmts.append(stmt) + expr.stmts = stmts + return expr + + @default_visit.register + def visit_loop(self, expr: behav.Loop, context): + expr.cond = expr.cond.generate(context) + stmts = [] + for stmt in expr.stmts: + stmt = stmt.generate(context) + stmts.append(stmt) + expr.stmts = stmts + return expr + + @default_visit.register + def visit_ternary_operation(self, expr: behav.Ternary, context): + expr.cond = expr.cond.generate(context) + expr.then_expr = expr.then_expr.generate(context) + expr.else_expr = expr.else_expr.generate(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_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 = arg.generate(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 = arg.generate(context) + args.append(arg) + epxr.args = args + return expr + + @default_visit.register + def visit_group(self, expr: behav.Group, context): + expr.expr = expr.expr.generate(context) + return expr diff --git a/m2isar/metamodel/utils/ExprVisitor.py b/m2isar/metamodel/utils/ExprVisitor.py index a6a66375..43619d97 100644 --- a/m2isar/metamodel/utils/ExprVisitor.py +++ b/m2isar/metamodel/utils/ExprVisitor.py @@ -34,134 +34,100 @@ def default_visit(self, expr: behav.BaseNode, context): @default_visit.register def visit_codeliteral(self, expr: behav.CodeLiteral, context): - return expr + pass @default_visit.register def visit_operator(self, expr: behav.Operator, context): - return expr + pass @default_visit.register def visit_operation(self, expr: behav.Operation, context): - statements = [] for stmt in expr.statements: - stmt = stmt.generate(context) - statements.append(stmt) - expr.statements = statements - return expr + stmt.generate(context) @default_visit.register def visit_block(self, expr: behav.Block, context): - statements = [] for stmt in expr.statements: - stmt = stmt.generate(context) - statements.append(stmt) - expr.statements = statements - return expr + stmt.generate(context) @default_visit.register def visit_binary_operation(self, expr: behav.BinaryOperation, context): - expr.left = expr.left.generate(context) - expr.right = expr.right.generate(context) - return expr + expr.left.generate(context) + expr.right.generate(context) @default_visit.register def visit_slice_operation(self, expr: behav.SliceOperation, context): - expr.expr = expr.expr.generate(context) - expr.left = expr.left.generate(context) - expr.right = expr.right.generate(context) - return expr + expr.expr.generate(context) + expr.left.generate(context) + expr.right.generate(context) @default_visit.register def visit_concat_operation(self, expr: behav.ConcatOperation, context): - expr.left = expr.left.generate(context) - expr.right = expr.right.generate(context) - return expr + expr.left.generate(context) + expr.right.generate(context) @default_visit.register def visit_number_literal(self, expr: behav.NumberLiteral, context): - return expr + pass @default_visit.register def visit_int_literal(self, expr: behav.IntLiteral, context): - return expr + pass @default_visit.register def visit_string_literal(self, expr: behav.StringLiteral, context): - return expr + pass @default_visit.register def visit_assignment(self, expr: behav.Assignment, context): - expr_target = expr.target.generate(context) - expr.expr = expr.expr.generate(context) - return expr + expr.target.generate(context) + expr.expr.generate(context) @default_visit.register def visit_conditional(self, expr: behav.Conditional, context): - conds = [] for cond in expr.conds: - cond = cond.generate(context) - conds.append(cond) - expr.conds = conds - stmts = [] + cond.generate(context) for stmt in expr.stmts: - smts = stmt.generate(context) - stmts.append(stmt) - expr.stmts = stmts - return expr + stmt.generate(context) @default_visit.register def visit_loop(self, expr: behav.Loop, context): - expr.cond = expr.cond.generate(context) - stmts = [] + expr.cond.generate(context) for stmt in expr.stmts: - stmt = stmt.generate(context) - stmts.append(stmt) - expr.stmts = stmts - return expr + stmt.generate(context) @default_visit.register def visit_ternary_operation(self, expr: behav.Ternary, context): - expr.cond = expr.cond.generate(context) - expr.then_expr = expr.then_expr.generate(context) - expr.else_expr = expr.else_expr.generate(context) - return expr + expr.cond.generate(context) + expr.then_expr.generate(context) + expr.else_expr.generate(context) @default_visit.register def visit_scalar_definition(self, expr: behav.ScalarDefinition, context): - return expr + pass @default_visit.register def visit_break(self, expr: behav.Break, context): - return expr + pass @default_visit.register def visit_named_reference(self, expr: behav.NamedReference, context): - return expr + pass @default_visit.register def visit_type_conv(self, expr: behav.TypeConv, context): - expr.expr = expr.expr.generate(context) - return expr + expr.expr.generate(context) @default_visit.register def visit_callable(self, expr: behav.Callable, context): - args = [] for arg in expr.args: - arg = arg.generate(context) - args.append(arg) - epxr.args = args - return expr + arg.generate(context) @default_visit.register def visit_procedure_call(self, expr: behav.Callable, context): - args = [] for arg in expr.args: - arg = arg.generate(context) - args.append(arg) - epxr.args = args - return expr + arg.generate(context) @default_visit.register def visit_group(self, expr: behav.Group, context): - expr.expr = expr.expr.generate(context) - return expr + expr.expr.generate(context) diff --git a/m2isar/transforms/infer_types/transform.py b/m2isar/transforms/infer_types/transform.py index 1fc7066d..92b5034e 100644 --- a/m2isar/transforms/infer_types/transform.py +++ b/m2isar/transforms/infer_types/transform.py @@ -15,7 +15,7 @@ from m2isar.metamodel import load_model, dump_model -from .visitor import InferTypesVisitor +from .visitor import InferTypesMutator def get_parser(): @@ -31,15 +31,15 @@ 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) - visitor = InferTypesVisitor() + mutator = InferTypesMutator() for _, instr_def in core_def.instructions.items(): logger.debug("inferring types for instr %s", instr_def.name) - visitor.generate(instr_def.operation, None) + 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) - visitor.generate(instr_def.operation, None) + mutator.generate(instr_def.operation, None) return model_obj diff --git a/m2isar/transforms/infer_types/visitor.py b/m2isar/transforms/infer_types/visitor.py index e068a968..a7242f95 100644 --- a/m2isar/transforms/infer_types/visitor.py +++ b/m2isar/transforms/infer_types/visitor.py @@ -25,14 +25,14 @@ from functools import singledispatchmethod from m2isar.metamodel import arch, behav -from ...metamodel.utils.ExprVisitor import ExprVisitor +from ...metamodel.utils.ExprMutator import ExprMutator logger = logging.getLogger("infer_types") # pylint: disable=unused-argument -class InferTypesVisitor(ExprVisitor): - """Visitor to annote inferred types to a metamodel.""" +class InferTypesMutator(ExprMutator): + """Mutator to annote inferred types to a metamodel.""" @singledispatchmethod def generate(self, expr: behav.BaseNode, context): From 7be64cb8914357d53b9cfe1af3ebf0e8d06b7c97 Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Thu, 16 Apr 2026 14:29:15 +0200 Subject: [PATCH 44/62] validate_behav: fix shift-overflow warning --- m2isar/transforms/validate_behav/visitor.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/m2isar/transforms/validate_behav/visitor.py b/m2isar/transforms/validate_behav/visitor.py index 8591d3fa..60b6571e 100644 --- a/m2isar/transforms/validate_behav/visitor.py +++ b/m2isar/transforms/validate_behav/visitor.py @@ -61,7 +61,8 @@ def binary_operation(self: behav.BinaryOperation, context): pass else: context.emit_warning(f"Signed vs. unsigned comparison", "sign-compare", logger=logger, line_info=self.line_info) - if op.value == "<<" and self.left.inferred_type.width <= self.right.inferred_type.width: + # TODO: also check possible range of non-literal rhs? + if op.value == "<<" and isinstance(self.left, behav.IntLiteral) and self.left.inferred_type.width <= self.right.value: context.emit_warning(f"Shift count overflow for << operation ({self.left.inferred_type.width} vs. {self.right.inferred_type.width})", "shift-overflow", logger=logger, line_info=self.line_info) return self From 20d8e59e2aba62a4678650b96eea78fc42f02519 Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Thu, 16 Apr 2026 14:29:32 +0200 Subject: [PATCH 45/62] WarningFlagAction: fix typos --- m2isar/warnings.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/m2isar/warnings.py b/m2isar/warnings.py index 20df07fc..68b1a81e 100644 --- a/m2isar/warnings.py +++ b/m2isar/warnings.py @@ -35,7 +35,9 @@ def errors(self): class WarningFlagAction(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): - warnings_info = getattr(namespace, 'warnings_info', WarningsInfo()) + warnings_info = getattr(namespace, 'warnings', None) + if warnings_info is None: + warnings_info = WarningsInfo() for val in values: if val == 'no-error': @@ -47,7 +49,7 @@ def __call__(self, parser, namespace, values, option_string=None): elif val.startswith('error='): warn = val[6:] assert warn in warnings_info.known, f"Unknown warning: {warn}" - warnings_info.error_set.add(warn) + warnings_info.as_error.add(warn) elif val == 'error': warnings_info.all_as_error = True elif val == 'all': From 8d025ecf6966f46ef62820ea95fb15c6c1886dc5 Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Thu, 16 Apr 2026 15:28:56 +0200 Subject: [PATCH 46/62] add missing handler to sample ExprVisitor & ExprMutator --- m2isar/metamodel/utils/ExprMutator.py | 16 ++++++++++++++++ m2isar/metamodel/utils/ExprVisitor.py | 13 +++++++++++++ 2 files changed, 29 insertions(+) diff --git a/m2isar/metamodel/utils/ExprMutator.py b/m2isar/metamodel/utils/ExprMutator.py index 4249d51e..d4471216 100644 --- a/m2isar/metamodel/utils/ExprMutator.py +++ b/m2isar/metamodel/utils/ExprMutator.py @@ -126,6 +126,17 @@ def visit_ternary_operation(self, expr: behav.Ternary, context): expr.else_expr = expr.else_expr.generate(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 @@ -138,6 +149,11 @@ def visit_break(self, expr: behav.Break, context): 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) diff --git a/m2isar/metamodel/utils/ExprVisitor.py b/m2isar/metamodel/utils/ExprVisitor.py index 43619d97..aebdc112 100644 --- a/m2isar/metamodel/utils/ExprVisitor.py +++ b/m2isar/metamodel/utils/ExprVisitor.py @@ -102,6 +102,15 @@ def visit_ternary_operation(self, expr: behav.Ternary, context): expr.then_expr.generate(context) expr.else_expr.generate(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 @@ -114,6 +123,10 @@ def visit_break(self, expr: behav.Break, context): 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): expr.expr.generate(context) From 1865918ba6aa3a61838b3a0ba36f67c5b3e51f2e Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Thu, 16 Apr 2026 15:29:15 +0200 Subject: [PATCH 47/62] validate_behav: port to new visitor --- m2isar/transforms/validate_behav/validate.py | 9 +- m2isar/transforms/validate_behav/visitor.py | 301 +++++++++---------- 2 files changed, 147 insertions(+), 163 deletions(-) diff --git a/m2isar/transforms/validate_behav/validate.py b/m2isar/transforms/validate_behav/validate.py index c9a931d4..041b491d 100644 --- a/m2isar/transforms/validate_behav/validate.py +++ b/m2isar/transforms/validate_behav/validate.py @@ -15,6 +15,7 @@ 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 @@ -38,17 +39,17 @@ def validate_behav(model_obj, warnings_info): for _, core_def in model_obj.cores.items(): logger.debug("validating behavior for core %s", core_def.name) context = ValidatorContext(warnings_info) - patch_model(visitor) + visitor = ValidateBehavVisitor() for _, instr_def in core_def.instructions.items(): logger.debug("validating behavior for instr %s", instr_def.name) - instr_def.operation.generate(context) + 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) - patch_model(visitor) + visitor = ValidateBehavVisitor() for _, instr_def in set_def.instructions.items(): logger.debug("validating behavior for instr %s", instr_def.name) - instr_def.operation.generate(context) + visitor.generate(instr_def.operation, context) # return model_obj diff --git a/m2isar/transforms/validate_behav/visitor.py b/m2isar/transforms/validate_behav/visitor.py index 60b6571e..c7603e05 100644 --- a/m2isar/transforms/validate_behav/visitor.py +++ b/m2isar/transforms/validate_behav/visitor.py @@ -1,3 +1,5 @@ + + # 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 @@ -6,172 +8,153 @@ # 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 m2isar.metamodel import arch, behav +from ...metamodel.utils.ExprVisitor import ExprVisitor logger = logging.getLogger("validate_behav") # 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) - - self.statements = statements - return self - - -def binary_operation(self: behav.BinaryOperation, context): - self.left = self.left.generate(context) - op = self.op - self.right = self.right.generate(context) - - assert self.left.inferred_type is not None - assert self.right.inferred_type is not None - if op.value in ["|", "&", "^"] and self.left.inferred_type.width != self.right.inferred_type.width: - context.emit_warning(f"Bitwise operations with differently size operands are discouraged.", "bit-op-missmatch", logger=logger, line_info=self.line_info) - if op.value in ["<<", ">>", ">>>"] and self.right.inferred_type.signed: - context.emit_warning(f"Shift by signed amount", "shift-signed", logger=logger, line_info=self.line_info) - if op.value in ["<", "<=", ">", ">=", "==", "!="] and self.left.inferred_type.signed != self.right.inferred_type.signed: - if isinstance(self.left, behav.IntLiteral) and self.left.value == 0: - pass - if isinstance(self.right, behav.IntLiteral) and self.right.value == 0: - pass - else: - context.emit_warning(f"Signed vs. unsigned comparison", "sign-compare", logger=logger, line_info=self.line_info) - # TODO: also check possible range of non-literal rhs? - if op.value == "<<" and isinstance(self.left, behav.IntLiteral) and self.left.inferred_type.width <= self.right.value: - context.emit_warning(f"Shift count overflow for << operation ({self.left.inferred_type.width} vs. {self.right.inferred_type.width})", "shift-overflow", logger=logger, line_info=self.line_info) - 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.IntLiteral, context): - return self - - -def int_literal(self: behav.IntLiteral, context): - return self - - -def scalar_definition(self: behav.ScalarDefinition, context): - return self - - -def assignment(self: behav.Assignment, context): - self.target = self.target.generate(context) - self.expr = self.expr.generate(context) - assert self.target.inferred_type is not None - assert self.expr.inferred_type is not None - if self.target.inferred_type.width < self.expr.inferred_type.width: - context.emit_warning(f"Implicit truncation {self.expr.inferred_type.width} -> {self.target.inferred_type.width} found", "implicit-trunc", logger=logger, line_info=self.line_info) - if self.target.inferred_type.width > self.expr.inferred_type.width: - context.emit_warning(f"Implicit extend {self.expr.inferred_type.width} -> {self.target.inferred_type.width} found", "implicit-extend", logger=logger, line_info=self.line_info) - return self - - -def conditional(self: behav.Conditional, context): - self.conds = [x.generate(context) for x in self.conds] - stmts = [] - for stmt in self.stmts: - if isinstance(stmt, list): # TODO: legacy? - new = [y.generate(context) for y in stmt] - else: - new = stmt.generate(context) - stmts.append(new) - self.stmts = stmts - return self - - -def loop(self: behav.Loop, context): - self.cond = self.cond.generate(context) - self.stmts = [x.generate(context) for x in self.stmts] - return self - - -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 self - - -def return_(self: behav.Return, context): - if self.expr is not None: - self.expr = self.expr.generate(context) - return self - - -def unary_operation(self: behav.UnaryOperation, context): - self.right = self.right.generate(context) - return self - - -def named_reference(self: behav.NamedReference, context): - return self - - -def indexed_reference(self: behav.IndexedReference, context): - self.index = self.index.generate(context) - return self - - -def type_conv(self: behav.TypeConv, context): - self.expr = self.expr.generate(context) - return self - - -def callable_(self: behav.Callable, context): - self.args = [stmt.generate(context) for stmt in self.args] - return self - - -def procedure_call(self: behav.ProcedureCall, context): - self.args = [stmt.generate(context) for stmt in self.args] - return self - - -def group(self: behav.Group, context): - self.expr = self.expr.generate(context) - if isinstance(self.expr, behav.IntLiteral): - return self.expr - return self - - -def break_(self: behav.Break, context): - return self +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.left, 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.inferred_type.width})", "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: + expr.expr = self.generate(expr.expr, context) + return expr + + @generate.register + def _(self, expr: behav.UnaryOperation, context): + expr.right = 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) From 9bcb958d5379dc748e54fd3db2e684eaa5feca84 Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Thu, 16 Apr 2026 15:36:52 +0200 Subject: [PATCH 48/62] fix implementation of ExprVisitor & ExprMutator --- m2isar/metamodel/utils/ExprMutator.py | 42 ++++++++++++------------- m2isar/metamodel/utils/ExprVisitor.py | 44 +++++++++++++-------------- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/m2isar/metamodel/utils/ExprMutator.py b/m2isar/metamodel/utils/ExprMutator.py index d4471216..0f787071 100644 --- a/m2isar/metamodel/utils/ExprMutator.py +++ b/m2isar/metamodel/utils/ExprMutator.py @@ -44,7 +44,7 @@ def visit_operator(self, expr: behav.Operator, context): def visit_operation(self, expr: behav.Operation, context): statements = [] for stmt in expr.statements: - stmt = stmt.generate(context) + stmt = self.generate(stmt, context) statements.append(stmt) expr.statements = statements return expr @@ -53,28 +53,28 @@ def visit_operation(self, expr: behav.Operation, context): def visit_block(self, expr: behav.Block, context): statements = [] for stmt in expr.statements: - stmt = stmt.generate(context) + 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 = expr.left.generate(context) - expr.right = expr.right.generate(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 = expr.expr.generate(context) - expr.left = expr.left.generate(context) - expr.right = expr.right.generate(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 = expr.left.generate(context) - expr.right = expr.right.generate(context) + expr.left = self.generate(expr.left, context) + expr.right = self.generate(expr.right, context) return expr @default_visit.register @@ -91,39 +91,39 @@ def visit_string_literal(self, expr: behav.StringLiteral, context): @default_visit.register def visit_assignment(self, expr: behav.Assignment, context): - expr_target = expr.target.generate(context) - expr.expr = expr.expr.generate(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 = cond.generate(context) + cond = self.generate(cond, context) conds.append(cond) expr.conds = conds stmts = [] for stmt in expr.stmts: - smts = stmt.generate(context) + 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 = expr.cond.generate(context) + expr.cond = self.generate(expr.cond, context) stmts = [] for stmt in expr.stmts: - stmt = stmt.generate(context) + 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 = expr.cond.generate(context) - expr.then_expr = expr.then_expr.generate(context) - expr.else_expr = expr.else_expr.generate(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 @@ -163,7 +163,7 @@ def visit_type_conv(self, expr: behav.TypeConv, context): def visit_callable(self, expr: behav.Callable, context): args = [] for arg in expr.args: - arg = arg.generate(context) + arg = self.generate(arg, context) args.append(arg) epxr.args = args return expr @@ -172,12 +172,12 @@ def visit_callable(self, expr: behav.Callable, context): def visit_procedure_call(self, expr: behav.Callable, context): args = [] for arg in expr.args: - arg = arg.generate(context) + 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 = expr.expr.generate(context) + expr.expr = self.generate(expr.expr, context) return expr diff --git a/m2isar/metamodel/utils/ExprVisitor.py b/m2isar/metamodel/utils/ExprVisitor.py index aebdc112..3c2071a3 100644 --- a/m2isar/metamodel/utils/ExprVisitor.py +++ b/m2isar/metamodel/utils/ExprVisitor.py @@ -43,28 +43,28 @@ def visit_operator(self, expr: behav.Operator, context): @default_visit.register def visit_operation(self, expr: behav.Operation, context): for stmt in expr.statements: - stmt.generate(context) + self.generate(stmt, context) @default_visit.register def visit_block(self, expr: behav.Block, context): for stmt in expr.statements: - stmt.generate(context) + self.generate(stmt, context) @default_visit.register def visit_binary_operation(self, expr: behav.BinaryOperation, context): - expr.left.generate(context) - expr.right.generate(context) + self.generate(expr.left, context) + self.generate(expr.right, context) @default_visit.register def visit_slice_operation(self, expr: behav.SliceOperation, context): - expr.expr.generate(context) - expr.left.generate(context) - expr.right.generate(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): - expr.left.generate(context) - expr.right.generate(context) + self.generate(expr.left, context) + self.generate(expr.right, context) @default_visit.register def visit_number_literal(self, expr: behav.NumberLiteral, context): @@ -80,27 +80,27 @@ def visit_string_literal(self, expr: behav.StringLiteral, context): @default_visit.register def visit_assignment(self, expr: behav.Assignment, context): - expr.target.generate(context) - expr.expr.generate(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: - cond.generate(context) + self.generate(cond, context) for stmt in expr.stmts: - stmt.generate(context) + self.generate(stmt, context) @default_visit.register def visit_loop(self, expr: behav.Loop, context): - expr.cond.generate(context) + self.generate(expr.cond, context) for stmt in expr.stmts: - stmt.generate(context) + self.generate(stmt, context) @default_visit.register def visit_ternary_operation(self, expr: behav.Ternary, context): - expr.cond.generate(context) - expr.then_expr.generate(context) - expr.else_expr.generate(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): @@ -129,18 +129,18 @@ def visit_indexed_reference(self, expr: behav.IndexedReference, context): @default_visit.register def visit_type_conv(self, expr: behav.TypeConv, context): - expr.expr.generate(context) + self.generate(expr.expr, context) @default_visit.register def visit_callable(self, expr: behav.Callable, context): for arg in expr.args: - arg.generate(context) + self.generate(arg, context) @default_visit.register def visit_procedure_call(self, expr: behav.Callable, context): for arg in expr.args: - arg.generate(context) + self.generate(arg, context) @default_visit.register def visit_group(self, expr: behav.Group, context): - expr.expr.generate(context) + self.generate(expr.expr, context) From 090484799eb4a6be2ed2bb87085809861e013338 Mon Sep 17 00:00:00 2001 From: jokap11 Date: Fri, 17 Apr 2026 08:52:38 +0200 Subject: [PATCH 49/62] [Frontend] IndexedRefs dont duplicate LHS to RHS Simplify RangedMem vs 1 Index --- m2isar/frontends/coredsl2/behavior_model_builder.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/m2isar/frontends/coredsl2/behavior_model_builder.py b/m2isar/frontends/coredsl2/behavior_model_builder.py index 0c31d6db..8e308a4d 100644 --- a/m2isar/frontends/coredsl2/behavior_model_builder.py +++ b/m2isar/frontends/coredsl2/behavior_model_builder.py @@ -286,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)) From 66c7b2028f9606d6a26c9f85ccabd0b116dba61c Mon Sep 17 00:00:00 2001 From: jokap11 Date: Fri, 17 Apr 2026 08:53:29 +0200 Subject: [PATCH 50/62] [Viewer] Distinguish RangedMemNodes in Viewer --- m2isar/backends/viewer/treegen.py | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/m2isar/backends/viewer/treegen.py b/m2isar/backends/viewer/treegen.py index f6605f01..6a8ee806 100644 --- a/m2isar/backends/viewer/treegen.py +++ b/m2isar/backends/viewer/treegen.py @@ -208,9 +208,31 @@ def indexed_reference(self, expr: behav.IndexedReference, context: "TreeGenConte context.tree.insert(context.parent, tk.END, text="Reference", values=(f"{expr.reference}",)) - context.push(context.tree.insert(context.parent, tk.END, text="Index")) - self.generate(expr.index, context) - context.pop() + # 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="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() + + else: + context.push(context.tree.insert(context.parent, tk.END, text="Index")) + self.generate(expr.index, context) + context.pop() + + else: + context.push(context.tree.insert(context.parent, tk.END, text="Index")) + self.generate(expr.index, context) + context.pop() context.pop() From c0f3cf1b520adfe708b02098024fd3ce4780f336 Mon Sep 17 00:00:00 2001 From: jokap11 Date: Fri, 17 Apr 2026 08:54:41 +0200 Subject: [PATCH 51/62] [Infer] Bugfix Prohibit TypeConv from propagating in wrong dir --- m2isar/transforms/infer_types/visitor.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/m2isar/transforms/infer_types/visitor.py b/m2isar/transforms/infer_types/visitor.py index a7242f95..29a21556 100644 --- a/m2isar/transforms/infer_types/visitor.py +++ b/m2isar/transforms/infer_types/visitor.py @@ -23,6 +23,7 @@ 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 @@ -330,6 +331,7 @@ def _(self, expr: behav.TypeConv, context): expr.expr = self.generate(expr.expr, context) ty = expr.expr.inferred_type + ty = deepcopy(expr.expr.inferred_type) if ty is None: logger.warning("Type conv needs inferred type. Skipping...") return expr From a2991212002055b6053c45738c0b5494cc9657a6 Mon Sep 17 00:00:00 2001 From: jokap11 Date: Fri, 17 Apr 2026 08:56:00 +0200 Subject: [PATCH 52/62] [ETISS] Remove memory correcting with inferred_type --- m2isar/backends/etiss/instruction_transform.py | 17 +---------------- m2isar/backends/etiss/instruction_utils.py | 1 - m2isar/transforms/infer_types/visitor.py | 1 - 3 files changed, 1 insertion(+), 18 deletions(-) diff --git a/m2isar/backends/etiss/instruction_transform.py b/m2isar/backends/etiss/instruction_transform.py index 7c6d428e..fe64c9a4 100644 --- a/m2isar/backends/etiss/instruction_transform.py +++ b/m2isar/backends/etiss/instruction_transform.py @@ -341,9 +341,6 @@ def _(self, expr: behav.Assignment, context: TransformerContext): for m_id in expr_str.mem_ids: m_id.write = False - if not expr_str.mem_corrected: - logger.debug("assuming mem read size at %d", target.size) - m_id.access_size = target.size if target.is_mem_access: if len(target.mem_ids) != 1: @@ -351,9 +348,6 @@ def _(self, expr: behav.Assignment, context: TransformerContext): target.mem_ids[0].write = True - if not target.mem_corrected: - logger.debug("assuming mem write size at %d", expr_str.size) - target.mem_ids[0].access_size = expr_str.size c = CodeString(f"{target.code} = {expr_str.code};", static, None, None, line_infos=[expr.line_info] + target.line_infos + expr_str.line_infos) @@ -526,13 +520,6 @@ def _(self, expr: behav.TypeConv, context: TransformerContext): expr._size = expr_str.size expr._actual_size = expr_str.actual_size - if expr_str.is_mem_access: - if not expr_str.mem_corrected and expr_str.mem_ids[-1].access_size != expr.size: - expr_str.mem_ids[-1].access_size = expr.size - expr_str.size = expr.size - expr_str.mem_corrected = True - elif expr_str.mem_ids[-1].access_size == expr.size: - expr_str.mem_corrected = True code_str = expr_str.code @@ -548,7 +535,6 @@ def _(self, expr: behav.TypeConv, context: TransformerContext): 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 - c.mem_corrected = expr_str.mem_corrected return c @@ -632,6 +618,7 @@ def _(self, expr: behav.IndexedReference, context: TransformerContext): static = StaticType.NONE if arch.MemoryAttribute.IS_MAIN_MEM in referred_mem.attributes: + 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) c.mem_ids.append(MemID(referred_mem, context.mem_var_count, index, size)) context.mem_var_count += 1 @@ -640,8 +627,6 @@ def _(self, expr: behav.IndexedReference, context: TransformerContext): 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=[expr.line_info] + index.line_infos) if arch.MemoryAttribute.IS_MAIN_REG in referred_mem.attributes: c.regs_affected.add(index_code) diff --git a/m2isar/backends/etiss/instruction_utils.py b/m2isar/backends/etiss/instruction_utils.py index db57ec06..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 diff --git a/m2isar/transforms/infer_types/visitor.py b/m2isar/transforms/infer_types/visitor.py index 29a21556..4b450f9f 100644 --- a/m2isar/transforms/infer_types/visitor.py +++ b/m2isar/transforms/infer_types/visitor.py @@ -330,7 +330,6 @@ def _(self, expr: behav.IndexedReference, context): def _(self, expr: behav.TypeConv, context): expr.expr = self.generate(expr.expr, context) - ty = expr.expr.inferred_type ty = deepcopy(expr.expr.inferred_type) if ty is None: logger.warning("Type conv needs inferred type. Skipping...") From 383e5c8ca025b72a16113440990ccaedd886cc51 Mon Sep 17 00:00:00 2001 From: jokap11 Date: Fri, 17 Apr 2026 09:39:22 +0200 Subject: [PATCH 53/62] [Transform] Include RangedMem Acess for typeinfer --- m2isar/transforms/infer_types/visitor.py | 41 +++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/m2isar/transforms/infer_types/visitor.py b/m2isar/transforms/infer_types/visitor.py index 4b450f9f..2015023d 100644 --- a/m2isar/transforms/infer_types/visitor.py +++ b/m2isar/transforms/infer_types/visitor.py @@ -319,7 +319,21 @@ def _(self, expr: behav.IndexedReference, context): 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] - size = expr.reference.size + 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_ @@ -381,3 +395,28 @@ def _(self, expr: behav.Group, context): @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 + + 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") From 0d1f4308f04650777524a60caab2d462bd98081b Mon Sep 17 00:00:00 2001 From: jokap11 Date: Fri, 17 Apr 2026 09:40:28 +0200 Subject: [PATCH 54/62] [ETISS] Use only base address variable Left or Right might be complex expression but not both. Just propagate base_address --- m2isar/backends/etiss/instruction_transform.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/m2isar/backends/etiss/instruction_transform.py b/m2isar/backends/etiss/instruction_transform.py index fe64c9a4..f0749917 100644 --- a/m2isar/backends/etiss/instruction_transform.py +++ b/m2isar/backends/etiss/instruction_transform.py @@ -620,7 +620,18 @@ def _(self, expr: behav.IndexedReference, context: TransformerContext): if arch.MemoryAttribute.IS_MAIN_MEM in referred_mem.attributes: 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) - c.mem_ids.append(MemID(referred_mem, context.mem_var_count, index, size)) + 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 From 33170e1bada9b187de046d70cdfd8509e7f6e715 Mon Sep 17 00:00:00 2001 From: jokap11 Date: Fri, 17 Apr 2026 09:56:48 +0200 Subject: [PATCH 55/62] [Transform] Force ranged mem access lhs within lower/upper bound --- m2isar/transforms/infer_types/visitor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/m2isar/transforms/infer_types/visitor.py b/m2isar/transforms/infer_types/visitor.py index 2015023d..b3504cf6 100644 --- a/m2isar/transforms/infer_types/visitor.py +++ b/m2isar/transforms/infer_types/visitor.py @@ -405,6 +405,7 @@ def helper_expr_size(sub_expr: behav.BaseNode): 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: From 740fa5f3434e043f31926caad130d860a38f0e16 Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Fri, 17 Apr 2026 11:08:47 +0200 Subject: [PATCH 56/62] m2isar/metamodel/behav.py: add assertion --- m2isar/metamodel/behav.py | 1 + 1 file changed, 1 insertion(+) diff --git a/m2isar/metamodel/behav.py b/m2isar/metamodel/behav.py index f8c392db..85889b80 100644 --- a/m2isar/metamodel/behav.py +++ b/m2isar/metamodel/behav.py @@ -124,6 +124,7 @@ def __init__(self, value: int, bit_size: int=None, signed: bool=None, line_info= 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 From da9444693fa17c41c44747651a8f15604c403a90 Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Fri, 17 Apr 2026 11:09:39 +0200 Subject: [PATCH 57/62] expr_simplifier: fallback to inferred types in TypeConv without explicit size --- m2isar/metamodel/utils/expr_simplifier.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/m2isar/metamodel/utils/expr_simplifier.py b/m2isar/metamodel/utils/expr_simplifier.py index f2d8d0ba..155ffcf6 100644 --- a/m2isar/metamodel/utils/expr_simplifier.py +++ b/m2isar/metamodel/utils/expr_simplifier.py @@ -228,7 +228,13 @@ def _(self, expr: behav.IndexedReference, context): def _(self, expr: behav.TypeConv, context): expr.expr = self.generate(expr.expr, context) if isinstance(expr.expr, behav.IntLiteral): - expr.expr.bit_size = expr.size + 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 From 9ef8c31a00b3fdbf1aeccabc1360bc6f48136ae0 Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Fri, 17 Apr 2026 11:10:09 +0200 Subject: [PATCH 58/62] validate_behav: fix shift-overflow warning --- m2isar/transforms/validate_behav/visitor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/m2isar/transforms/validate_behav/visitor.py b/m2isar/transforms/validate_behav/visitor.py index c7603e05..46ad42f3 100644 --- a/m2isar/transforms/validate_behav/visitor.py +++ b/m2isar/transforms/validate_behav/visitor.py @@ -57,8 +57,8 @@ def _(self, expr: behav.BinaryOperation, context): 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.left, 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.inferred_type.width})", "shift-overflow", logger=logger, line_info=expr.line_info) + 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 From bdf9c27acd3ed90cfd414f8d6445b58df86d798a Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Fri, 17 Apr 2026 11:10:43 +0200 Subject: [PATCH 59/62] validate_behav: fix visitor assigning None --- m2isar/transforms/validate_behav/visitor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/m2isar/transforms/validate_behav/visitor.py b/m2isar/transforms/validate_behav/visitor.py index 46ad42f3..b6b59c44 100644 --- a/m2isar/transforms/validate_behav/visitor.py +++ b/m2isar/transforms/validate_behav/visitor.py @@ -117,12 +117,12 @@ def _(self, expr: behav.Ternary, context): @generate.register def _(self, expr: behav.Return, context): if expr.expr is not None: - expr.expr = self.generate(expr.expr, context) + self.generate(expr.expr, context) return expr @generate.register def _(self, expr: behav.UnaryOperation, context): - expr.right = self.generate(expr.right, context) + self.generate(expr.right, context) return expr @generate.register From 610184cec0358f9ffd7b4833a42ec7c0b6ef5d32 Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Fri, 17 Apr 2026 12:09:17 +0200 Subject: [PATCH 60/62] etiss writer: handle duplicate instruction names (with different encodings) [TODO: make deterministic suffix] --- m2isar/backends/etiss/instruction_generator.py | 14 ++++++++++++++ .../etiss/templates/etiss_instruction.mako | 4 ++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/m2isar/backends/etiss/instruction_generator.py b/m2isar/backends/etiss/instruction_generator.py index 13cc4c22..ac32ff01 100644 --- a/m2isar/backends/etiss/instruction_generator.py +++ b/m2isar/backends/etiss/instruction_generator.py @@ -201,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 = [] @@ -236,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/templates/etiss_instruction.mako b/m2isar/backends/etiss/templates/etiss_instruction.mako index 3b4ec430..bd37fd32 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()}", (uint${enc_idx}_t) ${code_string}, From aae6937489b362cd8bd6a67ddd5fca91a8e22757 Mon Sep 17 00:00:00 2001 From: jokap11 Date: Fri, 17 Apr 2026 17:09:24 +0200 Subject: [PATCH 61/62] [Viewer] Bugfix always-true Assertion --- m2isar/backends/viewer/treegen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/m2isar/backends/viewer/treegen.py b/m2isar/backends/viewer/treegen.py index 6a8ee806..aef529a3 100644 --- a/m2isar/backends/viewer/treegen.py +++ b/m2isar/backends/viewer/treegen.py @@ -217,7 +217,7 @@ def indexed_reference(self, expr: behav.IndexedReference, context: "TreeGenConte context.push(context.tree.insert(context.parent, tk.END, text="Left")) self.generate(expr.index, context) context.pop() - assert(type(expr.right), behav.NamedReference) + assert(type(expr.right) == behav.NamedReference) context.push(context.tree.insert(context.parent, tk.END, text="Right")) self.generate(expr.right, context) context.pop() From 457992b30004df8b47fe598013d1a27c4e3c076f Mon Sep 17 00:00:00 2001 From: jokap11 Date: Fri, 17 Apr 2026 21:23:09 +0200 Subject: [PATCH 62/62] [ETISS] Add comments back to Generator --- .../backends/etiss/instruction_transform.py | 92 ++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-) diff --git a/m2isar/backends/etiss/instruction_transform.py b/m2isar/backends/etiss/instruction_transform.py index f0749917..1050c91b 100644 --- a/m2isar/backends/etiss/instruction_transform.py +++ b/m2isar/backends/etiss/instruction_transform.py @@ -37,7 +37,9 @@ def generate(self, expr: behav.BaseNode, context: TransformerContext): @generate.register def _(self, expr: behav.Operation, context: TransformerContext): - """Generate an `Operation` model object.""" + """Generate an `Operation` model object. Essentially generate all children, + concatenate their code, and add exception behavior if needed. + """ args: "list[CodeString]" = [] code_lines = [] @@ -108,6 +110,7 @@ def _(self, expr: behav.Operation, context: TransformerContext): container = CodePartsContainer() container.initial_required = '\n'.join(code_lines) + # 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";' @@ -175,6 +178,8 @@ def _(self, expr: behav.Break, context: TransformerContext): @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 @@ -196,29 +201,40 @@ def _(self, expr: behav.ScalarDefinition, context: TransformerContext): @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] + # 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 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 + # 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]) + # 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) + # add special behavior if this function is an exception entry point exc_code = "" if arch.FunctionAttribute.ETISS_TRAP_TRANSLATE_FN in fn.attributes: @@ -249,25 +265,35 @@ def _(self, expr: behav.ProcedureCall, context: TransformerContext): @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] + # 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 if ref is not None: + # if there is a function object, use its information + 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 + # 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]) + # 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]) @@ -296,14 +322,19 @@ def _(self, expr: behav.Callable, context: TransformerContext): @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) + # check staticness static = bool(target.static & StaticType.WRITE) and bool(expr_str.static) 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!') + # convert assignment value staticness if expr_str.static and not expr_str.is_literal: if bool(target.static & StaticType.WRITE): if context.ignore_static: @@ -313,9 +344,11 @@ def _(self, expr: behav.Assignment, context: TransformerContext): else: expr_str.code = context.make_static(expr_str.code, expr_str.signed) + # convert target staticness if bool(target.static & StaticType.READ): target.code = Template(target.code).safe_substitute(replacements.rename_write) + # keep track of affected and dependent registers context.affected_regs.update(target.regs_affected) context.dependent_regs.update(expr_str.regs_affected) @@ -361,10 +394,14 @@ def _(self, expr: behav.Assignment, context: TransformerContext): @generate.register def _(self, expr: behav.BinaryOperation, context: TransformerContext): + """Generate a binary expression""" + + # generate LHS and RHS of the expression left = self.generate(expr.left, context) op = expr.op right = self.generate(expr.right, context) + # 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: @@ -378,6 +415,7 @@ def _(self, expr: behav.BinaryOperation, context: TransformerContext): 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 @@ -392,6 +430,9 @@ def _(self, expr: behav.UnaryOperation, context: TransformerContext): @generate.register def _(self, expr: behav.Conditional, context: TransformerContext): + """Generate a conditional ('if' with optional 'else if' and 'else' blocks)""" + + # generate conditions and statement blocks conds: "list[CodeString]" = [self.generate(x, context) for x in expr.conds] stmts: "list[list[CodeString]]" = [] @@ -413,6 +454,7 @@ def _(self, expr: behav.Conditional, context: TransformerContext): else: stmts.append([ret]) + # check if all conditions are static static = all(x.static for x in conds) outputs: "list[CodeString]" = [] @@ -430,7 +472,10 @@ def _(self, expr: behav.Conditional, context: TransformerContext): if not static: context.dependent_regs.update(conds[0].regs_affected) + # generate first statement block outputs.extend(flatten(stmts[0])) + + # generate closing brace outputs.append(CodeString("} // conditional", static, None, None)) for elif_cond, elif_stmts in zip(conds[1:], stmts[1:]): @@ -451,6 +496,8 @@ def _(self, expr: behav.Conditional, context: TransformerContext): @generate.register def _(self, expr: behav.Loop, context: TransformerContext): + """Generate 'while' and 'do .. while' loops.""" + cond: CodeString = self.generate(expr.cond, context) stmts: "list[CodeString]" = [] @@ -483,12 +530,16 @@ def _(self, expr: behav.Loop, context: TransformerContext): @generate.register def _(self, expr: behav.Ternary, context: TransformerContext): + """Generate a ternary expression.""" + + # 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) static = StaticType.NONE not in [x.static for x in (cond, then_expr, else_expr)] + # 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) @@ -511,11 +562,16 @@ def _(self, expr: behav.Ternary, context: TransformerContext): @generate.register def _(self, expr: behav.TypeConv, context: TransformerContext): + """Generate a type cast expression""" + + # generate the expression to be type-casted expr_str = self.generate(expr.expr, context) + # 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 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 @@ -523,6 +579,7 @@ def _(self, expr: behav.TypeConv, context: TransformerContext): code_str = expr_str.code + # 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 @@ -530,6 +587,8 @@ def _(self, expr: behav.TypeConv, context: TransformerContext): 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})' @@ -540,15 +599,23 @@ def _(self, expr: behav.TypeConv, context: TransformerContext): @generate.register def _(self, expr: behav.NamedReference, context: TransformerContext): + """Generate a named reference""" + + # extract referred object referred_var = expr.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 constant if not static: ref = "*" if len(referred_var.children) > 0 else "" name = f"{ref}{replacements.default_prefix}{name}" @@ -557,6 +624,7 @@ def _(self, expr: behav.NamedReference, context: TransformerContext): context.used_arch_data = True elif isinstance(referred_var, arch.BitFieldDescr): + # function argument signed = referred_var.data_type == arch.DataType.S size = referred_var.size static = StaticType.READ @@ -599,8 +667,13 @@ def _(self, expr: behav.NamedReference, context: TransformerContext): @generate.register def _(self, expr: behav.IndexedReference, context: TransformerContext): + """Generate an indexed reference expression (for register banks or memory).""" + name = expr.reference.name + + # generate index expression index = self.generate(expr.index, context) + referred_mem = expr.reference if isinstance(referred_mem, arch.Memory): @@ -608,6 +681,7 @@ def _(self, expr: behav.IndexedReference, context: TransformerContext): size = referred_mem.size + # 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) @@ -618,6 +692,7 @@ def _(self, expr: behav.IndexedReference, context: TransformerContext): 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): @@ -635,6 +710,7 @@ def _(self, expr: behav.IndexedReference, context: TransformerContext): 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 @@ -645,6 +721,9 @@ def _(self, expr: behav.IndexedReference, context: TransformerContext): @generate.register def _(self, expr: behav.SliceOperation, context: TransformerContext): + """Generate a slice expression""" + + # 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) @@ -659,6 +738,7 @@ def _(self, expr: behav.SliceOperation, context: TransformerContext): 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 @@ -672,12 +752,15 @@ def _(self, expr: behav.SliceOperation, context: TransformerContext): 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 = f"((({expr_str.code}) >> ({right.code})) & {mask})" @@ -688,6 +771,9 @@ def _(self, expr: behav.SliceOperation, context: TransformerContext): @generate.register def _(self, expr: behav.ConcatOperation, context: TransformerContext): + """Generate a concatenation expression""" + + # generate LHS and RHS operands left: CodeString = self.generate(expr.left, context) right: CodeString = self.generate(expr.right, context) @@ -704,6 +790,7 @@ def _(self, expr: behav.ConcatOperation, context: TransformerContext): @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 @@ -717,6 +804,7 @@ def _(self, expr: behav.NumberLiteral, context: TransformerContext): @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 @@ -748,6 +836,8 @@ def _(self, expr: behav.Operator, context: TransformerContext): @generate.register def _(self, expr: behav.Group, context: TransformerContext): + """Generate a group of expressions.""" + expr_str = self.generate(expr.expr, context) if isinstance(expr_str, CodeString): expr_str.code = f'({expr_str.code})'