diff --git a/src/pystencilssfg/composer/basic_composer.py b/src/pystencilssfg/composer/basic_composer.py index d78e43deb53345aceca56baa6cf63f07b3a1d8de..9cf64ca3c1d74da70d2d7e0a54b8de62a0110593 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, Lambda 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 @@ -661,6 +664,7 @@ class SfgFunctionSequencerBase: # Qualifiers self._inline: bool = False + self._externC: bool = False self._constexpr: bool = False # Attributes @@ -702,6 +706,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( @@ -710,6 +718,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 5773455198187fef7d670ca1f4f9232fb1d7adde..6f314ebff951e35688ef3b5983561c17eff18131 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, clang_format_opts: ClangFormatOptions | None = None, @@ -30,6 +31,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 @@ -85,6 +88,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..68ac4af73a15bc6cbfe2113c6e0425d5fb50cf62 100644 --- a/src/pystencilssfg/emission/file_printer.py +++ b/src/pystencilssfg/emission/file_printer.py @@ -42,9 +42,25 @@ 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 += "#ifdef __cplusplus\n" \ + f"{includes}" \ + "#else\n" \ + f"{hybrid_includes}" \ + "#endif\n" + else: + code += includes if file.includes: code += "\n" @@ -56,7 +72,7 @@ class SfgFilePrinter: return code def visit( - self, elem: SfgNamespaceElement | SfgClassBodyElement, inclass: bool = False + self, elem: SfgNamespaceElement | SfgClassBodyElement, inclass: bool = False ) -> str: match elem: case str(): @@ -78,9 +94,9 @@ class SfgFilePrinter: assert False, "illegal code element" def visit_decl( - self, - declared_entity: SfgKernelHandle | SfgFunction | SfgClassMember | SfgClass, - inclass: bool = False, + self, + declared_entity: SfgKernelHandle | SfgFunction | SfgClassMember | SfgClass, + inclass: bool = False, ) -> str: match declared_entity: case SfgKernelHandle(kernel): @@ -109,9 +125,9 @@ class SfgFilePrinter: assert False, f"unsupported declared entity: {declared_entity}" def visit_defin( - self, - defined_entity: SfgKernelHandle | SfgFunction | SfgClassMember | SfgClassBody, - inclass: bool = False, + self, + defined_entity: SfgKernelHandle | SfgFunction | SfgClassMember | SfgClassBody, + inclass: bool = False, ) -> str: match defined_entity: case SfgKernelHandle(kernel): @@ -192,6 +208,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 fe4eb99d4cf15056e457fd14a7976d54bb0db6ae..02464266e475b87d94f21053174e1054f16f50e6 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: @@ -100,6 +101,15 @@ 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 @@ -111,6 +121,7 @@ class SourceFileGenerator: self._context = SfgContext( self._header_file, self._impl_file, + self._c_interfacing, namespace, config.codestyle, config.clang_format, @@ -147,6 +158,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"""