From f4629978b405ad6dc05a9d3de4019ea8ab92a2e0 Mon Sep 17 00:00:00 2001 From: Frederik Hennig <frederik.hennig@fau.de> Date: Wed, 18 Dec 2024 14:47:12 +0100 Subject: [PATCH] Extend mdspan interface and fix mdspan memory layout mapping --- docs/source/api/composer.rst | 6 +- docs/source/api/lang.rst | 16 ++- docs/source/conf.py | 7 +- src/pystencilssfg/composer/basic_composer.py | 3 +- src/pystencilssfg/emission/clang_format.py | 3 +- src/pystencilssfg/lang/cpp/std_mdspan.py | 125 ++++++++++++++---- src/pystencilssfg/lang/expressions.py | 7 +- tests/generator_scripts/index.yaml | 5 + .../source/JacobiMdspan.harness.cpp | 12 +- .../generator_scripts/source/JacobiMdspan.py | 6 +- .../source/MdSpanFixedShape.harness.cpp | 35 +++++ .../source/MdSpanFixedShapeLayouts.py | 55 ++++++++ .../source/MdSpanLbStreaming.harness.cpp | 91 +++++++++++++ .../source/MdSpanLbStreaming.py | 62 +++++++++ 14 files changed, 388 insertions(+), 45 deletions(-) create mode 100644 tests/generator_scripts/source/MdSpanFixedShape.harness.cpp create mode 100644 tests/generator_scripts/source/MdSpanFixedShapeLayouts.py create mode 100644 tests/generator_scripts/source/MdSpanLbStreaming.harness.cpp create mode 100644 tests/generator_scripts/source/MdSpanLbStreaming.py diff --git a/docs/source/api/composer.rst b/docs/source/api/composer.rst index a969d4f..667b0de 100644 --- a/docs/source/api/composer.rst +++ b/docs/source/api/composer.rst @@ -1,6 +1,6 @@ -*************************************** -Composer API (`pystencilssfg.composer`) -*************************************** +***************************************** +Composer API (``pystencilssfg.composer``) +***************************************** .. autoclass:: pystencilssfg.composer.SfgComposer :members: diff --git a/docs/source/api/lang.rst b/docs/source/api/lang.rst index efc69d9..c83317e 100644 --- a/docs/source/api/lang.rst +++ b/docs/source/api/lang.rst @@ -9,8 +9,20 @@ Expressions .. automodule:: pystencilssfg.lang.expressions :members: -C++ Standard Library (`pystencilssfg.lang.cpp`) ------------------------------------------------ +Header Files +------------ + +.. automodule:: pystencilssfg.lang.headers + :members: + +Data Types +---------- + +.. automodule:: pystencilssfg.lang.types + :members: + +C++ Standard Library (``pystencilssfg.lang.cpp``) +------------------------------------------------- Quick Access ^^^^^^^^^^^^ diff --git a/docs/source/conf.py b/docs/source/conf.py index 40d5a0f..4bdf700 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -65,12 +65,15 @@ html_theme_options = { intersphinx_mapping = { "python": ("https://docs.python.org/3.8", None), - "numpy": ("https://docs.scipy.org/doc/numpy/", None), - "matplotlib": ("https://matplotlib.org/", None), + "numpy": ("https://numpy.org/doc/stable/", None), "sympy": ("https://docs.sympy.org/latest/", None), "pystencils": ("https://da15siwa.pages.i10git.cs.fau.de/dev-docs/pystencils-nbackend/", None), } +# References + +# Treat `single-quoted` code blocks as references to any +default_role = "any" # Autodoc options diff --git a/src/pystencilssfg/composer/basic_composer.py b/src/pystencilssfg/composer/basic_composer.py index c48410c..90146e5 100644 --- a/src/pystencilssfg/composer/basic_composer.py +++ b/src/pystencilssfg/composer/basic_composer.py @@ -286,7 +286,7 @@ class SfgBasicComposer(SfgIComposer): When using `call`, the given kernel will simply be called as a function. To invoke a GPU kernel on a specified launch grid, use `cuda_invoke` - or the interfaces of `pystencilssfg.extensions.sycl` instead. + or the interfaces of ``pystencilssfg.extensions.sycl`` instead. Args: kernel_handle: Handle to a kernel previously added to some kernel namespace. @@ -300,6 +300,7 @@ class SfgBasicComposer(SfgIComposer): threads_per_block: ExprLike, stream: ExprLike | None, ): + """Dispatch a CUDA kernel to the device.""" num_blocks_str = str(num_blocks) tpb_str = str(threads_per_block) stream_str = str(stream) if stream is not None else None diff --git a/src/pystencilssfg/emission/clang_format.py b/src/pystencilssfg/emission/clang_format.py index 53540d3..1b15e8c 100644 --- a/src/pystencilssfg/emission/clang_format.py +++ b/src/pystencilssfg/emission/clang_format.py @@ -29,7 +29,8 @@ def invoke_clang_format(code: str, options: ClangFormatOptions) -> str: binary = options.get_option("binary") force = options.get_option("force") - args = [binary, f"--style={options.code_style}"] + style = options.get_option("code_style") + args = [binary, f"--style={style}"] if not shutil.which(binary): if force: diff --git a/src/pystencilssfg/lang/cpp/std_mdspan.py b/src/pystencilssfg/lang/cpp/std_mdspan.py index 2ede108..5f80ad6 100644 --- a/src/pystencilssfg/lang/cpp/std_mdspan.py +++ b/src/pystencilssfg/lang/cpp/std_mdspan.py @@ -11,94 +11,164 @@ from pystencils.types import ( from pystencilssfg.lang.expressions import AugExpr -from ...lang import SrcField, IFieldExtraction, cpptype, Ref, HeaderFile +from ...lang import SrcField, IFieldExtraction, cpptype, Ref, HeaderFile, ExprLike class StdMdspan(SrcField): """Represents an `std::mdspan` instance. - **On Standard Library Adoption** + The `std::mdspan <https://en.cppreference.com/w/cpp/container/mdspan>`_ + provides non-owning views into contiguous or strided n-dimensional arrays. + It has been added to the C++ STL with the C++23 standard. + As such, it is a natural data structure to target with pystencils kernels. - Since `std::mdspan` is not yet widely adopted + **Concerning Headers and Namespaces** + + Since ``std::mdspan`` is not yet widely adopted (libc++ ships it as of LLVM 18, but GCC libstdc++ does not include it yet), you might have to manually include an implementation in your project - (you can get a reference implementation [here](https://github.com/kokkos/mdspan)). + (you can get a reference implementation at https://github.com/kokkos/mdspan). However, when working with a non-standard mdspan implementation, the path to its the header and the namespace it is defined in will likely be different. - To tell pystencils-sfg which headers to include and which namespace to use for `mdspan`, - use `StdMdspan.configure`. + To tell pystencils-sfg which headers to include and which namespace to use for ``mdspan``, + use `StdMdspan.configure`; + for instance, adding this call before creating any ``mdspan`` objects will + set their namespace to `std::experimental`, and require ``<experimental/mdspan>`` to be imported: + + >>> from pystencilssfg.lang.cpp import std + >>> std.mdspan.configure("std::experimental", "<experimental/mdspan>") + + **Creation from pystencils fields** + + Using `from_field`, ``mdspan`` objects can be created directly from `Field <pystencils.Field>` instances. + The `extents`_ of the ``mdspan`` type will be inferred from the field; + each fixed entry in the field's shape will become a fixed entry of the ``mdspan``'s extents. + + The ``mdspan``'s `layout_policy`_ defaults to `std::layout_stride`_, + which might not be the optimal choice depending on the memory layout of your fields. + You may therefore override this by specifying the name of the desired layout policy. + To map pystencils field layout identifiers to layout policies, consult the following table: + + +------------------------+--------------------------+ + | pystencils Layout Name | ``mdspan`` Layout Policy | + +========================+==========================+ + | ``"fzyx"`` | `std::layout_left`_ | + | ``"soa"`` | | + | ``"f"`` | | + | ``"reverse_numpy"`` | | + +------------------------+--------------------------+ + | ``"c"`` | `std::layout_right`_ | + | ``"numpy"`` | | + +------------------------+--------------------------+ + | ``"zyxf"`` | `std::layout_stride`_ | + | ``"aos"`` | | + +------------------------+--------------------------+ + + The array-of-structures (``"aos"``, ``"zyxf"``) layout has no equivalent layout policy in the C++ standard, + so it can only be mapped onto ``layout_stride``. + + .. _extents: https://en.cppreference.com/w/cpp/container/mdspan/extents + .. _layout_policy: https://en.cppreference.com/w/cpp/named_req/LayoutMappingPolicy + .. _std::layout_left: https://en.cppreference.com/w/cpp/container/mdspan/layout_left + .. _std::layout_right: https://en.cppreference.com/w/cpp/container/mdspan/layout_right + .. _std::layout_stride: https://en.cppreference.com/w/cpp/container/mdspan/layout_stride + + Args: + T: Element type of the mdspan """ dynamic_extent = "std::dynamic_extent" _namespace = "std" - _template = cpptype("std::mdspan< {T}, {extents} >", "<mdspan>") + _template = cpptype("std::mdspan< {T}, {extents}, {layout_policy} >", "<mdspan>") @classmethod def configure(cls, namespace: str = "std", header: str | HeaderFile = "<mdspan>"): - """Configure the namespace and header `mdspan` is defined in.""" + """Configure the namespace and header ``std::mdspan`` is defined in.""" cls._namespace = namespace - cls._template = cpptype(f"{namespace}::mdspan< {{T}}, {{extents}} >", header) + cls._template = cpptype( + f"{namespace}::mdspan< {{T}}, {{extents}}, {{layout_policy}} >", header + ) def __init__( self, T: UserTypeSpec, extents: tuple[int | str, ...], - extents_type: PsType = PsUnsignedIntegerType(64), + index_type: UserTypeSpec = PsUnsignedIntegerType(64), + layout_policy: str | None = None, ref: bool = False, const: bool = False, ): T = create_type(T) - extents_type_str = extents_type.c_string() + extents_type_str = create_type(index_type).c_string() extents_str = f"{self._namespace}::extents< {extents_type_str}, {', '.join(str(e) for e in extents)} >" - dtype = self._template(T=T, extents=extents_str, const=const) + if layout_policy is None: + layout_policy = f"{self._namespace}::layout_stride" + elif layout_policy in ("layout_left", "layout_right", "layout_stride"): + layout_policy = f"{self._namespace}::{layout_policy}" + + dtype = self._template( + T=T, extents=extents_str, layout_policy=layout_policy, const=const + ) if ref: dtype = Ref(dtype) super().__init__(dtype) - self._extents = extents + self._extents_type = extents_str + self._layout_type = layout_policy self._dim = len(extents) + @property + def extents_type(self) -> str: + return self._extents_type + + @property + def layout_type(self) -> str: + return self._layout_type + + def extent(self, r: int | ExprLike) -> AugExpr: + return AugExpr.format("{}.extent({})", self, r) + + def stride(self, r: int | ExprLike) -> AugExpr: + return AugExpr.format("{}.stride({})", self, r) + + def data_handle(self) -> AugExpr: + return AugExpr.format("{}.data_handle()", self) + def get_extraction(self) -> IFieldExtraction: mdspan = self class Extraction(IFieldExtraction): def ptr(self) -> AugExpr: - return AugExpr.format("{}.data_handle()", mdspan) + return mdspan.data_handle() def size(self, coordinate: int) -> AugExpr | None: if coordinate > mdspan._dim: return None else: - return AugExpr.format("{}.extents().extent({})", mdspan, coordinate) + return mdspan.extent(coordinate) def stride(self, coordinate: int) -> AugExpr | None: if coordinate > mdspan._dim: return None else: - return AugExpr.format("{}.stride({})", mdspan, coordinate) + return mdspan.stride(coordinate) return Extraction() @staticmethod def from_field( field: Field, - extents_type: PsType = PsUnsignedIntegerType(64), + extents_type: UserTypeSpec = PsUnsignedIntegerType(64), + layout_policy: str | None = None, ref: bool = False, const: bool = False, ): """Creates a `std::mdspan` instance for a given pystencils field.""" - from pystencils.field import layout_string_to_tuple - - if field.layout != layout_string_to_tuple("soa", field.spatial_dimensions): - raise NotImplementedError( - "mdspan mapping is currently only available for structure-of-arrays fields" - ) - extents: list[str | int] = [] for s in field.spatial_shape: @@ -110,7 +180,12 @@ class StdMdspan(SrcField): extents.append(StdMdspan.dynamic_extent if isinstance(s, Symbol) else s) return StdMdspan( - field.dtype, tuple(extents), extents_type=extents_type, ref=ref, const=const + field.dtype, + tuple(extents), + index_type=extents_type, + layout_policy=layout_policy, + ref=ref, + const=const, ).var(field.name) diff --git a/src/pystencilssfg/lang/expressions.py b/src/pystencilssfg/lang/expressions.py index ccaf2d1..53064bd 100644 --- a/src/pystencilssfg/lang/expressions.py +++ b/src/pystencilssfg/lang/expressions.py @@ -180,7 +180,7 @@ class AugExpr: - Calling `var <AugExpr.var>` on an unbound `AugExpr` turns it into a *variable* with the given name. This variable expression takes its set of required header files from the - `required_headers <PsType.required_headers>` field of the data type of the `AugExpr`. + `required_headers <pystencils.types.PsType.required_headers>` field of the data type of the `AugExpr`. - Using `bind <AugExpr.bind>`, an unbound `AugExpr` can be bound to an arbitrary string of code. The `bind` method mirrors the interface of `str.format` to combine sub-expressions and collect their dependencies. @@ -206,6 +206,7 @@ class AugExpr: self._is_variable = False def var(self, name: str): + """Bind an unbound `AugExpr` instance as a new variable of given name.""" v = SfgVar(name, self.get_dtype()) expr = VarExpr(v) return self._bind(expr) @@ -220,6 +221,7 @@ class AugExpr: return AugExpr().bind(fmt, *deps, **kwdeps) def bind(self, fmt: str | AugExpr, *deps, **kwdeps): + """Bind an unbound `AugExpr` instance to an expression.""" if isinstance(fmt, AugExpr): if bool(deps) or bool(kwdeps): raise ValueError( @@ -342,7 +344,8 @@ def asvar(var: VarLike) -> SfgVar: Raises: ValueError: If given a non-variable `AugExpr`, - a `TypedSymbol` with a `DynamicType`, + a `TypedSymbol <pystencils.TypedSymbol>` + with a `DynamicType <pystencils.sympyextensions.typed_sympy.DynamicType>`, or any non-variable-like object. """ match var: diff --git a/tests/generator_scripts/index.yaml b/tests/generator_scripts/index.yaml index 91bb77b..c78b335 100644 --- a/tests/generator_scripts/index.yaml +++ b/tests/generator_scripts/index.yaml @@ -54,6 +54,11 @@ ScaleKernel: JacobiMdspan: StlContainers1D: +# std::mdspan + +MdSpanFixedShapeLayouts: +MdSpanLbStreaming: + # SYCL SyclKernels: diff --git a/tests/generator_scripts/source/JacobiMdspan.harness.cpp b/tests/generator_scripts/source/JacobiMdspan.harness.cpp index e2b8c71..529d12c 100644 --- a/tests/generator_scripts/source/JacobiMdspan.harness.cpp +++ b/tests/generator_scripts/source/JacobiMdspan.harness.cpp @@ -5,21 +5,21 @@ namespace stdex = std::experimental; -using field_t = stdex::mdspan<double, stdex::extents< int64_t, std::dynamic_extent, std::dynamic_extent>>; -using scalar_field_t = stdex::mdspan<double, stdex::extents< int64_t, std::dynamic_extent, std::dynamic_extent, 1>>; +using field_t = stdex::mdspan<double, stdex::extents<int64_t, std::dynamic_extent, std::dynamic_extent>, stdex::layout_left>; +using scalar_field_t = stdex::mdspan<double, stdex::extents<int64_t, std::dynamic_extent, std::dynamic_extent, 1>, stdex::layout_left>; int main(void) { auto data_f = std::make_unique<double[]>(64); - scalar_field_t f{ data_f.get(), 8, 8 }; + scalar_field_t f{data_f.get(), 8, 8}; auto data_u = std::make_unique<double[]>(64); - field_t u{ data_u.get(), 8, 8 }; + field_t u{data_u.get(), 8, 8}; auto data_u_tmp = std::make_unique<double[]>(64); - field_t u_tmp{ data_u_tmp.get(), 8, 8 }; + field_t u_tmp{data_u_tmp.get(), 8, 8}; - double h { 1.0 / 7.0 }; + double h{1.0 / 7.0}; gen::jacobi_smooth(f, h, u_tmp, u); } diff --git a/tests/generator_scripts/source/JacobiMdspan.py b/tests/generator_scripts/source/JacobiMdspan.py index 41f149a..bbe95ac 100644 --- a/tests/generator_scripts/source/JacobiMdspan.py +++ b/tests/generator_scripts/source/JacobiMdspan.py @@ -20,8 +20,8 @@ with SourceFileGenerator() as sfg: poisson_kernel = sfg.kernels.create(poisson_jacobi) sfg.function("jacobi_smooth")( - sfg.map_field(u_src, mdspan.from_field(u_src)), - sfg.map_field(u_dst, mdspan.from_field(u_dst)), - sfg.map_field(f, mdspan.from_field(f)), + sfg.map_field(u_src, mdspan.from_field(u_src, layout_policy="layout_left")), + sfg.map_field(u_dst, mdspan.from_field(u_dst, layout_policy="layout_left")), + sfg.map_field(f, mdspan.from_field(f, layout_policy="layout_left")), sfg.call(poisson_kernel) ) diff --git a/tests/generator_scripts/source/MdSpanFixedShape.harness.cpp b/tests/generator_scripts/source/MdSpanFixedShape.harness.cpp new file mode 100644 index 0000000..cc979c8 --- /dev/null +++ b/tests/generator_scripts/source/MdSpanFixedShape.harness.cpp @@ -0,0 +1,35 @@ +#include "MdSpanLayouts.hpp" + +#include <concepts> +#include <experimental/mdspan> + +namespace stdex = std::experimental; + +static_assert( std::is_same_v< gen::field_soa::layout_type, stdex::layout_left > ); +static_assert( std::is_same_v< gen::field_aos::layout_type, stdex::layout_stride > ); +static_assert( std::is_same_v< gen::field_c::layout_type, stdex::layout_right > ); + +static_assert( gen::field_soa::static_extent(0) == 17 ); +static_assert( gen::field_soa::static_extent(1) == 19 ); +static_assert( gen::field_soa::static_extent(2) == 32 ); +static_assert( gen::field_soa::static_extent(3) == 9 ); + +int main(void) { + gen::field_soa f_soa { nullptr }; + gen::checkLayoutSoa(f_soa); + + gen::field_aos::extents_type f_aos_extents { }; + std::array< uint64_t, 4 > strides_aos { + /* stride(x) */ f_aos_extents.extent(3), + /* stride(y) */ f_aos_extents.extent(3) * f_aos_extents.extent(0), + /* stride(z) */ f_aos_extents.extent(3) * f_aos_extents.extent(0) * f_aos_extents.extent(1), + /* stride(f) */ 1 + }; + + gen::field_aos::mapping_type f_aos_mapping { f_aos_extents, strides_aos }; + gen::field_aos f_aos { nullptr, f_aos_mapping }; + gen::checkLayoutAos(f_aos); + + gen::field_c f_c { nullptr }; + gen::checkLayoutC(f_c); +} diff --git a/tests/generator_scripts/source/MdSpanFixedShapeLayouts.py b/tests/generator_scripts/source/MdSpanFixedShapeLayouts.py new file mode 100644 index 0000000..c89fe24 --- /dev/null +++ b/tests/generator_scripts/source/MdSpanFixedShapeLayouts.py @@ -0,0 +1,55 @@ +import pystencils as ps +from pystencilssfg import SourceFileGenerator +from pystencilssfg.lang.cpp import std +from pystencilssfg.lang import strip_ptr_ref + +std.mdspan.configure(namespace="std::experimental", header="<experimental/mdspan>") + +with SourceFileGenerator() as sfg: + sfg.namespace("gen") + sfg.include("<cassert>") + + def check_layout(field: ps.Field, mdspan: std.mdspan): + seq = [] + + for d in range(field.spatial_dimensions + field.index_dimensions): + seq += [ + sfg.expr( + 'assert({} == {} && "Shape mismatch at coordinate {}");', + mdspan.extent(d), + field.shape[d], + d, + ), + sfg.expr( + 'assert({} == {} && "Stride mismatch at coordinate {}");', + mdspan.stride(d), + field.strides[d], + d, + ), + ] + + return seq + + f_soa = ps.fields("f_soa(9): double[17, 19, 32]", layout="soa") + f_soa_mdspan = std.mdspan.from_field(f_soa, layout_policy="layout_left", ref=True) + + sfg.code(f"using field_soa = {strip_ptr_ref(f_soa_mdspan.dtype)};") + sfg.function("checkLayoutSoa")( + *check_layout(f_soa, f_soa_mdspan) + ) + + f_aos = ps.fields("f_aos(9): double[17, 19, 32]", layout="aos") + f_aos_mdspan = std.mdspan.from_field(f_aos, ref=True) + sfg.code(f"using field_aos = {strip_ptr_ref(f_aos_mdspan.dtype)};") + + sfg.function("checkLayoutAos")( + *check_layout(f_aos, f_aos_mdspan) + ) + + f_c = ps.fields("f_c(9): double[17, 19, 32]", layout="c") + f_c_mdspan = std.mdspan.from_field(f_c, layout_policy="layout_right", ref=True) + sfg.code(f"using field_c = {strip_ptr_ref(f_c_mdspan.dtype)};") + + sfg.function("checkLayoutC")( + *check_layout(f_c, f_c_mdspan) + ) diff --git a/tests/generator_scripts/source/MdSpanLbStreaming.harness.cpp b/tests/generator_scripts/source/MdSpanLbStreaming.harness.cpp new file mode 100644 index 0000000..0b9b765 --- /dev/null +++ b/tests/generator_scripts/source/MdSpanLbStreaming.harness.cpp @@ -0,0 +1,91 @@ +#include "MdSpanLbStreaming.hpp" + +#include <concepts> +#include <experimental/mdspan> +#include <array> +#include <cassert> +#include <memory> +#include <span> + +namespace stdex = std::experimental; + +static_assert(std::is_same_v<gen::field_fzyx::layout_type, stdex::layout_left>); +static_assert(std::is_same_v<gen::field_zyxf::layout_type, stdex::layout_stride>); +static_assert(std::is_same_v<gen::field_c::layout_type, stdex::layout_right>); + +using shape_type = stdex::extents< int64_t, std::dynamic_extent, std::dynamic_extent, std::dynamic_extent, 6 >; +static_assert(std::is_same_v<gen::field_fzyx::extents_type, shape_type >); + +constexpr shape_type field_shape { 16l, 15l, 14l }; + +constexpr std::array<std::array<int64_t, 3>, 2> slice{ + {{3, 4, 5}, + {7, 10, 12}}}; + +template <typename Kernel, typename PdfField> +void test_streaming(Kernel &kernel, PdfField &src_field, PdfField &dst_field) +{ + kernel.setZero(src_field); + kernel.setZero(dst_field); + + for (int64_t z = slice[0][2]; z < slice[1][2]; ++z) + for (int64_t y = slice[0][1]; y < slice[1][1]; ++y) + for (int64_t x = slice[0][0]; x < slice[1][0]; ++x) + for (int64_t i = 0; i < int64_t(gen::STENCIL.size()); ++i) + { + src_field(x, y, z, i) = double(i); + } + + kernel(dst_field, src_field); + + for (int64_t z = slice[0][2]; z < slice[1][2]; ++z) + for (int64_t y = slice[0][1]; y < slice[1][1]; ++y) + for (int64_t x = slice[0][0]; x < slice[1][0]; ++x) + for (int64_t i = 0; i < int64_t(gen::STENCIL.size()); ++i) + { + const std::array<int64_t, 3> &offsets = gen::STENCIL[i]; + assert((dst_field(x + offsets[0], y + offsets[1], z + offsets[2], i) == double(i))); + } +} + +int main(void) +{ + constexpr size_t num_items { (size_t) field_shape.extent(0) * field_shape.extent(1) * field_shape.extent(2) * field_shape.extent(3) }; + + auto src_data = std::make_unique< double [] >( num_items ); + auto dst_data = std::make_unique< double [] >( num_items ); + + // Structure-of-Arrays + { + gen::Kernel_fzyx kernel; + gen::field_fzyx src_arr { src_data.get(), field_shape }; + gen::field_fzyx dst_arr { dst_data.get(), field_shape }; + test_streaming(kernel, src_arr, dst_arr ); + } + + // Array-of-Structures + { + gen::Kernel_zyxf kernel; + + std::array< uint64_t, 4 > strides_xyzf { + /* stride(x) */ field_shape.extent(3), + /* stride(y) */ field_shape.extent(3) * field_shape.extent(0), + /* stride(z) */ field_shape.extent(3) * field_shape.extent(0) * field_shape.extent(1), + /* stride(f) */ 1 + }; + + gen::field_zyxf::mapping_type zyxf_mapping { field_shape, strides_xyzf }; + + gen::field_zyxf src_arr { src_data.get(), zyxf_mapping }; + gen::field_zyxf dst_arr { dst_data.get(), zyxf_mapping }; + test_streaming(kernel, src_arr, dst_arr ); + } + + // C Row-Major + { + gen::Kernel_c kernel; + gen::field_c src_arr { src_data.get(), field_shape }; + gen::field_c dst_arr { dst_data.get(), field_shape }; + test_streaming(kernel, src_arr, dst_arr ); + } +} diff --git a/tests/generator_scripts/source/MdSpanLbStreaming.py b/tests/generator_scripts/source/MdSpanLbStreaming.py new file mode 100644 index 0000000..60049a8 --- /dev/null +++ b/tests/generator_scripts/source/MdSpanLbStreaming.py @@ -0,0 +1,62 @@ +import numpy as np +import pystencils as ps +from pystencilssfg import SourceFileGenerator, SfgComposer +from pystencilssfg.lang.cpp import std +from pystencilssfg.lang import strip_ptr_ref + +std.mdspan.configure(namespace="std::experimental", header="<experimental/mdspan>") + +stencil = ((-1, 0, 0), (1, 0, 0), (0, -1, 0), (0, 1, 0), (0, 0, 1), (0, 0, -1)) + + +def lbm_stream(sfg: SfgComposer, field_layout: str, layout_policy: str): + src, dst = ps.fields("src(6), dst(6): double[3D]", layout=field_layout) + + src_mdspan = std.mdspan.from_field(src, layout_policy=layout_policy, extents_type="int64", ref=True) + dst_mdspan = std.mdspan.from_field(dst, layout_policy=layout_policy, extents_type="int64", ref=True) + + asms = [] + asms_zero = [] + + for i, dir in enumerate(stencil): + asms.append(ps.Assignment(dst.center(i), src[-np.array(dir)](i))) + asms_zero.append(ps.Assignment(dst.center(i), 0)) + + khandle = sfg.kernels.create(asms, f"stream_{field_layout}") + khandle_zero = sfg.kernels.create(asms_zero, f"zero_{field_layout}") + + sfg.code(f"using field_{field_layout} = {strip_ptr_ref(src_mdspan.get_dtype())};") + + sfg.klass(f"Kernel_{field_layout}")( + sfg.public( + sfg.method("operator()")( + sfg.map_field(src, src_mdspan), + sfg.map_field(dst, dst_mdspan), + sfg.call(khandle), + ), + + sfg.method("setZero")( + sfg.map_field(dst, dst_mdspan), + sfg.call(khandle_zero), + ) + ) + ) + + +with SourceFileGenerator() as sfg: + sfg.namespace("gen") + sfg.include("<cassert>") + sfg.include("<array>") + + stencil_code = ( + "{{" + + ", ".join("{" + ", ".join(str(ci) for ci in c) + "}" for c in stencil) + + "}}" + ) + sfg.code( + f"constexpr std::array< std::array< int64_t, 3 >, 6 > STENCIL = {stencil_code};" + ) + + lbm_stream(sfg, "fzyx", "layout_left") + lbm_stream(sfg, "c", "layout_right") + lbm_stream(sfg, "zyxf", "layout_stride") -- GitLab