diff --git a/pystencils/astnodes.py b/pystencils/astnodes.py index 2f020d4e3e139b4e7525eabd2920c0fbc8a44e09..d21aeec6c0601428e321d9019a8474548f5c16b9 100644 --- a/pystencils/astnodes.py +++ b/pystencils/astnodes.py @@ -55,7 +55,8 @@ class Node: for arg in self.args: if isinstance(arg, arg_type): result.add(arg) - result.update(arg.atoms(arg_type)) + arg_atoms = arg.atoms(arg_type) if (isinstance(arg, sp.Basic) or isinstance(arg, Node) or isinstance(arg, sp.Expr)) else {} + result.update(arg_atoms) return result @@ -232,7 +233,8 @@ class KernelFunction(Node): @property def fields_accessed(self) -> Set[Field]: """Set of Field instances: fields which are accessed inside this kernel function""" - return set(o.field for o in itertools.chain(self.atoms(ResolvedFieldAccess))) + fields = self.atoms(ResolvedFieldAccess) + return set(o.field for o in itertools.chain(fields)) @property def fields_written(self) -> Set[Field]: @@ -265,7 +267,9 @@ class KernelFunction(Node): return tuple(field_map[fn] for fn in symbol.field_names) return () - argument_symbols = self._body.undefined_symbols - self.global_variables + symbols_defined = self.body.symbols_defined + undefined_symbols = self._body.undefined_symbols + argument_symbols = undefined_symbols - self.global_variables - symbols_defined argument_symbols.update(sizes) parameters = [self.Parameter(symbol, get_fields(symbol)) for symbol in argument_symbols] if hasattr(self, 'indexing'): @@ -303,7 +307,7 @@ class SkipIteration(Node): class Block(Node): - def __init__(self, nodes: Union[Node, List[Node]]): + def __init__(self, nodes: List[Node]): super(Block, self).__init__() if not isinstance(nodes, list): nodes = [nodes] @@ -345,6 +349,14 @@ class Block(Node): assert self._nodes.count(insert_before) == 1 idx = self._nodes.index(insert_before) + # move all assignment (definitions to the top) + if isinstance(new_node, SympyAssignment) and new_node.is_declaration: + while idx > 0: + pn = self._nodes[idx - 1] + if isinstance(pn, LoopOverCoordinate) or isinstance(pn, Conditional): + idx -= 1 + else: + break if not if_not_exists or self._nodes[idx] != new_node: self._nodes.insert(idx, new_node) @@ -353,6 +365,14 @@ class Block(Node): assert self._nodes.count(insert_after) == 1 idx = self._nodes.index(insert_after) + 1 + # move all assignment (definitions to the top) + if isinstance(new_node, SympyAssignment) and new_node.is_declaration: + while idx > 0: + pn = self._nodes[idx - 1] + if isinstance(pn, LoopOverCoordinate) or isinstance(pn, Conditional): + idx -= 1 + else: + break if not if_not_exists or not (self._nodes[idx - 1] == new_node or (idx < len(self._nodes) and self._nodes[idx] == new_node)): self._nodes.insert(idx, new_node) @@ -404,7 +424,8 @@ class Block(Node): else: result.update(a.undefined_symbols) defined_symbols.update(a.symbols_defined) - return result - defined_symbols + ret = result - defined_symbols + return ret def __str__(self): return "Block " + ''.join('{!s}\n'.format(node) for node in self._nodes) @@ -561,10 +582,10 @@ class SympyAssignment(Node): def __init__(self, lhs_symbol, rhs_expr, is_const=True, use_auto=False): super(SympyAssignment, self).__init__(parent=None) self._lhs_symbol = sp.sympify(lhs_symbol) - self._rhs = sp.sympify(rhs_expr) + self.rhs = sp.sympify(rhs_expr) self._is_const = is_const self._is_declaration = self.__is_declaration() - self._use_auto = use_auto + self.use_auto = use_auto def __is_declaration(self): from pystencils.typing import CastFunc @@ -580,28 +601,15 @@ class SympyAssignment(Node): def lhs(self): return self._lhs_symbol - @property - def rhs(self): - return self._rhs - @lhs.setter def lhs(self, new_value): self._lhs_symbol = new_value self._is_declaration = self.__is_declaration() - @rhs.setter - def rhs(self, new_rhs_expr): - self._rhs = new_rhs_expr - def subs(self, subs_dict): self.lhs = fast_subs(self.lhs, subs_dict) self.rhs = fast_subs(self.rhs, subs_dict) - def fast_subs(self, subs_dict, skip=None): - self.lhs = fast_subs(self.lhs, subs_dict, skip) - self.rhs = fast_subs(self.rhs, subs_dict, skip) - return self - def optimize(self, optimizations): try: from sympy.codegen.rewriting import optimize @@ -611,7 +619,7 @@ class SympyAssignment(Node): @property def args(self): - return [self._lhs_symbol, self.rhs] + return [self._lhs_symbol, self.rhs, sp.sympify(self._is_const)] @property def symbols_defined(self): @@ -642,10 +650,6 @@ class SympyAssignment(Node): def is_const(self): return self._is_const - @property - def use_auto(self): - return self._use_auto - def replace(self, child, replacement): if child == self.lhs: replacement.parent = self @@ -928,15 +932,15 @@ class ForLoop(Node): def __str__(self): - return 'for({!s}={!s}; {!s}<{!s}; {!s}+={!s})\n{!s}'.format(self.ctr_to_loop_over.name, self.start, - self.ctr_to_loop_over.name, self.stop, - self.ctr_to_loop_over.name, self.step, + return 'for({!s}={!s}; {!s}<{!s}; {!s}+={!s})\n{!s}'.format(self.loop_counter_name, self.start, + self.loop_counter_name, self.stop, + self.loop_counter_name, self.step, ("\t" + "\t".join(str(self.body).splitlines(True)))) def __repr__(self): - return 'for({!s}={!s}; {!s}<{!s}; {!s}+={!s})'.format(self.ctr_to_loop_over.name, self.start, - self.ctr_to_loop_over.name, self.stop, - self.ctr_to_loop_over.name, self.step) + return 'for({!s}={!s}; {!s}<{!s}; {!s}+={!s})'.format(self.loop_counter_name, self.start, + self.loop_counter_name, self.stop, + self.loop_counter_name, self.step) class ArrayDeclaration(Node): @@ -962,7 +966,18 @@ class ArrayDeclaration(Node): @property def undefined_symbols(self): - return self._entries.atoms(TypedSymbol) + undefined_symbols = set() + for e in self._entries: + if isinstance(e, Node): + undefined_symbols = undefined_symbols | e.undefined_symbols + elif isinstance(e, sp.Basic) or isinstance(e, sp.Expr): + undefined_symbols = undefined_symbols | e.free_symbols + + return undefined_symbols + + def subs(self, substitutions, skip=None): + self._lhs = self.lhs.subs(substitutions) + self._entries = [e.subs(substitutions) for e in self._entries] def __str__(self): entry_str = ", ".join( [str(e) for e in self._entries]) diff --git a/pystencils/backends/cbackend.py b/pystencils/backends/cbackend.py index 874307628c08514b47b8b9d9c5f09160c670bda3..adfaa232de7d245d8445b8cca22456e2d9f579ee 100644 --- a/pystencils/backends/cbackend.py +++ b/pystencils/backends/cbackend.py @@ -224,13 +224,6 @@ class CBackend: def _print_AbstractType(self, node): return str(node) - def _print_ArrayDeclaration(self, node): - return str(node) - - def _print_ForLoop(self, node): - return f"{node.__repr__()}\n{self._print(node.body)}" - - def _print_KernelFunction(self, node): function_arguments = [f"{self._print(s.symbol.dtype)} {s.symbol.name}" for s in node.get_parameters() if not type(s.symbol) is CFunction] @@ -248,7 +241,7 @@ class CBackend: return func_declaration + "\n" + body def _print_Block(self, node): - if node is None: + if node == None: return "\n" block_contents = "\n".join([self._print(child) for child in node.args]) @@ -272,14 +265,10 @@ class CBackend: return f"{prefix}{loop_str}\n{self._print(node.body)}" def _print_SympyAssignment(self, node): - printed_lhs = self.sympy_printer.doprint(node.lhs) - printed_rhs = self.sympy_printer.doprint(node.rhs) - if node.is_declaration: if node.use_auto: - data_type = 'auto' + data_type = 'auto ' else: - data_type = self._print(node.lhs.dtype).replace(' const', '') if node.is_const: prefix = 'const ' else: @@ -364,10 +353,12 @@ class CBackend: code += f"\nif ({flushcond}) {{\n\t{code2}\n}} else {{\n\t{code1}\n}}" return pre_code + code else: - return f"{printed_lhs} = {printed_rhs};" + return f"{self.sympy_printer.doprint(node.lhs)} = {self.sympy_printer.doprint(node.rhs)};" def _print_ArrayDeclaration(self, node): - return node.__str__() + entry_str = ", ".join([self.sympy_printer.doprint(e) for e in node._entries]) + return f"const {node._lhs.dtype.base_type} {node._lhs.name} [] = {{{entry_str}}};\n" + def _print_ForLoop(self, node): counter_symbol = node.ctr_to_loop_over @@ -685,7 +676,8 @@ class VectorizedCustomSympyPrinter(CustomSympyPrinter): return self.instruction_set[instruction].format(*printed_args, **self._kwargs) else: if arg.is_Number and not isinstance(arg, (sp.core.numbers.Infinity, sp.core.numbers.NegativeInfinity)): - return self._typed_vectorized_number(arg, data_type) + tmp = self._typed_vectorized_number(arg, data_type) + return tmp elif isinstance(arg, TypedSymbol): return self._typed_vectorized_symbol(arg, data_type) elif isinstance(arg, (InverseTrigonometricFunction, TrigonometricFunction, HyperbolicFunction)) \ @@ -779,6 +771,8 @@ class VectorizedCustomSympyPrinter(CustomSympyPrinter): return result args = expr.args + + # special treatment for all-integer args, for loop index arithmetic until we have proper int vectorization suffix = "" if all([(type(e) is CastFunc and str(e.dtype) == self.instruction_set['int']) or isinstance(e, sp.Integer) @@ -798,6 +792,7 @@ class VectorizedCustomSympyPrinter(CustomSympyPrinter): t = self._print(term) sign = 1 summands.append(self.SummandInfo(sign, t)) + # Use positive terms first summands.sort(key=lambda e: e.sign, reverse=True) # if no positive term exists, prepend a zero @@ -836,7 +831,10 @@ class VectorizedCustomSympyPrinter(CustomSympyPrinter): if exp.is_integer and exp.is_number and 0 < exp < 8: return self._print(sp.Mul(*[expr.base] * exp, evaluate=False)) elif exp.is_integer and exp.is_number and -8 < exp < 0: - return self._print(sp.Mul(*[DivFunc(CastFunc(1.0, expr.base.dtype), expr.base)] * (-exp), evaluate=False)) + arg_types = [get_type_of_expression(a, default_float_type='float') for a in expr.args] + + target_type = collate_types(arg_types) + return self._print(sp.Mul(*[DivFunc(CastFunc(1.0,target_type),expr.base)] * (-exp), evaluate=False)) elif exp == 0.5: return root elif exp == -0.5: @@ -902,6 +900,7 @@ class VectorizedCustomSympyPrinter(CustomSympyPrinter): result = self._scalarFallback('_print_Relational', expr) if result: return result + tmp = self.instruction_set[expr.rel_op].format(self._print(expr.lhs), self._print(expr.rhs), **self._kwargs) return self.instruction_set[expr.rel_op].format(self._print(expr.lhs), self._print(expr.rhs), **self._kwargs) def _print_Equality(self, expr): @@ -915,6 +914,7 @@ class VectorizedCustomSympyPrinter(CustomSympyPrinter): if result: return result + if expr.args[-1].cond is not sp.sympify(True): # We need the last conditional to be a True, otherwise the resulting # function may not return a result. @@ -931,6 +931,9 @@ class VectorizedCustomSympyPrinter(CustomSympyPrinter): result, **self._kwargs) else: # noinspection SpellCheckingInspection + cond = self._print(condition) + true = self._print(true_expr) + result = self.instruction_set['blendv'].format(result, self._print(true_expr), self._print(condition), **self._kwargs) return result diff --git a/pystencils/cpu/vectorization.py b/pystencils/cpu/vectorization.py index 7e0b15aa649b30a4f831ed30521a419c50506968..71e82740b7c517bb08db7a8430a89d0d4d50557f 100644 --- a/pystencils/cpu/vectorization.py +++ b/pystencils/cpu/vectorization.py @@ -50,7 +50,7 @@ class CachelineSize(ast.Node): symbol = sp.Symbol("_clsize") mask_symbol = sp.Symbol("_clsize_mask") last_symbol = sp.Symbol("_cl_lastvec") - + def __init__(self): super(CachelineSize, self).__init__(parent=None) @@ -142,7 +142,7 @@ def vectorize_inner_loops_and_adapt_load_stores(ast_node, assume_aligned, nontem """Goes over all innermost loops, changes increment to vector width and replaces field accesses by vector type.""" vector_width = ast_node.instruction_set['width'] - all_loops = filtered_tree_iteration(ast_node, ast.LoopOverCoordinate, stop_type=ast.SympyAssignment) + all_loops = filtered_tree_iteration(ast_node, ast.LoopOverCoordinate, stop_types=[ast.SympyAssignment, ast.ArrayDeclaration]) inner_loops = [loop for loop in all_loops if loop.is_innermost_loop] zero_loop_counters = {loop.loop_counter_symbol: 0 for loop in all_loops} @@ -283,7 +283,7 @@ def insert_vector_casts(ast_node, instruction_set, loop_counter_symbol, default_ # TODO Vectorization Revamp: get rid of default_type def visit_expr(expr, default_type='double', force_vectorize=False): if isinstance(expr, ast.ResolvedFieldAccess): - assert (expr.args[1] != loop_counter_symbol) and (expr.args[1].name != loop_counter_symbol.name), f"ResolvedFieldAccess {expr} is indexed by the coordinate that is vectorized over, not handled." + #assert (expr.args[1] != loop_counter_symbol) and (expr.args[1].name != loop_counter_symbol.name), f"ResolvedFieldAccess {expr} is indexed by the coordinate that is vectorized over, not handled." if force_vectorize: return CastFunc(expr, VectorType(expr.field.dtype, instruction_set['width'])) else: diff --git a/pystencils/transformations.py b/pystencils/transformations.py index 4a50a4dc4fe7b1838d76e3d29bef5f7aa3c9c299..398148508c1070fbdc34a6f49230a4f771a39bb3 100644 --- a/pystencils/transformations.py +++ b/pystencils/transformations.py @@ -4,11 +4,9 @@ import warnings from collections import OrderedDict from copy import deepcopy from types import MappingProxyType -from typing import Set import sympy as sp -import pystencils as ps import pystencils.astnodes as ast from pystencils.astnodes import Conditional @@ -78,14 +76,16 @@ class NestedScopes: return len(self._defined) -def filtered_tree_iteration(node, node_type, stop_type=None): +def filtered_tree_iteration(node, node_type, stop_types=None): + if not hasattr(node, 'args'): + raise Exception(node) for arg in node.args: if isinstance(arg, node_type): yield arg - elif stop_type and isinstance(node, stop_type): + elif stop_types and any([isinstance(node, stop_type) for stop_type in stop_types]): continue - yield from filtered_tree_iteration(arg, node_type) + yield from filtered_tree_iteration(arg, node_type, stop_types) def generic_visit(term, visitor): @@ -523,7 +523,7 @@ def resolve_field_accesses(ast_node, read_only_field_names=None, coord_dict = create_coordinate_dict(group) new_ptr, offset = create_intermediate_base_pointer(field_access, coord_dict, last_pointer) if new_ptr not in enclosing_block.symbols_defined: - new_assignment = ast.SympyAssignment(new_ptr, last_pointer + offset, is_const=False, use_auto=False) + new_assignment = ast.SympyAssignment(new_ptr, last_pointer + offset, is_const=False) enclosing_block.insert_before(new_assignment, sympy_assignment) last_pointer = new_ptr @@ -587,70 +587,21 @@ def move_constants_before_loop(ast_node, rename_moved_consts = False, lhs_symbol """ assert isinstance(node.parent, ast.Block) - def modifies_or_declares(node: ast.Node, symbol_names: Set[str]) -> bool: - if isinstance(node, (ps.Assignment, ast.SympyAssignment)): - if isinstance(node.lhs, ast.ResolvedFieldAccess): - return node.lhs.typed_symbol.name in symbol_names - else: - return node.lhs.name in symbol_names - elif isinstance(node, ast.Block): - for arg in node.args: - if isinstance(arg, ast.SympyAssignment) and arg.is_declaration: - continue - if modifies_or_declares(arg, symbol_names): - return True - return False - elif isinstance(node, ast.LoopOverCoordinate): - return modifies_or_declares(node.body, symbol_names) - elif isinstance(node, ast.Conditional): - return ( - modifies_or_declares(node.true_block, symbol_names) - or (node.false_block and modifies_or_declares(node.false_block, symbol_names)) - ) - elif isinstance(node, ast.KernelFunction): - return False - else: - defs = {s.name for s in node.symbols_defined} - return bool(symbol_names.intersection(defs)) - - dependencies = {s.name for s in node.undefined_symbols} - last_block = node.parent last_block_child = node element = node.parent prev_element = node - while element: - if isinstance(element, (ast.Conditional, ast.KernelFunction)): - # Never move out of Conditionals or KernelFunctions. - break - - elif isinstance(element, ast.Block): + if isinstance(element, ast.Block): last_block = element last_block_child = prev_element - if any(modifies_or_declares(sibling, dependencies) for sibling in element.args): - # The node depends on one of the statements in this block. - # Do not move further out. - break - - elif isinstance(element, ast.LoopOverCoordinate) : - if element.loop_counter_symbol.name in dependencies: - # The node depends on the loop counter. - # Do not move out of this loop. - break - elif isinstance(element, ast.ForLoop) : - if element.ctr_to_loop_over.name in dependencies: - # The node depends on the loop counter. - # Do not move out of this loop. - break - + if isinstance(element, ast.Conditional): + break else: - raise NotImplementedError(f'Due to defensive programming we handle only specific expressions.\n' - f'The expression {element} of type {type(element)} is not known yet.') - - # No dependencies to symbols defined/modified within the current element. - # We can move the node up one level and in front of the current element. + critical_symbols = set([s.name for s in element.symbols_defined]) + if set([s.name for s in node.undefined_symbols]).intersection(critical_symbols): + break prev_element = element element = element.parent return last_block, last_block_child @@ -679,13 +630,16 @@ def move_constants_before_loop(ast_node, rename_moved_consts = False, lhs_symbol consts_moved_subs = {} for child in children: - child.subs(consts_moved_subs) if not isinstance(child, ast.SympyAssignment): # only move SympyAssignments block.append(child) continue - if isinstance(child, ast.SympyAssignment) and (lhs_symbol_ignore_predicate(child.lhs) or isinstance(child.lhs, ResolvedFieldAccess)): + if isinstance(child, ast.SympyAssignment) and lhs_symbol_ignore_predicate(child.lhs): + block.append(child) + continue + + if isinstance(child, ast.SympyAssignment) and isinstance(child.lhs, ResolvedFieldAccess): # dont move field accesses block.append(child) continue @@ -702,10 +656,10 @@ def move_constants_before_loop(ast_node, rename_moved_consts = False, lhs_symbol old_symbol = deepcopy(child.lhs) if rename_moved_consts: moved_symbol = TypedSymbol(old_symbol.name + "_mov" + str(moves_ctr), old_symbol.dtype) - moved_nodes.add(moved_symbol) - consts_moved_subs.update({old_symbol: moved_symbol}) + moved_nodes.add(moved_symbol.name) + consts_moved_subs.update({old_symbol : moved_symbol}) else: - moved_nodes.add(child.lhs) + moved_nodes.add(child.lhs.name) if not exists_already: target.insert_before(child, child_to_insert_before) @@ -720,13 +674,18 @@ def move_constants_before_loop(ast_node, rename_moved_consts = False, lhs_symbol # -> symbol has to be renamed assert isinstance(child.lhs, TypedSymbol) new_symbol = TypedSymbol(sp.Dummy().name, child.lhs.dtype) + moved_nodes.add(new_symbol.name) target.insert_before(ast.SympyAssignment(new_symbol, child.rhs, is_const=child.is_const), child_to_insert_before) - consts_moved_subs.update({old_symbol: new_symbol}) + #block.append(ast.SympyAssignment(child.lhs, new_symbol, is_const=child.is_const)) + consts_moved_subs.update({old_symbol : new_symbol}) + if bool(consts_moved_subs): + for child in children: + child.subs(consts_moved_subs) + moves_ctr += 1 return moved_nodes - def split_inner_loop(ast_node: ast.Node, symbol_groups): """ Splits inner loop into multiple loops to minimize the amount of simultaneous load/store streams @@ -801,7 +760,7 @@ def split_inner_loop(ast_node: ast.Node, symbol_groups): outer_loop.parent.append(free_node) -def cut_loop(loop_node, cutting_points, with_conditional: bool = False): +def cut_loop(loop_node, cutting_points, with_conditional : bool = False): """Cuts loop at given cutting points. One loop is transformed into len(cuttingPoints)+1 new loops that range from @@ -824,7 +783,7 @@ def cut_loop(loop_node, cutting_points, with_conditional: bool = False): new_body.subs({loop_node.loop_counter_symbol: new_start}) if with_conditional: conditional_expr = sp.And(sp.Ge(new_start, loop_node.start), sp.Le(new_start, loop_node.stop)) - new_loops.append(ast.Conditional(conditional_expr, new_body)) + new_loops.append(Conditional(conditional_expr, new_body)) else: new_loops.append(new_body) elif new_end - new_start == 0: diff --git a/pystencils/typing/leaf_typing.py b/pystencils/typing/leaf_typing.py index ff1ebe6309e71161f91b70b305c9db44aa168e33..7d05723b69d4d8c2f4651d42e224bc5612533624 100644 --- a/pystencils/typing/leaf_typing.py +++ b/pystencils/typing/leaf_typing.py @@ -10,6 +10,7 @@ from sympy.core.relational import Relational from sympy.functions.elementary.piecewise import ExprCondPair from sympy.functions.elementary.trigonometric import TrigonometricFunction, InverseTrigonometricFunction from sympy.functions.elementary.hyperbolic import HyperbolicFunction +from sympy.codegen import Assignment from sympy.logic.boolalg import BooleanFunction from sympy.logic.boolalg import BooleanAtom @@ -50,7 +51,7 @@ class TypeAdder: def visit(self, obj): if isinstance(obj, (list, tuple)): return [self.visit(e) for e in obj] - if isinstance(obj, ast.SympyAssignment): + if isinstance(obj, (sp.Eq, ast.SympyAssignment, Assignment)): return self.process_assignment(obj) elif isinstance(obj, ast.Conditional): condition, condition_type = self.figure_out_type(obj.condition_expr) @@ -61,6 +62,8 @@ class TypeAdder: return ast.Conditional(condition, true_block=true_block, false_block=false_block) elif isinstance(obj, ast.Block): return ast.Block([self.visit(e) for e in obj.args]) + elif isinstance(obj, ast.ArrayDeclaration): + return ast.ArrayDeclaration(obj.lhs, [self.figure_out_type(e)[0] for e in obj._entries]) elif isinstance(obj, ast.LoopOverCoordinate): return ast.LoopOverCoordinate(ast.Block([self.visit(e) for e in obj.body.args]), obj.coordinate_to_loop_over, obj.start, obj.stop) @@ -71,7 +74,7 @@ class TypeAdder: else: raise ValueError("Invalid object " + str(obj) + " in kernel " + str(type(obj))) - def process_assignment(self, assignment: ast.SympyAssignment) -> ast.SympyAssignment: + def process_assignment(self, assignment: Union[sp.Eq, ast.SympyAssignment, Assignment]) -> ast.SympyAssignment: # for checks it is crucial to process rhs before lhs to catch e.g. a = a + 1 new_rhs, rhs_type = self.figure_out_type(assignment.rhs) @@ -84,12 +87,16 @@ class TypeAdder: new_lhs, lhs_type = self.figure_out_type(lhs) assert isinstance(new_lhs, (Field.Access, TypedSymbol)) + # take over assignments qualifiers if they have, use std-initializers else + const_qualifier = assignment.is_const if isinstance(assignment, ast.SympyAssignment) else True + auto_qualifier = assignment.use_auto if isinstance(assignment, ast.SympyAssignment) else False + if lhs_type != rhs_type: - logging.debug(f'Lhs"{new_lhs} of type "{lhs_type}" is assigned with a different datatype ' - f'rhs: "{new_rhs}" of type "{rhs_type}".') - return ast.SympyAssignment(new_lhs, CastFunc(new_rhs, lhs_type), assignment.is_const, assignment.use_auto) + logging.warning(f'Lhs"{new_lhs} of type "{lhs_type}" is assigned with a different datatype ' + f'rhs: "{new_rhs}" of type "{rhs_type}".') + return ast.SympyAssignment(new_lhs, CastFunc(new_rhs, lhs_type), const_qualifier, auto_qualifier) else: - return ast.SympyAssignment(new_lhs, new_rhs, assignment.is_const, assignment.use_auto) + return ast.SympyAssignment(new_lhs, new_rhs, const_qualifier, auto_qualifier) # Type System Specification # - Defined Types: TypedSymbol, Field, Field.Access, ...?