├── .editorconfig ├── .github ├── dependabot.yml └── workflows │ ├── autopush.yml │ └── ci.yml ├── .gitignore ├── .gitlab-ci.yml ├── LICENSE ├── README.rst ├── cgen ├── __init__.py ├── cuda.py ├── ispc.py ├── mapper.py ├── opencl.py ├── py.typed └── version.py ├── doc ├── Makefile ├── cgen.rst ├── conf.py ├── faq.rst ├── index.rst └── upload-docs.sh ├── examples └── hello-cgen.py ├── pyproject.toml └── test └── test_cgen.py /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org/ 2 | # https://github.com/editorconfig/editorconfig-vim 3 | # https://github.com/editorconfig/editorconfig-emacs 4 | 5 | root = true 6 | 7 | [*] 8 | indent_style = space 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | 14 | [*.py] 15 | indent_size = 4 16 | 17 | [*.rst] 18 | indent_size = 4 19 | 20 | [*.cpp] 21 | indent_size = 2 22 | 23 | [*.hpp] 24 | indent_size = 2 25 | 26 | # There may be one in doc/ 27 | [Makefile] 28 | indent_style = tab 29 | 30 | # https://github.com/microsoft/vscode/issues/1679 31 | [*.md] 32 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Set update schedule for GitHub Actions 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" 8 | 9 | # vim: sw=4 10 | -------------------------------------------------------------------------------- /.github/workflows/autopush.yml: -------------------------------------------------------------------------------- 1 | name: Gitlab mirror 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | jobs: 8 | autopush: 9 | name: Automatic push to gitlab.tiker.net 10 | if: startsWith(github.repository, 'inducer/') 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - run: | 15 | curl -L -O https://tiker.net/ci-support-v0 16 | . ./ci-support-v0 17 | mirror_github_to_gitlab 18 | 19 | env: 20 | GITLAB_AUTOPUSH_KEY: ${{ secrets.GITLAB_AUTOPUSH_KEY }} 21 | 22 | # vim: sw=4 23 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | paths-ignore: 8 | - 'doc/*.rst' 9 | schedule: 10 | - cron: '17 3 * * 0' 11 | 12 | jobs: 13 | ruff: 14 | name: Ruff 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: actions/setup-python@v5 19 | - name: "Main Script" 20 | run: | 21 | pip install ruff 22 | ruff check 23 | 24 | typos: 25 | name: Typos 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v4 29 | - uses: crate-ci/typos@master 30 | 31 | pylint: 32 | name: Pylint 33 | runs-on: ubuntu-latest 34 | steps: 35 | - uses: actions/checkout@v4 36 | - name: "Main Script" 37 | run: | 38 | curl -L -O https://gitlab.tiker.net/inducer/ci-support/raw/main/prepare-and-run-pylint.sh 39 | . ./prepare-and-run-pylint.sh "$(basename $GITHUB_REPOSITORY)" examples/*.py test/test_*.py 40 | 41 | mypy: 42 | name: Mypy 43 | runs-on: ubuntu-latest 44 | steps: 45 | - uses: actions/checkout@v4 46 | - 47 | uses: actions/setup-python@v5 48 | with: 49 | python-version: '3.x' 50 | - name: "Main Script" 51 | run: | 52 | EXTRA_INSTALL="mypy pytest" 53 | curl -L -O https://tiker.net/ci-support-v0 54 | . ./ci-support-v0 55 | 56 | build_py_project_in_venv 57 | python -m mypy "$(basename $GITHUB_REPOSITORY)" examples test 58 | 59 | pytest: 60 | name: Pytest on Py${{ matrix.python-version }} 61 | runs-on: ubuntu-latest 62 | strategy: 63 | matrix: 64 | python-version: ['3.10', '3.12', '3.x'] 65 | steps: 66 | - uses: actions/checkout@v4 67 | - 68 | uses: actions/setup-python@v5 69 | with: 70 | python-version: ${{ matrix.python-version }} 71 | - name: "Main Script" 72 | run: | 73 | curl -L -O https://tiker.net/ci-support-v0 74 | . ./ci-support-v0 75 | 76 | build_py_project_in_venv 77 | test_py_project 78 | 79 | docs: 80 | name: Documentation 81 | runs-on: ubuntu-latest 82 | steps: 83 | - uses: actions/checkout@v4 84 | - 85 | uses: actions/setup-python@v5 86 | with: 87 | python-version: '3.x' 88 | - name: "Main Script" 89 | run: | 90 | curl -L -O https://tiker.net/ci-support-v0 91 | . ci-support-v0 92 | build_py_project_in_venv 93 | build_docs 94 | 95 | downstream_tests: 96 | strategy: 97 | matrix: 98 | downstream_project: [loopy] 99 | name: Tests for downstream project ${{ matrix.downstream_project }} 100 | runs-on: ubuntu-latest 101 | steps: 102 | - uses: actions/checkout@v4 103 | - name: "Main Script" 104 | env: 105 | DOWNSTREAM_PROJECT: ${{ matrix.downstream_project }} 106 | run: | 107 | curl -L -O https://tiker.net/ci-support-v0 108 | . ./ci-support-v0 109 | test_downstream "$DOWNSTREAM_PROJECT" 110 | 111 | # vim: sw=4 112 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .*.sw[po] 3 | .swp 4 | *~ 5 | *.pyc 6 | *.pyo 7 | *.egg-info 8 | MANIFEST 9 | dist 10 | setuptools*egg 11 | setuptools.pth 12 | setuptools*.tar.gz 13 | distribute*egg 14 | distribute*tar.gz 15 | _build 16 | 17 | .cache 18 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | Python 3: 2 | script: 3 | - export PY_EXE=python3 4 | - export EXTRA_INSTALL="numpy" 5 | - curl -L -O https://gitlab.tiker.net/inducer/ci-support/raw/main/build-and-test-py-project.sh 6 | - . ./build-and-test-py-project.sh 7 | tags: 8 | - python3 9 | except: 10 | - tags 11 | artifacts: 12 | reports: 13 | junit: test/pytest.xml 14 | 15 | Documentation: 16 | script: 17 | - curl -L -O https://gitlab.tiker.net/inducer/ci-support/raw/main/build-docs.sh 18 | - . ./build-docs.sh 19 | tags: 20 | - linux 21 | 22 | Ruff: 23 | script: | 24 | pipx install ruff 25 | ruff check 26 | tags: 27 | - docker-runner 28 | except: 29 | - tags 30 | 31 | Pylint: 32 | script: 33 | - curl -L -O https://gitlab.tiker.net/inducer/ci-support/raw/main/prepare-and-run-pylint.sh 34 | - . ./prepare-and-run-pylint.sh "$CI_PROJECT_NAME" test/test_*.py 35 | tags: 36 | - python3 37 | except: 38 | - tags 39 | 40 | Downstream: 41 | parallel: 42 | matrix: 43 | - DOWNSTREAM_PROJECT: [loopy] 44 | tags: 45 | - large-node 46 | - "docker-runner" 47 | script: | 48 | curl -L -O https://tiker.net/ci-support-v0 49 | . ./ci-support-v0 50 | test_downstream "$DOWNSTREAM_PROJECT" 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | cgen is licensed to you under the MIT/X Consortium license: 2 | 3 | Copyright (c) 2009-16 Andreas Klöckner and Contributors. 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | cgen: An Abstract Syntax Tree for C, in Python 2 | ---------------------------------------------- 3 | 4 | .. image:: https://gitlab.tiker.net/inducer/cgen/badges/main/pipeline.svg 5 | :alt: Gitlab Build Status 6 | :target: https://gitlab.tiker.net/inducer/cgen/commits/main 7 | .. image:: https://github.com/inducer/cgen/workflows/CI/badge.svg?branch=main&event=push 8 | :alt: Github Build Status 9 | :target: https://github.com/inducer/cgen/actions?query=branch%3Amain+workflow%3ACI+event%3Apush 10 | .. image:: https://badge.fury.io/py/cgen.svg 11 | :alt: Python Package Index Release Page 12 | :target: https://pypi.org/project/cgen/ 13 | 14 | cgen offers a simple abstract syntax tree for C and related languages 15 | (C++/CUDA/OpenCL) to allow structured code generation from Python. 16 | To represent mathematical expressions, cgen can be used with `pymbolic 17 | `_. 18 | 19 | Places on the web related to cgen: 20 | 21 | * `Python package index `_ (download releases) 22 | * `Documentation `_ (read how things work) 23 | * `Github `_ (get latest source code, file bugs) 24 | 25 | cgen is licensed under the liberal `MIT license 26 | `_ and free for commercial, academic, 27 | and private use. All of cgen's dependencies can be automatically installed from 28 | the package index after using:: 29 | 30 | pip install cgen 31 | -------------------------------------------------------------------------------- /cgen/__init__.py: -------------------------------------------------------------------------------- 1 | """Generator for C/C++.""" 2 | 3 | from __future__ import annotations 4 | 5 | 6 | __copyright__ = "Copyright (C) 2008 Andreas Kloeckner" 7 | 8 | __license__ = """ 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in 17 | all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | THE SOFTWARE. 26 | """ 27 | 28 | from abc import ABC, abstractmethod 29 | from typing import TYPE_CHECKING, Any, ClassVar, TypeAlias 30 | 31 | import numpy as np 32 | 33 | from pytools import memoize, memoize_method 34 | 35 | 36 | try: 37 | # NOTE: pycuda still needs this for complex number support. 38 | import pycuda._pvt_struct as _struct 39 | except ImportError: 40 | import struct as _struct 41 | 42 | if TYPE_CHECKING: 43 | from collections.abc import Callable, Iterable, Iterator, Sequence 44 | 45 | 46 | @memoize 47 | def is_long_64_bit() -> bool: 48 | return bool(_struct.calcsize("l") == 8) 49 | 50 | 51 | def dtype_to_ctype(dtype: Any) -> str: 52 | if dtype is None: 53 | raise ValueError("dtype may not be None") 54 | 55 | dtype = np.dtype(dtype) 56 | if dtype == np.int64: 57 | if is_long_64_bit(): 58 | return "long" 59 | else: 60 | return "long long" 61 | elif dtype == np.uint64: 62 | if is_long_64_bit(): 63 | return "unsigned long" 64 | else: 65 | return "unsigned long long" 66 | elif dtype == np.int32: 67 | return "int" 68 | elif dtype == np.uint32: 69 | return "unsigned int" 70 | elif dtype == np.int16: 71 | return "short int" 72 | elif dtype == np.uint16: 73 | return "short unsigned int" 74 | elif dtype == np.int8: 75 | return "signed char" 76 | elif dtype == np.uint8: 77 | return "unsigned char" 78 | elif dtype == np.float32: 79 | return "float" 80 | elif dtype == np.float64: 81 | return "double" 82 | elif dtype == np.complex64: 83 | return "std::complex" 84 | elif dtype == np.complex128: 85 | return "std::complex" 86 | elif dtype == np.void: 87 | return "void" 88 | else: 89 | raise ValueError(f"unable to map dtype '{dtype}'") 90 | 91 | 92 | class Generable(ABC): 93 | mapper_method: ClassVar[str] 94 | 95 | def __str__(self) -> str: 96 | """ 97 | :returns: a single string (possibly containing newlines) representing 98 | this code construct. 99 | """ 100 | return "\n".join(line.rstrip() for line in self.generate()) 101 | 102 | @abstractmethod 103 | def generate(self, with_semicolon: bool = True) -> Iterator[str]: 104 | """Generate (i.e. yield) the lines making up this code construct.""" 105 | 106 | 107 | # {{{ declarators 108 | 109 | DeclPair: TypeAlias = tuple[list[str], str | None] 110 | 111 | 112 | class Declarator(Generable, ABC): 113 | def generate(self, with_semicolon: bool = True) -> Iterator[str]: 114 | tp_lines, tp_decl = self.get_decl_pair() 115 | 116 | yield from tp_lines[:-1] 117 | 118 | sc = ";" if with_semicolon else "" 119 | if tp_decl is None: 120 | yield f"{tp_lines[-1]}{sc}" 121 | else: 122 | yield f"{tp_lines[-1]} {tp_decl}{sc}" 123 | 124 | @abstractmethod 125 | def get_decl_pair(self) -> DeclPair: 126 | """ 127 | :returns: a tuple ``(type_lines, rhs)``. *type_lines* is a non-empty list 128 | of lines (most often just a single one) describing the type of this 129 | declarator. *rhs* is the right-hand side that actually contains the 130 | function/array/constness notation making up the bulk of the 131 | declarator syntax. 132 | """ 133 | 134 | def struct_format(self) -> str: 135 | raise NotImplementedError 136 | 137 | def alignment_requirement(self) -> int: 138 | raise NotImplementedError 139 | 140 | def struct_maker_code(self, data: str) -> str: 141 | raise NotImplementedError 142 | 143 | def default_value(self) -> Any: 144 | raise NotImplementedError 145 | 146 | def inline(self, with_semicolon: bool = False) -> str: 147 | """ 148 | :returns: the declarator as a single line. 149 | """ 150 | tp_lines, tp_decl = self.get_decl_pair() 151 | tp = " ".join(tp_lines) 152 | 153 | sc = ";" if with_semicolon else "" 154 | if tp_decl is None: 155 | return f"{tp}{sc}" 156 | else: 157 | return f"{tp} {tp_decl}{sc}" 158 | 159 | 160 | class POD(Declarator): 161 | """A simple declarator: The type is given as a :class:`numpy.dtype` 162 | and the *name* is given as a string. 163 | """ 164 | 165 | def __init__(self, dtype: Any, name: str) -> None: 166 | self.dtype = np.dtype(dtype) 167 | self.name = name 168 | 169 | def get_decl_pair(self) -> DeclPair: 170 | return [dtype_to_ctype(self.dtype)], self.name 171 | 172 | def struct_maker_code(self, name: str) -> str: 173 | return name 174 | 175 | def struct_format(self) -> str: 176 | return str(self.dtype.char) 177 | 178 | def alignment_requirement(self) -> int: 179 | return int(_struct.calcsize(self.struct_format())) 180 | 181 | def default_value(self) -> Any: 182 | return 0 183 | 184 | mapper_method = "map_pod" 185 | 186 | 187 | class Value(Declarator): 188 | """A simple declarator: *typename* and *name* are given as strings.""" 189 | 190 | def __init__(self, typename: str, name: str) -> None: 191 | self.typename = typename 192 | self.name = name 193 | 194 | def get_decl_pair(self) -> DeclPair: 195 | return [self.typename], self.name 196 | 197 | def struct_maker_code(self, name: str) -> str: 198 | raise RuntimeError("named-type values can't be put into structs") 199 | 200 | def struct_format(self) -> str: 201 | raise RuntimeError("named-type values have no struct format") 202 | 203 | def default_value(self) -> Any: 204 | return 0 205 | 206 | mapper_method = "map_value" 207 | 208 | 209 | class NestedDeclarator(Declarator): 210 | def __init__(self, subdecl: Declarator) -> None: 211 | self.subdecl = subdecl 212 | 213 | @property 214 | def name(self) -> str: 215 | if hasattr(self.subdecl, "name"): 216 | return str(self.subdecl.name) 217 | 218 | raise AttributeError("name") 219 | 220 | def struct_format(self) -> str: 221 | return self.subdecl.struct_format() 222 | 223 | def alignment_requirement(self) -> int: 224 | return self.subdecl.alignment_requirement() 225 | 226 | def struct_maker_code(self, data: str) -> str: 227 | return self.subdecl.struct_maker_code(data) 228 | 229 | def get_decl_pair(self) -> DeclPair: 230 | return self.subdecl.get_decl_pair() 231 | 232 | 233 | class DeclSpecifier(NestedDeclarator): 234 | def __init__(self, subdecl: Declarator, spec: str, sep: str = " ") -> None: 235 | super().__init__(subdecl) 236 | self.spec = spec 237 | self.sep = sep 238 | 239 | def get_decl_pair(self) -> DeclPair: 240 | def add_spec(sub_it: list[str]) -> Iterator[str]: 241 | it = iter(sub_it) 242 | try: 243 | yield f"{self.spec}{self.sep}{next(it)}" 244 | except StopIteration: 245 | pass 246 | 247 | yield from it 248 | 249 | sub_tp, sub_decl = self.subdecl.get_decl_pair() 250 | return list(add_spec(sub_tp)), sub_decl 251 | 252 | 253 | class NamespaceQualifier(DeclSpecifier): 254 | def __init__(self, namespace: str, subdecl: Declarator) -> None: 255 | super().__init__(subdecl, namespace, sep="::") 256 | 257 | @property 258 | def namespace(self) -> str: 259 | return self.spec 260 | 261 | mapper_method = "map_namespace_qualifier" 262 | 263 | 264 | class Typedef(DeclSpecifier): 265 | def __init__(self, subdecl: Declarator) -> None: 266 | super().__init__(subdecl, "typedef") 267 | 268 | mapper_method = "map_typedef" 269 | 270 | 271 | class Static(DeclSpecifier): 272 | def __init__(self, subdecl: Declarator) -> None: 273 | super().__init__(subdecl, "static") 274 | 275 | mapper_method = "map_static" 276 | 277 | 278 | class Const(NestedDeclarator): 279 | def get_decl_pair(self) -> DeclPair: 280 | sub_tp, sub_decl = self.subdecl.get_decl_pair() 281 | return sub_tp, f"const {sub_decl}" 282 | 283 | mapper_method = "map_const" 284 | 285 | 286 | class Volatile(NestedDeclarator): 287 | def get_decl_pair(self) -> DeclPair: 288 | sub_tp, sub_decl = self.subdecl.get_decl_pair() 289 | return sub_tp, f"volatile {sub_decl}" 290 | 291 | mapper_method = "map_volatile" 292 | 293 | 294 | class Extern(DeclSpecifier): 295 | def __init__(self, language: str, subdecl: Declarator) -> None: 296 | self.language = language 297 | super().__init__(subdecl, f'extern "{language}"') 298 | 299 | mapper_method = "map_extern" 300 | 301 | 302 | class TemplateSpecializer(NestedDeclarator): 303 | def __init__(self, specializer: str, subdecl: Declarator) -> None: 304 | self.specializer = specializer 305 | super().__init__(subdecl) 306 | 307 | def get_decl_pair(self) -> DeclPair: 308 | sub_tp, sub_decl = self.subdecl.get_decl_pair() 309 | sub_tp[-1] = f"{sub_tp[-1]}<{self.specializer}>" 310 | return sub_tp, sub_decl 311 | 312 | mapper_method = "map_template_specializer" 313 | 314 | 315 | class MaybeUnused(NestedDeclarator): 316 | def get_decl_pair(self) -> DeclPair: 317 | sub_tp, sub_decl = self.subdecl.get_decl_pair() 318 | return sub_tp, f"{sub_decl} __attribute__ ((unused))" 319 | 320 | mapper_method = "map_maybe_unused" 321 | 322 | 323 | class AlignedAttribute(NestedDeclarator): 324 | """ 325 | Assigns an alignment for a definition of a type or an array. 326 | """ 327 | def __init__(self, align_bytes: int, subdecl: Declarator) -> None: 328 | super().__init__(subdecl) 329 | self.align_bytes = align_bytes 330 | 331 | def get_decl_pair(self) -> DeclPair: 332 | sub_tp, sub_decl = self.subdecl.get_decl_pair() 333 | return sub_tp, f"{sub_decl} __attribute__ ((aligned ({self.align_bytes})))" 334 | 335 | mapper_method = "map_aligned" 336 | 337 | 338 | class AlignValueAttribute(NestedDeclarator): 339 | """ 340 | Assigns an alignment for value of a pointer. 341 | 342 | This is used for pointers where the user guarantees to the compiler that the 343 | value of the pointer has the alignment stated. :class:`AlignedAttribute` on 344 | the other hand tells the compiler to declare the type or the array with the 345 | given alignment and cannot be used for telling the compiler that an existing 346 | pointer has a certain alignment guarantee. 347 | 348 | This attribute is currently supported by clang [1]_ and Intel [2]_ and is ignored 349 | by ``gcc``. 350 | 351 | .. [1] https://reviews.llvm.org/D4635 352 | 353 | .. [2] https://www.intel.com/content/www/us/en/develop/documentation/oneapi-dpcpp-cpp-compiler-dev-guide-and-reference/top/compiler-reference/attributes/align-value.html 354 | """ 355 | def __init__(self, align_bytes: int, subdecl: Declarator) -> None: 356 | super().__init__(subdecl) 357 | self.align_bytes = align_bytes 358 | 359 | def get_decl_pair(self) -> DeclPair: 360 | sub_tp, sub_decl = self.subdecl.get_decl_pair() 361 | return (sub_tp, 362 | f"{sub_decl} __attribute__ ((align_value ({self.align_bytes})))") 363 | 364 | mapper_method = "map_align_value" 365 | 366 | 367 | class Pointer(NestedDeclarator): 368 | def get_decl_pair(self) -> DeclPair: 369 | sub_tp, sub_decl = self.subdecl.get_decl_pair() 370 | return sub_tp, f"*{sub_decl}" 371 | 372 | def struct_maker_code(self, data: str) -> str: 373 | raise NotImplementedError 374 | 375 | def struct_format(self) -> str: 376 | return "P" 377 | 378 | def alignment_requirement(self) -> int: 379 | return int(_struct.calcsize(self.struct_format())) 380 | 381 | mapper_method = "map_pointer" 382 | 383 | 384 | class RestrictPointer(Pointer): 385 | def get_decl_pair(self) -> DeclPair: 386 | sub_tp, sub_decl = self.subdecl.get_decl_pair() 387 | return sub_tp, f"*__restrict__ {sub_decl}" 388 | 389 | mapper_method = "map_restrict_pointer" 390 | 391 | 392 | class Reference(Pointer): 393 | def get_decl_pair(self) -> DeclPair: 394 | sub_tp, sub_decl = self.subdecl.get_decl_pair() 395 | return sub_tp, f"&{sub_decl}" 396 | 397 | mapper_method = "map_reference" 398 | 399 | 400 | class ArrayOf(NestedDeclarator): 401 | def __init__(self, subdecl: Declarator, count: int | None = None) -> None: 402 | super().__init__(subdecl) 403 | self.count = count 404 | 405 | def get_decl_pair(self) -> DeclPair: 406 | sub_tp, sub_decl = self.subdecl.get_decl_pair() 407 | if self.count is None: 408 | count_str = "" 409 | else: 410 | count_str = str(self.count) 411 | return sub_tp, f"{sub_decl}[{count_str}]" 412 | 413 | def struct_maker_code(self, name: str) -> str: 414 | if self.count is None: 415 | return name 416 | 417 | return ", ".join(f"{name}[{i}]" for i in range(self.count)) 418 | 419 | def struct_format(self) -> str: 420 | if self.count is None: 421 | return "P" 422 | else: 423 | return f"{self.count}{self.subdecl.struct_format()}" 424 | 425 | def alignment_requirement(self) -> int: 426 | return self.subdecl.alignment_requirement() 427 | 428 | def default_value(self) -> Any: 429 | if self.count is None: 430 | return [] 431 | 432 | return self.count*[self.subdecl.default_value()] 433 | 434 | mapper_method = "map_array_of" 435 | 436 | 437 | class FunctionDeclaration(NestedDeclarator): 438 | def __init__(self, subdecl: Declarator, arg_decls: Sequence[Declarator]) -> None: 439 | super().__init__(subdecl) 440 | self.arg_decls = tuple(arg_decls) 441 | 442 | def get_decl_pair(self) -> DeclPair: 443 | sub_tp, sub_decl = self.subdecl.get_decl_pair() 444 | 445 | return sub_tp, ("{}({})".format( 446 | sub_decl, 447 | ", ".join(ad.inline() for ad in self.arg_decls))) 448 | 449 | def struct_maker_code(self, data: str) -> str: 450 | raise RuntimeError("function pointers can't be put into structs") 451 | 452 | def struct_format(self) -> str: 453 | raise RuntimeError("function pointers have no struct format") 454 | 455 | mapper_method = "map_function_declaration" 456 | 457 | # }}} 458 | 459 | 460 | # {{{ struct-like 461 | 462 | class Struct(Declarator): 463 | """A structure declarator.""" 464 | 465 | def __init__(self, 466 | tpname: str, 467 | fields: Sequence[Declarator], 468 | declname: str | None = None, 469 | pad_bytes: int = 0) -> None: 470 | """Initialize the structure declarator. 471 | 472 | :arg tpname: the name of the structure. 473 | :arg fields: a sequence of :class:`Declarator` instances. 474 | :arg declname: the name used for the declarator. 475 | :arg pad_bytes: the number of padding bytes added at the end of the structure. 476 | """ 477 | 478 | self.tpname = tpname 479 | self.fields = tuple(fields) 480 | self.declname = declname 481 | self.pad_bytes = pad_bytes 482 | 483 | def get_decl_pair(self) -> DeclPair: 484 | def get_tp() -> Iterator[str]: 485 | if self.tpname is not None: 486 | yield f"struct {self.tpname}" 487 | else: 488 | yield "struct" 489 | 490 | yield "{" 491 | 492 | for f in self.fields: 493 | for f_line in f.generate(): 494 | yield f" {f_line}" 495 | 496 | if self.pad_bytes: 497 | yield f" unsigned char _cgen_pad[{self.pad_bytes}];" 498 | 499 | yield "} " + self.struct_attributes() 500 | 501 | return list(get_tp()), self.declname 502 | 503 | def alignment_requirement(self) -> int: 504 | return max((f.alignment_requirement() for f in self.fields), default=0) 505 | 506 | def struct_attributes(self) -> str: 507 | return "" 508 | 509 | mapper_method = "map_struct" 510 | 511 | 512 | class GenerableStruct(Struct): 513 | def __init__(self, 514 | tpname: str, 515 | fields: Sequence[Declarator], 516 | declname: str | None = None, 517 | align_bytes: int | None = None, 518 | aligned_prime_to: Sequence[int] | None = None) -> None: 519 | """Initialize a structure declarator. 520 | 521 | :arg tpname: the name of the structure. 522 | :arg declname: the name used for the declarator. 523 | :arg pad_bytes: the number of padding bytes added at the end of the structure. 524 | :arg fields: a list of :class:`Declarator` instances. 525 | 526 | :arg align_bytes: an integer that causes the structure to be padded to 527 | an integer multiple of itself. 528 | :arg aligned_prime_to: a sequence of integers. If the resulting structure's 529 | size is ``s``, then ``s//align_bytes`` will be made prime to all 530 | numbers in *aligned_prime_to*. (Sounds obscure? It's needed for 531 | avoiding bank conflicts in CUDA programming.) 532 | """ 533 | if aligned_prime_to is None: 534 | aligned_prime_to = () 535 | aligned_prime_to = tuple(aligned_prime_to) 536 | 537 | fields = tuple(fields) 538 | format = "".join(f.struct_format() for f in fields) 539 | nbytes = _struct.calcsize(format) 540 | 541 | natural_align_bytes = max(f.alignment_requirement() for f in fields) 542 | if align_bytes is None: 543 | align_bytes = natural_align_bytes 544 | elif align_bytes < natural_align_bytes: 545 | from warnings import warn 546 | warn("requested struct alignment smaller than natural alignment", 547 | stacklevel=2) 548 | 549 | padded_bytes = ((nbytes + align_bytes - 1) // align_bytes) * align_bytes 550 | 551 | def satisfies_primality(n: int) -> bool: 552 | import math 553 | for p in aligned_prime_to: 554 | if math.gcd(n, p) != 1: 555 | return False 556 | return True 557 | 558 | while not satisfies_primality(padded_bytes // align_bytes): 559 | padded_bytes += align_bytes 560 | 561 | super().__init__(tpname, fields, declname, padded_bytes - nbytes) 562 | 563 | self.align_bytes = align_bytes 564 | self.aligned_prime_to = aligned_prime_to 565 | 566 | if self.pad_bytes: 567 | self.format = format + f"{self.pad_bytes}x" 568 | self.bytes = padded_bytes 569 | else: 570 | self.format = format 571 | self.bytes = nbytes 572 | 573 | assert _struct.calcsize(self.format) == self.bytes 574 | 575 | # until nvcc bug is fixed 576 | # def struct_attributes(self): 577 | # return "__attribute__ ((packed))" 578 | 579 | def alignment_requirement(self) -> int: 580 | return self.align_bytes 581 | 582 | def make(self, **kwargs: Any) -> str: 583 | """Build a binary, packed representation of *self* in a 584 | :class:`str` instance with members set to the values specified 585 | in *kwargs*. 586 | """ 587 | return self._maker()(_struct.pack, **kwargs) 588 | 589 | def make_with_defaults(self, **kwargs: Any) -> str: 590 | """Build a binary, packed representation of *self* in a 591 | :class:`str` instance with members set to the values specified 592 | in *kwargs*. 593 | 594 | Unlike :meth:`make`, not all members have to occur in *kwargs*. 595 | """ 596 | return self._maker(with_defaults=True)(_struct.pack, **kwargs) 597 | 598 | @memoize_method 599 | def _maker(self, with_defaults: bool = False) -> Callable[..., str]: 600 | def format_arg(f: Declarator) -> str: 601 | # FIXME: should this be narrowed from "Declarator"? 602 | assert hasattr(f, "name") 603 | if with_defaults: 604 | return f"{f.name}={f.default_value()!r}" 605 | else: 606 | return str(f.name) 607 | 608 | code = "lambda pack, {}: pack({}, {})".format( 609 | ", ".join(format_arg(f) for f in self.fields), 610 | repr(self.struct_format()), 611 | ", ".join(f.struct_maker_code(f.name) for f in self.fields)) # type: ignore[attr-defined] 612 | 613 | return eval(code) # type: ignore[no-any-return] 614 | 615 | def struct_format(self) -> str: 616 | """Return the format of the struct as digested by the :mod:`struct` 617 | module. 618 | """ 619 | return self.format 620 | 621 | def __len__(self) -> int: 622 | """Return the number of bytes occupied by this struct.""" 623 | return int(self.bytes) 624 | 625 | mapper_method = "map_generable_struct" 626 | 627 | 628 | class Enum(Generable): 629 | """An enum-like class for Python that can generate an equivalent C-level 630 | declaration. Does not work within the usual "declarator" framework 631 | because it uses macros to define values, and a separate typedef to define the 632 | value type. 633 | 634 | .. versionadded:: 2013.2 635 | 636 | To use, derive from this class, and define the following things: 637 | 638 | .. autoattribute:: c_name 639 | .. autoattribute:: dtype 640 | .. autoattribute:: c_value_prefix 641 | .. attribute:: VALUE 642 | 643 | Any class attribute with an all-upper-case name is taken to be as an 644 | enum value. 645 | """ 646 | 647 | c_name: ClassVar[str] 648 | dtype: ClassVar[np.dtype[Any]] 649 | c_value_prefix: ClassVar[str] 650 | """A (usually upper-case) prefix to be used as a prefix to the names defined 651 | on the Python side. 652 | """ 653 | 654 | @classmethod 655 | def get_flag_names_and_values(cls) -> list[tuple[str, Any]]: 656 | return [(name, getattr(cls, name)) 657 | for name in sorted(dir(cls)) 658 | if name[0].isupper()] 659 | 660 | @classmethod 661 | def get_c_defines_lines(cls) -> list[str]: 662 | return [ 663 | f"#define {cls.c_value_prefix}{flag_name} {value}" 664 | for flag_name, value in cls.get_flag_names_and_values()] 665 | 666 | @classmethod 667 | def get_c_defines(cls) -> str: 668 | """Return a string with C defines corresponding to these constants. 669 | """ 670 | 671 | return "\n".join(cls.get_c_defines_lines()) 672 | 673 | @classmethod 674 | def get_c_typedef_line(cls) -> str: 675 | """Returns a typedef to define this enum in C.""" 676 | 677 | from pyopencl.tools import dtype_to_ctype # pylint: disable=import-error 678 | return f"typedef {dtype_to_ctype(cls.dtype)} {cls.c_name};" 679 | 680 | @classmethod 681 | def get_c_typedef(cls) -> str: 682 | return f"\n\n{cls.get_c_typedef_line()}\n\n" 683 | 684 | @classmethod 685 | def generate(cls, with_semicolon: bool = True) -> Iterator[str]: 686 | yield cls.get_c_typedef_line() 687 | yield from cls.get_c_defines_lines() 688 | 689 | @classmethod 690 | def stringify_value(cls, val: Any) -> str: 691 | """Return a string description of the flags set in *val*.""" 692 | 693 | return "|".join( 694 | flag_name 695 | for flag_name, flag_value in cls.get_flag_names_and_values() 696 | if val & flag_value) 697 | 698 | # }}} 699 | 700 | 701 | # {{{ template 702 | 703 | class Template(NestedDeclarator): 704 | def __init__(self, template_spec: str, subdecl: Declarator) -> None: 705 | self.template_spec = template_spec 706 | self.subdecl = subdecl 707 | 708 | def generate(self, with_semicolon: bool = False) -> Iterator[str]: 709 | yield f"template <{self.template_spec}>" 710 | yield from self.subdecl.generate(with_semicolon=with_semicolon) 711 | 712 | mapper_method = "map_template" 713 | 714 | # }}} 715 | 716 | 717 | # {{{ control flow/statement stuff 718 | 719 | class If(Generable): 720 | def __init__(self, 721 | condition: str, 722 | then_: Generable, 723 | else_: Generable | None = None) -> None: 724 | assert isinstance(then_, Generable) 725 | if else_ is not None: 726 | assert isinstance(else_, Generable) 727 | 728 | self.condition = condition 729 | self.then_ = then_ 730 | self.else_ = else_ 731 | 732 | def generate(self, with_semicolon: bool = False) -> Iterator[str]: 733 | cond_str = str(self.condition) 734 | condition_lines = cond_str.split("\n") 735 | 736 | if len(condition_lines) > 1: 737 | yield "if (" 738 | for line in condition_lines: 739 | yield f" {line}" 740 | yield " )" 741 | else: 742 | yield f"if ({self.condition})" 743 | 744 | if isinstance(self.then_, Block): 745 | for line in self.then_.generate(): 746 | yield line 747 | else: 748 | for line in self.then_.generate(): 749 | yield f" {line}" 750 | 751 | if self.else_ is not None: 752 | yield "else" 753 | if isinstance(self.else_, Block): 754 | for line in self.else_.generate(): 755 | yield line 756 | else: 757 | for line in self.else_.generate(): 758 | yield f" {line}" 759 | 760 | mapper_method = "map_if" 761 | 762 | 763 | class Loop(Generable): 764 | def __init__(self, body: Generable) -> None: 765 | self.body = body 766 | 767 | def intro_line(self) -> str | None: 768 | raise NotImplementedError 769 | 770 | def generate(self, with_semicolon: bool = False) -> Iterator[str]: 771 | intro_line = self.intro_line() 772 | if intro_line is not None: 773 | yield intro_line 774 | 775 | if isinstance(self.body, Block): 776 | for line in self.body.generate(): 777 | yield line 778 | else: 779 | for line in self.body.generate(): 780 | yield f" {line}" 781 | 782 | outro_line = self.outro_line() # pylint: disable=assignment-from-none 783 | if outro_line is not None: 784 | yield outro_line 785 | 786 | def outro_line(self) -> str | None: 787 | return None 788 | 789 | 790 | class CustomLoop(Loop): 791 | def __init__(self, 792 | intro_line: str | None, 793 | body: Generable, 794 | outro_line: str | None = None) -> None: 795 | self.intro_line_ = intro_line 796 | self.body = body 797 | self.outro_line_ = outro_line 798 | 799 | def intro_line(self) -> str | None: 800 | return self.intro_line_ 801 | 802 | def outro_line(self) -> str | None: 803 | return self.outro_line_ 804 | 805 | mapper_method = "map_custom_loop" 806 | 807 | 808 | class While(Loop): 809 | def __init__(self, condition: str, body: Generable) -> None: 810 | assert isinstance(body, Generable) 811 | 812 | self.condition = condition 813 | self.body = body 814 | 815 | def intro_line(self) -> str | None: 816 | return f"while ({self.condition})" 817 | 818 | mapper_method = "map_while" 819 | 820 | 821 | class For(Loop): 822 | def __init__(self, 823 | start: str, condition: str, update: str, 824 | body: Generable) -> None: 825 | self.start = start 826 | self.condition = condition 827 | self.update = update 828 | 829 | assert isinstance(body, Generable) 830 | self.body = body 831 | 832 | def intro_line(self) -> str | None: 833 | return f"for ({self.start}; {self.condition}; {self.update})" 834 | 835 | mapper_method = "map_for" 836 | 837 | 838 | class DoWhile(Loop): 839 | def __init__(self, condition: str, body: Generable) -> None: 840 | assert isinstance(body, Generable) 841 | 842 | self.condition = condition 843 | self.body = body 844 | 845 | def intro_line(self) -> str | None: 846 | return "do" 847 | 848 | def outro_line(self) -> str | None: 849 | return f"while ({self.condition});" 850 | 851 | mapper_method = "map_do_while" 852 | 853 | 854 | def make_multiple_ifs(conditions_and_blocks: Sequence[tuple[str, Generable]], 855 | base: str | Generable | None = None) -> Generable | None: 856 | if base == "last": 857 | _, base = conditions_and_blocks[-1] 858 | conditions_and_blocks = conditions_and_blocks[:-1] 859 | 860 | assert not isinstance(base, str) 861 | for cond, block in conditions_and_blocks[::-1]: 862 | base = If(cond, block, base) 863 | 864 | return base 865 | 866 | # }}} 867 | 868 | 869 | # {{{ simple statements 870 | 871 | class Define(Generable): 872 | def __init__(self, symbol: str, value: str) -> None: 873 | self.symbol = symbol 874 | self.value = value 875 | 876 | def generate(self, with_semicolon: bool = False) -> Iterator[str]: 877 | yield f"#define {self.symbol} {self.value}" 878 | 879 | mapper_method = "map_define" 880 | 881 | 882 | class Include(Generable): 883 | def __init__(self, filename: str, system: bool = True) -> None: 884 | self.filename = filename 885 | self.system = system 886 | 887 | def generate(self, with_semicolon: bool = False) -> Iterator[str]: 888 | if self.system: 889 | yield f"#include <{self.filename}>" 890 | else: 891 | yield f'#include "{self.filename}"' 892 | 893 | mapper_method = "map_include" 894 | 895 | 896 | class Pragma(Generable): 897 | def __init__(self, value: str) -> None: 898 | self.value = value 899 | 900 | def generate(self, with_semicolon: bool = False) -> Iterator[str]: 901 | yield f"#pragma {self.value}" 902 | 903 | mapper_method = "map_pragma" 904 | 905 | 906 | class Statement(Generable): 907 | def __init__(self, text: str) -> None: 908 | self.text = text 909 | 910 | def generate(self, with_semicolon: bool = True) -> Iterator[str]: 911 | sc = ";" if with_semicolon else "" 912 | yield f"{self.text}{sc}" 913 | 914 | mapper_method = "map_statement" 915 | 916 | 917 | class ExpressionStatement(Generable): 918 | def __init__(self, expr: str) -> None: 919 | self.expr = expr 920 | 921 | def generate(self, with_semicolon: bool = True) -> Iterator[str]: 922 | sc = ";" if with_semicolon else "" 923 | yield f"{self.expr}{sc}" 924 | 925 | mapper_method = "map_expression_statement" 926 | 927 | 928 | class Assign(Generable): 929 | def __init__(self, lvalue: str, rvalue: str) -> None: 930 | self.lvalue = lvalue 931 | self.rvalue = rvalue 932 | 933 | def generate(self, with_semicolon: bool = True) -> Iterator[str]: 934 | sc = ";" if with_semicolon else "" 935 | yield f"{self.lvalue} = {self.rvalue}{sc}" 936 | 937 | mapper_method = "map_assignment" 938 | 939 | 940 | class Line(Generable): 941 | def __init__(self, text: str = "") -> None: 942 | self.text = text 943 | 944 | def generate(self, with_semicolon: bool = False) -> Iterator[str]: 945 | yield self.text 946 | 947 | mapper_method = "map_line" 948 | 949 | 950 | class Comment(Generable): 951 | def __init__(self, text: str, skip_space: bool = False) -> None: 952 | self.text = text 953 | if skip_space: 954 | self.fmt_str = "/*{comment}*/" 955 | else: 956 | self.fmt_str = "/* {comment} */" 957 | 958 | def generate(self, with_semicolon: bool = False) -> Iterator[str]: 959 | yield self.fmt_str.format(comment=self.text) 960 | 961 | mapper_method = "map_comment" 962 | 963 | 964 | class MultilineComment(Generable): 965 | def __init__(self, text: str, skip_space: bool = False) -> None: 966 | self.text = text 967 | self.skip_space = skip_space 968 | 969 | def generate(self, with_semicolon: bool = False) -> Iterator[str]: 970 | yield "/**" 971 | 972 | if self.skip_space: 973 | line_begin, comment_end = "*", "*/" 974 | else: 975 | line_begin, comment_end = " * ", " */" 976 | 977 | for line in self.text.splitlines(): 978 | yield line_begin + line 979 | 980 | yield comment_end 981 | 982 | mapper_method = "map_multiline_comment" 983 | 984 | 985 | class LineComment(Generable): 986 | def __init__(self, text: str) -> None: 987 | assert "\n" not in text 988 | self.text = text 989 | 990 | def generate(self, with_semicolon: bool = False) -> Iterator[str]: 991 | yield f"// {self.text}" 992 | 993 | mapper_method = "map_line_comment" 994 | 995 | 996 | def add_comment(comment: str | None, stmt: Generable) -> Generable: 997 | if comment is None: 998 | return stmt 999 | 1000 | if isinstance(stmt, Block): 1001 | result = Block([Comment(comment), Line()]) 1002 | result.extend(stmt.contents) 1003 | return result 1004 | else: 1005 | return Block([Comment(comment), Line(), stmt]) 1006 | 1007 | # }}} 1008 | 1009 | 1010 | # {{{ initializers 1011 | 1012 | class Initializer(Generable): 1013 | def __init__(self, vdecl: Declarator, data: str) -> None: 1014 | self.vdecl = vdecl 1015 | self.data = data 1016 | 1017 | def generate(self, with_semicolon: bool = True) -> Iterator[str]: 1018 | tp_lines, tp_decl = self.vdecl.get_decl_pair() 1019 | tp_lines = list(tp_lines) 1020 | yield from tp_lines[:-1] 1021 | 1022 | sc = ";" if with_semicolon else "" 1023 | if isinstance(self.data, str) and "\n" in self.data: 1024 | data_lines = self.data.split("\n") 1025 | yield f"{tp_lines[-1]} {tp_decl} =" 1026 | for i, line in enumerate(data_lines): 1027 | if i == len(data_lines)-1: 1028 | yield f" {line}{sc}" 1029 | else: 1030 | yield f" {line}" 1031 | else: 1032 | yield f"{tp_lines[-1]} {tp_decl} = {self.data}{sc}" 1033 | 1034 | mapper_method = "map_initializer" 1035 | 1036 | 1037 | class InlineInitializer(Initializer): 1038 | """ 1039 | Class to represent Initializers (int i=0) without the semicolon in the end 1040 | (e.g. in a for statement) 1041 | Usage: same as cgen.Initializer 1042 | Result: same as cgen.Initializer except for the lack of a semi-colon at the end 1043 | """ 1044 | def generate(self, with_semicolon: bool = True) -> Iterator[str]: 1045 | for v in super().generate(with_semicolon=with_semicolon): 1046 | if v.endswith(";"): 1047 | yield v[:-1] 1048 | else: 1049 | yield v 1050 | 1051 | 1052 | def Constant(vdecl: Declarator, data: str) -> Initializer: # noqa: N802 1053 | return Initializer(Const(vdecl), data) 1054 | 1055 | 1056 | class ArrayInitializer(Generable): 1057 | def __init__(self, vdecl: Declarator, data: Sequence[str]) -> None: 1058 | self.vdecl = vdecl 1059 | self.data = tuple(data) 1060 | 1061 | def generate(self, with_semicolon: bool = True) -> Iterator[str]: 1062 | yield from self.vdecl.generate(with_semicolon=False) 1063 | 1064 | sc = ";" if with_semicolon else "" 1065 | yield " = { %s }%s" % (", ".join(str(item) for item in self.data), sc) 1066 | 1067 | mapper_method = "map_array_initializer" 1068 | 1069 | 1070 | class FunctionBody(Generable): 1071 | # FIXME: fdecl should be a FunctionDeclaration, but loopy uses a light 1072 | # wrapper around it, so we can't specialize at the moment 1073 | def __init__(self, fdecl: NestedDeclarator, body: Block) -> None: 1074 | """Initialize a function definition.""" 1075 | 1076 | self.fdecl = fdecl 1077 | self.body = body 1078 | 1079 | def generate(self, with_semicolon: bool = False) -> Iterator[str]: 1080 | yield from self.fdecl.generate(with_semicolon=False) 1081 | yield from self.body.generate() 1082 | 1083 | mapper_method = "map_function_body" 1084 | 1085 | # }}} 1086 | 1087 | 1088 | # {{{ block 1089 | 1090 | class Block(Generable): 1091 | def __init__(self, contents: Sequence[Generable] | None = None) -> None: 1092 | if contents is None: 1093 | contents = [] 1094 | 1095 | if isinstance(contents, Block): 1096 | contents = contents.contents 1097 | 1098 | self.contents: list[Generable] = list(contents) 1099 | for item in self.contents: 1100 | assert isinstance(item, Generable) 1101 | 1102 | def generate(self, with_semicolon: bool = True) -> Iterator[str]: 1103 | yield "{" 1104 | for item in self.contents: 1105 | for item_line in item.generate(): 1106 | yield f" {item_line}" 1107 | yield "}" 1108 | 1109 | def append(self, data: Generable) -> None: 1110 | self.contents.append(data) 1111 | 1112 | def extend(self, data: Iterable[Generable]) -> None: 1113 | self.contents.extend(data) 1114 | 1115 | def insert(self, i: int, data: Generable) -> None: 1116 | self.contents.insert(i, data) 1117 | 1118 | def extend_log_block(self, descr: str, data: Iterable[Generable]) -> None: 1119 | self.contents.append(Comment(descr)) 1120 | self.contents.extend(data) 1121 | self.contents.append(Line()) 1122 | 1123 | mapper_method = "map_block" 1124 | 1125 | 1126 | def block_if_necessary(contents: Sequence[Generable]) -> Generable: 1127 | if len(contents) == 1: 1128 | return contents[0] 1129 | else: 1130 | return Block(contents) 1131 | 1132 | 1133 | class LiteralLines(Generable): 1134 | def __init__(self, text: str) -> None: 1135 | # accommodate pyopencl syntax highlighting 1136 | if text.startswith("//CL//"): 1137 | text = text[6:] 1138 | 1139 | if not text.startswith("\n"): 1140 | raise ValueError("Expected newline as first character in literal lines") 1141 | 1142 | lines = text.split("\n") 1143 | while lines[0].strip() == "": 1144 | lines.pop(0) 1145 | while lines[-1].strip() == "": 1146 | lines.pop(-1) 1147 | 1148 | if lines: 1149 | base_indent = 0 1150 | while lines[0][base_indent] in " \t": 1151 | base_indent += 1 1152 | 1153 | for line in lines[1:]: 1154 | if line[:base_indent].strip(): 1155 | raise ValueError("Inconsistent indentation") 1156 | 1157 | self.lines = [line[base_indent:] for line in lines] 1158 | 1159 | def generate(self, with_semicolon: bool = False) -> Iterator[str]: 1160 | yield from self.lines 1161 | 1162 | mapper_method = "map_literal_lines" 1163 | 1164 | 1165 | class LiteralBlock(LiteralLines): 1166 | def generate(self, with_semicolon: bool = False) -> Iterator[str]: 1167 | yield "{" 1168 | for line in self.lines: 1169 | yield f" {line}" 1170 | yield "}" 1171 | 1172 | 1173 | class Collection(Block): 1174 | def generate(self, with_semicolon: bool = False) -> Iterator[str]: 1175 | for c in self.contents: 1176 | yield from c.generate() 1177 | 1178 | 1179 | Module = Collection 1180 | 1181 | 1182 | class IfDef(Module): 1183 | """Class to represent IfDef-Else-EndIf construct for the C preprocessor.""" 1184 | 1185 | def __init__(self, 1186 | condition: str, 1187 | iflines: Iterable[Generable], 1188 | elselines: Iterable[Generable]) -> None: 1189 | """ 1190 | :arg condition: the condition in IfDef. 1191 | :arg iflines: the block of code inside the if. 1192 | :arg elselines: the block of code inside the else. 1193 | """ 1194 | 1195 | iflines = list(iflines) 1196 | elselines = list(elselines) 1197 | 1198 | ifdef_line = Line(f"#ifdef {condition}") 1199 | if len(elselines): 1200 | elselines.insert(0, Line("#else")) 1201 | endif_line = Line("#endif") 1202 | lines = [ifdef_line, *iflines, *elselines, endif_line] 1203 | super().__init__(lines) 1204 | 1205 | mapper_method = "map_ifdef" 1206 | 1207 | 1208 | class IfNDef(Module): 1209 | """ 1210 | Class to represent IfNDef-Else-EndIf construct for the C preprocessor. 1211 | :param condition: the condition in IfNDef 1212 | :param ifndeflines: the block of code inside the if not 1213 | [an array of type Generable] 1214 | :param elselines: the block of code inside the else [an array of type Generable] 1215 | """ 1216 | def __init__(self, 1217 | condition: str, 1218 | ifndeflines: Iterable[Generable], 1219 | elselines: Iterable[Generable]) -> None: 1220 | ifndeflines = list(ifndeflines) 1221 | elselines = list(elselines) 1222 | 1223 | ifndefdef_line = Line(f"#ifndef {condition}") 1224 | if len(elselines): 1225 | elselines.insert(0, Line("#else")) 1226 | lines = [ifndefdef_line, *ifndeflines, *elselines, Line("#endif")] 1227 | super().__init__(lines) 1228 | 1229 | mapper_method = "map_ifndef" 1230 | 1231 | 1232 | class PrivateNamespace(Block): 1233 | def get_namespace_name(self) -> str: 1234 | import hashlib 1235 | checksum = hashlib.md5() 1236 | 1237 | for c in self.contents: 1238 | for line in c.generate(): 1239 | checksum.update(line.encode("utf-8")) 1240 | 1241 | return f"private_namespace_{checksum.hexdigest()}" 1242 | 1243 | def generate(self, with_semicolon: bool = True) -> Iterator[str]: 1244 | name = self.get_namespace_name() 1245 | 1246 | sc = ";" if with_semicolon else "" 1247 | yield f"namespace {name}" 1248 | yield "{" 1249 | for item in self.contents: 1250 | for item_line in item.generate(): 1251 | yield f" {item_line}" 1252 | yield "}" 1253 | yield "" 1254 | yield f"using namespace {name}{sc}" 1255 | 1256 | # }}} 1257 | 1258 | # vim: fdm=marker 1259 | -------------------------------------------------------------------------------- /cgen/cuda.py: -------------------------------------------------------------------------------- 1 | __copyright__ = "Copyright (C) 2011-20 Andreas Kloeckner" 2 | 3 | __license__ = """ 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 18 | THE SOFTWARE. 19 | """ 20 | 21 | 22 | from cgen import Declarator, DeclPair, DeclSpecifier, NestedDeclarator, Pointer 23 | 24 | 25 | class CudaGlobal(DeclSpecifier): 26 | def __init__(self, subdecl: Declarator) -> None: 27 | super().__init__(subdecl, "__global__") 28 | 29 | mapper_method = "map_cuda_global" 30 | 31 | 32 | class CudaDevice(DeclSpecifier): 33 | def __init__(self, subdecl: Declarator) -> None: 34 | super().__init__(subdecl, "__device__") 35 | 36 | mapper_method = "map_cuda_device" 37 | 38 | 39 | class CudaShared(DeclSpecifier): 40 | def __init__(self, subdecl: Declarator) -> None: 41 | super().__init__(subdecl, "__shared__") 42 | 43 | mapper_method = "map_cuda_shared" 44 | 45 | 46 | class CudaConstant(DeclSpecifier): 47 | def __init__(self, subdecl: Declarator) -> None: 48 | super().__init__(subdecl, "__constant__") 49 | 50 | mapper_method = "map_cuda_constant" 51 | 52 | 53 | class CudaRestrictPointer(Pointer): 54 | def get_decl_pair(self) -> DeclPair: 55 | sub_tp, sub_decl = self.subdecl.get_decl_pair() 56 | return sub_tp, f"*__restrict__ {sub_decl}" 57 | 58 | mapper_method = "map_cuda_restrict_pointer" 59 | 60 | 61 | class CudaLaunchBounds(NestedDeclarator): 62 | def __init__(self, 63 | max_threads_per_block: int, 64 | subdecl: Declarator, 65 | min_blocks_per_mp: int | None = None) -> None: 66 | self.max_threads_per_block = max_threads_per_block 67 | self.min_blocks_per_mp = min_blocks_per_mp 68 | 69 | super().__init__(subdecl) 70 | 71 | def get_decl_pair(self) -> DeclPair: 72 | if self.min_blocks_per_mp is not None: 73 | lb = f"{self.max_threads_per_block}, {self.min_blocks_per_mp}" 74 | else: 75 | lb = f"{self.max_threads_per_block}" 76 | 77 | sub_tp, sub_decl = self.subdecl.get_decl_pair() 78 | return sub_tp, f"__launch_bounds__({lb}) {sub_decl}" 79 | 80 | mapper_method = "map_cuda_launch_bounds" 81 | -------------------------------------------------------------------------------- /cgen/ispc.py: -------------------------------------------------------------------------------- 1 | __copyright__ = "Copyright (C) 2015 Andreas Kloeckner" 2 | 3 | __license__ = """ 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | """ 22 | 23 | from collections.abc import Sequence 24 | 25 | from cgen import Declarator, DeclPair, DeclSpecifier, Pointer, Statement 26 | 27 | 28 | class ISPCVarying(DeclSpecifier): 29 | def __init__(self, subdecl: Declarator) -> None: 30 | super().__init__(subdecl, "varying") 31 | 32 | mapper_method = "map_ispc_varying" 33 | 34 | 35 | class ISPCUniform(DeclSpecifier): 36 | def __init__(self, subdecl: Declarator) -> None: 37 | super().__init__(subdecl, "uniform") 38 | 39 | mapper_method = "map_ispc_uniform" 40 | 41 | 42 | class ISPCExport(DeclSpecifier): 43 | def __init__(self, subdecl: Declarator) -> None: 44 | super().__init__(subdecl, "export") 45 | 46 | mapper_method = "map_ispc_export" 47 | 48 | 49 | class ISPCTask(DeclSpecifier): 50 | def __init__(self, subdecl: Declarator) -> None: 51 | super().__init__(subdecl, "task") 52 | 53 | mapper_method = "map_ispc_task" 54 | 55 | 56 | class ISPCVaryingPointer(Pointer): 57 | def get_decl_pair(self) -> DeclPair: 58 | sub_tp, sub_decl = self.subdecl.get_decl_pair() 59 | return sub_tp, f"*varying {sub_decl}" 60 | 61 | mapper_method = "map_ispc_varying_pointer" 62 | 63 | 64 | class ISPCUniformPointer(Pointer): 65 | def get_decl_pair(self) -> DeclPair: 66 | sub_tp, sub_decl = self.subdecl.get_decl_pair() 67 | return sub_tp, f"*uniform {sub_decl}" 68 | 69 | mapper_method = "map_ispc_uniform_pointer" 70 | 71 | 72 | class ISPCLaunch(Statement): 73 | def __init__(self, grid: Sequence[str | int], expr: str) -> None: 74 | if grid: 75 | launch_spec = "[{}]".format(", ".join(str(gs_i) for gs_i in grid)) 76 | else: 77 | launch_spec = "" 78 | 79 | super().__init__(f"launch{launch_spec} {expr}") 80 | self.grid = grid 81 | self.expr = expr 82 | 83 | mapper_method = "map_ispc_launch" 84 | -------------------------------------------------------------------------------- /cgen/mapper.py: -------------------------------------------------------------------------------- 1 | """Generator for C/C++.""" 2 | 3 | 4 | __copyright__ = "Copyright (C) 2016 Andreas Kloeckner" 5 | 6 | __license__ = """ 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | """ 25 | 26 | from typing import Any, Generic, ParamSpec, TypeVar, cast 27 | 28 | import cgen 29 | from cgen import cuda, ispc, opencl as cl 30 | 31 | 32 | R = TypeVar("R") 33 | P = ParamSpec("P") 34 | 35 | 36 | class UnsupportedNodeError(ValueError): 37 | pass 38 | 39 | 40 | class ASTMapper(Generic[P, R]): 41 | def __call__(self, node: object, *args: P.args, **kwargs: P.kwargs) -> R: 42 | """Dispatch *node* to its corresponding mapper method. Pass on 43 | ``*args`` and ``**kwargs`` unmodified. 44 | 45 | This method is intended as the top-level dispatch entry point and may 46 | be overridden by subclasses to present a different/more convenient 47 | interface. :meth:`rec` on the other hand is intended as the recursive 48 | dispatch method to be used to recurse within mapper method 49 | implementations. 50 | """ 51 | 52 | try: 53 | method = getattr(self, node.mapper_method) # type: ignore[attr-defined] 54 | except AttributeError: 55 | return self.handle_unsupported(node, *args, **kwargs) 56 | 57 | return cast("R", method(node, *args, **kwargs)) 58 | 59 | def handle_unsupported( 60 | self, node: Any, *args: P.args, **kwargs: P.kwargs) -> R: 61 | """Mapper method that is invoked for node subclasses for which a mapper 62 | method does not exist in this mapper. 63 | """ 64 | 65 | raise UnsupportedNodeError( 66 | f"{type(self)} cannot handle nodes of type {type(node)}") 67 | 68 | rec = __call__ 69 | 70 | 71 | class IdentityMapper(ASTMapper[P, cgen.Generable]): 72 | def rec(self, node: R, *args: P.args, **kwargs: P.kwargs) -> R: # type: ignore[override] 73 | return cast("R", super().rec(node, *args, **kwargs)) 74 | 75 | def map_expression( 76 | self, node: R, *args: P.args, **kwargs: P.kwargs 77 | ) -> R: 78 | raise NotImplementedError 79 | 80 | def map_pod( 81 | self, node: cgen.POD, *args: P.args, **kwargs: P.kwargs 82 | ) -> cgen.POD: 83 | return type(node)(node.dtype, node.name) 84 | 85 | def map_value( 86 | self, node: cgen.Value, *args: P.args, **kwargs: P.kwargs 87 | ) -> cgen.Value: 88 | return type(node)(node.typename, node.name) 89 | 90 | def map_namespace_qualifier( 91 | self, node: cgen.NamespaceQualifier, *args: P.args, **kwargs: P.kwargs 92 | ) -> cgen.NamespaceQualifier: 93 | return type(node)(node.namespace, self.rec(node.subdecl, *args, **kwargs)) 94 | 95 | def map_typedef( 96 | self, node: cgen.Typedef, *args: P.args, **kwargs: P.kwargs 97 | ) -> cgen.Typedef: 98 | return type(node)(self.rec(node.subdecl, *args, **kwargs)) 99 | 100 | def map_static( 101 | self, node: cgen.Static, *args: P.args, **kwargs: P.kwargs 102 | ) -> cgen.Static: 103 | return type(node)(self.rec(node.subdecl, *args, **kwargs)) 104 | 105 | def map_const( 106 | self, node: cgen.Const, *args: P.args, **kwargs: P.kwargs 107 | ) -> cgen.Const: 108 | return type(node)(self.rec(node.subdecl, *args, **kwargs)) 109 | 110 | def map_volatile( 111 | self, node: cgen.Volatile, *args: P.args, **kwargs: P.kwargs 112 | ) -> cgen.Volatile: 113 | return type(node)(self.rec(node.subdecl, *args, **kwargs)) 114 | 115 | def map_maybe_unused( 116 | self, node: cgen.MaybeUnused, *args: P.args, **kwargs: P.kwargs 117 | ) -> cgen.MaybeUnused: 118 | return type(node)(self.rec(node.subdecl, *args, **kwargs)) 119 | 120 | def map_pointer( 121 | self, node: cgen.Pointer, *args: P.args, **kwargs: P.kwargs 122 | ) -> cgen.Pointer: 123 | return type(node)(self.rec(node.subdecl, *args, **kwargs)) 124 | 125 | def map_restrict_pointer( 126 | self, node: cgen.RestrictPointer, *args: P.args, **kwargs: P.kwargs 127 | ) -> cgen.RestrictPointer: 128 | return type(node)(self.rec(node.subdecl, *args, **kwargs)) 129 | 130 | def map_reference( 131 | self, node: cgen.Reference, *args: P.args, **kwargs: P.kwargs 132 | ) -> cgen.Reference: 133 | return type(node)(self.rec(node.subdecl, *args, **kwargs)) 134 | 135 | def map_extern( 136 | self, node: cgen.Extern, *args: P.args, **kwargs: P.kwargs 137 | ) -> cgen.Extern: 138 | return type(node)(node.language, self.rec(node.subdecl, *args, **kwargs)) 139 | 140 | def map_template_specializer( 141 | self, node: cgen.TemplateSpecializer, *args: P.args, **kwargs: P.kwargs 142 | ) -> cgen.TemplateSpecializer: 143 | return type(node)(node.specializer, self.rec(node.subdecl, *args, **kwargs)) 144 | 145 | def map_aligned( 146 | self, node: cgen.AlignedAttribute, *args: P.args, **kwargs: P.kwargs 147 | ) -> cgen.AlignedAttribute: 148 | return type(node)(node.align_bytes, self.rec(node.subdecl, *args, **kwargs)) 149 | 150 | def map_array_of( 151 | self, node: cgen.ArrayOf, *args: P.args, **kwargs: P.kwargs 152 | ) -> cgen.ArrayOf: 153 | return type(node)( 154 | self.rec(node.subdecl, *args, **kwargs), 155 | self.map_expression(node.count, *args, **kwargs)) 156 | 157 | def map_function_declaration( 158 | self, node: cgen.FunctionDeclaration, *args: P.args, **kwargs: P.kwargs 159 | ) -> cgen.FunctionDeclaration: 160 | return type(node)( 161 | self.rec(node.subdecl, *args, **kwargs), 162 | tuple(self.rec(sd, *args, **kwargs) for sd in node.arg_decls)) 163 | 164 | def map_struct( 165 | self, node: cgen.Struct, *args: P.args, **kwargs: P.kwargs 166 | ) -> cgen.Struct: 167 | return type(node)( 168 | node.tpname, 169 | tuple(self.rec(f, *args, **kwargs) for f in node.fields), 170 | node.declname, 171 | node.pad_bytes) 172 | 173 | def map_generable_struct( 174 | self, node: cgen.GenerableStruct, *args: P.args, **kwargs: P.kwargs 175 | ) -> cgen.GenerableStruct: 176 | return type(node)( 177 | node.tpname, 178 | tuple(self.rec(f, *args, **kwargs) for f in node.fields), 179 | node.declname, 180 | node.align_bytes, 181 | node.aligned_prime_to) 182 | 183 | def map_template( 184 | self, node: cgen.Template, *args: P.args, **kwargs: P.kwargs 185 | ) -> cgen.Template: 186 | return type(node)( 187 | node.template_spec, 188 | self.rec(node.subdecl, *args, **kwargs)) 189 | 190 | def map_if( 191 | self, node: cgen.If, *args: P.args, **kwargs: P.kwargs 192 | ) -> cgen.If: 193 | return type(node)( 194 | self.map_expression(node.condition, *args, **kwargs), 195 | self.rec(node.then_, *args, **kwargs), 196 | self.rec(node.else_, *args, **kwargs) 197 | if node.else_ is not None 198 | else None) 199 | 200 | def map_while( 201 | self, node: cgen.While, *args: P.args, **kwargs: P.kwargs 202 | ) -> cgen.While: 203 | return type(node)( 204 | self.map_expression(node.condition, *args, **kwargs), 205 | self.rec(node.body, *args, **kwargs)) 206 | 207 | def map_for( 208 | self, node: cgen.For, *args: P.args, **kwargs: P.kwargs 209 | ) -> cgen.For: 210 | return type(node)( 211 | self.rec(node.start, *args, **kwargs), 212 | self.map_expression(node.condition, *args, **kwargs), 213 | self.map_expression(node.update, *args, **kwargs), 214 | self.rec(node.body, *args, **kwargs)) 215 | 216 | def map_do_while( 217 | self, node: cgen.DoWhile, *args: P.args, **kwargs: P.kwargs 218 | ) -> cgen.DoWhile: 219 | return type(node)( 220 | self.map_expression(node.condition, *args, **kwargs), 221 | self.rec(node.body, *args, **kwargs)) 222 | 223 | def map_define( 224 | self, node: cgen.Define, *args: P.args, **kwargs: P.kwargs 225 | ) -> cgen.Define: 226 | return node 227 | 228 | def map_include( 229 | self, node: cgen.Include, *args: P.args, **kwargs: P.kwargs 230 | ) -> cgen.Include: 231 | return node 232 | 233 | def map_pragma( 234 | self, node: cgen.Pragma, *args: P.args, **kwargs: P.kwargs 235 | ) -> cgen.Pragma: 236 | return node 237 | 238 | def map_statement( 239 | self, node: cgen.Statement, *args: P.args, **kwargs: P.kwargs 240 | ) -> cgen.Statement: 241 | return type(node)(node.text) 242 | 243 | def map_expression_statement( 244 | self, node: cgen.ExpressionStatement, *args: P.args, **kwargs: P.kwargs 245 | ) -> cgen.ExpressionStatement: 246 | return type(node)( 247 | self.map_expression(node.expr, *args, **kwargs)) 248 | 249 | def map_assignment( 250 | self, node: cgen.Assign, *args: P.args, **kwargs: P.kwargs 251 | ) -> cgen.Assign: 252 | return type(node)( 253 | self.map_expression(node.lvalue, *args, **kwargs), 254 | self.map_expression(node.rvalue, *args, **kwargs)) 255 | 256 | def map_line( 257 | self, node: cgen.Line, *args: P.args, **kwargs: P.kwargs 258 | ) -> cgen.Line: 259 | return node 260 | 261 | def map_comment( 262 | self, node: cgen.Comment, *args: P.args, **kwargs: P.kwargs 263 | ) -> cgen.Comment: 264 | return node 265 | 266 | def map_multiline_comment( 267 | self, node: cgen.MultilineComment, *args: P.args, **kwargs: P.kwargs 268 | ) -> cgen.MultilineComment: 269 | return node 270 | 271 | def map_line_comment( 272 | self, node: cgen.LineComment, *args: P.args, **kwargs: P.kwargs 273 | ) -> cgen.LineComment: 274 | return node 275 | 276 | def map_initializer( 277 | self, node: cgen.Initializer, *args: P.args, **kwargs: P.kwargs 278 | ) -> cgen.Initializer: 279 | return type(node)( 280 | self.rec(node.vdecl, *args, **kwargs), 281 | self.map_expression(node.data, *args, **kwargs)) 282 | 283 | def map_array_initializer( 284 | self, node: cgen.ArrayInitializer, *args: P.args, **kwargs: P.kwargs 285 | ) -> cgen.ArrayInitializer: 286 | return type(node)( 287 | self.rec(node.vdecl, *args, **kwargs), 288 | tuple( 289 | self.map_expression(expr, *args, **kwargs) 290 | for expr in node.data)) 291 | 292 | def map_function_body( 293 | self, node: cgen.FunctionBody, *args: P.args, **kwargs: P.kwargs 294 | ) -> cgen.FunctionBody: 295 | return type(node)( 296 | self.rec(node.fdecl, *args, **kwargs), 297 | self.rec(node.body, *args, **kwargs)) 298 | 299 | def map_block( 300 | self, node: cgen.Block, *args: P.args, **kwargs: P.kwargs 301 | ) -> cgen.Block: 302 | return type(node)( 303 | tuple(self.rec(c, *args, **kwargs) 304 | for c in node.contents)) 305 | 306 | def map_literal_lines( 307 | self, node: cgen.LiteralLines, *args: P.args, **kwargs: P.kwargs 308 | ) -> cgen.LiteralLines: 309 | return node 310 | 311 | def map_literal_block( 312 | self, node: cgen.LiteralBlock, *args: P.args, **kwargs: P.kwargs 313 | ) -> cgen.LiteralBlock: 314 | return node 315 | 316 | def map_ifdef( 317 | self, node: cgen.IfDef, *args: P.args, **kwargs: P.kwargs 318 | ) -> cgen.IfDef: 319 | return node 320 | 321 | def map_ifndef( 322 | self, node: cgen.IfNDef, *args: P.args, **kwargs: P.kwargs 323 | ) -> cgen.IfNDef: 324 | return node 325 | 326 | def map_custom_loop( 327 | self, node: cgen.CustomLoop, *args: P.args, **kwargs: P.kwargs 328 | ) -> cgen.CustomLoop: 329 | return type(node)(node.intro_line_, 330 | self.rec(node.body, *args, **kwargs), 331 | node.outro_line_, 332 | ) 333 | 334 | # {{{ opencl 335 | 336 | def map_cl_kernel( 337 | self, node: cl.CLKernel, *args: P.args, **kwargs: P.kwargs 338 | ) -> cl.CLKernel: 339 | return type(node)(self.rec(node.subdecl, *args, **kwargs)) 340 | 341 | def map_cl_constant( 342 | self, node: cl.CLConstant, *args: P.args, **kwargs: P.kwargs 343 | ) -> cl.CLConstant: 344 | return type(node)(self.rec(node.subdecl, *args, **kwargs)) 345 | 346 | def map_cl_global( 347 | self, node: cl.CLGlobal, *args: P.args, **kwargs: P.kwargs 348 | ) -> cl.CLGlobal: 349 | return type(node)(self.rec(node.subdecl, *args, **kwargs)) 350 | 351 | def map_cl_local( 352 | self, node: cl.CLLocal, *args: P.args, **kwargs: P.kwargs 353 | ) -> cl.CLLocal: 354 | return type(node)(self.rec(node.subdecl, *args, **kwargs)) 355 | 356 | def map_cl_image( 357 | self, node: cl.CLImage, *args: P.args, **kwargs: P.kwargs 358 | ) -> cl.CLImage: 359 | return node 360 | 361 | def map_cl_vec_type_hint( 362 | self, node: cl.CLVecTypeHint, *args: P.args, **kwargs: P.kwargs 363 | ) -> cl.CLVecTypeHint: 364 | return type(node)( 365 | self.rec(node.subdecl, *args, **kwargs), 366 | node.dtype, node.count, node.type_str) 367 | 368 | def map_cl_workgroup_size_hint( 369 | self, node: cl.CLWorkGroupSizeHint, *args: P.args, **kwargs: P.kwargs 370 | ) -> cl.CLWorkGroupSizeHint: 371 | return type(node)(node.dim, self.rec(node.subdecl, *args, **kwargs)) 372 | 373 | def map_cl_required_wokgroup_size( 374 | self, node: cl.CLRequiredWorkGroupSize, *args: P.args, **kwargs: P.kwargs 375 | ) -> cl.CLRequiredWorkGroupSize: 376 | return type(node)(node.dim, self.rec(node.subdecl, *args, **kwargs)) 377 | 378 | # }}} 379 | 380 | # {{{ ispc 381 | 382 | def map_ispc_varying( 383 | self, node: ispc.ISPCVarying, *args: P.args, **kwargs: P.kwargs 384 | ) -> ispc.ISPCVarying: 385 | return type(node)(self.rec(node.subdecl, *args, **kwargs)) 386 | 387 | def map_ispc_uniform( 388 | self, node: ispc.ISPCUniform, *args: P.args, **kwargs: P.kwargs 389 | ) -> ispc.ISPCUniform: 390 | return type(node)(self.rec(node.subdecl, *args, **kwargs)) 391 | 392 | def map_ispc_export( 393 | self, node: ispc.ISPCExport, *args: P.args, **kwargs: P.kwargs 394 | ) -> ispc.ISPCExport: 395 | return type(node)(self.rec(node.subdecl, *args, **kwargs)) 396 | 397 | def map_ispc_task( 398 | self, node: ispc.ISPCTask, *args: P.args, **kwargs: P.kwargs 399 | ) -> ispc.ISPCTask: 400 | return type(node)(self.rec(node.subdecl, *args, **kwargs)) 401 | 402 | def map_ispc_varying_pointer( 403 | self, node: ispc.ISPCVaryingPointer, *args: P.args, **kwargs: P.kwargs 404 | ) -> ispc.ISPCVaryingPointer: 405 | return type(node)(self.rec(node.subdecl, *args, **kwargs)) 406 | 407 | def map_ispc_uniform_pointer( 408 | self, node: ispc.ISPCUniformPointer, *args: P.args, **kwargs: P.kwargs 409 | ) -> ispc.ISPCUniformPointer: 410 | return type(node)(self.rec(node.subdecl, *args, **kwargs)) 411 | 412 | def map_ispc_launch( 413 | self, node: ispc.ISPCLaunch, *args: P.args, **kwargs: P.kwargs 414 | ) -> ispc.ISPCLaunch: 415 | return type(node)( 416 | tuple(self.map_expression(gs_i, *args, **kwargs) for gs_i in node.grid), 417 | self.map_expression(node.expr, *args, **kwargs)) 418 | 419 | # }}} 420 | 421 | # {{{ cuda 422 | 423 | def map_cuda_global( 424 | self, node: cuda.CudaGlobal, *args: P.args, **kwargs: P.kwargs 425 | ) -> cuda.CudaGlobal: 426 | return type(node)(self.rec(node.subdecl, *args, **kwargs)) 427 | 428 | def map_cuda_device( 429 | self, node: cuda.CudaDevice, *args: P.args, **kwargs: P.kwargs 430 | ) -> cuda.CudaDevice: 431 | return type(node)(self.rec(node.subdecl, *args, **kwargs)) 432 | 433 | def map_cuda_shared( 434 | self, node: cuda.CudaShared, *args: P.args, **kwargs: P.kwargs 435 | ) -> cuda.CudaShared: 436 | return type(node)(self.rec(node.subdecl, *args, **kwargs)) 437 | 438 | def map_cuda_constant( 439 | self, node: cuda.CudaConstant, *args: P.args, **kwargs: P.kwargs 440 | ) -> cuda.CudaConstant: 441 | return type(node)(self.rec(node.subdecl, *args, **kwargs)) 442 | 443 | def map_cuda_restrict_pointer( 444 | self, node: cuda.CudaRestrictPointer, *args: P.args, **kwargs: P.kwargs 445 | ) -> cuda.CudaRestrictPointer: 446 | return type(node)(self.rec(node.subdecl, *args, **kwargs)) 447 | 448 | def map_cuda_launch_bounds( 449 | self, node: cuda.CudaLaunchBounds, *args: P.args, **kwargs: P.kwargs 450 | ) -> cuda.CudaLaunchBounds: 451 | return type(node)( 452 | node.max_threads_per_block, 453 | self.rec(node.subdecl, *args, **kwargs), 454 | node.min_blocks_per_mp) 455 | 456 | # }}} 457 | 458 | # vim: foldmethod=marker 459 | -------------------------------------------------------------------------------- /cgen/opencl.py: -------------------------------------------------------------------------------- 1 | __copyright__ = "Copyright (C) 2011-20 Andreas Kloeckner" 2 | 3 | __license__ = """ 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 18 | THE SOFTWARE. 19 | """ 20 | 21 | from typing import Any, Literal, TypeAlias 22 | 23 | import numpy as np 24 | 25 | from cgen import Declarator, DeclPair, DeclSpecifier, NestedDeclarator, Value 26 | 27 | 28 | def dtype_to_cltype(dtype: Any) -> str: 29 | if dtype is None: 30 | raise ValueError("dtype may not be None") 31 | 32 | dtype = np.dtype(dtype) 33 | if dtype == np.int64: 34 | return "long" 35 | elif dtype == np.uint64: 36 | return "ulong" 37 | elif dtype == np.int32: 38 | return "int" 39 | elif dtype == np.uint32: 40 | return "uint" 41 | elif dtype == np.int16: 42 | return "short" 43 | elif dtype == np.uint16: 44 | return "ushort" 45 | elif dtype == np.int8: 46 | return "char" 47 | elif dtype == np.uint8: 48 | return "uchar" 49 | elif dtype == np.float32: 50 | return "float" 51 | elif dtype == np.float64: 52 | return "double" 53 | else: 54 | raise ValueError(f"unable to map dtype '{dtype}'") 55 | 56 | 57 | # {{{ kernel 58 | 59 | class CLKernel(DeclSpecifier): 60 | def __init__(self, subdecl: Declarator) -> None: 61 | super().__init__(subdecl, "__kernel") 62 | 63 | mapper_method = "map_cl_kernel" 64 | 65 | # }}} 66 | 67 | 68 | # {{{ kernel args 69 | 70 | Mode: TypeAlias = Literal["r"] | Literal["w"] 71 | 72 | 73 | class CLConstant(DeclSpecifier): 74 | def __init__(self, subdecl: Declarator) -> None: 75 | super().__init__(subdecl, "__constant") 76 | 77 | mapper_method = "map_cl_constant" 78 | 79 | 80 | class CLLocal(DeclSpecifier): 81 | def __init__(self, subdecl: Declarator) -> None: 82 | super().__init__(subdecl, "__local") 83 | 84 | mapper_method = "map_cl_local" 85 | 86 | 87 | class CLGlobal(DeclSpecifier): 88 | def __init__(self, subdecl: Declarator) -> None: 89 | super().__init__(subdecl, "__global") 90 | 91 | mapper_method = "map_cl_global" 92 | 93 | 94 | class CLImage(Value): 95 | def __init__(self, dims: int, mode: Mode, name: str) -> None: 96 | if mode == "r": 97 | spec = "__read_only" 98 | elif mode == "w": 99 | spec = "__write_only" 100 | else: 101 | raise ValueError("mode must be one of 'r' or 'w'") 102 | 103 | super().__init__(f"{spec} image{dims}d_t", name) 104 | 105 | mapper_method = "map_cl_image" 106 | 107 | # }}} 108 | 109 | 110 | # {{{ function attributes 111 | 112 | class CLVecTypeHint(NestedDeclarator): 113 | def __init__(self, 114 | subdecl: Declarator, 115 | dtype: Any = None, 116 | count: int | None = None, 117 | type_str: str | None = None) -> None: 118 | if (dtype is None) != (count is None): 119 | raise ValueError( 120 | "'dtype' and 'count' must always be specified together: " 121 | f"dtype is '{dtype}' and count is '{count}'") 122 | 123 | if ( 124 | (dtype is None and type_str is None) 125 | or (dtype is not None and type_str is not None)): 126 | raise ValueError( 127 | "Exactly one of 'dtype' and 'type_str' must be specified: " 128 | f"dtype is '{dtype}' and type_str is '{type_str}'") 129 | 130 | dtype = np.dtype(dtype) 131 | if type_str is None: 132 | type_str = f"{dtype_to_cltype(dtype)}{count}" 133 | else: 134 | type_str = type_str 135 | 136 | super().__init__(subdecl) 137 | self.dtype = dtype 138 | self.count = count 139 | self.type_str = type_str 140 | 141 | def get_decl_pair(self) -> DeclPair: 142 | sub_tp, sub_decl = self.subdecl.get_decl_pair() 143 | return sub_tp, ( 144 | "__attribute__ ((vec_type_hint({}))) {}" 145 | .format(sub_decl, self.type_str)) 146 | 147 | mapper_method = "map_cl_vec_type_hint" 148 | 149 | 150 | class _CLWorkGroupSizeDeclarator(NestedDeclarator): 151 | def __init__(self, dim: tuple[int, ...], subdecl: Declarator) -> None: 152 | super().__init__(subdecl) 153 | 154 | while len(dim) < 3: 155 | dim = (*dim, 1) 156 | 157 | self.dim = dim 158 | 159 | 160 | class CLWorkGroupSizeHint(_CLWorkGroupSizeDeclarator): 161 | """ 162 | See Sec 6.7.2 of OpenCL 2.0 spec, Version V2.2-11. 163 | """ 164 | 165 | def get_decl_pair(self) -> DeclPair: 166 | sub_tp, sub_decl = self.subdecl.get_decl_pair() 167 | return sub_tp, ( 168 | "__attribute__ ((work_group_size_hint({}))) {}" 169 | .format(", ".join(str(d) for d in self.dim), sub_decl)) 170 | 171 | mapper_method = "map_cl_workgroup_size_hint" 172 | 173 | 174 | class CLRequiredWorkGroupSize(_CLWorkGroupSizeDeclarator): 175 | """ 176 | See Sec 6.7.2 of OpenCL 2.0 spec, Version V2.2-11. 177 | """ 178 | 179 | def get_decl_pair(self) -> DeclPair: 180 | sub_tp, sub_decl = self.subdecl.get_decl_pair() 181 | return sub_tp, ( 182 | "__attribute__ ((reqd_work_group_size({}))) {}" 183 | .format(", ".join(str(d) for d in self.dim), sub_decl)) 184 | 185 | mapper_method = "map_cl_required_wokgroup_size" 186 | 187 | # }}} 188 | 189 | 190 | # {{{ vector PODs 191 | 192 | class CLVectorPOD(Declarator): 193 | def __init__(self, dtype: Any, count: int, name: str) -> None: 194 | self.dtype = np.dtype(dtype) 195 | self.count = count 196 | self.name = name 197 | 198 | def get_decl_pair(self) -> DeclPair: 199 | return [f"{dtype_to_cltype(self.dtype)}{self.count}"], self.name 200 | 201 | def struct_maker_code(self, name: str) -> str: 202 | return name 203 | 204 | def struct_format(self) -> str: 205 | return f"{self.count}{self.dtype.char}" 206 | 207 | def alignment_requirement(self) -> int: 208 | from struct import calcsize 209 | return calcsize(self.struct_format()) 210 | 211 | def default_value(self) -> Any: 212 | return [0] * self.count 213 | 214 | mapper_method = "map_cl_vector_pod" 215 | 216 | # }}} 217 | 218 | # vim: fdm=marker 219 | -------------------------------------------------------------------------------- /cgen/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inducer/cgen/d31fcc9b1689014a9e687747b53dc3f644ad4ebe/cgen/py.typed -------------------------------------------------------------------------------- /cgen/version.py: -------------------------------------------------------------------------------- 1 | from importlib import metadata 2 | 3 | 4 | def _parse_version(version: str) -> tuple[tuple[int, ...], str]: 5 | import re 6 | 7 | m = re.match(r"^([0-9.]+)([a-z0-9]*?)$", VERSION_TEXT) 8 | assert m is not None 9 | 10 | return tuple(int(nr) for nr in m.group(1).split(".")), m.group(2) 11 | 12 | 13 | VERSION_TEXT = metadata.version("cgen") 14 | VERSION, VERSION_STATUS = _parse_version(VERSION_TEXT) 15 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = python `which sphinx-build` 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pymbolic.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pymbolic.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/pymbolic" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pymbolic" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /doc/cgen.rst: -------------------------------------------------------------------------------- 1 | :mod:`cgen` -- C-Generation Reference Documentation 2 | ========================================================== 3 | 4 | .. module:: cgen 5 | 6 | .. autofunction:: dtype_to_ctype 7 | 8 | .. autoclass:: Generable 9 | :members: generate, __str__ 10 | :show-inheritance: 11 | 12 | .. autoclass:: Block 13 | :show-inheritance: 14 | :members: append, extend, extend_log_block 15 | 16 | .. autoclass:: Collection 17 | :show-inheritance: 18 | 19 | .. autoclass:: Module 20 | :show-inheritance: 21 | 22 | .. autoclass:: Line 23 | :show-inheritance: 24 | 25 | Data and Functions 26 | ------------------ 27 | 28 | .. autoclass:: FunctionBody 29 | :show-inheritance: 30 | 31 | .. autoclass:: Initializer 32 | :show-inheritance: 33 | 34 | Preprocessor Code 35 | ----------------- 36 | 37 | .. autoclass:: Define 38 | :show-inheritance: 39 | 40 | .. autoclass:: Comment 41 | :show-inheritance: 42 | 43 | .. autoclass:: Include 44 | :show-inheritance: 45 | 46 | .. autoclass:: Pragma 47 | :show-inheritance: 48 | 49 | Declarators 50 | ----------- 51 | 52 | .. autoclass:: Declarator 53 | :members: 54 | :show-inheritance: 55 | 56 | .. autoclass:: Value 57 | :show-inheritance: 58 | 59 | .. autoclass:: POD 60 | :show-inheritance: 61 | 62 | .. autoclass:: Struct 63 | :show-inheritance: 64 | 65 | .. autoclass:: GenerableStruct 66 | :show-inheritance: 67 | :members: __init__, make, make_with_defaults, __len__, struct_format 68 | 69 | .. autoclass:: Enum 70 | :show-inheritance: 71 | :members: stringify_value, get_c_typedef, get_c_defines, get_flag_names_and_values 72 | 73 | Nested Declarators 74 | ------------------ 75 | 76 | .. autoclass:: NestedDeclarator 77 | :show-inheritance: 78 | 79 | .. autoclass:: ArrayOf 80 | :show-inheritance: 81 | 82 | .. autoclass:: Const 83 | :show-inheritance: 84 | 85 | .. autoclass:: FunctionDeclaration 86 | :show-inheritance: 87 | 88 | .. autoclass:: MaybeUnused 89 | :show-inheritance: 90 | 91 | .. autoclass:: Pointer 92 | :show-inheritance: 93 | 94 | .. autoclass:: Reference 95 | :show-inheritance: 96 | 97 | .. autoclass:: Template 98 | :show-inheritance: 99 | 100 | .. autoclass:: AlignedAttribute 101 | :show-inheritance: 102 | 103 | .. autoclass:: AlignValueAttribute 104 | :show-inheritance: 105 | 106 | Declaration Specifiers 107 | ---------------------- 108 | 109 | .. autoclass:: DeclSpecifier 110 | :show-inheritance: 111 | 112 | .. autoclass:: Static 113 | :show-inheritance: 114 | 115 | .. autoclass:: Typedef 116 | :show-inheritance: 117 | 118 | Statements 119 | ---------- 120 | 121 | .. autoclass:: Statement 122 | :show-inheritance: 123 | 124 | .. autoclass:: Assign 125 | :show-inheritance: 126 | 127 | .. autoclass:: If 128 | :show-inheritance: 129 | 130 | .. autofunction:: make_multiple_ifs 131 | 132 | .. autoclass:: Loop 133 | :show-inheritance: 134 | 135 | .. autoclass:: DoWhile 136 | :show-inheritance: 137 | 138 | .. autoclass:: For 139 | :show-inheritance: 140 | 141 | .. autoclass:: While 142 | :show-inheritance: 143 | 144 | :mod:`cgen.cuda` -- Extensions to generate CUDA-compatible C Sources 145 | --------------------------------------------------------------------------- 146 | 147 | .. automodule:: cgen.cuda 148 | 149 | This module adds a few Nvidia `CUDA `_ features to 150 | cgen's repertoire. This makes cgen a perfect complement to 151 | `PyCuda `_: cgen generates 152 | the code, PyCuda compiles it, uploads it to the GPU and executes it. 153 | 154 | The PyCuda manual has a tutorial on using the two together. 155 | 156 | .. autoclass:: CudaGlobal 157 | :show-inheritance: 158 | 159 | .. autoclass:: CudaDevice 160 | :show-inheritance: 161 | 162 | .. autoclass:: CudaShared 163 | :show-inheritance: 164 | 165 | .. autoclass:: CudaConstant 166 | :show-inheritance: 167 | 168 | :mod:`cgen.opencl` -- Extensions to generate OpenCL-compatible C Sources 169 | ------------------------------------------------------------------------------- 170 | 171 | .. automodule:: cgen.opencl 172 | 173 | .. autofunction:: dtype_to_cltype 174 | 175 | Kernels and Kernel Arguments 176 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 177 | 178 | .. autoclass:: CLKernel 179 | :show-inheritance: 180 | 181 | .. autoclass:: CLConstant 182 | :show-inheritance: 183 | 184 | .. autoclass:: CLLocal 185 | :show-inheritance: 186 | 187 | .. autoclass:: CLGlobal 188 | :show-inheritance: 189 | 190 | .. autoclass:: CLImage 191 | :show-inheritance: 192 | 193 | Function Attributes 194 | ^^^^^^^^^^^^^^^^^^^ 195 | 196 | .. autoclass:: CLVecTypeHint 197 | :show-inheritance: 198 | 199 | .. autoclass:: CLWorkGroupSizeHint 200 | 201 | .. autoclass:: CLRequiredWorkGroupSize 202 | 203 | Vector PODs 204 | ^^^^^^^^^^^ 205 | 206 | .. autoclass:: CLVectorPOD 207 | :show-inheritance: 208 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | from importlib import metadata 2 | from urllib.request import urlopen 3 | 4 | 5 | _conf_url = \ 6 | "https://raw.githubusercontent.com/inducer/sphinxconfig/main/sphinxconfig.py" 7 | with urlopen(_conf_url) as _inf: 8 | exec(compile(_inf.read(), _conf_url, "exec"), globals()) 9 | 10 | copyright = "2011, Andreas Kloeckner" 11 | release = metadata.version("cgen") 12 | version = ".".join(release.split(".")[:2]) 13 | 14 | intersphinx_mapping = { 15 | "python": ("https://docs.python.org/3/", None), 16 | "numpy": ("https://numpy.org/doc/stable/", None), 17 | "pymbolic": ("https://documen.tician.de/pymbolic/", None), 18 | } 19 | -------------------------------------------------------------------------------- /doc/faq.rst: -------------------------------------------------------------------------------- 1 | License 2 | ======= 3 | 4 | cgen is licensed to you under the MIT/X Consortium license: 5 | 6 | Copyright (c) 2011 Andreas Klöckner 7 | 8 | Permission is hereby granted, free of charge, to any person 9 | obtaining a copy of this software and associated documentation 10 | files (the "Software"), to deal in the Software without 11 | restriction, including without limitation the rights to use, 12 | copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the 14 | Software is furnished to do so, subject to the following 15 | conditions: 16 | 17 | The above copyright notice and this permission notice shall be 18 | included in all copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | OTHER DEALINGS IN THE SOFTWARE. 28 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to cgen's documentation! 2 | ================================== 3 | 4 | cgen offers a simple abstract syntax tree for C and related languages 5 | (C++/CUDA/OpenCL) to allow structured code generation from Python. 6 | 7 | Here's an example, to give you an impression: 8 | 9 | .. literalinclude:: ../examples/hello-cgen.py 10 | 11 | This prints the following:: 12 | 13 | char const *greet() 14 | { 15 | return "hello world"; 16 | } 17 | 18 | :mod:`cgen` deliberately does not worry about expression trees. Its sister 19 | package :mod:`pymbolic` can help you build those. 20 | 21 | 22 | Contents 23 | -------- 24 | 25 | .. toctree:: 26 | :maxdepth: 2 27 | 28 | cgen 29 | faq 30 | 🚀 Github 31 | 💾 Download Releases 32 | 33 | Indices and tables 34 | ================== 35 | 36 | * :ref:`genindex` 37 | * :ref:`modindex` 38 | * :ref:`search` 39 | -------------------------------------------------------------------------------- /doc/upload-docs.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | rsync --verbose --archive --delete _build/html/ doc-upload:doc/cgen 4 | -------------------------------------------------------------------------------- /examples/hello-cgen.py: -------------------------------------------------------------------------------- 1 | import cgen as c 2 | 3 | 4 | func = c.FunctionBody( 5 | c.FunctionDeclaration(c.Const(c.Pointer(c.Value("char", "greet"))), []), 6 | c.Block([ 7 | c.Statement('return "hello world"') 8 | ]) 9 | ) 10 | 11 | print(func) 12 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "cgen" 7 | version = "2025.0" 8 | description = "C/C++ source generation from an AST" 9 | readme = "README.rst" 10 | license = { text = "MIT" } 11 | authors = [ 12 | { name = "Andreas Kloeckner", email = "inform@tiker.net" }, 13 | ] 14 | requires-python = ">=3.10" 15 | classifiers = [ 16 | "Development Status :: 4 - Beta", 17 | "Intended Audience :: Developers", 18 | "Intended Audience :: Other Audience", 19 | "Intended Audience :: Science/Research", 20 | "License :: OSI Approved :: MIT License", 21 | "Natural Language :: English", 22 | "Programming Language :: Python", 23 | "Programming Language :: Python :: 3 :: Only", 24 | "Topic :: Scientific/Engineering", 25 | "Topic :: Software Development :: Libraries", 26 | "Topic :: Utilities", 27 | ] 28 | dependencies = [ 29 | "numpy>=1.6", 30 | "pytools>=2022.1.14", 31 | ] 32 | 33 | [project.optional-dependencies] 34 | test = [ 35 | "pytest", 36 | "ruff", 37 | ] 38 | 39 | [project.urls] 40 | Documentation = "https://documen.tician.de/cgen" 41 | Homepage = "https://github.com/inducer/cgen" 42 | 43 | [tool.ruff] 44 | preview = true 45 | 46 | [tool.ruff.lint] 47 | extend-select = [ 48 | "B", # flake8-bugbear 49 | "C", # flake8-comprehensions 50 | "E", # pycodestyle 51 | "F", # pyflakes 52 | "G", # flake8-logging-format 53 | "I", # flake8-isort 54 | "N", # pep8-naming 55 | "NPY", # numpy 56 | "Q", # flake8-quotes 57 | "RUF", # ruff 58 | "TC", # flake8-type-checking 59 | "UP", # pyupgrade 60 | "W", # pycodestyle 61 | ] 62 | extend-ignore = [ 63 | "C90", # McCabe complexity 64 | "E226", # missing whitespace around arithmetic operator 65 | "E241", # multiple spaces after comma 66 | "E242", # tab after comma 67 | "E402", # module level import not at the top of file 68 | "UP031", # use f-strings instead of % 69 | "UP032", # use f-strings instead of .format 70 | ] 71 | 72 | [tool.ruff.lint.flake8-quotes] 73 | docstring-quotes = "double" 74 | inline-quotes = "double" 75 | multiline-quotes = "double" 76 | 77 | [tool.ruff.lint.isort] 78 | combine-as-imports = true 79 | known-first-party = [ "pytools" ] 80 | known-local-folder = [ "cgen" ] 81 | lines-after-imports = 2 82 | 83 | [tool.mypy] 84 | python_version = "3.10" 85 | strict = true 86 | warn_unused_ignores = true 87 | 88 | [[tool.mypy.overrides]] 89 | module = [ 90 | "pyopencl.*", 91 | "pycuda.*", 92 | ] 93 | ignore_missing_imports = true 94 | -------------------------------------------------------------------------------- /test/test_cgen.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from cgen import ( 4 | POD, 5 | ArrayOf, 6 | Assign, 7 | Block, 8 | Comment, 9 | For, 10 | FunctionBody, 11 | FunctionDeclaration, 12 | If, 13 | Struct, 14 | Template, 15 | Value, 16 | ) 17 | 18 | 19 | def test_cgen() -> None: 20 | s = Struct("yuck", [ 21 | POD(np.float32, "h", ), 22 | POD(np.float32, "order"), 23 | POD(np.float32, "face_jacobian"), 24 | ArrayOf(POD(np.float32, "normal"), 17), 25 | POD(np.uint16, "a_base"), 26 | POD(np.uint16, "b_base"), 27 | # CudaGlobal(POD(np.uint8, "a_ilist_number")), 28 | POD(np.uint8, "b_ilist_number"), 29 | POD(np.uint8, "bdry_flux_number"), # 0 if not on boundary 30 | POD(np.uint8, "reserved"), 31 | POD(np.uint32, "b_global_base"), 32 | ]) 33 | f_decl = FunctionDeclaration(POD(np.uint16, "get_num"), [ 34 | POD(np.uint8, "reserved"), 35 | POD(np.uint32, "b_global_base"), 36 | ]) 37 | f_body = FunctionBody(f_decl, Block([ 38 | POD(np.uint32, "i"), 39 | For("i = 0", "i < 17", "++i", 40 | If( 41 | "a > b", 42 | Assign("a", "b"), 43 | Block([ 44 | Assign("a", "b-1"), 45 | # Break(), 46 | ]) 47 | ), 48 | ), 49 | # BlankLine(), 50 | Comment("all done"), 51 | ])) 52 | t_decl = Template("typename T", 53 | FunctionDeclaration(Value("CUdeviceptr", "scan"), 54 | [Value("CUdeviceptr", "inputPtr"), 55 | Value("int", "length")])) 56 | 57 | print(s) 58 | print(f_body) 59 | print(t_decl) 60 | --------------------------------------------------------------------------------