diff --git a/src/pystencilssfg/composer/basic_composer.py b/src/pystencilssfg/composer/basic_composer.py index 31337a6282932420f8d5b6d9093deec5c2caea1a..cdf425db66e83bfb3805c3c5cc7af6ef85f7c506 100644 --- a/src/pystencilssfg/composer/basic_composer.py +++ b/src/pystencilssfg/composer/basic_composer.py @@ -15,6 +15,7 @@ from pystencils import ( ) from pystencils.codegen import Kernel from pystencils.types import create_type, UserTypeSpec, PsType +from pystencilssfg.ir import SfgSourceFileType from ..context import SfgContext, SfgCursor from .custom import CustomGenerator @@ -383,6 +384,8 @@ class SfgBasicComposer(SfgIComposer): if self._ctx.impl_file is None: seq.inline() + if self._ctx.c_interfacing: + seq._externC = True return seq @@ -672,6 +675,7 @@ class SfgFunctionSequencerBase: # Qualifiers self._inline: bool = False + self._externC: bool = False self._constexpr: bool = False # Attributes @@ -713,6 +717,10 @@ class SfgFunctionSequencer(SfgFunctionSequencerBase): """Sequencer for constructing functions.""" def __call__(self, *args: SequencerArg) -> None: + # check if header is in HYBRID mode for c_interfacing enabled + if self._cursor.context.c_interfacing: + assert self._cursor.context.header_file.file_type == SfgSourceFileType.HYBRID_HEADER + """Populate the function body""" tree = make_sequence(*args) func = SfgFunction( @@ -721,6 +729,7 @@ class SfgFunctionSequencer(SfgFunctionSequencerBase): tree, return_type=self._return_type, inline=self._inline, + externC=self._externC, constexpr=self._constexpr, attributes=self._attributes, required_params=self._params, diff --git a/src/pystencilssfg/config.py b/src/pystencilssfg/config.py index 34d2f27c0fe2364786a7a58a8afe02ae11654154..7ced262f5cf7c029390caaf374141de8d7936412 100644 --- a/src/pystencilssfg/config.py +++ b/src/pystencilssfg/config.py @@ -123,6 +123,12 @@ class SfgConfig(ConfigBase): This will cause all definitions to be generated ``inline``. """ + c_interfacing: BasicOption[bool] = BasicOption(False) + """If set to `True`, generates header files compatible for interfacing with C. + + This will cause all definitions to be generated ``extern "C"``. + """ + outer_namespace: BasicOption[str | _GlobalNamespace] = BasicOption(GLOBAL_NAMESPACE) """The outermost namespace in the generated file. May be a valid C++ nested namespace qualifier (like ``a::b::c``) or `GLOBAL_NAMESPACE` if no outer namespace should be generated. diff --git a/src/pystencilssfg/context.py b/src/pystencilssfg/context.py index 1622a1e3bd33259cd89eef171ad043c4ea9ef536..48156500887d89304f054e09c2e533f5053299f1 100644 --- a/src/pystencilssfg/context.py +++ b/src/pystencilssfg/context.py @@ -21,6 +21,7 @@ class SfgContext: self, header_file: SfgSourceFile, impl_file: SfgSourceFile | None, + c_interfacing: bool = False, namespace: str | None = None, codestyle: CodeStyle | None = None, argv: Sequence[str] | None = None, @@ -29,6 +30,8 @@ class SfgContext: self._argv = argv self._project_info = project_info + self._c_interfacing = c_interfacing + self._outer_namespace = namespace self._inner_namespace: str | None = None @@ -77,6 +80,10 @@ class SfgContext: def header_file(self) -> SfgSourceFile: return self._header_file + @property + def c_interfacing(self) -> bool: + return self._c_interfacing + @property def impl_file(self) -> SfgSourceFile | None: return self._impl_file diff --git a/src/pystencilssfg/emission/file_printer.py b/src/pystencilssfg/emission/file_printer.py index 648e41971336fac709f21848f729b025de27ff36..ebd89cc84d1c384ce3ba1e7180261c312e410b06 100644 --- a/src/pystencilssfg/emission/file_printer.py +++ b/src/pystencilssfg/emission/file_printer.py @@ -42,9 +42,26 @@ class SfgFilePrinter: if file.file_type == SfgSourceFileType.HEADER: code += "#pragma once\n\n" + includes = "" for header in file.includes: incl = str(header) if header.system_header else f'"{str(header)}"' - code += f"#include {incl}\n" + includes += f"#include {incl}\n" + + if file.file_type == SfgSourceFileType.HYBRID_HEADER: + hybrid_includes = "" + for header in file.hybrid_includes: + incl = str(header) if header.system_header else f'"{str(header)}"' + hybrid_includes += f"#include {incl}\n" + + # include different headers and wrap around guard distinguishing C++/C compilations + code += f""" + #ifdef __cplusplus\n + {includes} + #else\n + {hybrid_includes} + #endif\n""" + else: + code += includes if file.includes: code += "\n" @@ -192,6 +209,9 @@ class SfgFilePrinter: if func.inline and not inclass: code += "inline " + if isinstance(func, SfgFunction) and func.externC and not inclass: + code += "EXTERNC " + if isinstance(func, SfgMethod) and inclass: if func.static: code += "static " diff --git a/src/pystencilssfg/generator.py b/src/pystencilssfg/generator.py index c314d67bbc45d5da639d3b7a2b2f92667cc77586..8f720b404e4f09bc3746584caa71eb4f570eeb1d 100644 --- a/src/pystencilssfg/generator.py +++ b/src/pystencilssfg/generator.py @@ -78,15 +78,16 @@ class SourceFileGenerator: config.override(sfg_config) self._header_only: bool = config.get_option("header_only") + self._c_interfacing: bool = config.get_option("c_interfacing") self._output_dir: Path = config.get_option("output_directory") output_files = config._get_output_files(basename) from .ir import SfgSourceFile, SfgSourceFileType - self._header_file = SfgSourceFile( - output_files[0].name, SfgSourceFileType.HEADER - ) + header_type = SfgSourceFileType.HYBRID_HEADER \ + if self._c_interfacing else SfgSourceFileType.HEADER + self._header_file = SfgSourceFile(output_files[0].name, header_type) self._impl_file: SfgSourceFile | None if self._header_only: @@ -102,6 +103,16 @@ class SourceFileGenerator: # TODO: Find a way to not hard-code the restrict qualifier in pystencils self._header_file.elements.append("#define RESTRICT __restrict__") + # TODO: Find a way to not hard-code the 'extern" C"' qualifier in pystencils + self._header_file.elements.append( + """#ifdef __cplusplus\n + #define EXTERNC extern \"C\"\n + #else\n + #define EXTERNC \n + #endif\n + """ + ) + outer_namespace: str | _GlobalNamespace = config.get_option("outer_namespace") namespace: str | None @@ -113,6 +124,7 @@ class SourceFileGenerator: self._context = SfgContext( self._header_file, self._impl_file, + self._c_interfacing, namespace, config.codestyle, argv=script_args, @@ -152,6 +164,21 @@ class SourceFileGenerator: ) self._header_file.includes.sort(key=self._include_sort_key) + if self._c_interfacing: + # from: https://en.cppreference.com/w/cpp/header + c_compatibility_headers = [ + "<cassert", "<cctype>", "<cerrno>", "<cfenv>", "<cfloat>", + "<cinttypes>", "<climits>", "<clocale>", "<cmath>", + "<csetjmp>", "<csignal>", "<cstdarg>", "<cstddef>", "<cstdint>", + "<cstdio>", "<cstdlib>", "<cstring>", "<ctime>", "<cuchar>", + "<cwchar>", "<cwctype>" + ] + + for inc in self._header_file.includes: + if inc.system_header and inc.__str__() in c_compatibility_headers: + c_header = inc.__str__().replace("<c", "<").replace(">", ".h>") + self._header_file.hybrid_includes += [HeaderFile.parse(c_header)] + if self._impl_file is not None: impl_includes = collect_includes(self._impl_file) # If some header is already included by the generated header file, do not duplicate that inclusion diff --git a/src/pystencilssfg/ir/entities.py b/src/pystencilssfg/ir/entities.py index 0edde2209a7ec7bab102de33202aecd3011bfd8a..41f5daa26b8371039aabc23f9f34700ed16bbd60 100644 --- a/src/pystencilssfg/ir/entities.py +++ b/src/pystencilssfg/ir/entities.py @@ -222,6 +222,7 @@ class CommonFunctionProperties: parameters: tuple[SfgVar, ...] return_type: PsType inline: bool + externC: bool constexpr: bool attributes: Sequence[str] @@ -258,6 +259,7 @@ class SfgFunction(SfgCodeEntity, CommonFunctionProperties): tree: SfgCallTreeNode, return_type: PsType = void, inline: bool = False, + externC: bool = False, constexpr: bool = False, attributes: Sequence[str] = (), required_params: Sequence[SfgVar] | None = None, @@ -272,6 +274,7 @@ class SfgFunction(SfgCodeEntity, CommonFunctionProperties): parameters, return_type, inline, + externC, constexpr, attributes, ) @@ -380,6 +383,8 @@ class SfgMethod(SfgClassMember, CommonFunctionProperties): self._virtual = virtual self._override = override + externC = False + parameters = self.collect_params(tree, required_params) CommonFunctionProperties.__init__( @@ -388,6 +393,7 @@ class SfgMethod(SfgClassMember, CommonFunctionProperties): parameters, return_type, inline, + externC, constexpr, attributes, ) diff --git a/src/pystencilssfg/ir/syntax.py b/src/pystencilssfg/ir/syntax.py index cdbd4c283b6bb0078e1051f89565b3b6b32d8d21..71d7fd04fd888da2a37dd40c1491cffa4106e18c 100644 --- a/src/pystencilssfg/ir/syntax.py +++ b/src/pystencilssfg/ir/syntax.py @@ -181,6 +181,7 @@ SfgNamespaceElement = ( class SfgSourceFileType(Enum): HEADER = auto() + HYBRID_HEADER = auto() TRANSLATION_UNIT = auto() @@ -200,6 +201,7 @@ class SfgSourceFile: self._file_type: SfgSourceFileType = file_type self._prelude: str | None = prelude self._includes: list[HeaderFile] = [] + self._hybrid_includes: list[HeaderFile] = [] self._elements: list[SfgNamespaceElement] = [] @property @@ -230,6 +232,15 @@ class SfgSourceFile: def includes(self, incl: Iterable[HeaderFile]): self._includes = list(incl) + @property + def hybrid_includes(self) -> list[HeaderFile]: + """Sequence of header files to be included at the top of this file""" + return self._hybrid_includes + + @hybrid_includes.setter + def hybrid_includes(self, incl: Iterable[HeaderFile]): + self._hybrid_includes = list(incl) + @property def elements(self) -> list[SfgNamespaceElement]: """Sequence of source elements comprising the body of this file"""