From 02b6e240cf56af1dba2f60aac09f20993398cb31 Mon Sep 17 00:00:00 2001 From: Frederik Hennig <frederik.hennig@fau.de> Date: Fri, 21 Feb 2025 14:07:23 +0100 Subject: [PATCH 1/6] Remove Inline output mode. Refactor output_mode to `header_only` switch. --- docs/source/api/generation.rst | 3 - docs/source/usage/config_and_cli.md | 3 +- docs/source/usage/project_integration.md | 5 +- integration/test_sycl_buffer.py | 8 +- src/pystencilssfg/__init__.py | 3 +- .../cmake/modules/PystencilsSfg.cmake | 10 ++- src/pystencilssfg/config.py | 79 ++++--------------- src/pystencilssfg/generator.py | 30 +++---- tests/generator/test_config.py | 20 ++++- tests/generator_scripts/README.md | 8 +- tests/generator_scripts/index.yaml | 8 +- tests/generator_scripts/source/SyclKernels.py | 5 +- .../test_generator_scripts.py | 9 ++- .../integration/cmake_project/CMakeLists.txt | 4 +- tests/integration/test_cli.py | 3 +- 15 files changed, 79 insertions(+), 119 deletions(-) diff --git a/docs/source/api/generation.rst b/docs/source/api/generation.rst index 8e7b0b4..7a0d013 100644 --- a/docs/source/api/generation.rst +++ b/docs/source/api/generation.rst @@ -21,9 +21,6 @@ Categories, Parameter Types, and Special Values .. autoclass:: _GlobalNamespace .. autodata:: GLOBAL_NAMESPACE -.. autoclass:: OutputMode - :members: - .. autoclass:: FileExtensions :members: diff --git a/docs/source/usage/config_and_cli.md b/docs/source/usage/config_and_cli.md index 1fdc9df..80169b1 100644 --- a/docs/source/usage/config_and_cli.md +++ b/docs/source/usage/config_and_cli.md @@ -42,6 +42,7 @@ The file extensions of the generated files can be modified through {any}`cfg.extensions.header <FileExtensions.header>` and {any}`cfg.extensions.impl <FileExtensions.impl>`; and the output directory of the code generator can be set through {any}`cfg.output_directory <SfgConfig.output_directory>`. +Header-only code generation can be enabled using {any}`cfg.header_only <SfgConfig.header_only>`. :::{danger} @@ -76,7 +77,7 @@ on invocation. These include: - `--sfg-output-dir <path>`: Set the output directory of the generator script. This corresponds to {any}`SfgConfig.output_directory`. - `--sfg-file-extensions <exts>`: Set the file extensions used for the generated files; `exts` must be a comma-separated list not containing any spaces. Corresponds to {any}`SfgConfig.extensions`. -- `--sfg-output-mode <mode>`: Set the output mode of the generator script. Corresponds to {any}`SfgConfig.output_mode`. +- `[--no]--sfg-header-only`: Enable or disable header-only code generation. Corresponds to {any}`SfgConfig.header_only`. If any configuration option is set to conflicting values on the command line and in the inline configuration, the generator script will terminate with an error. diff --git a/docs/source/usage/project_integration.md b/docs/source/usage/project_integration.md index 47b1b68..0ad13e8 100644 --- a/docs/source/usage/project_integration.md +++ b/docs/source/usage/project_integration.md @@ -120,9 +120,9 @@ pystencilssfg_generate_target_sources( <target> [SCRIPT_ARGS arg1 [arg2 ...]] [DEPENDS dependency1.py [dependency2.py...]] [FILE_EXTENSIONS <header-extension> <impl-extension>] - [OUTPUT_MODE <standalone|inline|header-only>] [CONFIG_MODULE <path-to-config-module.py>] [OUTPUT_DIRECTORY <output-directory>] + [HEADER_ONLY] ) ``` @@ -135,12 +135,13 @@ The function takes the following options: - `SCRIPT_ARGS`: A list of custom command line arguments passed to the generator scripts; see [](#custom_cli_args) - `DEPENDS`: A list of dependencies for the generator scripts - `FILE_EXTENSION`: The desired extensions for the generated files - - `OUTPUT_MODE`: Sets the output mode of the code generator; see {any}`SfgConfig.output_mode`. - `CONFIG_MODULE`: Set the configuration module for all scripts registered with this call. If set, this overrides the value of `PystencilsSfg_CONFIG_MODULE` in the current scope (see [](#cmake_set_config_module)) - `OUTPUT_DIRECTORY`: Custom output directory for generated files. If `OUTPUT_DIRECTORY` is a relative path, it will be interpreted relative to the current build directory. + - `HEADER_ONLY`: If this option is set, instruct the generator scripts to only generate header files + (see {any}`SfgConfig.header_only`). If `OUTPUT_DIRECTORY` is *not* specified, any C++ header files generated by the above call can be included in any files belonging to `target` via: diff --git a/integration/test_sycl_buffer.py b/integration/test_sycl_buffer.py index a1e52eb..a49d4e4 100644 --- a/integration/test_sycl_buffer.py +++ b/integration/test_sycl_buffer.py @@ -1,14 +1,14 @@ from pystencils import Target, CreateKernelConfig, no_jit from lbmpy import create_lb_update_rule, LBMOptimisation -from pystencilssfg import SourceFileGenerator, SfgConfig, OutputMode -from pystencilssfg.lang.cpp.sycl_accessor import sycl_accessor_ref +from pystencilssfg import SourceFileGenerator, SfgConfig +from pystencilssfg.lang.cpp.sycl_accessor import SyclAccessor import pystencilssfg.extensions.sycl as sycl from itertools import chain sfg_config = SfgConfig( output_directory="out/test_sycl_buffer", outer_namespace="gen_code", - output_mode=OutputMode.INLINE, + header_only=True ) with SourceFileGenerator(sfg_config) as sfg: @@ -21,7 +21,7 @@ with SourceFileGenerator(sfg_config) as sfg: cgh = sfg.sycl_handler("handler") rang = sfg.sycl_range(update.method.dim, "range") mappings = [ - sfg.map_field(field, sycl_accessor_ref(field)) + sfg.map_field(field, SyclAccessor.from_field(field)) for field in chain(update.free_fields, update.bound_fields) ] diff --git a/src/pystencilssfg/__init__.py b/src/pystencilssfg/__init__.py index fea6f8a..cea370c 100644 --- a/src/pystencilssfg/__init__.py +++ b/src/pystencilssfg/__init__.py @@ -1,4 +1,4 @@ -from .config import SfgConfig, GLOBAL_NAMESPACE, OutputMode +from .config import SfgConfig, GLOBAL_NAMESPACE from .generator import SourceFileGenerator from .composer import SfgComposer from .context import SfgContext @@ -8,7 +8,6 @@ from .exceptions import SfgException __all__ = [ "SfgConfig", "GLOBAL_NAMESPACE", - "OutputMode", "SourceFileGenerator", "SfgComposer", "SfgContext", diff --git a/src/pystencilssfg/cmake/modules/PystencilsSfg.cmake b/src/pystencilssfg/cmake/modules/PystencilsSfg.cmake index 0779599..eb4c234 100644 --- a/src/pystencilssfg/cmake/modules/PystencilsSfg.cmake +++ b/src/pystencilssfg/cmake/modules/PystencilsSfg.cmake @@ -54,15 +54,17 @@ endfunction() function(pystencilssfg_generate_target_sources TARGET) - set(options) - set(oneValueArgs OUTPUT_MODE CONFIG_MODULE OUTPUT_DIRECTORY) + set(options HEADER_ONLY) + set(oneValueArgs CONFIG_MODULE OUTPUT_DIRECTORY) set(multiValueArgs SCRIPTS DEPENDS FILE_EXTENSIONS SCRIPT_ARGS) cmake_parse_arguments(_pssfg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) set(generatorArgs) - if(DEFINED _pssfg_OUTPUT_MODE) - list(APPEND generatorArgs "--sfg-output-mode=${_pssfg_OUTPUT_MODE}") + if(_pssfg_HEADER_ONLY) + list(APPEND generatorArgs "--sfg-header-only") + else() + list(APPEND generatorArgs "--no-sfg-header-only") endif() if(DEFINED _pssfg_CONFIG_MODULE) diff --git a/src/pystencilssfg/config.py b/src/pystencilssfg/config.py index bbe2389..482ca8e 100644 --- a/src/pystencilssfg/config.py +++ b/src/pystencilssfg/config.py @@ -1,6 +1,6 @@ from __future__ import annotations -from argparse import ArgumentParser +from argparse import ArgumentParser, BooleanOptionalAction from types import ModuleType from typing import Any, Sequence, Callable @@ -25,7 +25,7 @@ class FileExtensions(ConfigBase): header: BasicOption[str] = BasicOption("hpp") """File extension for generated header file.""" - impl: BasicOption[str] = BasicOption() + impl: BasicOption[str] = BasicOption("cpp") """File extension for generated implementation file.""" @header.validate @@ -37,25 +37,6 @@ class FileExtensions(ConfigBase): return ext -class OutputMode(Enum): - """Output mode of the source file generator.""" - - STANDALONE = auto() - """Generate a header/implementation file pair (e.g. ``.hpp/.cpp``) where the implementation file will - be compiled to a standalone object.""" - - INLINE = auto() - """Generate a header/inline implementation file pair (e.g. ``.hpp/.ipp``) where all implementations - are inlined by including the implementation file at the end of the header file.""" - - HEADER_ONLY = auto() - """Generate only a header file. - - At the moment, header-only mode does not support generation of kernels and requires that all functions - and methods are marked ``inline``. - """ - - @dataclass class CodeStyle(ConfigBase): """Options affecting the code style used by the source file generator.""" @@ -137,14 +118,10 @@ class SfgConfig(ConfigBase): FileExtensions.impl """ - output_mode: BasicOption[OutputMode] = BasicOption(OutputMode.STANDALONE) - """The generator's output mode; defines which files to generate, and the set of legal file extensions. - - Possible parameters: - .. autosummary:: - OutputMode.STANDALONE - OutputMode.INLINE - OutputMode.HEADER_ONLY + header_only: BasicOption[bool] = BasicOption(False) + """If set to `True`, generate only a header file. + + This will cause all definitions to be generated ``inline``. """ outer_namespace: BasicOption[str | _GlobalNamespace] = BasicOption(GLOBAL_NAMESPACE) @@ -187,16 +164,9 @@ class SfgConfig(ConfigBase): header_ext = self.extensions.get_option("header") impl_ext = self.extensions.get_option("impl") output_files = [output_dir / f"{basename}.{header_ext}"] - output_mode = self.get_option("output_mode") - - if impl_ext is None: - match output_mode: - case OutputMode.INLINE: - impl_ext = "ipp" - case OutputMode.STANDALONE: - impl_ext = "cpp" + header_only = self.get_option("header_only") - if output_mode != OutputMode.HEADER_ONLY: + if not header_only: assert impl_ext is not None output_files.append(output_dir / f"{basename}.{impl_ext}") @@ -219,11 +189,10 @@ class CommandLineParameters: help="Comma-separated list of file extensions", ) config_group.add_argument( - "--sfg-output-mode", - type=str, - default=None, - choices=("standalone", "inline", "header-only"), - dest="output_mode", + "--sfg-header-only", + action=BooleanOptionalAction, + dest="header_only", + help="Generate only a header file." ) config_group.add_argument( "--sfg-config-module", type=str, default=None, dest="config_module_path" @@ -234,21 +203,7 @@ class CommandLineParameters: def __init__(self, args) -> None: self._cl_config_module_path: str | None = args.config_module_path - if args.output_mode is not None: - match args.output_mode.lower(): - case "standalone": - output_mode = OutputMode.STANDALONE - case "inline": - output_mode = OutputMode.INLINE - case "header-only": - output_mode = OutputMode.HEADER_ONLY - case _: - assert False, "invalid output mode" - else: - output_mode = None - - self._cl_output_mode = output_mode - + self._cl_header_only: bool | None = args.header_only self._cl_output_dir: str | None = args.output_directory if args.file_extensions is not None: @@ -279,8 +234,8 @@ class CommandLineParameters: ): self._config_module.configure_sfg(cfg) - if self._cl_output_mode is not None: - cfg.output_mode = self._cl_output_mode + if self._cl_header_only is not None: + cfg.header_only = self._cl_header_only if self._cl_header_ext is not None: cfg.extensions.header = self._cl_header_ext if self._cl_impl_ext is not None: @@ -292,7 +247,7 @@ class CommandLineParameters: def find_conflicts(self, cfg: SfgConfig): for name, mine, theirs in ( - ("output_mode", self._cl_output_mode, cfg.output_mode), + ("header_only", self._cl_header_only, cfg.header_only), ("extensions.header", self._cl_header_ext, cfg.extensions.header), ("extensions.impl", self._cl_impl_ext, cfg.extensions.impl), ("output_directory", self._cl_output_dir, cfg.output_directory), @@ -320,7 +275,7 @@ class CommandLineParameters: extensions = tuple((ext[1:] if ext[0] == "." else ext) for ext in extensions) HEADER_FILE_EXTENSIONS = {"h", "hpp", "hxx", "h++", "cuh"} - IMPL_FILE_EXTENSIONS = {"c", "cpp", "cxx", "c++", "cu", ".impl.h", "ipp", "hip"} + IMPL_FILE_EXTENSIONS = {"c", "cpp", "cxx", "c++", "cu", "hip"} for ext in extensions: if ext in HEADER_FILE_EXTENSIONS: diff --git a/src/pystencilssfg/generator.py b/src/pystencilssfg/generator.py index 91a124a..c314d67 100644 --- a/src/pystencilssfg/generator.py +++ b/src/pystencilssfg/generator.py @@ -4,7 +4,6 @@ from typing import Callable, Any from .config import ( SfgConfig, CommandLineParameters, - OutputMode, _GlobalNamespace, ) from .context import SfgContext @@ -78,7 +77,7 @@ class SourceFileGenerator: cli_params.find_conflicts(sfg_config) config.override(sfg_config) - self._output_mode: OutputMode = config.get_option("output_mode") + self._header_only: bool = config.get_option("header_only") self._output_dir: Path = config.get_option("output_directory") output_files = config._get_output_files(basename) @@ -90,20 +89,15 @@ class SourceFileGenerator: ) self._impl_file: SfgSourceFile | None - match self._output_mode: - case OutputMode.HEADER_ONLY: - self._impl_file = None - case OutputMode.STANDALONE: - self._impl_file = SfgSourceFile( - output_files[1].name, SfgSourceFileType.TRANSLATION_UNIT - ) - self._impl_file.includes.append( - HeaderFile.parse(self._header_file.name) - ) - case OutputMode.INLINE: - self._impl_file = SfgSourceFile( - output_files[1].name, SfgSourceFileType.HEADER - ) + if self._header_only: + self._impl_file = None + else: + self._impl_file = SfgSourceFile( + output_files[1].name, SfgSourceFileType.TRANSLATION_UNIT + ) + self._impl_file.includes.append( + HeaderFile.parse(self._header_file.name) + ) # TODO: Find a way to not hard-code the restrict qualifier in pystencils self._header_file.elements.append("#define RESTRICT __restrict__") @@ -150,10 +144,6 @@ class SourceFileGenerator: impl_path.unlink() def _finish_files(self) -> None: - if self._output_mode == OutputMode.INLINE: - assert self._impl_file is not None - self._header_file.elements.append(f'#include "{self._impl_file.name}"') - from .ir import collect_includes header_includes = collect_includes(self._header_file) diff --git a/tests/generator/test_config.py b/tests/generator/test_config.py index 250c158..9d542cd 100644 --- a/tests/generator/test_config.py +++ b/tests/generator/test_config.py @@ -3,7 +3,6 @@ from pathlib import Path from pystencilssfg.config import ( SfgConfig, - OutputMode, GLOBAL_NAMESPACE, CommandLineParameters, SfgConfigException @@ -13,7 +12,7 @@ from pystencilssfg.config import ( def test_defaults(): cfg = SfgConfig() - assert cfg.get_option("output_mode") == OutputMode.STANDALONE + assert cfg.get_option("header_only") is False assert cfg.extensions.get_option("header") == "hpp" assert cfg.codestyle.get_option("indent_width") == 2 assert cfg.clang_format.get_option("binary") == "clang-format" @@ -90,6 +89,23 @@ def test_from_commandline(sample_config_module): assert cfg.output_directory == Path(".out") assert cfg.extensions.header == "h++" assert cfg.extensions.impl == "c++" + assert cfg.header_only is None + + args = parser.parse_args( + ["--sfg-header-only"] + ) + cli_args = CommandLineParameters(args) + cfg = cli_args.get_config() + + assert cfg.header_only is True + + args = parser.parse_args( + ["--no-sfg-header-only"] + ) + cli_args = CommandLineParameters(args) + cfg = cli_args.get_config() + + assert cfg.header_only is False args = parser.parse_args( ["--sfg-output-dir", "gen_sources", "--sfg-config-module", sample_config_module] diff --git a/tests/generator_scripts/README.md b/tests/generator_scripts/README.md index 185e270..5dfc474 100644 --- a/tests/generator_scripts/README.md +++ b/tests/generator_scripts/README.md @@ -85,12 +85,12 @@ The test suite parses the following (groups of) parameters: SFG-related command-line parameters passed to the generator script. These may be: -- `output-mode`: Define the output mode, can be either `standalone`, `inline` or `header-only`. -If `header-only` is specified, the set of expected output files is reduced to `{".hpp"}`. +- `header-only` (`true` or `false`): Enable or disable header-only code generation. + If `true`, the set of expected output files is reduced to `{".hpp"}`. - `file-extensions`: List of file extensions for the output files of the generator script. -If specified, these are taken as the expected output files by the test suite. + If specified, these are taken as the expected output files by the test suite. - `config-module`: Path to a config module, relative to `source/`. -The Python file referred to by this option will be passed as a configuration module to the generator script. + The Python file referred to by this option will be passed as a configuration module to the generator script. #### `extra-args` List of additional command line parameters passed to the script. diff --git a/tests/generator_scripts/index.yaml b/tests/generator_scripts/index.yaml index 68352fe..5e2db9a 100644 --- a/tests/generator_scripts/index.yaml +++ b/tests/generator_scripts/index.yaml @@ -19,7 +19,7 @@ TestIllegalArgs: TestIncludeSorting: sfg-args: - output-mode: header-only + header-only: true expect-code: hpp: - regex: >- @@ -32,7 +32,7 @@ TestIncludeSorting: BasicDefinitions: sfg-args: - output-mode: header-only + header-only: true expect-code: hpp: - regex: >- @@ -45,7 +45,7 @@ BasicDefinitions: SimpleClasses: sfg-args: - output-mode: header-only + header-only: true ComposerFeatures: expect-code: @@ -67,7 +67,7 @@ Conditionals: NestedNamespaces: sfg-args: - output-mode: header-only + header-only: true # Kernel Generation diff --git a/tests/generator_scripts/source/SyclKernels.py b/tests/generator_scripts/source/SyclKernels.py index 8417743..f181a3d 100644 --- a/tests/generator_scripts/source/SyclKernels.py +++ b/tests/generator_scripts/source/SyclKernels.py @@ -1,12 +1,11 @@ import sympy as sp import pystencils as ps -from pystencilssfg import SourceFileGenerator, SfgConfig, OutputMode +from pystencilssfg import SourceFileGenerator, SfgConfig from pystencilssfg.extensions.sycl import SyclComposer cfg = SfgConfig() -cfg.output_mode = OutputMode.INLINE -cfg.extensions.impl = "ipp" +cfg.header_only = True with SourceFileGenerator(cfg) as sfg: sfg = SyclComposer(sfg) diff --git a/tests/generator_scripts/test_generator_scripts.py b/tests/generator_scripts/test_generator_scripts.py index bba12af..6f2ff16 100644 --- a/tests/generator_scripts/test_generator_scripts.py +++ b/tests/generator_scripts/test_generator_scripts.py @@ -72,11 +72,12 @@ class GenScriptTest: sfg_args: dict = test_description.get("sfg-args", dict()) - if (output_mode := sfg_args.get("output-mode", None)) is not None: - if output_mode == "header-only": + if (header_only := sfg_args.get("header-only", None)) is not None: + if header_only: expected_extensions = ["hpp"] - - self._script_args += ["--sfg-output-mode", output_mode] + self._script_args += ["--sfg-header-only"] + else: + self._script_args += ["--no--sfg-header-only"] if (file_exts := sfg_args.get("file-extensions", None)) is not None: expected_extensions = file_exts diff --git a/tests/integration/cmake_project/CMakeLists.txt b/tests/integration/cmake_project/CMakeLists.txt index ee7afae..f93b090 100644 --- a/tests/integration/cmake_project/CMakeLists.txt +++ b/tests/integration/cmake_project/CMakeLists.txt @@ -35,12 +35,12 @@ pystencilssfg_generate_target_sources( TestApp SCRIPTS CliTest.py SCRIPT_ARGS apples bananas unicorns - OUTPUT_MODE header-only + HEADER_ONLY ) pystencilssfg_generate_target_sources( TestApp SCRIPTS CustomDirTest.py OUTPUT_DIRECTORY my-output - OUTPUT_MODE header-only + HEADER_ONLY ) diff --git a/tests/integration/test_cli.py b/tests/integration/test_cli.py index d3c2585..41cdda8 100644 --- a/tests/integration/test_cli.py +++ b/tests/integration/test_cli.py @@ -29,8 +29,7 @@ def test_list_files_headeronly(): "list-files", "--sfg-output-dir", output_dir, - "--sfg-output-mode", - "header-only", + "--sfg-header-only", "genscript.py", ] -- GitLab From 3f572623ac6573702b54db2894ea13edc7d17cea Mon Sep 17 00:00:00 2001 From: Frederik Hennig <frederik.hennig@fau.de> Date: Fri, 21 Feb 2025 14:47:58 +0100 Subject: [PATCH 2/6] adapt composer to automatically generate inline defs in header-only mode. Add test cases. --- src/pystencilssfg/composer/basic_composer.py | 53 ++++++++++++++----- src/pystencilssfg/composer/class_composer.py | 5 +- src/pystencilssfg/context.py | 4 ++ src/pystencilssfg/emission/file_printer.py | 16 ++++-- src/pystencilssfg/ir/entities.py | 14 ++++- tests/generator_scripts/index.yaml | 17 ++++-- .../source/ComposerHeaderOnly.harness.cpp | 18 +++++++ .../source/ComposerHeaderOnly.py | 26 +++++++++ 8 files changed, 131 insertions(+), 22 deletions(-) create mode 100644 tests/generator_scripts/source/ComposerHeaderOnly.harness.cpp create mode 100644 tests/generator_scripts/source/ComposerHeaderOnly.py diff --git a/src/pystencilssfg/composer/basic_composer.py b/src/pystencilssfg/composer/basic_composer.py index 49b6c73..ff1ab2d 100644 --- a/src/pystencilssfg/composer/basic_composer.py +++ b/src/pystencilssfg/composer/basic_composer.py @@ -88,11 +88,16 @@ SequencerArg: TypeAlias = tuple | ExprLike | SfgCallTreeNode | SfgNodeBuilder class KernelsAdder: """Handle on a kernel namespace that permits registering kernels.""" - def __init__(self, ctx: SfgContext, loc: SfgNamespaceBlock): - self._ctx = ctx - self._loc = loc - assert isinstance(loc.namespace, SfgKernelNamespace) - self._kernel_namespace = loc.namespace + def __init__(self, cursor: SfgCursor, knamespace: SfgKernelNamespace): + self._cursor = cursor + self._kernel_namespace = knamespace + self._inline: bool = False + self._loc: SfgNamespaceBlock | None = None + + def inline(self) -> KernelsAdder: + """Generate kernel definitions ``inline`` in the header file.""" + self._inline = True + return self def add(self, kernel: Kernel, name: str | None = None): """Adds an existing pystencils AST to this namespace. @@ -111,14 +116,22 @@ class KernelsAdder: if name is not None: kernel.name = kernel_name - khandle = SfgKernelHandle(kernel_name, self._kernel_namespace, kernel) + khandle = SfgKernelHandle( + kernel_name, self._kernel_namespace, kernel, inline=self._inline + ) self._kernel_namespace.add_kernel(khandle) - self._loc.elements.append(SfgEntityDef(khandle)) + loc = self._get_loc() + loc.elements.append(SfgEntityDef(khandle)) for header in kernel.required_headers: - assert self._ctx.impl_file is not None - self._ctx.impl_file.includes.append(HeaderFile.parse(header)) + hfile = HeaderFile.parse(header) + if self._inline: + self._cursor.context.header_file.includes.append(hfile) + else: + impl_file = self._cursor.context.impl_file + assert impl_file is not None + impl_file.includes.append(hfile) return khandle @@ -147,6 +160,18 @@ class KernelsAdder: kernel = create_kernel(assignments, config=config) return self.add(kernel) + def _get_loc(self) -> SfgNamespaceBlock: + if self._loc is None: + kns_block = SfgNamespaceBlock(self._kernel_namespace) + + if self._inline: + self._cursor.write_header(kns_block) + else: + self._cursor.write_impl(kns_block) + + self._loc = kns_block + return self._loc + class SfgBasicComposer(SfgIComposer): """Composer for basic source components, and base class for all composer mix-ins.""" @@ -281,9 +306,10 @@ class SfgBasicComposer(SfgIComposer): f"The existing entity {kns.fqname} is not a kernel namespace" ) - kns_block = SfgNamespaceBlock(kns) - self._cursor.write_impl(kns_block) - return KernelsAdder(self._ctx, kns_block) + kadder = KernelsAdder(self._cursor, kns) + if self._ctx.impl_file is None: + kadder.inline() + return kadder def include(self, header: str | HeaderFile, private: bool = False): """Include a header file. @@ -356,6 +382,9 @@ class SfgBasicComposer(SfgIComposer): ) seq.returns(return_type) + if self._ctx.impl_file is None: + seq.inline() + return seq def call(self, kernel_handle: SfgKernelHandle) -> SfgCallTreeNode: diff --git a/src/pystencilssfg/composer/class_composer.py b/src/pystencilssfg/composer/class_composer.py index 7787150..5dbb4c6 100644 --- a/src/pystencilssfg/composer/class_composer.py +++ b/src/pystencilssfg/composer/class_composer.py @@ -253,7 +253,10 @@ class SfgClassComposer(SfgComposerMixIn): name: The method name """ - return SfgMethodSequencer(self._cursor, name) + seq = SfgMethodSequencer(self._cursor, name) + if self._ctx.impl_file is None: + seq.inline() + return seq # INTERNALS diff --git a/src/pystencilssfg/context.py b/src/pystencilssfg/context.py index 199c678..1622a1e 100644 --- a/src/pystencilssfg/context.py +++ b/src/pystencilssfg/context.py @@ -115,6 +115,10 @@ class SfgCursor: else: self._loc[f] = f.elements + @property + def context(self) -> SfgContext: + return self._ctx + @property def current_namespace(self) -> SfgNamespace: return self._cur_namespace diff --git a/src/pystencilssfg/emission/file_printer.py b/src/pystencilssfg/emission/file_printer.py index 765bf70..648e419 100644 --- a/src/pystencilssfg/emission/file_printer.py +++ b/src/pystencilssfg/emission/file_printer.py @@ -27,9 +27,7 @@ from ..config import CodeStyle class SfgFilePrinter: def __init__(self, code_style: CodeStyle) -> None: self._code_style = code_style - self._kernel_printer = CAstPrinter( - indent_width=code_style.get_option("indent_width") - ) + self._indent_width = code_style.get_option("indent_width") def __call__(self, file: SfgSourceFile) -> str: code = "" @@ -86,7 +84,11 @@ class SfgFilePrinter: ) -> str: match declared_entity: case SfgKernelHandle(kernel): - return self._kernel_printer.print_signature(kernel) + ";" + kernel_printer = CAstPrinter( + indent_width=self._indent_width, + func_prefix="inline" if declared_entity.inline else None, + ) + return kernel_printer.print_signature(kernel) + ";" case SfgFunction(name, _, params) | SfgMethod(name, _, params): return self._func_signature(declared_entity, inclass) + ";" @@ -113,7 +115,11 @@ class SfgFilePrinter: ) -> str: match defined_entity: case SfgKernelHandle(kernel): - return self._kernel_printer(kernel) + kernel_printer = CAstPrinter( + indent_width=self._indent_width, + func_prefix="inline" if defined_entity.inline else None, + ) + return kernel_printer(kernel) case SfgFunction(name, tree, params) | SfgMethod(name, tree, params): sig = self._func_signature(defined_entity, inclass) diff --git a/src/pystencilssfg/ir/entities.py b/src/pystencilssfg/ir/entities.py index 40abb14..0edde22 100644 --- a/src/pystencilssfg/ir/entities.py +++ b/src/pystencilssfg/ir/entities.py @@ -141,12 +141,20 @@ class SfgKernelHandle(SfgCodeEntity): __match_args__ = ("kernel", "parameters") - def __init__(self, name: str, namespace: SfgKernelNamespace, kernel: Kernel): + def __init__( + self, + name: str, + namespace: SfgKernelNamespace, + kernel: Kernel, + inline: bool = False, + ): super().__init__(name, namespace) self._kernel = kernel self._parameters = [SfgKernelParamVar(p) for p in kernel.parameters] + self._inline: bool = inline + self._scalar_params: set[SfgVar] = set() self._fields: set[Field] = set() @@ -176,6 +184,10 @@ class SfgKernelHandle(SfgCodeEntity): """Underlying pystencils kernel object""" return self._kernel + @property + def inline(self) -> bool: + return self._inline + class SfgKernelNamespace(SfgNamespace): """A namespace grouping together a number of kernels.""" diff --git a/tests/generator_scripts/index.yaml b/tests/generator_scripts/index.yaml index 5e2db9a..e7db347 100644 --- a/tests/generator_scripts/index.yaml +++ b/tests/generator_scripts/index.yaml @@ -53,6 +53,16 @@ ComposerFeatures: - regex: >- \[\[nodiscard\]\]\s*static\s*double\s*geometric\(\s*double\s*q,\s*uint64_t\s*k\) +ComposerHeaderOnly: + sfg-args: + header-only: true + expect-code: + hpp: + - regex: >- + inline\sint32_t\stwice\( + - regex: >- + inline\svoid\skernel\( + Conditionals: expect-code: cpp: @@ -84,10 +94,11 @@ MdSpanLbStreaming: SyclKernels: sfg-args: - output-mode: inline - file-extensions: [hpp, ipp] + header-only: true expect-code: - ipp: + hpp: + - regex: >- + inline\svoid\skernel\( - regex: >- cgh\.parallel_for\(range,\s*\[=\]\s*\(const\s+sycl::item<\s*2\s*>\s+sycl_item\s*\)\s*\{\s*kernels::kernel\(.*\);\s*\}\); diff --git a/tests/generator_scripts/source/ComposerHeaderOnly.harness.cpp b/tests/generator_scripts/source/ComposerHeaderOnly.harness.cpp new file mode 100644 index 0000000..f6c549a --- /dev/null +++ b/tests/generator_scripts/source/ComposerHeaderOnly.harness.cpp @@ -0,0 +1,18 @@ +#include "ComposerHeaderOnly.hpp" + +#include <vector> + +#undef NDEBUG +#include <cassert> + +int main(void) { + assert( twice(13) == 26 ); + + std::vector< int64_t > arr { 1, 2, 3, 4, 5, 6 }; + twiceKernel(arr); + + std::vector< int64_t > expected { 2, 4, 6, 8, 10, 12 }; + assert ( arr == expected ); + + return 0; +} diff --git a/tests/generator_scripts/source/ComposerHeaderOnly.py b/tests/generator_scripts/source/ComposerHeaderOnly.py new file mode 100644 index 0000000..0bd1a07 --- /dev/null +++ b/tests/generator_scripts/source/ComposerHeaderOnly.py @@ -0,0 +1,26 @@ +from pystencilssfg import SourceFileGenerator, SfgConfig +from pystencilssfg.lang.cpp import std +import pystencils as ps + +cfg = SfgConfig(header_only=True) + +with SourceFileGenerator(cfg) as sfg: + n = sfg.var("n", "int32") + + # Should be automatically marked inline + sfg.function("twice").returns("int32")( + sfg.expr("return 2 * {};", n) + ) + + # Inline kernel + + arr = ps.fields("arr: int64[1D]") + vec = std.vector.from_field(arr) + + asm = ps.Assignment(arr(0), 2 * arr(0)) + khandle = sfg.kernels.create(asm) + + sfg.function("twiceKernel")( + sfg.map_field(arr, vec), + sfg.call(khandle) + ) -- GitLab From f283398e82abacd34d18996d9ea13d68c6e5e39b Mon Sep 17 00:00:00 2001 From: Frederik Hennig <frederik.hennig@fau.de> Date: Fri, 21 Feb 2025 14:48:38 +0100 Subject: [PATCH 3/6] code formatting --- src/pystencilssfg/config.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pystencilssfg/config.py b/src/pystencilssfg/config.py index 482ca8e..34d2f27 100644 --- a/src/pystencilssfg/config.py +++ b/src/pystencilssfg/config.py @@ -5,7 +5,6 @@ from argparse import ArgumentParser, BooleanOptionalAction from types import ModuleType from typing import Any, Sequence, Callable from dataclasses import dataclass -from enum import Enum, auto from os import path from importlib import util as iutil from pathlib import Path @@ -120,7 +119,7 @@ class SfgConfig(ConfigBase): header_only: BasicOption[bool] = BasicOption(False) """If set to `True`, generate only a header file. - + This will cause all definitions to be generated ``inline``. """ @@ -192,7 +191,7 @@ class CommandLineParameters: "--sfg-header-only", action=BooleanOptionalAction, dest="header_only", - help="Generate only a header file." + help="Generate only a header file.", ) config_group.add_argument( "--sfg-config-module", type=str, default=None, dest="config_module_path" -- GitLab From c0c1aebe8ea34b6c31f182a9e3afbdbf91182b59 Mon Sep 17 00:00:00 2001 From: Frederik Hennig <frederik.hennig@fau.de> Date: Fri, 21 Feb 2025 14:52:47 +0100 Subject: [PATCH 4/6] attempt to fix regexes --- tests/generator_scripts/index.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/generator_scripts/index.yaml b/tests/generator_scripts/index.yaml index e7db347..1c97aaf 100644 --- a/tests/generator_scripts/index.yaml +++ b/tests/generator_scripts/index.yaml @@ -59,9 +59,9 @@ ComposerHeaderOnly: expect-code: hpp: - regex: >- - inline\sint32_t\stwice\( + inline\s+int32_t\s+twice\s*\( - regex: >- - inline\svoid\skernel\( + inline\s+void\s+kernel\s*\( Conditionals: expect-code: @@ -98,7 +98,7 @@ SyclKernels: expect-code: hpp: - regex: >- - inline\svoid\skernel\( + inline\s+void\s+kernel\s*\( - regex: >- cgh\.parallel_for\(range,\s*\[=\]\s*\(const\s+sycl::item<\s*2\s*>\s+sycl_item\s*\)\s*\{\s*kernels::kernel\(.*\);\s*\}\); -- GitLab From ec46da109cc51b60a7fdfc501e27b01a7e32c2df Mon Sep 17 00:00:00 2001 From: Frederik Hennig <frederik.hennig@fau.de> Date: Fri, 21 Feb 2025 15:04:34 +0100 Subject: [PATCH 5/6] Add section on header-only mode to docs --- docs/source/usage/config_and_cli.md | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/docs/source/usage/config_and_cli.md b/docs/source/usage/config_and_cli.md index 80169b1..785ff52 100644 --- a/docs/source/usage/config_and_cli.md +++ b/docs/source/usage/config_and_cli.md @@ -42,14 +42,15 @@ The file extensions of the generated files can be modified through {any}`cfg.extensions.header <FileExtensions.header>` and {any}`cfg.extensions.impl <FileExtensions.impl>`; and the output directory of the code generator can be set through {any}`cfg.output_directory <SfgConfig.output_directory>`. -Header-only code generation can be enabled using {any}`cfg.header_only <SfgConfig.header_only>`. +The [header-only mode](#header_only_mode) can be enabled using {any}`cfg.header_only <SfgConfig.header_only>`. :::{danger} -When running generator scripts through [CMake](#cmake_integration), you should *never* set the file extensions -and the output directory in the inline configuration. -Both are managed by the pystencils-sfg CMake module, and setting them manually inside the script will -lead to an error. +When running generator scripts through [CMake](#cmake_integration), the file extensions, +output directory, and header-only mode settings will be managed fully by the pystencils-sfg +CMake module and the (optional) project configuration module. +They should therefore not be set in the inline configuration, +as this will likely lead to errors being raised during code generation. ::: ### Outer Namespace @@ -89,6 +90,17 @@ with the `--help` flag: $ python kernels.py --help ``` +(header_only_mode)= +## Header-Only Mode + +When the header-only output mode is enabled, +the code generator will emit only a header file and no separate implementation file. +In this case, the composer will automatically place all function, method, +and kernel definitions in the header file. + +Header-only code generation can be enabled by setting the `--header-only` command-line flag +or the {any}`SfgConfig.header_only` configuration option. + (custom_cli_args)= ## Adding Custom Command-Line Options -- GitLab From f91ed584cbb2dddd1ede1bf638589f2a146a427f Mon Sep 17 00:00:00 2001 From: Frederik Hennig <frederik.hennig@fau.de> Date: Thu, 27 Feb 2025 17:46:35 +0100 Subject: [PATCH 6/6] add class method and constructor generation to header-only test --- .../source/ComposerHeaderOnly.harness.cpp | 20 ++++++++++++++---- .../source/ComposerHeaderOnly.py | 21 ++++++++++++++++++- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/tests/generator_scripts/source/ComposerHeaderOnly.harness.cpp b/tests/generator_scripts/source/ComposerHeaderOnly.harness.cpp index f6c549a..76f2de4 100644 --- a/tests/generator_scripts/source/ComposerHeaderOnly.harness.cpp +++ b/tests/generator_scripts/source/ComposerHeaderOnly.harness.cpp @@ -8,11 +8,23 @@ int main(void) { assert( twice(13) == 26 ); - std::vector< int64_t > arr { 1, 2, 3, 4, 5, 6 }; - twiceKernel(arr); + { + std::vector< int64_t > arr { 1, 2, 3, 4, 5, 6 }; + twiceKernel(arr); - std::vector< int64_t > expected { 2, 4, 6, 8, 10, 12 }; - assert ( arr == expected ); + std::vector< int64_t > expected { 2, 4, 6, 8, 10, 12 }; + assert ( arr == expected ); + } + + { + std::vector< int64_t > arr { 1, 2, 3, 4, 5, 6 }; + ScaleKernel ker { 3 }; + + ker( arr ); + + std::vector< int64_t > expected { 3, 6, 9, 12, 15, 18 }; + assert ( arr == expected ); + } return 0; } diff --git a/tests/generator_scripts/source/ComposerHeaderOnly.py b/tests/generator_scripts/source/ComposerHeaderOnly.py index 0bd1a07..3881457 100644 --- a/tests/generator_scripts/source/ComposerHeaderOnly.py +++ b/tests/generator_scripts/source/ComposerHeaderOnly.py @@ -17,10 +17,29 @@ with SourceFileGenerator(cfg) as sfg: arr = ps.fields("arr: int64[1D]") vec = std.vector.from_field(arr) - asm = ps.Assignment(arr(0), 2 * arr(0)) + c = ps.TypedSymbol("c", "int64") + + asm = ps.Assignment(arr(0), c * arr(0)) khandle = sfg.kernels.create(asm) sfg.function("twiceKernel")( sfg.map_field(arr, vec), + sfg.set_param(c, "2"), sfg.call(khandle) ) + + # Inline class members + + sfg.klass("ScaleKernel")( + sfg.private( + c + ), + sfg.public( + sfg.constructor(c).init(c)(c), + sfg.method("operator()")( + sfg.map_field(arr, vec), + sfg.set_param(c, "this->c"), + sfg.call(khandle) + ) + ) + ) -- GitLab