Skip to content
Snippets Groups Projects
Commit 1344ac46 authored by Frederik Hennig's avatar Frederik Hennig
Browse files

create full iteration space

parent 9d95a959
No related merge requests found
...@@ -2,5 +2,13 @@ class PsInternalCompilerError(Exception): ...@@ -2,5 +2,13 @@ class PsInternalCompilerError(Exception):
pass pass
class PsOptionsError(Exception):
pass
class PsInputError(Exception):
pass
class PsMalformedAstException(Exception): class PsMalformedAstException(Exception):
pass pass
...@@ -3,7 +3,7 @@ from typing import cast ...@@ -3,7 +3,7 @@ from typing import cast
from dataclasses import dataclass from dataclasses import dataclass
from ...field import Field from ...field import Field, FieldType
from ...typing import TypedSymbol, BasicType from ...typing import TypedSymbol, BasicType
from ..arrays import PsLinearizedArray from ..arrays import PsLinearizedArray
...@@ -12,13 +12,16 @@ from ..types.quick import make_type ...@@ -12,13 +12,16 @@ from ..types.quick import make_type
from ..constraints import PsKernelConstraint from ..constraints import PsKernelConstraint
from ..exceptions import PsInternalCompilerError from ..exceptions import PsInternalCompilerError
from .options import KernelCreationOptions
from .iteration_space import IterationSpace, FullIterationSpace, SparseIterationSpace from .iteration_space import IterationSpace, FullIterationSpace, SparseIterationSpace
@dataclass @dataclass
class PsArrayDescriptor: class FieldsInKernel:
field: Field domain_fields: set[Field] = set()
array: PsLinearizedArray index_fields: set[Field] = set()
custom_fields: set[Field] = set()
buffer_fields: set[Field] = set()
class KernelCreationContext: class KernelCreationContext:
...@@ -44,16 +47,21 @@ class KernelCreationContext: ...@@ -44,16 +47,21 @@ class KernelCreationContext:
or full iteration space. or full iteration space.
""" """
def __init__(self, index_dtype: PsIntegerType): def __init__(self, options: KernelCreationOptions):
self._index_dtype = index_dtype self._options = options
self._arrays: dict[Field, PsLinearizedArray] = dict() self._arrays: dict[Field, PsLinearizedArray] = dict()
self._constraints: list[PsKernelConstraint] = [] self._constraints: list[PsKernelConstraint] = []
self._fields_collection = FieldsInKernel()
self._ispace: IterationSpace | None = None self._ispace: IterationSpace | None = None
@property
def options(self) -> KernelCreationOptions:
return self._options
@property @property
def index_dtype(self) -> PsIntegerType: def index_dtype(self) -> PsIntegerType:
return self._index_dtype return self._options.index_dtype
def add_constraints(self, *constraints: PsKernelConstraint): def add_constraints(self, *constraints: PsKernelConstraint):
self._constraints += constraints self._constraints += constraints
...@@ -62,6 +70,24 @@ class KernelCreationContext: ...@@ -62,6 +70,24 @@ class KernelCreationContext:
def constraints(self) -> tuple[PsKernelConstraint, ...]: def constraints(self) -> tuple[PsKernelConstraint, ...]:
return tuple(self._constraints) return tuple(self._constraints)
@property
def fields(self) -> FieldsInKernel:
return self._fields_collection
def add_field(self, field: Field):
"""Add the given field to the context's fields collection"""
match field.field_type:
case FieldType.GENERIC | FieldType.STAGGERED | FieldType.STAGGERED_FLUX:
self._fields_collection.domain_fields.add(field)
case FieldType.BUFFER:
self._fields_collection.buffer_fields.add(field)
case FieldType.INDEXED:
self._fields_collection.index_fields.add(field)
case FieldType.CUSTOM:
self._fields_collection.custom_fields.add(field)
case _:
assert False, "unreachable code"
def get_array(self, field: Field) -> PsLinearizedArray: def get_array(self, field: Field) -> PsLinearizedArray:
if field not in self._arrays: if field not in self._arrays:
arr_shape = tuple( arr_shape = tuple(
...@@ -90,6 +116,8 @@ class KernelCreationContext: ...@@ -90,6 +116,8 @@ class KernelCreationContext:
return self._arrays[field] return self._arrays[field]
def set_iteration_space(self, ispace: IterationSpace): def set_iteration_space(self, ispace: IterationSpace):
if self._ispace is not None:
raise PsInternalCompilerError("Iteration space was already set.")
self._ispace = ispace self._ispace = ispace
def get_iteration_space(self) -> IterationSpace: def get_iteration_space(self) -> IterationSpace:
......
from types import EllipsisType from typing import Sequence
from itertools import chain
from ...simp import AssignmentCollection from ...simp import AssignmentCollection
from ...field import Field from ...field import Field, FieldType
from ...kernel_contrains_check import KernelConstraintsCheck from ...kernel_contrains_check import KernelConstraintsCheck
from ..types.quick import SInt
from ..ast import PsBlock from ..ast import PsBlock
from .context import KernelCreationContext, FullIterationSpace from .context import KernelCreationContext, IterationSpace
from .freeze import FreezeExpressions from .freeze import FreezeExpressions
from .typification import Typifier from .typification import Typifier
from .options import KernelCreationOptions
from ..exceptions import PsInputError, PsInternalCompilerError
# flake8: noqa # flake8: noqa
def create_domain_kernel(assignments: AssignmentCollection): def create_kernel(assignments: AssignmentCollection, options: KernelCreationOptions):
# TODO: Assemble configuration
# 1. Prepare context # 1. Prepare context
ctx = KernelCreationContext(SInt(64)) # TODO: how to determine index type? ctx = KernelCreationContext(options)
# 2. Check kernel constraints and collect all fields # 2. Check kernel constraints and collect all fields
"""
TODO: Replace the KernelConstraintsCheck by a KernelAnalysis pass.
The kernel analysis should:
- Check constraints on the assignments (SSA form, independence conditions, ...)
- Collect all fields and register them in the context
- Maybe collect all field accesses and register them at the context
Running all this analysis in a single pass will likely improve compiler performance
since no additional searches, e.g. for field accesses, are necessary later.
"""
check = KernelConstraintsCheck() # TODO: config check = KernelConstraintsCheck() # TODO: config
check.visit(assignments) check.visit(assignments)
# All steps up to this point are the same in domain and indexed kernels; # Collect all fields
# the difference now comes with the iteration space. for f in chain(check.fields_written, check.fields_read):
# ctx.add_field(f)
# Domain kernels create a full iteration space from their iteration slice
# which is either explicitly given or computed from ghost layer requirements.
# Indexed kernels, on the other hand, have to create a sparse iteration space
# from one index list.
# 3. Create iteration space # 3. Create iteration space
ghost_layers: int = NotImplemented # determine required ghost layers ispace: IterationSpace = (
common_shape: tuple[ create_sparse_iteration_space(ctx, assignments)
int | EllipsisType, ... if len(ctx.fields.index_fields) > 0
] = NotImplemented # unify field shapes, add parameter constraints else create_full_iteration_space(ctx, assignments)
# don't forget custom iteration slice
ispace: FullIterationSpace = (
NotImplemented # create from ghost layers and with given shape
) )
ctx.set_iteration_space(ispace)
# 4. Freeze assignments # 4. Freeze assignments
# This call is the same for both domain and indexed kernels # This call is the same for both domain and indexed kernels
freeze = FreezeExpressions(ctx) freeze = FreezeExpressions(ctx)
...@@ -66,3 +74,63 @@ def create_domain_kernel(assignments: AssignmentCollection): ...@@ -66,3 +74,63 @@ def create_domain_kernel(assignments: AssignmentCollection):
# - Loop Splitting, Tiling, Blocking # - Loop Splitting, Tiling, Blocking
# 8. Create and return kernel function. # 8. Create and return kernel function.
def create_sparse_iteration_space(
ctx: KernelCreationContext, assignments: AssignmentCollection
) -> IterationSpace:
return NotImplemented
def create_full_iteration_space(
ctx: KernelCreationContext, assignments: AssignmentCollection
) -> IterationSpace:
assert not ctx.fields.index_fields
# Collect all relative accesses into domain fields
def access_filter(acc: Field.Access):
return acc.field.field_type in (
FieldType.GENERIC,
FieldType.STAGGERED,
FieldType.STAGGERED_FLUX,
)
domain_field_accesses = assignments.atoms(Field.Access)
domain_field_accesses = set(filter(access_filter, domain_field_accesses))
# The following scenarios exist:
# - We have at least one domain field -> find the common field and use it to determine the iteration region
# - We have no domain fields, but at least one custom field -> determine common field from custom fields
# - We have neither domain nor custom fields -> Error
from ...transformations import get_common_field
if len(domain_field_accesses) > 0:
archetype_field = get_common_field(ctx.fields.domain_fields)
inferred_gls = max(
[fa.required_ghost_layers for fa in domain_field_accesses]
)
elif len(ctx.fields.custom_fields) > 0:
archetype_field = get_common_field(ctx.fields.custom_fields)
inferred_gls = 0
else:
raise PsInputError(
"Unable to construct iteration space: The kernel contains no accesses to domain or custom fields."
)
# If the user provided a ghost layer specification, use that
# Otherwise, if an iteration slice was specified, use that
# Otherwise, use the inferred ghost layers
from .iteration_space import FullIterationSpace
if ctx.options.ghost_layers is not None:
return FullIterationSpace.create_with_ghost_layers(
ctx, archetype_field, ctx.options.ghost_layers
)
elif ctx.options.iteration_slice is not None:
raise PsInternalCompilerError("Iteration slices not supported yet")
else:
return FullIterationSpace.create_with_ghost_layers(
ctx, archetype_field, inferred_gls
)
...@@ -6,7 +6,6 @@ from functools import reduce ...@@ -6,7 +6,6 @@ from functools import reduce
from operator import mul from operator import mul
from ...field import Field from ...field import Field
from ...transformations import get_common_field
from ..typed_expressions import ( from ..typed_expressions import (
PsTypedVariable, PsTypedVariable,
...@@ -57,14 +56,13 @@ class FullIterationSpace(IterationSpace): ...@@ -57,14 +56,13 @@ class FullIterationSpace(IterationSpace):
@staticmethod @staticmethod
def create_with_ghost_layers( def create_with_ghost_layers(
ctx: KernelCreationContext, ctx: KernelCreationContext,
fields: set[Field], archetype_field: Field,
ghost_layers: int | Sequence[int | tuple[int, int]], ghost_layers: int | Sequence[int | tuple[int, int]],
) -> FullIterationSpace: ) -> FullIterationSpace:
"""Create an iteration space for a collection of fields with ghost layers.""" """Create an iteration space for a collection of fields with ghost layers."""
repr_field: Field = get_common_field(fields) archetype_array = ctx.get_array(archetype_field)
repr_arr = ctx.get_array(repr_field) dim = archetype_field.spatial_dimensions
dim = repr_field.spatial_dimensions
counters = [ counters = [
PsTypedVariable(name, ctx.index_dtype) PsTypedVariable(name, ctx.index_dtype)
for name in Defaults.spatial_counter_names for name in Defaults.spatial_counter_names
...@@ -92,7 +90,7 @@ class FullIterationSpace(IterationSpace): ...@@ -92,7 +90,7 @@ class FullIterationSpace(IterationSpace):
dimensions = [ dimensions = [
FullIterationSpace.Dimension(gl_left, shape - gl_right, one, ctr) FullIterationSpace.Dimension(gl_left, shape - gl_right, one, ctr)
for (gl_left, gl_right), shape, ctr in zip( for (gl_left, gl_right), shape, ctr in zip(
ghost_layer_exprs, repr_arr.shape, counters, strict=True ghost_layer_exprs, archetype_array.shape, counters, strict=True
) )
] ]
......
from typing import Sequence
from dataclasses import dataclass
from ...enums import Target
from ..exceptions import PsOptionsError
from ..types import PsIntegerType
from .defaults import Sympy as SpDefaults
@dataclass
class KernelCreationOptions:
"""Options for create_kernel."""
target: Target = Target.CPU
"""The code generation target.
TODO: Enhance `Target` from enum to a larger target spec, e.g. including vectorization architecture, ...
"""
function_name: str = "kernel"
"""Name of the generated function"""
ghost_layers: None | int | Sequence[int | tuple[int, int]] = None
"""Specifies the number of ghost layers of the iteration region.
Options:
- `None`: Required ghost layers are inferred from field accesses
- `int`: A uniform number of ghost layers in each spatial coordinate is applied
- `Sequence[int, tuple[int, int]]`: Ghost layers are specified for each spatial coordinate.
In each coordinate, a single integer specifies the ghost layers at both the lower and upper iteration limit,
while a pair of integers specifies the lower and upper ghost layers separately.
When manually specifying ghost layers, it is the user's responsibility to avoid out-of-bounds memory accesses.
If `ghost_layers=None` is specified, the iteration region may otherwise be set using the `iteration_slice` option.
"""
iteration_slice: None | tuple[slice, ...] = None
"""Specifies the kernel's iteration slice.
`iteration_slice` may only be set if `ghost_layers = None`.
If it is set, a slice must be specified for each spatial coordinate.
TODO: Specification of valid slices and their behaviour
"""
index_dtype: PsIntegerType = SpDefaults.index_dtype
"""Data type used for all index calculations."""
def __post_init__(self):
if self.iteration_slice is not None and self.ghost_layers is not None:
raise PsOptionsError(
"Parameters `iteration_slice` and `ghost_layers` are mutually exclusive; "
"at most one of them may be set."
)
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment