diff --git a/.github/workflows/general-ci.yml b/.github/workflows/general-ci.yml index 138726ef1d..063c1f3e7d 100644 --- a/.github/workflows/general-ci.yml +++ b/.github/workflows/general-ci.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.7,'3.11'] + python-version: [3.7,'3.12'] simplify: [0,1,autoopt] steps: diff --git a/dace/codegen/control_flow.py b/dace/codegen/control_flow.py index 28bf38f14d..a198ed371b 100644 --- a/dace/codegen/control_flow.py +++ b/dace/codegen/control_flow.py @@ -30,7 +30,7 @@ x < 5 /------>[s2]--------\\ - [s1] \ ->[s5] + [s1] \\ ->[s5] ------>[s3]->[s4]--/ x >= 5 diff --git a/dace/codegen/cppunparse.py b/dace/codegen/cppunparse.py index 77dd34d478..e4456e3e18 100644 --- a/dace/codegen/cppunparse.py +++ b/dace/codegen/cppunparse.py @@ -87,6 +87,21 @@ from dace import dtypes from dace.codegen.tools import type_inference + +if sys.version_info < (3, 8): + BytesConstant = ast.Bytes + EllipsisConstant = ast.Ellipsis + NameConstant = ast.NameConstant + NumConstant = ast.Num + StrConstant = ast.Str +else: + BytesConstant = ast.Constant + EllipsisConstant = ast.Constant + NameConstant = ast.Constant + NumConstant = ast.Constant + StrConstant = ast.Constant + + # Large float and imaginary literals get turned into infinities in the AST. # We unparse those infinities to INFSTR. INFSTR = "1e" + repr(sys.float_info.max_10_exp + 1) @@ -574,7 +589,7 @@ def _generic_FunctionDef(self, t, is_async=False): self.write('/* async */ ') if getattr(t, "returns", False): - if isinstance(t.returns, ast.NameConstant): + if isinstance(t.returns, NameConstant): if t.returns.value is None: self.write('void') else: @@ -729,25 +744,26 @@ def _Repr(self, t): raise NotImplementedError('Invalid C++') def _Num(self, t): - repr_n = repr(t.n) + t_n = t.value if sys.version_info >= (3, 8) else t.n + repr_n = repr(t_n) # For complex values, use DTYPE_TO_TYPECLASS dictionary - if isinstance(t.n, complex): + if isinstance(t_n, complex): dtype = dtypes.DTYPE_TO_TYPECLASS[complex] # Handle large integer values - if isinstance(t.n, int): - bits = t.n.bit_length() + if isinstance(t_n, int): + bits = t_n.bit_length() if bits == 32: # Integer, potentially unsigned - if t.n >= 0: # unsigned + if t_n >= 0: # unsigned repr_n += 'U' else: # signed, 64-bit repr_n += 'LL' elif 32 < bits <= 63: repr_n += 'LL' - elif bits == 64 and t.n >= 0: + elif bits == 64 and t_n >= 0: repr_n += 'ULL' elif bits >= 64: - warnings.warn(f'Value wider than 64 bits encountered in expression ({t.n}), emitting as-is') + warnings.warn(f'Value wider than 64 bits encountered in expression ({t_n}), emitting as-is') if repr_n.endswith("j"): self.write("%s(0, %s)" % (dtype, repr_n.replace("inf", INFSTR)[:-1])) @@ -898,13 +914,13 @@ def _BinOp(self, t): self.write(")") # Special cases for powers elif t.op.__class__.__name__ == 'Pow': - if isinstance(t.right, (ast.Num, ast.Constant, ast.UnaryOp)): + if isinstance(t.right, (NumConstant, ast.Constant, ast.UnaryOp)): power = None - if isinstance(t.right, (ast.Num, ast.Constant)): - power = t.right.n + if isinstance(t.right, (NumConstant, ast.Constant)): + power = t.right.value if sys.version_info >= (3, 8) else t.right.n elif isinstance(t.right, ast.UnaryOp) and isinstance(t.right.op, ast.USub): - if isinstance(t.right.operand, (ast.Num, ast.Constant)): - power = -t.right.operand.n + if isinstance(t.right.operand, (NumConstant, ast.Constant)): + power = - (t.right.operand.value if sys.version_info >= (3, 8) else t.right.operand.n) if power is not None and int(power) == power: negative = power < 0 @@ -984,7 +1000,9 @@ def _Attribute(self, t): # Special case: 3.__abs__() is a syntax error, so if t.value # is an integer literal then we need to either parenthesize # it or add an extra space to get 3 .__abs__(). - if (isinstance(t.value, (ast.Num, ast.Constant)) and isinstance(t.value.n, int)): + if isinstance(t.value, ast.Constant) and isinstance(t.value.value, int): + self.write(" ") + elif sys.version_info < (3, 8) and isinstance(t.value, ast.Num) and isinstance(t.value.n, int): self.write(" ") if (isinstance(t.value, ast.Name) and t.value.id in ('dace', 'dace::math', 'dace::cmath')): self.write("::") diff --git a/dace/codegen/instrumentation/papi.py b/dace/codegen/instrumentation/papi.py index bc7163ea9b..c0d3b657a1 100644 --- a/dace/codegen/instrumentation/papi.py +++ b/dace/codegen/instrumentation/papi.py @@ -448,7 +448,7 @@ class PAPIUtils(object): def available_counters() -> Dict[str, int]: """ Returns the available PAPI counters on this machine. Only works on - \*nix based systems with ``grep`` and ``papi-tools`` installed. + *nix based systems with ``grep`` and ``papi-tools`` installed. :return: A set of available PAPI counters in the form of a dictionary mapping from counter name to the number of native hardware diff --git a/dace/codegen/targets/cpp.py b/dace/codegen/targets/cpp.py index c3bf9c4027..3d26f76214 100644 --- a/dace/codegen/targets/cpp.py +++ b/dace/codegen/targets/cpp.py @@ -1,4 +1,4 @@ -# Copyright 2019-2021 ETH Zurich and the DaCe authors. All rights reserved. +# Copyright 2019-2023 ETH Zurich and the DaCe authors. All rights reserved. """ Helper functions for C++ code generation. NOTE: The C++ code generator is currently located in cpu.py. @@ -9,6 +9,7 @@ import itertools import math import numbers +import sys import warnings import sympy as sp @@ -1275,7 +1276,8 @@ def visit_BinOp(self, node: ast.BinOp): evaluated_constant = symbolic.evaluate(unparsed, self.constants) evaluated = symbolic.symstr(evaluated_constant, cpp_mode=True) value = ast.parse(evaluated).body[0].value - if isinstance(evaluated_node, numbers.Number) and evaluated_node != value.n: + if isinstance(evaluated_node, numbers.Number) and evaluated_node != ( + value.value if sys.version_info >= (3, 8) else value.n): raise TypeError node.right = ast.parse(evaluated).body[0].value except (TypeError, AttributeError, NameError, KeyError, ValueError, SyntaxError): diff --git a/dace/codegen/tools/type_inference.py b/dace/codegen/tools/type_inference.py index 8ee8632c65..f159088461 100644 --- a/dace/codegen/tools/type_inference.py +++ b/dace/codegen/tools/type_inference.py @@ -338,7 +338,15 @@ def _BinOp(t, symbols, inferred_symbols): return dtypes.result_type_of(type_left, type_right) # Special case for integer power elif t.op.__class__.__name__ == 'Pow': - if (isinstance(t.right, (ast.Num, ast.Constant)) and int(t.right.n) == t.right.n and t.right.n >= 0): + if (sys.version_info >= (3, 8) and isinstance(t.right, ast.Constant) and + int(t.right.value) == t.right.value and t.right.value >= 0): + if t.right.value != 0: + type_left = _dispatch(t.left, symbols, inferred_symbols) + for i in range(int(t.right.n) - 1): + _dispatch(t.left, symbols, inferred_symbols) + return dtypes.result_type_of(type_left, dtypes.typeclass(np.uint32)) + elif (sys.version_info < (3, 8) and isinstance(t.right, ast.Num) and + int(t.right.n) == t.right.n and t.right.n >= 0): if t.right.n != 0: type_left = _dispatch(t.left, symbols, inferred_symbols) for i in range(int(t.right.n) - 1): diff --git a/dace/frontend/python/astutils.py b/dace/frontend/python/astutils.py index faf214fdeb..67d8b6aded 100644 --- a/dace/frontend/python/astutils.py +++ b/dace/frontend/python/astutils.py @@ -15,6 +15,12 @@ from dace import dtypes, symbolic +if sys.version_info >= (3, 8): + NumConstant = ast.Constant +else: + NumConstant = ast.Num + + def _remove_outer_indentation(src: str): """ Removes extra indentation from a source Python function. @@ -66,8 +72,9 @@ def is_constant(node: ast.AST) -> bool: if sys.version_info >= (3, 8): if isinstance(node, ast.Constant): return True - if isinstance(node, (ast.Num, ast.Str, ast.NameConstant)): # For compatibility - return True + else: + if isinstance(node, (ast.Num, ast.Str, ast.NameConstant)): # For compatibility + return True return False @@ -82,13 +89,14 @@ def evalnode(node: ast.AST, gvars: Dict[str, Any]) -> Any: """ if not isinstance(node, ast.AST): return node - if isinstance(node, ast.Index): # For compatibility + if sys.version_info < (3, 9) and isinstance(node, ast.Index): # For compatibility node = node.value - if isinstance(node, ast.Num): # For compatibility - return node.n if sys.version_info >= (3, 8): if isinstance(node, ast.Constant): return node.value + else: + if isinstance(node, ast.Num): # For compatibility + return node.n # Replace internal constants with their values node = copy_tree(node) @@ -112,7 +120,7 @@ def rname(node): if isinstance(node, str): return node - if isinstance(node, ast.Num): + if sys.version_info < (3, 8) and isinstance(node, ast.Num): return str(node.n) if isinstance(node, ast.Name): # form x return node.id @@ -174,11 +182,11 @@ def subscript_to_ast_slice(node, without_array=False): # Python <3.9 compatibility result_slice = None - if isinstance(node.slice, ast.Index): + if sys.version_info < (3, 9) and isinstance(node.slice, ast.Index): slc = node.slice.value if not isinstance(slc, ast.Tuple): result_slice = [slc] - elif isinstance(node.slice, ast.ExtSlice): + elif sys.version_info < (3, 9) and isinstance(node.slice, ast.ExtSlice): slc = tuple(node.slice.dims) else: slc = node.slice @@ -196,7 +204,7 @@ def subscript_to_ast_slice(node, without_array=False): # Slice if isinstance(s, ast.Slice): result_slice.append((s.lower, s.upper, s.step)) - elif isinstance(s, ast.Index): # Index (Python <3.9) + elif sys.version_info < (3, 9) and isinstance(s, ast.Index): # Index (Python <3.9) result_slice.append(s.value) else: # Index result_slice.append(s) @@ -226,7 +234,7 @@ def _Subscript(self, t): self.dispatch(t.value) self.write('[') # Compatibility - if isinstance(t.slice, ast.Index): + if sys.version_info < (3, 9) and isinstance(t.slice, ast.Index): slc = t.slice.value else: slc = t.slice @@ -600,9 +608,9 @@ def visit_Name(self, node: ast.Name): def visit_Constant(self, node): return self.visit_Num(node) - def visit_Num(self, node: ast.Num): + def visit_Num(self, node: NumConstant): newname = f'__uu{self.id}' - self.gvars[newname] = node.n + self.gvars[newname] = node.value if sys.version_info >= (3, 8) else node.n self.id += 1 return ast.copy_location(ast.Name(id=newname, ctx=ast.Load()), node) diff --git a/dace/frontend/python/memlet_parser.py b/dace/frontend/python/memlet_parser.py index aa9d4ddb0d..a95bf82046 100644 --- a/dace/frontend/python/memlet_parser.py +++ b/dace/frontend/python/memlet_parser.py @@ -1,7 +1,7 @@ -# Copyright 2019-2021 ETH Zurich and the DaCe authors. All rights reserved. +# Copyright 2019-2023 ETH Zurich and the DaCe authors. All rights reserved. import ast import copy -import re +import sys from collections import namedtuple from typing import Any, Dict, List, Optional, Tuple, Union from dataclasses import dataclass @@ -16,6 +16,22 @@ MemletType = Union[ast.Call, ast.Attribute, ast.Subscript, ast.Name] +if sys.version_info < (3, 8): + _simple_ast_nodes = (ast.Constant, ast.Name, ast.NameConstant, ast.Num) + BytesConstant = ast.Bytes + EllipsisConstant = ast.Ellipsis + NameConstant = ast.NameConstant + NumConstant = ast.Num + StrConstant = ast.Str +else: + _simple_ast_nodes = (ast.Constant, ast.Name) + BytesConstant = ast.Constant + EllipsisConstant = ast.Constant + NameConstant = ast.Constant + NumConstant = ast.Constant + StrConstant = ast.Constant + + @dataclass class MemletExpr: name: str @@ -114,7 +130,7 @@ def _fill_missing_slices(das, ast_ndslice, array, indices): offsets.append(idx) idx += 1 new_idx += 1 - elif (isinstance(dim, ast.Ellipsis) or dim is Ellipsis + elif ((sys.version_info < (3, 8) and isinstance(dim, ast.Ellipsis)) or dim is Ellipsis or (isinstance(dim, ast.Constant) and dim.value is Ellipsis) or (isinstance(dim, ast.Name) and dim.id is Ellipsis)): if has_ellipsis: @@ -125,7 +141,7 @@ def _fill_missing_slices(das, ast_ndslice, array, indices): ndslice[j] = (0, array.shape[j] - 1, 1) idx += 1 new_idx += 1 - elif (dim is None or (isinstance(dim, (ast.Constant, ast.NameConstant)) and dim.value is None)): + elif (dim is None or (isinstance(dim, (ast.Constant, NameConstant)) and dim.value is None)): new_axes.append(new_idx) new_idx += 1 # NOTE: Do not increment idx here diff --git a/dace/frontend/python/newast.py b/dace/frontend/python/newast.py index 0329e31641..733c3c7f62 100644 --- a/dace/frontend/python/newast.py +++ b/dace/frontend/python/newast.py @@ -50,6 +50,36 @@ DependencyType = Dict[str, Tuple[SDFGState, Union[Memlet, nodes.Tasklet], Tuple[int]]] +if sys.version_info < (3, 8): + _simple_ast_nodes = (ast.Constant, ast.Name, ast.NameConstant, ast.Num) + BytesConstant = ast.Bytes + EllipsisConstant = ast.Ellipsis + NameConstant = ast.NameConstant + NumConstant = ast.Num + StrConstant = ast.Str +else: + _simple_ast_nodes = (ast.Constant, ast.Name) + BytesConstant = ast.Constant + EllipsisConstant = ast.Constant + NameConstant = ast.Constant + NumConstant = ast.Constant + StrConstant = ast.Constant + + +if sys.version_info < (3, 9): + Index = ast.Index + ExtSlice = ast.ExtSlice +else: + Index = type(None) + ExtSlice = type(None) + + +if sys.version_info < (3, 12): + TypeAlias = type(None) +else: + TypeAlias = ast.TypeAlias + + class SkipCall(Exception): """ Exception used to skip calls to functions that cannot be parsed. """ pass @@ -279,7 +309,7 @@ def repl_callback(repldict): # Extra AST node types that are disallowed after preprocessing _DISALLOWED_STMTS = DISALLOWED_STMTS + [ 'Global', 'Assert', 'Print', 'Nonlocal', 'Raise', 'Starred', 'AsyncFor', 'ListComp', 'GeneratorExp', 'SetComp', - 'DictComp', 'comprehension' + 'DictComp', 'comprehension', 'TypeAlias', 'TypeVar', 'ParamSpec', 'TypeVarTuple' ] TaskletType = Union[ast.FunctionDef, ast.With, ast.For] @@ -981,16 +1011,16 @@ def visit_TopLevelExpr(self, node): raise DaceSyntaxError(self, node, 'Local variable is already a tasklet input or output') self.outputs[connector] = memlet return None # Remove from final tasklet code - elif isinstance(node.value, ast.Str): + elif isinstance(node.value, StrConstant): return self.visit_TopLevelStr(node.value) return self.generic_visit(node) # Detect external tasklet code - def visit_TopLevelStr(self, node: ast.Str): + def visit_TopLevelStr(self, node: StrConstant): if self.extcode != None: raise DaceSyntaxError(self, node, 'Cannot provide more than one intrinsic implementation ' + 'for tasklet') - self.extcode = node.s + self.extcode = node.value if sys.version_info >= (3, 8) else node.s # TODO: Should get detected by _parse_Tasklet() if self.lang is None: @@ -1611,7 +1641,7 @@ def _parse_for_indices(self, node: ast.Expr): return indices - def _parse_value(self, node: Union[ast.Name, ast.Num, ast.Constant]): + def _parse_value(self, node: Union[ast.Name, NumConstant, ast.Constant]): """Parses a value Arguments: @@ -1626,7 +1656,7 @@ def _parse_value(self, node: Union[ast.Name, ast.Num, ast.Constant]): if isinstance(node, ast.Name): return node.id - elif isinstance(node, ast.Num): + elif sys.version_info < (3, 8) and isinstance(node, ast.Num): return str(node.n) elif isinstance(node, ast.Constant): return str(node.value) @@ -1646,14 +1676,14 @@ def _parse_slice(self, node: ast.Slice): return (self._parse_value(node.lower), self._parse_value(node.upper), self._parse_value(node.step) if node.step is not None else "1") - def _parse_index_as_range(self, node: Union[ast.Index, ast.Tuple]): + def _parse_index_as_range(self, node: Union[Index, ast.Tuple]): """ Parses an index as range :param node: Index node :return: Range in (from, to, step) format """ - if isinstance(node, ast.Index): + if sys.version_info < (3, 9) and isinstance(node, ast.Index): val = self._parse_value(node.value) elif isinstance(node, ast.Tuple): val = self._parse_value(node.elts) @@ -1760,7 +1790,7 @@ def visit_ast_or_value(arg): iterator = 'dace.map' else: ranges = [] - if isinstance(node.slice, (ast.Tuple, ast.ExtSlice)): + if isinstance(node.slice, (ast.Tuple, ExtSlice)): for s in node.slice.dims: ranges.append(self._parse_slice(s)) elif isinstance(node.slice, ast.Slice): @@ -2344,12 +2374,11 @@ def _is_test_simple(self, node: ast.AST): # Fix for scalar promotion tests # TODO: Maybe those tests should use the SDFG API instead of the # Python frontend which can change how it handles conditions. - simple_ast_nodes = (ast.Constant, ast.Name, ast.NameConstant, ast.Num) - is_test_simple = isinstance(node, simple_ast_nodes) + is_test_simple = isinstance(node, _simple_ast_nodes) if not is_test_simple: if isinstance(node, ast.Compare): - is_left_simple = isinstance(node.left, simple_ast_nodes) - is_right_simple = (len(node.comparators) == 1 and isinstance(node.comparators[0], simple_ast_nodes)) + is_left_simple = isinstance(node.left, _simple_ast_nodes) + is_right_simple = (len(node.comparators) == 1 and isinstance(node.comparators[0], _simple_ast_nodes)) if is_left_simple and is_right_simple: return True elif isinstance(node, ast.BoolOp): @@ -4293,7 +4322,7 @@ def visit_Call(self, node: ast.Call, create_callbacks=False): func = None funcname = None # If the call directly refers to an SDFG or dace-compatible program - if isinstance(node.func, ast.Num): + if sys.version_info < (3, 8) and isinstance(node.func, ast.Num): if self._has_sdfg(node.func.n): func = node.func.n elif isinstance(node.func, ast.Constant): @@ -4612,15 +4641,15 @@ def _visitname(self, name: str, node: ast.AST): return rname #### Visitors that return arrays - def visit_Str(self, node: ast.Str): + def visit_Str(self, node: StrConstant): # A string constant returns a string literal return StringLiteral(node.s) - def visit_Bytes(self, node: ast.Bytes): + def visit_Bytes(self, node: BytesConstant): # A bytes constant returns a string literal return StringLiteral(node.s) - def visit_Num(self, node: ast.Num): + def visit_Num(self, node: NumConstant): if isinstance(node.n, bool): return dace.bool_(node.n) if isinstance(node.n, (int, float, complex)): @@ -4640,7 +4669,7 @@ def visit_Name(self, node: ast.Name): # If visiting a name, check if it is a defined variable or a global return self._visitname(node.id, node) - def visit_NameConstant(self, node: ast.NameConstant): + def visit_NameConstant(self, node: NameConstant): return self.visit_Constant(node) def visit_Attribute(self, node: ast.Attribute): @@ -4689,6 +4718,9 @@ def visit_Dict(self, node: ast.Dict): def visit_Lambda(self, node: ast.Lambda): # Return a string representation of the function return astutils.unparse(node) + + def visit_TypeAlias(self, node: TypeAlias): + raise NotImplementedError('Type aliases are not supported in DaCe') ############################################################ @@ -4915,7 +4947,7 @@ def _promote(node: ast.AST) -> Union[Any, str, symbolic.symbol]: res = self.visit(s) else: res = self._visit_ast_or_value(s) - elif isinstance(s, ast.Index): + elif sys.version_info < (3, 9) and isinstance(s, ast.Index): res = self._parse_subscript_slice(s.value) elif isinstance(s, ast.Slice): lower = s.lower @@ -4933,7 +4965,7 @@ def _promote(node: ast.AST) -> Union[Any, str, symbolic.symbol]: res = ((lower, upper, step), ) elif isinstance(s, ast.Tuple): res = tuple(self._parse_subscript_slice(d, multidim=True) for d in s.elts) - elif isinstance(s, ast.ExtSlice): + elif sys.version_info < (3, 9) and isinstance(s, ast.ExtSlice): res = tuple(self._parse_subscript_slice(d, multidim=True) for d in s.dims) else: res = _promote(s) @@ -4995,8 +5027,8 @@ def visit_Subscript(self, node: ast.Subscript, inference: bool = False): # If the value is a tuple of constants (e.g., array.shape) and the # slice is constant, return the value itself nslice = self.visit(node.slice) - if isinstance(nslice, (ast.Index, Number)): - if isinstance(nslice, ast.Index): + if isinstance(nslice, (Index, Number)): + if sys.version_info < (3, 9) and isinstance(nslice, ast.Index): v = self._parse_value(nslice.value) else: v = nslice @@ -5060,7 +5092,7 @@ def _visit_ast_or_value(self, node: ast.AST) -> Any: out = out[0] return out - def visit_Index(self, node: ast.Index) -> Any: + def visit_Index(self, node: Index) -> Any: if isinstance(node.value, ast.Tuple): for i, elt in enumerate(node.value.elts): node.value.elts[i] = self._visit_ast_or_value(elt) @@ -5068,7 +5100,7 @@ def visit_Index(self, node: ast.Index) -> Any: node.value = self._visit_ast_or_value(node.value) return node - def visit_ExtSlice(self, node: ast.ExtSlice) -> Any: + def visit_ExtSlice(self, node: ExtSlice) -> Any: for i, dim in enumerate(node.dims): node.dims[i] = self._visit_ast_or_value(dim) diff --git a/dace/frontend/python/preprocessing.py b/dace/frontend/python/preprocessing.py index 239875118f..1636e57ad0 100644 --- a/dace/frontend/python/preprocessing.py +++ b/dace/frontend/python/preprocessing.py @@ -20,6 +20,20 @@ from dace.frontend.python.common import (DaceSyntaxError, SDFGConvertible, SDFGClosure, StringLiteral) +if sys.version_info < (3, 8): + BytesConstant = ast.Bytes + EllipsisConstant = ast.Ellipsis + NameConstant = ast.NameConstant + NumConstant = ast.Num + StrConstant = ast.Str +else: + BytesConstant = ast.Constant + EllipsisConstant = ast.Constant + NameConstant = ast.Constant + NumConstant = ast.Constant + StrConstant = ast.Constant + + class DaceRecursionError(Exception): """ Exception that indicates a recursion in a data-centric parsed context. @@ -342,13 +356,13 @@ def remake_dict(args): # Remake keyword argument names from AST kwarg_names = [] for kw in arg.keys: - if isinstance(kw, ast.Num): + if sys.version_info >= (3, 8) and isinstance(kw, ast.Constant): + kwarg_names.append(kw.value) + elif sys.version_info < (3, 8) and isinstance(kw, ast.Num): kwarg_names.append(kw.n) - elif isinstance(kw, (ast.Str, ast.Bytes)): + elif sys.version_info < (3, 8) and isinstance(kw, (ast.Str, ast.Bytes)): kwarg_names.append(kw.s) - elif isinstance(kw, ast.NameConstant): - kwarg_names.append(kw.value) - elif sys.version_info >= (3, 8) and isinstance(kw, ast.Constant): + elif sys.version_info < (3, 8) and isinstance(kw, ast.NameConstant): kwarg_names.append(kw.value) else: raise NotImplementedError(f'Key type {type(kw).__name__} is not supported') @@ -754,7 +768,8 @@ def visit_Subscript(self, node: ast.Subscript) -> Any: def visit_Call(self, node: ast.Call) -> Any: from dace.frontend.python.interface import in_program, inline # Avoid import loop - if hasattr(node.func, 'n') and isinstance(node.func.n, SDFGConvertible): + if (hasattr(node.func, 'value') and isinstance(node.func.value, SDFGConvertible) or + sys.version_info < (3, 8) and hasattr(node.func, 'n') and isinstance(node.func.n, SDFGConvertible)): # Skip already-parsed calls return self.generic_visit(node) @@ -858,7 +873,8 @@ def visit_JoinedStr(self, node: ast.JoinedStr) -> Any: parsed = [ not isinstance(v, ast.FormattedValue) or isinstance(v.value, ast.Constant) for v in visited.values ] - values = [v.s if isinstance(v, ast.Str) else astutils.unparse(v.value) for v in visited.values] + values = [v.s if sys.version_info < (3, 8) and isinstance(v, ast.Str) else astutils.unparse(v.value) + for v in visited.values] return ast.copy_location( ast.Constant(kind='', value=''.join(('{%s}' % v) if not p else v for p, v in zip(parsed, values))), node) @@ -1358,7 +1374,7 @@ def _get_given_args(self, node: ast.Call, function: 'DaceProgram') -> Set[str]: def visit_Call(self, node: ast.Call): # Only parse calls to parsed SDFGConvertibles - if not isinstance(node.func, (ast.Num, ast.Constant)): + if not isinstance(node.func, (NumConstant, ast.Constant)): self.seen_calls.add(astutils.unparse(node.func)) return self.generic_visit(node) if hasattr(node.func, 'oldnode'): @@ -1366,10 +1382,7 @@ def visit_Call(self, node: ast.Call): self.seen_calls.add(astutils.unparse(node.func.oldnode.func)) else: self.seen_calls.add(astutils.rname(node.func.oldnode)) - if isinstance(node.func, ast.Num): - value = node.func.n - else: - value = node.func.value + value = node.func.value if sys.version_info >= (3, 8) else node.func.n if not hasattr(value, '__sdfg__') or isinstance(value, SDFG): return self.generic_visit(node) diff --git a/dace/libraries/stencil/subscript_converter.py b/dace/libraries/stencil/subscript_converter.py index 8abb3fc6c8..d159b345cb 100644 --- a/dace/libraries/stencil/subscript_converter.py +++ b/dace/libraries/stencil/subscript_converter.py @@ -1,9 +1,34 @@ -# Copyright 2019-2021 ETH Zurich and the DaCe authors. All rights reserved. +# Copyright 2019-2023 ETH Zurich and the DaCe authors. All rights reserved. import ast +import sys from collections import defaultdict from typing import Tuple +if sys.version_info < (3, 8): + _simple_ast_nodes = (ast.Constant, ast.Name, ast.NameConstant, ast.Num) + BytesConstant = ast.Bytes + EllipsisConstant = ast.Ellipsis + NameConstant = ast.NameConstant + NumConstant = ast.Num + StrConstant = ast.Str +else: + _simple_ast_nodes = (ast.Constant, ast.Name) + BytesConstant = ast.Constant + EllipsisConstant = ast.Constant + NameConstant = ast.Constant + NumConstant = ast.Constant + StrConstant = ast.Constant + + +if sys.version_info < (3, 9): + Index = ast.Index + ExtSlice = ast.ExtSlice +else: + Index = type(None) + ExtSlice = type(None) + + class SubscriptConverter(ast.NodeTransformer): """ Finds all subscript accesses using constant indices in the given code, and @@ -67,9 +92,9 @@ def visit_Subscript(self, node: ast.Subscript): # This can be a bunch of different things, varying between Python 3.8 # and Python 3.9, so try hard to unpack it into an index we can use. index_tuple = node.slice - if isinstance(index_tuple, (ast.Subscript, ast.Index)): + if isinstance(index_tuple, (ast.Subscript, Index)): index_tuple = index_tuple.value - if isinstance(index_tuple, (ast.Constant, ast.Num)): + if isinstance(index_tuple, (ast.Constant, NumConstant)): index_tuple = (index_tuple, ) if isinstance(index_tuple, ast.Tuple): index_tuple = index_tuple.elts diff --git a/dace/transformation/dataflow/mpi.py b/dace/transformation/dataflow/mpi.py index 8138b86b26..b6a467dc21 100644 --- a/dace/transformation/dataflow/mpi.py +++ b/dace/transformation/dataflow/mpi.py @@ -23,9 +23,9 @@ class MPITransformMap(transformation.SingleStateTransformation): .. code-block:: text Input1 - Output1 - \ / + \\ / Input2 --- MapEntry -- Arbitrary R -- MapExit -- Output2 - / \ + / \\ InputN - OutputN diff --git a/setup.py b/setup.py index 6f97086543..a0ac2e2d49 100644 --- a/setup.py +++ b/setup.py @@ -63,7 +63,7 @@ "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", ], - python_requires='>=3.6, <3.12', + python_requires='>=3.6, <3.13', packages=find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests"]), package_data={ '': [ diff --git a/tests/blas/nodes/dot_test.py b/tests/blas/nodes/dot_test.py index d5f1d24263..a936be60a9 100755 --- a/tests/blas/nodes/dot_test.py +++ b/tests/blas/nodes/dot_test.py @@ -92,20 +92,23 @@ def run_test(target, size, vector_length): def test_dot_pure(): - return run_test("pure", 64, 1) + assert isinstance(run_test("pure", 64, 1), dace.SDFG) +# TODO: Refactor to use assert or return True/False (pytest deprecation of returning non-booleans) @xilinx_test() def test_dot_xilinx(): return run_test("xilinx", 64, 16) +# TODO: Refactor to use assert or return True/False (pytest deprecation of returning non-booleans) @xilinx_test() def test_dot_xilinx_decoupled(): with set_temporary("compiler", "xilinx", "decouple_array_interfaces", value=True): return run_test("xilinx", 64, 16) +# TODO: Refactor to use assert or return True/False (pytest deprecation of returning non-booleans) @intel_fpga_test() def test_dot_intel_fpga(): return run_test("intel_fpga", 64, 16) @@ -119,4 +122,4 @@ def test_dot_intel_fpga(): args = parser.parse_args() size = args.N - run_test(target, size, vector_length) + run_test(args.target, size, args.vector_length) diff --git a/tests/compile_sdfg_test.py b/tests/compile_sdfg_test.py index 33ace1156a..3120359262 100644 --- a/tests/compile_sdfg_test.py +++ b/tests/compile_sdfg_test.py @@ -51,7 +51,7 @@ def tester(a: int): return a + 1 csdfg = tester.to_sdfg().compile() - with pytest.warns(None, match='Casting'): + with pytest.warns(UserWarning, match='Casting'): result = csdfg(0.1) assert result.item() == 1 diff --git a/tests/fpga/hbm_transform_test.py b/tests/fpga/hbm_transform_test.py index 6438ac7492..0346837fbc 100644 --- a/tests/fpga/hbm_transform_test.py +++ b/tests/fpga/hbm_transform_test.py @@ -1,7 +1,6 @@ -# Copyright 2019-2021 ETH Zurich and the DaCe authors. All rights reserved. +# Copyright 2019-2023 ETH Zurich and the DaCe authors. All rights reserved. from dace.fpga_testing import xilinx_test -from numpy.lib import math from dace.sdfg.state import SDFGState import numpy as np from dace import dtypes diff --git a/tests/library/gemm_test.py b/tests/library/gemm_test.py index df60d1aa43..07e9006ece 100644 --- a/tests/library/gemm_test.py +++ b/tests/library/gemm_test.py @@ -1,4 +1,4 @@ -# Copyright 2019-2021 ETH Zurich and the DaCe authors. All rights reserved. +# Copyright 2019-2023 ETH Zurich and the DaCe authors. All rights reserved. import pytest import warnings import itertools @@ -132,7 +132,10 @@ def numpy_gemm(A, B, C, transA, transB, alpha, beta): assert diff <= 1e-5 -@pytest.mark.parametrize(('implementation', ), [('pure', ), ('MKL', ), pytest.param('cuBLAS', marks=pytest.mark.gpu)]) +@pytest.mark.parametrize( + ('implementation', ), + [('pure', ), pytest.param('MKL', marks=pytest.mark.mkl), + pytest.param('cuBLAS', marks=pytest.mark.gpu)]) def test_library_gemm(implementation): param_grid_trans = dict( transA=[True, False], diff --git a/tests/python_frontend/type_statement_test.py b/tests/python_frontend/type_statement_test.py new file mode 100644 index 0000000000..16ec1613db --- /dev/null +++ b/tests/python_frontend/type_statement_test.py @@ -0,0 +1,22 @@ +# Copyright 2019-2023 ETH Zurich and the DaCe authors. All rights reserved. +import dace +import pytest + + +# TODO: Investigate why pytest parses the DaCeProgram, even when the test is not supposed to run. +# @pytest.mark.py312 +# def test_type_statement(): + +# @dace.program +# def type_statement(): +# type Scalar[T] = T +# A: Scalar[dace.float32] = 0 +# return A + +# with pytest.raises(dace.frontend.python.common.DaceSyntaxError): +# type_statement() + + +if __name__ == '__main__': + # test_type_statement() + pass diff --git a/tests/transformations/move_loop_into_map_test.py b/tests/transformations/move_loop_into_map_test.py index 67c60c01bf..dca775bb7a 100644 --- a/tests/transformations/move_loop_into_map_test.py +++ b/tests/transformations/move_loop_into_map_test.py @@ -96,17 +96,17 @@ def test_multiple_edges(self): def test_itervar_in_map_range(self): sdfg = should_not_apply_1.to_sdfg(simplify=True) count = sdfg.apply_transformations(MoveLoopIntoMap) - self.assertEquals(count, 0) + self.assertEqual(count, 0) def test_itervar_in_data(self): sdfg = should_not_apply_2.to_sdfg(simplify=True) count = sdfg.apply_transformations(MoveLoopIntoMap) - self.assertEquals(count, 0) + self.assertEqual(count, 0) def test_non_injective_index(self): sdfg = should_not_apply_3.to_sdfg(simplify=True) count = sdfg.apply_transformations(MoveLoopIntoMap) - self.assertEquals(count, 0) + self.assertEqual(count, 0) def test_apply_multiple_times(self): sdfg = apply_multiple_times.to_sdfg(simplify=True)