diff --git a/docs/source/api/generation.rst b/docs/source/api/generation.rst index 8e7b0b4961b34154e3c20694f1e69c225de6623b..7a0d0131f1206248a21b7a49fa77f81bd42fdc38 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 1fdc9df1e7fdb3468fc74de1de3c7b4b720e8ef1..785ff52ed1d5153b04be9e900363af156ff94e7c 100644 --- a/docs/source/usage/config_and_cli.md +++ b/docs/source/usage/config_and_cli.md @@ -42,13 +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>`. +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 @@ -76,7 +78,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. @@ -88,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 diff --git a/docs/source/usage/project_integration.md b/docs/source/usage/project_integration.md index 47b1b6875a502f9c8011df1b51964a85b1ad6281..0ad13e8b98572f721107512e6b2b466a5730c859 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 a1e52eb85af599814a2192b726afd4aae2fd084d..a49d4e492136c24c03182db971f0ce07726b8872 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 fea6f8a10e198f86f18be34a6cad381ed3238e19..cea370c65d25242cfb0399e89a5dc7f42058f4ec 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 0779599a0bd9dd1d90a5a1f1fd5f351f6a2fcde4..eb4c2346ec53e044fd12899a613cbc17a5ad9f86 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/composer/basic_composer.py b/src/pystencilssfg/composer/basic_composer.py index 49b6c73e8bd06b0d5fb44402b8af285362072b29..ff1ab2df679aeefd2112d539b90dd59cf93e015b 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 7787150c5bffd4e7332aecd2ff7a4a66cac0adc6..5dbb4c65d53c9305225239cdaf9753413d07d106 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/config.py b/src/pystencilssfg/config.py index bbe2389946c4b426dcbd14c5e3e4205b81cef976..34d2f27c0fe2364786a7a58a8afe02ae11654154 100644 --- a/src/pystencilssfg/config.py +++ b/src/pystencilssfg/config.py @@ -1,11 +1,10 @@ from __future__ import annotations -from argparse import ArgumentParser +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 @@ -25,7 +24,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 +36,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 +117,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. + header_only: BasicOption[bool] = BasicOption(False) + """If set to `True`, generate only a header file. - Possible parameters: - .. autosummary:: - OutputMode.STANDALONE - OutputMode.INLINE - OutputMode.HEADER_ONLY + This will cause all definitions to be generated ``inline``. """ outer_namespace: BasicOption[str | _GlobalNamespace] = BasicOption(GLOBAL_NAMESPACE) @@ -187,16 +163,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 +188,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 +202,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 +233,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 +246,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 +274,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/context.py b/src/pystencilssfg/context.py index 199c678ba28e449f42b29c56147b9a4fd0d523bb..1622a1e3bd33259cd89eef171ad043c4ea9ef536 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 765bf70550504fd499746504236f1adaa224664a..648e41971336fac709f21848f729b025de27ff36 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/generator.py b/src/pystencilssfg/generator.py index 91a124a8439c06dcb22853975cf08a5e9526ecde..c314d67bbc45d5da639d3b7a2b2f92667cc77586 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/src/pystencilssfg/ir/entities.py b/src/pystencilssfg/ir/entities.py index 40abb148d90eb35300afd0af4e25d2f8df1c091e..0edde2209a7ec7bab102de33202aecd3011bfd8a 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/test_config.py b/tests/generator/test_config.py index 250c158c633d6f8fc2f11f0d4b3b2cbd13a13128..9d542cd358d8b0195d578788cc38a79ef8f26426 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 185e2703e2206a0c1d6377effbf6c9961cae83a4..5dfc474f582cb3093c7fa8941aac692cd1e1bbbb 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 68352fe2c2904c11e551955eeb29a6bf9424e126..1c97aaf6a34b0171f4829c56da0f345934c50176 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: @@ -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\s+int32_t\s+twice\s*\( + - regex: >- + inline\s+void\s+kernel\s*\( + Conditionals: expect-code: cpp: @@ -67,7 +77,7 @@ Conditionals: NestedNamespaces: sfg-args: - output-mode: header-only + header-only: true # Kernel Generation @@ -84,10 +94,11 @@ MdSpanLbStreaming: SyclKernels: sfg-args: - output-mode: inline - file-extensions: [hpp, ipp] + header-only: true expect-code: - ipp: + hpp: + - regex: >- + 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*\}\); diff --git a/tests/generator_scripts/source/ComposerHeaderOnly.harness.cpp b/tests/generator_scripts/source/ComposerHeaderOnly.harness.cpp new file mode 100644 index 0000000000000000000000000000000000000000..76f2de4c99da5bb7bb968a404f4c43b4dcfb7482 --- /dev/null +++ b/tests/generator_scripts/source/ComposerHeaderOnly.harness.cpp @@ -0,0 +1,30 @@ +#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 ); + } + + { + 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 new file mode 100644 index 0000000000000000000000000000000000000000..3881457634e8e5182ce0922700e7ad1541199cca --- /dev/null +++ b/tests/generator_scripts/source/ComposerHeaderOnly.py @@ -0,0 +1,45 @@ +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) + + 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) + ) + ) + ) diff --git a/tests/generator_scripts/source/SyclKernels.py b/tests/generator_scripts/source/SyclKernels.py index 841774383de6c11f7fcc893d694a8ff7d0f33205..f181a3df343156801d7958c2eb2b7200045cf249 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 bba12af2374d9f0177b7ec76bc8777627da366fe..6f2ff160e8b9ec2367020a4a3f91d4a071a71e39 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 ee7afae6d5781df607246813f47328bc1d45cfd3..f93b090e2e5de0a6f32e391286c33644194b17ce 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 d3c2585453627e7dc706083f4b1508160ea2f09c..41cdda849e0cb6d35ccb1e78656aee69a697478d 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", ]