diff --git a/docs/source/api/symbolic_language/astnodes.rst b/docs/source/api/symbolic_language/astnodes.rst index 4d5c4b89f410ba7bcbde695819eb4b7351fcd71b..ff31c98ecbb5822ef3b1fba8b18f577f2c352e0e 100644 --- a/docs/source/api/symbolic_language/astnodes.rst +++ b/docs/source/api/symbolic_language/astnodes.rst @@ -4,6 +4,6 @@ Kernel Structure .. automodule:: pystencils.sympyextensions.astnodes -.. autoclass:: pystencils.sympyextensions.AssignmentCollection +.. autoclass:: pystencils.AssignmentCollection :members: diff --git a/docs/source/backend/index.rst b/docs/source/backend/index.rst index f2fe9346dbe4d38722b69dd9c279d0eb11c98773..fb7c9c33b8e221e7f501084470ff937203e2efb6 100644 --- a/docs/source/backend/index.rst +++ b/docs/source/backend/index.rst @@ -13,6 +13,7 @@ who wish to customize or extend the behaviour of the code generator in their app ast iteration_space translation + typification platforms transformations jit diff --git a/docs/source/backend/translation.rst b/docs/source/backend/translation.rst index a4c7d36b58701f76e7e35283e12d01e705af1890..fb418b719fdb1d5031b24b4cc0423437cab4a7a0 100644 --- a/docs/source/backend/translation.rst +++ b/docs/source/backend/translation.rst @@ -13,6 +13,3 @@ Kernel Translation .. autoclass:: pystencils.backend.kernelcreation.FreezeExpressions :members: - -.. autoclass:: pystencils.backend.kernelcreation.Typifier - :members: diff --git a/docs/source/backend/typification.rst b/docs/source/backend/typification.rst new file mode 100644 index 0000000000000000000000000000000000000000..b79ee831123876d684877b0d6cc3e7a768cffda3 --- /dev/null +++ b/docs/source/backend/typification.rst @@ -0,0 +1,31 @@ +********** +AST Typing +********** + +In order for many transformations and code emission to function, all symbols and expressions +occuring inside a backend syntax tree must have a valid type. +In order to determine these types, pystencils provides an AST visitor (the `Typifier`) that attempts to +figure those out from the expression tree's structure and a few default data types. + +.. autoclass:: pystencils.backend.kernelcreation.Typifier + :members: + +.. autoclass:: pystencils.backend.kernelcreation.typification.TypificationError + :members: + +Typifier Internal Classes +========================= + +.. autoclass:: pystencils.backend.kernelcreation.typification.TypeContext + :members: + +.. autoclass:: pystencils.backend.kernelcreation.typification.TypeHint + :members: + +.. autoclass:: pystencils.backend.kernelcreation.typification.ToDefault + :members: + +.. autoclass:: pystencils.backend.kernelcreation.typification.DereferencableTo + :members: + +.. autodata :: pystencils.backend.kernelcreation.typification.InferenceHook diff --git a/src/pystencils/backend/kernelcreation/typification.py b/src/pystencils/backend/kernelcreation/typification.py index d85c5341073e548301ca74131ad091ef5db35595..1fede168a28809310d9224f1f093dbb712ce7e6b 100644 --- a/src/pystencils/backend/kernelcreation/typification.py +++ b/src/pystencils/backend/kernelcreation/typification.py @@ -49,7 +49,7 @@ from ..ast.expressions import ( ) from ..functions import PsMathFunction, CFunction -__all__ = ["Typifier"] +__all__ = ["Typifier", "TypificationError"] class TypificationError(Exception): @@ -104,29 +104,43 @@ class TypeContext: Instances of this class are used to propagate and check data types across expression subtrees of the AST. Each type context has: - - A target type `target_type`, which shall be applied to all expressions it covers - - A set of restrictions on the target type: - - `require_nonconst` to make sure the target type is not `const`, as required on assignment left-hand sides - - Additional restrictions may be added in the future. - - Each typing context needs to be resolved at some point. - This can happen immediately during its expansion in the following ways: - - - During expansion, a node with a fixed type is encountered; then, that type is applied to the context; or - - A type is enforced directly from a surrounding or otherwise associated context; or - - the context is supplied with a type hint through `apply_hint`, which it can either use to figure out its type - directly, or pass on to any number of registered `InferenceHook`s; - one of those *must* then provide the target type. - - If a type context cannot be resolved while it is being processed, its resolution needs to be deferred. - It must then be hooked into its surrounding (parent) context using an `InferenceHook`. - Through this hook, it receives two second chances for resolution: - - - If the surrounding context gets resolved to a type, the type of the nested context *must* be inferred - from that surrounding type - - If the surrounding context is supplied with a type hint, that type hint is given to the inference hook - which then *may* use that to infer the type of its nested context; - it that is successful, the hook must also provide the type for the surrounding context. + - A target type ``target_type``, which shall be applied to all expressions it covers + - A set of restrictions on the target type: + - ``require_nonconst`` to make sure the target type is not ``const``, + as required on assignment left-hand sides + - Additional restrictions may be added in the future. + + **Target type** + + Each typing context needs to be assigned its target type at some point. + The target type may be + + - predetermined by specifying it at the context's construction, or otherwise + - obtained from expressions covered by the context for which a type could be inferred, or otherwise + - inferred from the type of the enclosing context via an inference hook, or as a last resort + - determined from a type hint applied to the enclosing context via an inference hook. + + **Expansion** + + Expression nodes are added to a type context using either `apply_dtype` or `infer_dtype`. + In both cases, the context's target type will be applied to the node, + unless it already has a conflicting type. + If no target type is set yet, `infer_dtype` stores the node as *deferred*, + and the target type will be retroactively applied to it once it becomes known. + + **Type Hints and Inference Hooks** + + If a type context could not be resolved during its expansion, its type must be inferred from information + about its enclosing context. + To this end, it must be linked to its surrounding context using an `InferenceHook`. + If the surrounding context already has a target type, the inference hook is called directly with that type. + Otherwise, it will be called later when the surrounding context receives either its target type or a type hint. + + If the enclosing context is resolved to a type, the nested context *must* be resolved by the inference hook, + or an error must be raised. + If the enclosing context receives a type hint instead, the inference hook *may* infer both the target types of + its inner **and** its outer context. In that case, the type of the enclosing context must be returned by the hook + callback. """ def __init__( @@ -222,7 +236,7 @@ class TypeContext: """Infer the data type for the given expression. If the target_type of this context is already known, it will be applied to the given expression. - Otherwise, the expression is deferred, and a type will be applied to it as soon as `apply_type` is + Otherwise, the expression is deferred, and a type will be applied to it as soon as `apply_dtype` is called on this context. If the expression already has a data type set, it must be compatible with the target type @@ -235,6 +249,10 @@ class TypeContext: self._apply_target_type(expr) def _propagate_target_type(self): + """Propagates the target type to any registered inference hooks and applies it to any deferred nodes. + + Call after the target type of this context has been set. + """ assert self._target_type is not None for hook in self._inference_hooks: @@ -246,6 +264,7 @@ class TypeContext: self._deferred_exprs = [] def _apply_target_type(self, expr: PsExpression): + """Apply the target type to an expression node.""" assert self._target_type is not None if expr.dtype is not None: @@ -328,7 +347,8 @@ class TypeContext: else: return dtype == self._target_type - def _fix_constness(self, dtype: PsType, expr: PsExpression | None = None): + def _fix_constness(self, dtype: PsType, expr: PsExpression | None = None) -> PsType: + """Check and convert the constness of a type according to the ``require_nonconst`` restriction.""" if self._require_nonconst: if dtype.const: if expr is None: @@ -355,8 +375,8 @@ class Typifier: All nodes covered by the same typing context must have the same type. Starting from an expression's root, a typing context is implicitly expanded through - the recursive descent into a node's children. In particular, a child is typified within - the same context as its parent if the node's semantics require parent and child to have + the recursive descent into a node's children. In particular, a type context is extended from a parent + to its child node if the expressions's semantics require parent and child to have the same type (e.g. at arithmetic operators, mathematical functions, etc.). If a node's child is required to have a different type, a new context is opened. @@ -373,13 +393,13 @@ class Typifier: The following general rules apply: - - The context's ``default_dtype`` is applied to all untyped symbols encountered inside a right-hand side expression - - If an untyped symbol is encountered on an assignment's left-hand side, it will first be attempted to infer its - type from the right-hand side. If that fails, the context's ``default_dtype`` will be applied. - - It is an error if an untyped symbol occurs in the same type context as a typed symbol or constant - with a non-default data type. - - By default, all expressions receive a ``const`` type unless they occur on a (non-declaration) assignment's - left-hand side + - The context's ``default_dtype`` is applied to all untyped symbols encountered inside a right-hand side expression + - If an untyped symbol is encountered on an assignment's left-hand side, it will first be attempted to infer its + type from the right-hand side. If that fails, the context's ``default_dtype`` will be applied. + - It is an error if an untyped symbol occurs in the same type context as a typed symbol or constant + with a non-default data type. + - By default, all expressions receive a ``const`` type unless they occur on a (non-declaration) assignment's + left-hand side **Typing of symbol expressions**