diff --git a/src/pystencils/backend/arrays.py b/src/pystencils/backend/arrays.py index 8c7a9306f09e8d6ee6c5922705bb9426d17d355f..233f5d275357083a0fce56e5f450a1c5e934d3d6 100644 --- a/src/pystencils/backend/arrays.py +++ b/src/pystencils/backend/arrays.py @@ -52,6 +52,9 @@ from .types import ( PsIntegerType, PsUnsignedIntegerType, PsSignedIntegerType, + PsScalarType, + PsVectorType, + PsTypeError ) from .typed_expressions import PsTypedVariable, ExprOrConstant, PsTypedConstant @@ -293,3 +296,39 @@ class PsArrayAccess(pb.Subscript): def dtype(self) -> PsAbstractType: """Data type of this expression, i.e. the element type of the underlying array""" return self._base_ptr.array.element_type + + +class PsVectorArrayAccess(pb.AlgebraicLeaf): + mapper_method = intern("map_vector_array_access") + + def __init__(self, base_ptr: PsArrayBasePointer, base_index: ExprOrConstant, vector_width: int, stride: int = 1): + element_type = base_ptr.array.element_type + + if not isinstance(element_type, PsScalarType): + raise PsTypeError("Cannot generate vector accesses to arrays with non-scalar elements") + + self._base_ptr = base_ptr + self._base_index = base_index + self._vector_type = PsVectorType(element_type, vector_width, const=element_type.const) + self._stride = stride + + @property + def base_ptr(self) -> PsArrayBasePointer: + return self._base_ptr + + @property + def array(self) -> PsLinearizedArray: + return self._base_ptr.array + + @property + def base_index(self) -> ExprOrConstant: + return self._base_index + + @property + def dtype(self) -> PsVectorType: + """Data type of this expression, i.e. the resulting generic vector type""" + return self._vector_type + + @property + def stride(self) -> int: + return self._stride diff --git a/src/pystencils/backend/ast/__init__.py b/src/pystencils/backend/ast/__init__.py index 80e710c482370138840e224ab00b7bdfb8774539..7c372d0f4d7a1a9f7ded0b81323d4afa993a4f17 100644 --- a/src/pystencils/backend/ast/__init__.py +++ b/src/pystencils/backend/ast/__init__.py @@ -4,6 +4,7 @@ from .nodes import ( PsExpression, PsLvalueExpr, PsSymbolExpr, + PsStatement, PsAssignment, PsDeclaration, PsLoop, @@ -23,6 +24,7 @@ __all__ = [ "PsExpression", "PsLvalueExpr", "PsSymbolExpr", + "PsStatement", "PsAssignment", "PsDeclaration", "PsLoop", diff --git a/src/pystencils/backend/ast/kernelfunction.py b/src/pystencils/backend/ast/kernelfunction.py index 05ec77bdec1c7733d937f6ae5c1e110e209d945b..dd3029a254c5a84f4dcdb5dcb600d4a4e9a9ed66 100644 --- a/src/pystencils/backend/ast/kernelfunction.py +++ b/src/pystencils/backend/ast/kernelfunction.py @@ -8,6 +8,7 @@ from pymbolic.mapper.dependency import DependencyMapper from .nodes import PsAstNode, PsBlock, failing_cast from ..constraints import PsKernelConstraint +from ..platforms import Platform from ..typed_expressions import PsTypedVariable from ..arrays import PsLinearizedArray, PsArrayBasePointer, PsArrayAssocVar from ..jit import JitBase, no_jit @@ -69,13 +70,14 @@ class PsKernelFunction(PsAstNode): __match_args__ = ("body",) def __init__( - self, body: PsBlock, target: Target, name: str = "kernel", jit: JitBase = no_jit + self, body: PsBlock, target: Target, name: str, required_headers: set[str], jit: JitBase = no_jit ): self._body: PsBlock = body self._target = target self._name = name self._jit = jit + self._required_headers = required_headers self._constraints: list[PsKernelConstraint] = [] @property @@ -108,6 +110,10 @@ class PsKernelFunction(PsAstNode): def instruction_set(self) -> str | None: """For backward compatibility""" return None + + @property + def required_headers(self) -> set[str]: + return self._required_headers def get_children(self) -> tuple[PsAstNode, ...]: return (self._body,) @@ -136,11 +142,5 @@ class PsKernelFunction(PsAstNode): tuple(params_list), tuple(arrays), tuple(self._constraints) ) - def get_required_headers(self) -> set[str]: - # To Do: Headers from target/instruction set/... - from .collectors import collect_required_headers - - return collect_required_headers(self) - def compile(self) -> Callable[..., None]: return self._jit.compile(self) diff --git a/src/pystencils/backend/ast/nodes.py b/src/pystencils/backend/ast/nodes.py index ef73d8b16a532a39803417268f67130aeddbe4d7..03f1fa2976c236d07f0998450c008e7ac7410b65 100644 --- a/src/pystencils/backend/ast/nodes.py +++ b/src/pystencils/backend/ast/nodes.py @@ -135,6 +135,29 @@ class PsSymbolExpr(PsLvalueExpr): self._expr = symbol +class PsStatement(PsAstNode): + __match_args__ = ("expression") + + def __init__(self, expr: PsExpression): + self._expression = expr + + @property + def expression(self) -> PsExpression: + return self._expression + + @expression.setter + def expression(self, expr: PsExpression): + self._expression = expr + + def get_children(self) -> tuple[PsAstNode, ...]: + return (self._expression,) + + def set_child(self, idx: int, c: PsAstNode): + idx = [0][idx] + assert idx == 0 + self._expression = failing_cast(PsExpression, c) + + PsLvalue: TypeAlias = Variable | PsArrayAccess """Types of expressions that may occur on the left-hand side of assignments.""" diff --git a/src/pystencils/backend/emission.py b/src/pystencils/backend/emission.py index c76d118dbbde554e6c2e85481e151457c9e2c608..4c18bd1c945b5b022393863a148e259e8e14e3f0 100644 --- a/src/pystencils/backend/emission.py +++ b/src/pystencils/backend/emission.py @@ -7,6 +7,7 @@ from .ast import ( PsAstNode, PsBlock, PsExpression, + PsStatement, PsDeclaration, PsAssignment, PsLoop, @@ -76,6 +77,10 @@ class CAstPrinter: @visit.case(PsExpression) def pymb_expression(self, expr: PsExpression): return self._expr_printer(expr.expression) + + @visit.case(PsStatement) + def statement(self, stmt: PsStatement): + return self.indent(f"{self.visit(stmt.expression)};") @visit.case(PsDeclaration) def declaration(self, decl: PsDeclaration): @@ -90,7 +95,7 @@ class CAstPrinter: def assignment(self, asm: PsAssignment): lhs_code = self.visit(asm.lhs) rhs_code = self.visit(asm.rhs) - return self.indent(f"{lhs_code} = {rhs_code};\n") + return self.indent(f"{lhs_code} = {rhs_code};") @visit.case(PsLoop) def loop(self, loop: PsLoop): diff --git a/src/pystencils/backend/jit/cpu_extension_module.py b/src/pystencils/backend/jit/cpu_extension_module.py index f58e8cb1b2ad81fe30c502682205dec114af3712..680b48fe7e05e936e15853ac13b24c73327ffdcb 100644 --- a/src/pystencils/backend/jit/cpu_extension_module.py +++ b/src/pystencils/backend/jit/cpu_extension_module.py @@ -63,9 +63,9 @@ class PsKernelExtensioNModule: code = "" # Collect headers - headers = {"<math.h>", "<stdint.h>"} + headers = {"<stdint.h>"} for kernel in self._kernels.values(): - headers |= kernel.get_required_headers() + headers |= kernel.required_headers header_list = sorted(headers) header_list.insert(0, '"Python.h"') diff --git a/src/pystencils/backend/platforms/__init__.py b/src/pystencils/backend/platforms/__init__.py index 72eb2b762783647594ca7c3eef183695178c8cd4..7de61a12bbe6fc0b42f3282993ae699b7cfee9f0 100644 --- a/src/pystencils/backend/platforms/__init__.py +++ b/src/pystencils/backend/platforms/__init__.py @@ -1,3 +1,4 @@ -from .basic_cpu import BasicCpu +from .platform import Platform +from .generic_cpu import GenericCpu, GenericVectorCpu -__all__ = ["BasicCpu"] +__all__ = ["Platform", "GenericCpu", "GenericVectorCpu"] diff --git a/src/pystencils/backend/platforms/basic_cpu.py b/src/pystencils/backend/platforms/generic_cpu.py similarity index 54% rename from src/pystencils/backend/platforms/basic_cpu.py rename to src/pystencils/backend/platforms/generic_cpu.py index 27d32818a3393e77f856eb1da54c504b35f2412e..e9fe2ef149d85a01f51a1b7b5b1ff14ebafc0546 100644 --- a/src/pystencils/backend/platforms/basic_cpu.py +++ b/src/pystencils/backend/platforms/generic_cpu.py @@ -1,3 +1,8 @@ +from typing import Sequence +from abc import ABC, abstractmethod + +import pymbolic.primitives as pb + from .platform import Platform from ..kernelcreation.iteration_space import ( @@ -7,11 +12,18 @@ from ..kernelcreation.iteration_space import ( ) from ..ast import PsDeclaration, PsSymbolExpr, PsExpression, PsLoop, PsBlock +from ..types import PsVectorType, PsCustomType from ..typed_expressions import PsTypedConstant -from ..arrays import PsArrayAccess +from ..arrays import PsArrayAccess, PsVectorArrayAccess +from ..transformations.vector_intrinsics import IntrinsicOps + + +class GenericCpu(Platform): + @property + def required_headers(self) -> set[str]: + return {"<math.h>"} -class BasicCpu(Platform): def materialize_iteration_space( self, body: PsBlock, ispace: IterationSpace ) -> PsBlock: @@ -69,3 +81,40 @@ class BasicCpu(Platform): ) return PsBlock([loop]) + + +class IntrinsicsError(Exception): + """Exception indicating a fatal error during intrinsic materialization.""" + + +class GenericVectorCpu(GenericCpu, ABC): + + @abstractmethod + def type_intrinsic(self, vector_type: PsVectorType) -> PsCustomType: + """Return the intrinsic vector type for the given generic vector type, + or raise an `IntrinsicsError` if type is not supported.""" + + @abstractmethod + def constant_vector(self, c: PsTypedConstant) -> pb.Expression: + """Return an expression that initializes a constant vector, + or raise an `IntrinsicsError` if not supported.""" + + @abstractmethod + def op_intrinsic( + self, op: IntrinsicOps, vtype: PsVectorType, args: Sequence[pb.Expression] + ) -> pb.Expression: + """Return an expression intrinsically invoking the given operation + on the given arguments with the given vector type, + or raise an `IntrinsicsError` if not supported.""" + + @abstractmethod + def vector_load(self, acc: PsVectorArrayAccess) -> pb.Expression: + """Return an expression intrinsically performing a vector load, + or raise an `IntrinsicsError` if not supported.""" + + @abstractmethod + def vector_store( + self, acc: PsVectorArrayAccess, arg: pb.Expression + ) -> pb.Expression: + """Return an expression intrinsically performing a vector store, + or raise an `IntrinsicsError` if not supported.""" diff --git a/src/pystencils/backend/platforms/platform.py b/src/pystencils/backend/platforms/platform.py index 8013837f4e72398ddde3d59f302e4892a3eb91bc..ddac626157ca85b94f7d289a9461b87f4d3703cb 100644 --- a/src/pystencils/backend/platforms/platform.py +++ b/src/pystencils/backend/platforms/platform.py @@ -18,6 +18,11 @@ class Platform(ABC): def __init__(self, ctx: KernelCreationContext) -> None: self._ctx = ctx + @property + @abstractmethod + def required_headers(self) -> set[str]: + pass + @abstractmethod def materialize_iteration_space( self, block: PsBlock, ispace: IterationSpace diff --git a/src/pystencils/backend/transformations/__init__.py b/src/pystencils/backend/transformations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..1abd074af598a8b86e34a705f8e10410dc202664 --- /dev/null +++ b/src/pystencils/backend/transformations/__init__.py @@ -0,0 +1,5 @@ +from .erase_anonymous_structs import EraseAnonymousStructTypes + +__all__ = [ + "EraseAnonymousStructTypes" +] \ No newline at end of file diff --git a/src/pystencils/backend/kernelcreation/transformations.py b/src/pystencils/backend/transformations/erase_anonymous_structs.py similarity index 97% rename from src/pystencils/backend/kernelcreation/transformations.py rename to src/pystencils/backend/transformations/erase_anonymous_structs.py index 07e6a5b3791adbbf48f6a5e5e30ab98bb95d41b1..97c50b7eb31effe1bfff95c9d1bb13d248be53e1 100644 --- a/src/pystencils/backend/kernelcreation/transformations.py +++ b/src/pystencils/backend/transformations/erase_anonymous_structs.py @@ -5,7 +5,7 @@ from typing import TypeVar import pymbolic.primitives as pb from pymbolic.mapper import IdentityMapper -from .context import KernelCreationContext +from ..kernelcreation.context import KernelCreationContext from ..ast import PsAstNode, PsExpression from ..arrays import PsArrayAccess, TypeErasedBasePointer diff --git a/src/pystencils/backend/transformations/vector_intrinsics.py b/src/pystencils/backend/transformations/vector_intrinsics.py new file mode 100644 index 0000000000000000000000000000000000000000..4cfd112c978bcd4713e7cf250075a35dd0eb1969 --- /dev/null +++ b/src/pystencils/backend/transformations/vector_intrinsics.py @@ -0,0 +1,107 @@ +from typing import TypeVar, TYPE_CHECKING +from enum import Enum, auto + +import pymbolic.primitives as pb +from pymbolic.mapper import IdentityMapper + +from ..ast import PsAstNode, PsExpression, PsAssignment, PsStatement +from ..types import PsVectorType +from ..typed_expressions import PsTypedVariable, PsTypedConstant, ExprOrConstant +from ..arrays import PsVectorArrayAccess +from ..exceptions import PsInternalCompilerError + +if TYPE_CHECKING: + from ..platforms import GenericVectorCpu + + +__all__ = ["IntrinsicOps", "MaterializeVectorIntrinsics"] + +NodeT = TypeVar("NodeT", bound=PsAstNode) + + +class IntrinsicOps(Enum): + ADD = auto() + SUB = auto() + MUL = auto() + DIV = auto() + FMA = auto() + + +class VectorizationError(Exception): + """Exception indicating a fatal error during vectorization.""" + + +class VecTypeCtx: + def __init__(self): + self._dtype: None | PsVectorType = None + + def get(self) -> PsVectorType | None: + return self._dtype + + def set(self, dtype: PsVectorType): + if self._dtype is not None: + raise PsInternalCompilerError("Ambiguous vector types.") + self._dtype = dtype + + def reset(self): + self._dtype = None + + +class MaterializeVectorIntrinsics(IdentityMapper): + def __init__(self, platform: GenericVectorCpu): + self._platform = platform + + def __call__(self, node: PsAstNode) -> PsAstNode: + match node: + case PsExpression(expr): + # descend into expr + node.expression = self.rec(expr, VecTypeCtx()) + return node + case PsAssignment(lhs, rhs) if isinstance(lhs.expression, PsVectorArrayAccess): + vc = VecTypeCtx() + vc.set(lhs.expression.dtype) + store_arg = self.rec(rhs.expression, vc) + return PsStatement(PsExpression(self._platform.vector_store(lhs.expression, store_arg))) + case other: + for c in other.children: + self(c) + return node + + def map_typed_variable(self, tv: PsTypedVariable, vc: VecTypeCtx) -> PsTypedVariable: + if isinstance(tv.dtype, PsVectorType): + intrin_type = self._platform.type_intrinsic(tv.dtype) + vc.set(tv.dtype) + return PsTypedVariable(tv.name, intrin_type) + else: + return tv + + def map_constant(self, c: PsTypedConstant, vc: VecTypeCtx) -> ExprOrConstant: + if isinstance(c.dtype, PsVectorType): + vc.set(c.dtype) + return self._platform.constant_vector(c) + else: + return c + + def map_vector_array_access(self, acc: PsVectorArrayAccess, vc: VecTypeCtx) -> pb.Expression: + vc.set(acc.dtype) + return self._platform.vector_load(acc) + + def map_sum(self, expr: pb.Sum, vc: VecTypeCtx) -> pb.Expression: + args = [self.rec(arg, vc) for arg in expr.children] + vtype = vc.get() + if vtype is not None: + if len(args) != 2: + raise VectorizationError("Cannot vectorize non-binary sums") + return self._platform.op_intrinsic(IntrinsicOps.ADD, vtype, args) + else: + return expr + + def map_product(self, expr: pb.Product, vc: VecTypeCtx) -> pb.Expression: + args = [self.rec(arg, vc) for arg in expr.children] + vtype = vc.get() + if vtype is not None: + if len(args) != 2: + raise VectorizationError("Cannot vectorize non-binary products") + return self._platform.op_intrinsic(IntrinsicOps.MUL, vtype, args) + else: + return expr diff --git a/src/pystencils/backend/types/__init__.py b/src/pystencils/backend/types/__init__.py index d7eb490c5d3973ca682df0144e56edd3df96cce4..4aee28086fe043f6e6dd5c8ee80caf86958d3811 100644 --- a/src/pystencils/backend/types/__init__.py +++ b/src/pystencils/backend/types/__init__.py @@ -4,6 +4,7 @@ from .basic_types import ( PsStructType, PsNumericType, PsScalarType, + PsVectorType, PsPointerType, PsIntegerType, PsUnsignedIntegerType, @@ -24,6 +25,7 @@ __all__ = [ "PsPointerType", "PsNumericType", "PsScalarType", + "PsVectorType", "PsIntegerType", "PsUnsignedIntegerType", "PsSignedIntegerType", diff --git a/src/pystencils/backend/types/basic_types.py b/src/pystencils/backend/types/basic_types.py index 49d15968e8b92b2ee3a69cd099987e08751b2381..178eeafd7b0e14b4a3259dac75d102cb215abd71 100644 --- a/src/pystencils/backend/types/basic_types.py +++ b/src/pystencils/backend/types/basic_types.py @@ -66,23 +66,20 @@ class PsAbstractType(ABC): return "const " if self._const else "" @abstractmethod - def c_string(self) -> str: - ... + def c_string(self) -> str: ... # ------------------------------------------------------------------------------------------- # Dunder Methods # ------------------------------------------------------------------------------------------- @abstractmethod - def __eq__(self, other: object) -> bool: - ... + def __eq__(self, other: object) -> bool: ... def __str__(self) -> str: return self.c_string() @abstractmethod - def __hash__(self) -> int: - ... + def __hash__(self) -> int: ... class PsCustomType(PsAbstractType): @@ -274,6 +271,22 @@ class PsNumericType(PsAbstractType, ABC): PsTypeError: If the given value cannot be interpreted in this type. """ + @abstractmethod + def is_int(self) -> bool: ... + + @abstractmethod + def is_sint(self) -> bool: ... + + @abstractmethod + def is_uint(self) -> bool: ... + + @abstractmethod + def is_float(self) -> bool: ... + + +class PsScalarType(PsNumericType, ABC): + """Class to model scalar numeric types.""" + @abstractmethod def create_literal(self, value: Any) -> str: """Create a C numerical literal for a constant of this type. @@ -282,37 +295,103 @@ class PsNumericType(PsAbstractType, ABC): PsTypeError: If the given value's type is not the numeric type's compiler-internal representation. """ - @abstractmethod def is_int(self) -> bool: - ... + return isinstance(self, PsIntegerType) - @abstractmethod def is_sint(self) -> bool: - ... + return isinstance(self, PsIntegerType) and self.signed - @abstractmethod def is_uint(self) -> bool: - ... + return isinstance(self, PsIntegerType) and not self.signed - @abstractmethod def is_float(self) -> bool: - ... + return isinstance(self, PsIeeeFloatType) -class PsScalarType(PsNumericType, ABC): - """Class to model scalar numeric types.""" +class PsVectorType(PsNumericType): + """Class to model packed vectors of numeric type. + + Args: + element_type: Underlying scalar data type + num_entries: Number of entries in the vector + """ + + def __init__( + self, scalar_type: PsScalarType, vector_width: int, const: bool = False + ): + super().__init__(const) + self._vector_width = vector_width + self._scalar_type = constify(scalar_type) if const else deconstify(scalar_type) + + @property + def scalar_type(self) -> PsScalarType: + return self._scalar_type + + @property + def vector_width(self) -> int: + return self._vector_width def is_int(self) -> bool: - return isinstance(self, PsIntegerType) + return self._scalar_type.is_int() def is_sint(self) -> bool: - return isinstance(self, PsIntegerType) and self.signed + return self._scalar_type.is_sint() def is_uint(self) -> bool: - return isinstance(self, PsIntegerType) and not self.signed + return self._scalar_type.is_uint() def is_float(self) -> bool: - return isinstance(self, PsIeeeFloatType) + return self._scalar_type.is_float() + + @property + def itemsize(self) -> int | None: + if self._scalar_type.itemsize is None: + return None + else: + return self._vector_width * self._scalar_type.itemsize + + @property + def numpy_dtype(self): + return np.dtype((self._scalar_type.numpy_dtype, (self._vector_width,))) + + def create_constant(self, value: Any) -> Any: + if ( + isinstance(value, np.ndarray) + and value.dtype == self.scalar_type.numpy_dtype + and value.shape == (self._vector_width,) + ): + return value.copy() + + element = self._scalar_type.create_constant(value) + return np.array( + [element] * self._vector_width, dtype=self.scalar_type.numpy_dtype + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, PsVectorType): + return False + + return ( + self._base_equal(other) + and self._scalar_type == other._scalar_type + and self._vector_width == other._vector_width + ) + + def __hash__(self) -> int: + return hash( + ("PsVectorType", self._scalar_type, self._vector_width, self._const) + ) + + def c_string(self) -> str: + raise PsInternalCompilerError( + "Cannot retrieve C type string for generic vector types." + ) + + def __str__(self) -> str: + return f"vector[{self._scalar_type}, {self._vector_width}]" + + def __repr__(self) -> str: + return f"PsVectorType( scalar_type={repr(self._scalar_type)}, vector_width={self._vector_width}, const={self.const} )" class PsIntegerType(PsScalarType, ABC): diff --git a/src/pystencils/kernelcreation.py b/src/pystencils/kernelcreation.py index 852401aa43913e6ab70e8f2fe58a3b605e9578c3..5c1910d2a993b3fafca0adab064bd915bc0a8569 100644 --- a/src/pystencils/kernelcreation.py +++ b/src/pystencils/kernelcreation.py @@ -11,7 +11,9 @@ from .backend.kernelcreation.iteration_space import ( create_sparse_iteration_space, create_full_iteration_space, ) -from .backend.kernelcreation.transformations import EraseAnonymousStructTypes + +from .backend.ast.collectors import collect_required_headers +from .backend.transformations import EraseAnonymousStructTypes from .enums import Target from .sympyextensions import AssignmentCollection, Assignment @@ -54,10 +56,10 @@ def create_kernel( match config.target: case Target.CPU: - from .backend.platforms import BasicCpu + from .backend.platforms import GenericCpu # TODO: CPU platform should incorporate instruction set info, OpenMP, etc. - platform = BasicCpu(ctx) + platform = GenericCpu(ctx) case _: # TODO: CUDA/HIP platform # TODO: SYCL platform (?) @@ -73,8 +75,9 @@ def create_kernel( kernel_ast = platform.optimize(kernel_ast) assert config.jit is not None + req_headers = collect_required_headers(kernel_ast) | platform.required_headers function = PsKernelFunction( - kernel_ast, config.target, name=config.function_name, jit=config.jit + kernel_ast, config.target, config.function_name, req_headers, jit=config.jit ) function.add_constraints(*ctx.constraints) diff --git a/tests/nbackend/kernelcreation/platform/test_basic_cpu.py b/tests/nbackend/kernelcreation/platform/test_basic_cpu.py index d42981f8ca2e95bee2bdff7d1baf8062bb1fbd75..028ffc12202d43b69d92b505d73a2c67ba90d95c 100644 --- a/tests/nbackend/kernelcreation/platform/test_basic_cpu.py +++ b/tests/nbackend/kernelcreation/platform/test_basic_cpu.py @@ -9,14 +9,14 @@ from pystencils.backend.kernelcreation import ( from pystencils.backend.ast import PsBlock, PsLoop, PsComment, dfs_preorder -from pystencils.backend.platforms import BasicCpu +from pystencils.backend.platforms import GenericCpu @pytest.mark.parametrize("layout", ["fzyx", "zyxf", "c", "f"]) def test_loop_nest(layout): ctx = KernelCreationContext() body = PsBlock([PsComment("Loop body goes here")]) - platform = BasicCpu(ctx) + platform = GenericCpu(ctx) # FZYX Order archetype_field = Field.create_generic("fzyx_field", spatial_dimensions=3, layout=layout)