python-cc 0.0.7__py3-none-any.whl → 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. pcc/__init__.py +3 -0
  2. pcc/api.py +329 -0
  3. pcc/ast/ast.py +2 -1
  4. pcc/ast/ast_transforms.py +4 -1
  5. pcc/ast/c_ast.py +148 -6
  6. pcc/backend/__init__.py +165 -0
  7. pcc/backend/self_backend.py +1843 -0
  8. pcc/codegen/c_codegen.py +4591 -690
  9. pcc/evaluater/c_evaluator.py +1137 -92
  10. pcc/extern/__init__.py +145 -0
  11. pcc/generator/c_generator.py +60 -2
  12. pcc/ir_passes/__init__.py +252 -0
  13. pcc/ir_passes/adce.py +410 -0
  14. pcc/ir_passes/alias_analysis.py +171 -0
  15. pcc/ir_passes/alignment_from_assumptions.py +229 -0
  16. pcc/ir_passes/arg_opt.py +306 -0
  17. pcc/ir_passes/argpromotion.py +282 -0
  18. pcc/ir_passes/bdce.py +120 -0
  19. pcc/ir_passes/called_value_prop.py +166 -0
  20. pcc/ir_passes/callsite_splitting.py +336 -0
  21. pcc/ir_passes/constant_lattice.py +241 -0
  22. pcc/ir_passes/constraint_elimination.py +296 -0
  23. pcc/ir_passes/correlated_propagation.py +241 -0
  24. pcc/ir_passes/dce.py +227 -0
  25. pcc/ir_passes/dominator_tree.py +401 -0
  26. pcc/ir_passes/dse.py +340 -0
  27. pcc/ir_passes/early_cse.py +281 -0
  28. pcc/ir_passes/elim_avail_extern.py +140 -0
  29. pcc/ir_passes/float2int.py +289 -0
  30. pcc/ir_passes/function_attrs.py +498 -0
  31. pcc/ir_passes/globalopt.py +545 -0
  32. pcc/ir_passes/gvn.py +474 -0
  33. pcc/ir_passes/indvars.py +545 -0
  34. pcc/ir_passes/infer_alignment.py +207 -0
  35. pcc/ir_passes/inline.py +675 -0
  36. pcc/ir_passes/instcombine.py +2112 -0
  37. pcc/ir_passes/instsimplify.py +455 -0
  38. pcc/ir_passes/ipo_passes.py +109 -0
  39. pcc/ir_passes/ipsccp.py +296 -0
  40. pcc/ir_passes/ir_mutator.py +622 -0
  41. pcc/ir_passes/jump_threading.py +383 -0
  42. pcc/ir_passes/late_scalar.py +199 -0
  43. pcc/ir_passes/libcalls_shrinkwrap.py +46 -0
  44. pcc/ir_passes/licm.py +654 -0
  45. pcc/ir_passes/loop_deletion.py +325 -0
  46. pcc/ir_passes/loop_distribute.py +266 -0
  47. pcc/ir_passes/loop_info.py +185 -0
  48. pcc/ir_passes/loop_instsimplify.py +171 -0
  49. pcc/ir_passes/loop_load_elim.py +130 -0
  50. pcc/ir_passes/loop_passes.py +50 -0
  51. pcc/ir_passes/loop_rotate.py +232 -0
  52. pcc/ir_passes/loop_simplify.py +284 -0
  53. pcc/ir_passes/loop_simplifycfg.py +169 -0
  54. pcc/ir_passes/loop_sink.py +203 -0
  55. pcc/ir_passes/loop_unroll.py +1616 -0
  56. pcc/ir_passes/loop_vectorize.py +313 -0
  57. pcc/ir_passes/lower_constant_intrinsics.py +113 -0
  58. pcc/ir_passes/lower_expect.py +136 -0
  59. pcc/ir_passes/manager.py +308 -0
  60. pcc/ir_passes/mem2reg.py +739 -0
  61. pcc/ir_passes/memcpyopt.py +222 -0
  62. pcc/ir_passes/memory_ssa.py +174 -0
  63. pcc/ir_passes/meta_passes.py +266 -0
  64. pcc/ir_passes/mldst_motion.py +326 -0
  65. pcc/ir_passes/newgvn.py +163 -0
  66. pcc/ir_passes/parity.py +487 -0
  67. pcc/ir_passes/reassociate.py +304 -0
  68. pcc/ir_passes/sccp.py +220 -0
  69. pcc/ir_passes/simple_loop_unswitch.py +261 -0
  70. pcc/ir_passes/simplifycfg.py +1576 -0
  71. pcc/ir_passes/slp_vectorizer.py +221 -0
  72. pcc/ir_passes/speculative_execution.py +246 -0
  73. pcc/ir_passes/sroa.py +496 -0
  74. pcc/ir_passes/ssa_utils.py +213 -0
  75. pcc/ir_passes/tailcallelim.py +310 -0
  76. pcc/ir_passes/trivial_simplify.py +193 -0
  77. pcc/ir_passes/vector_combine.py +110 -0
  78. pcc/ir_passes/vectorize_passes.py +28 -0
  79. pcc/lex/c_lexer.py +37 -6
  80. pcc/llvm_capi/__init__.py +227 -0
  81. pcc/llvm_capi/binding.py +609 -0
  82. pcc/llvm_capi/compat.py +104 -0
  83. pcc/llvm_capi/ir.py +1569 -0
  84. pcc/parse/__init__.py +26 -0
  85. pcc/parse/ast_normalize.py +106 -0
  86. pcc/parse/c_lex.py +563 -0
  87. pcc/parse/c_parse_driver.py +241 -0
  88. pcc/parse/c_parser.py +247 -40
  89. pcc/parse/c_parser_actions.py +1247 -0
  90. pcc/parse/c_parsetab.py +1079 -0
  91. pcc/parse/file_parser.py +2 -1
  92. pcc/parse/py_lex.py +341 -0
  93. pcc/parse/py_lift.py +650 -0
  94. pcc/parse/py_parse.py +1542 -0
  95. pcc/passes/__init__.py +84 -0
  96. pcc/passes/alloc_decision.py +95 -0
  97. pcc/passes/ast_utils.py +552 -0
  98. pcc/passes/base.py +470 -0
  99. pcc/passes/canonicalize.py +420 -0
  100. pcc/passes/chez_transforms.py +477 -0
  101. pcc/passes/clang_compat.py +191 -0
  102. pcc/passes/context.py +333 -0
  103. pcc/passes/control_flow.py +353 -0
  104. pcc/passes/dce.py +319 -0
  105. pcc/passes/escape_analysis.py +227 -0
  106. pcc/passes/global_dce.py +99 -0
  107. pcc/passes/groups.py +188 -0
  108. pcc/passes/inline_opt.py +314 -0
  109. pcc/passes/ipo_boundary.py +80 -0
  110. pcc/passes/ir_metadata.py +290 -0
  111. pcc/passes/llvm_builtin_registry.py +237 -0
  112. pcc/passes/llvm_explicit.py +1916 -0
  113. pcc/passes/llvm_loop_explicit.py +582 -0
  114. pcc/passes/llvm_python_registry.py +1114 -0
  115. pcc/passes/llvm_text_pipeline.py +325 -0
  116. pcc/passes/loop_opt.py +372 -0
  117. pcc/passes/lower_expect.py +55 -0
  118. pcc/passes/memory_opt.py +583 -0
  119. pcc/passes/nsw_inference.py +173 -0
  120. pcc/passes/propagation.py +710 -0
  121. pcc/passes/ssa_adce.py +339 -0
  122. pcc/passes/ssa_bootstrap.py +53 -0
  123. pcc/passes/ssa_branch_prune.py +94 -0
  124. pcc/passes/ssa_dse.py +261 -0
  125. pcc/passes/ssa_gvn.py +55 -0
  126. pcc/passes/ssa_gvn_rewrite.py +285 -0
  127. pcc/passes/ssa_loop_phi.py +58 -0
  128. pcc/passes/ssa_sccp.py +64 -0
  129. pcc/passes/ssa_sccp_rewrite.py +310 -0
  130. pcc/passes/tbaa.py +129 -0
  131. pcc/passes/whole_program.py +235 -0
  132. pcc/passes/whole_program_pass.py +60 -0
  133. pcc/pcc.py +363 -37
  134. pcc/ply/ctokens.py +2 -3
  135. pcc/preprocessor.py +272 -10
  136. pcc/project.py +71 -17
  137. pcc/py_frontend/__init__.py +5 -0
  138. pcc/py_frontend/codegen/__init__.py +9 -0
  139. pcc/py_frontend/codegen/class_gen.py +1173 -0
  140. pcc/py_frontend/codegen/layer1.py +7920 -0
  141. pcc/py_frontend/codegen/marshal.py +262 -0
  142. pcc/py_frontend/codegen/runtime_abi.py +285 -0
  143. pcc/py_frontend/parser.py +1109 -0
  144. pcc/py_frontend/pipeline.py +599 -0
  145. pcc/py_frontend/py_ast.py +352 -0
  146. pcc/py_frontend/type_infer.py +1151 -0
  147. pcc/py_frontend/types.py +295 -0
  148. pcc/py_runtime/Makefile +66 -0
  149. pcc/py_runtime/include/py_runtime.h +286 -0
  150. pcc/py_runtime/libpy_runtime.a +0 -0
  151. pcc/py_runtime/src/py_class.c +472 -0
  152. pcc/py_runtime/src/py_dict.c +350 -0
  153. pcc/py_runtime/src/py_exc.c +441 -0
  154. pcc/py_runtime/src/py_int.c +1208 -0
  155. pcc/py_runtime/src/py_internal.h +556 -0
  156. pcc/py_runtime/src/py_libpython.c +455 -0
  157. pcc/py_runtime/src/py_list.c +407 -0
  158. pcc/py_runtime/src/py_obj.c +185 -0
  159. pcc/py_runtime/src/py_obj_ops.c +452 -0
  160. pcc/py_runtime/src/py_obj_stubs.c +88 -0
  161. pcc/py_runtime/src/py_print.c +167 -0
  162. pcc/py_runtime/src/py_set.c +201 -0
  163. pcc/py_runtime/src/py_str.c +905 -0
  164. pcc/py_runtime/src/py_tuple.c +77 -0
  165. pcc/py_stdlib/README.md +59 -0
  166. pcc/py_stdlib/__init__.py +11 -0
  167. pcc/py_stdlib/abc.py +21 -0
  168. pcc/py_stdlib/collections.py +133 -0
  169. pcc/py_stdlib/concurrent.py +117 -0
  170. pcc/py_stdlib/contextlib.py +46 -0
  171. pcc/py_stdlib/copy.ll +847 -0
  172. pcc/py_stdlib/copy.py +50 -0
  173. pcc/py_stdlib/ctypes.py +197 -0
  174. pcc/py_stdlib/dataclasses.py +93 -0
  175. pcc/py_stdlib/enum.py +43 -0
  176. pcc/py_stdlib/fcntl.py +31 -0
  177. pcc/py_stdlib/functools.py +70 -0
  178. pcc/py_stdlib/io.py +61 -0
  179. pcc/py_stdlib/itertools.py +82 -0
  180. pcc/py_stdlib/json.py +245 -0
  181. pcc/py_stdlib/logging.py +54 -0
  182. pcc/py_stdlib/math.py +86 -0
  183. pcc/py_stdlib/multiprocessing.py +118 -0
  184. pcc/py_stdlib/operator.py +58 -0
  185. pcc/py_stdlib/os.py +89 -0
  186. pcc/py_stdlib/pathlib.py +71 -0
  187. pcc/py_stdlib/platform.py +36 -0
  188. pcc/py_stdlib/re.py +91 -0
  189. pcc/py_stdlib/shlex.py +53 -0
  190. pcc/py_stdlib/shutil.py +38 -0
  191. pcc/py_stdlib/string.py +12 -0
  192. pcc/py_stdlib/struct.py +61 -0
  193. pcc/py_stdlib/subprocess.py +64 -0
  194. pcc/py_stdlib/sys.py +43 -0
  195. pcc/py_stdlib/tempfile.py +51 -0
  196. pcc/py_stdlib/time.py +45 -0
  197. pcc/py_stdlib/traceback.py +27 -0
  198. pcc/py_stdlib/typing.py +62 -0
  199. pcc/py_stdlib/warnings.py +37 -0
  200. pcc/ssa/__init__.py +77 -0
  201. pcc/ssa/adce.py +122 -0
  202. pcc/ssa/builder.py +4238 -0
  203. pcc/ssa/gvn.py +152 -0
  204. pcc/ssa/ir.py +293 -0
  205. pcc/ssa/loop_phi.py +279 -0
  206. pcc/ssa/sccp.py +418 -0
  207. pcc/util.py +13 -24
  208. python_cc-0.1.0.dist-info/METADATA +660 -0
  209. python_cc-0.1.0.dist-info/RECORD +329 -0
  210. utils/fake_libc_include/_fake_typedefs.h +2 -2
  211. utils/fake_libc_include/errno.h +3 -0
  212. utils/fake_libc_include/limits.h +64 -0
  213. utils/internal/refresh_c_testsuite_manifest.py +157 -0
  214. python_cc-0.0.7.dist-info/METADATA +0 -429
  215. python_cc-0.0.7.dist-info/RECORD +0 -137
  216. {python_cc-0.0.7.dist-info → python_cc-0.1.0.dist-info}/WHEEL +0 -0
  217. {python_cc-0.0.7.dist-info → python_cc-0.1.0.dist-info}/entry_points.txt +0 -0
  218. {python_cc-0.0.7.dist-info → python_cc-0.1.0.dist-info}/licenses/LICENSE +0 -0
pcc/__init__.py CHANGED
@@ -0,0 +1,3 @@
1
+ from .api import module, build, BuildArtifact, Module
2
+
3
+ __all__ = ["module"]
pcc/api.py ADDED
@@ -0,0 +1,329 @@
1
+ """Top-level Python API for pcc.
2
+
3
+ from pcc import build, module
4
+
5
+ build(...) compiles C sources into an artifact (executable, shared lib, or object).
6
+ module(...) compiles and loads C sources as a Python-callable module.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import ctypes
12
+ import os
13
+ import platform
14
+ import shutil
15
+ import subprocess
16
+ import tempfile
17
+ from dataclasses import dataclass, field
18
+ from pathlib import Path
19
+ from typing import Any, Sequence
20
+
21
+ from .evaluater.c_evaluator import CEvaluator
22
+ from .project import TranslationUnit, collect_translation_units, translation_unit_include_dirs
23
+
24
+
25
+ # ---------------------------------------------------------------------------
26
+ # Artifact
27
+ # ---------------------------------------------------------------------------
28
+
29
+ @dataclass
30
+ class BuildArtifact:
31
+ """Result of a build() call."""
32
+
33
+ kind: str
34
+ backend: str
35
+ output_path: str
36
+ compiled_units: list[tuple[str, ...]]
37
+ pass_report: dict[str, Any]
38
+ exports: list[str]
39
+ optimize: int
40
+ rebuilt: bool
41
+ libs: list[str]
42
+ ir_text: str | None = None
43
+
44
+ def __repr__(self):
45
+ return (
46
+ f"BuildArtifact(kind={self.kind!r}, backend={self.backend!r}, output_path={self.output_path!r}, "
47
+ f"exports={self.exports!r}, libs={self.libs!r})"
48
+ )
49
+
50
+
51
+ # ---------------------------------------------------------------------------
52
+ # build(...)
53
+ # ---------------------------------------------------------------------------
54
+
55
+ def build(
56
+ sources: str | Path | Sequence[str | Path],
57
+ *,
58
+ include_dirs: Sequence[str | Path] | None = None,
59
+ cpp_args: Sequence[str] | None = None,
60
+ libs: Sequence[str] | None = None,
61
+ link_args: Sequence[str] | None = None,
62
+ optimize: int | bool = 2,
63
+ kind: str = "exe",
64
+ backend: str | None = None,
65
+ out_dir: str | Path | None = None,
66
+ use_compile_cache: bool = True,
67
+ jobs: int = 1,
68
+ ) -> BuildArtifact:
69
+ """Compile C sources into an artifact.
70
+
71
+ Args:
72
+ sources: One or more C source file paths.
73
+ include_dirs: Extra -I include directories.
74
+ cpp_args: Extra preprocessor flags (e.g. ["-DFOO=1"]).
75
+ libs: System libraries to link (e.g. ["z", "ssl"]).
76
+ Translates to -lz, -lssl at link time. Works like libc —
77
+ uses pre-compiled system libraries, does not compile them.
78
+ link_args: Raw linker flags (escape hatch).
79
+ optimize: Optimization level (0-3 or bool).
80
+ kind: "exe", "sharedlib", or "object".
81
+ backend: Backend implementation to use. Defaults to the current LLVM path.
82
+ out_dir: Output directory (default: temp dir).
83
+ use_compile_cache: Enable compilation cache.
84
+ jobs: Parallel compilation jobs.
85
+
86
+ Returns:
87
+ BuildArtifact with output_path, exports, pass_report, etc.
88
+ """
89
+ # Normalize sources
90
+ if isinstance(sources, (str, Path)):
91
+ sources = [sources]
92
+ source_paths = [str(Path(s).resolve()) for s in sources]
93
+
94
+ for s in source_paths:
95
+ if not os.path.isfile(s):
96
+ raise FileNotFoundError(f"source file not found: {s}")
97
+
98
+ # Collect translation units
99
+ units = []
100
+ base_dir = str(Path(source_paths[0]).parent)
101
+ for src in source_paths:
102
+ with open(src) as f:
103
+ source_text = f.read()
104
+ units.append(TranslationUnit(
105
+ name=os.path.basename(src),
106
+ path=src,
107
+ source=source_text,
108
+ ))
109
+
110
+ # Build include_dirs
111
+ all_include_dirs = list(include_dirs or [])
112
+ # Add source directories as implicit include dirs
113
+ for src in source_paths:
114
+ d = str(Path(src).parent)
115
+ if d not in all_include_dirs:
116
+ all_include_dirs.append(d)
117
+
118
+ # Compile — use internal _compile_translation_units to get full artifacts
119
+ # (pass_report, ir_text) instead of just the stripped compiled_unit tuples.
120
+ ev = CEvaluator(backend=backend)
121
+ opt_level = ev._normalize_opt_level(optimize)
122
+ use_system_cpp = ev._has_system_cpp()
123
+ from .evaluater.c_evaluator import _artifact_to_compiled_unit
124
+
125
+ artifacts = ev._compile_translation_units(
126
+ units,
127
+ base_dir,
128
+ use_system_cpp,
129
+ jobs,
130
+ include_dirs=all_include_dirs or None,
131
+ cpp_args=list(cpp_args) if cpp_args else None,
132
+ use_compile_cache=use_compile_cache,
133
+ frontend_opt_level=opt_level,
134
+ )
135
+ compiled_units = [_artifact_to_compiled_unit(a) for a in artifacts]
136
+
137
+ # Build link_args with libs
138
+ final_link_args = list(link_args or [])
139
+ lib_list = list(libs or [])
140
+ for lib in lib_list:
141
+ flag = f"-l{lib}"
142
+ if flag not in final_link_args:
143
+ final_link_args.append(flag)
144
+
145
+ # Determine output
146
+ if out_dir is None:
147
+ out_dir = tempfile.mkdtemp(prefix="pcc_build_")
148
+ else:
149
+ out_dir = str(Path(out_dir).resolve())
150
+ os.makedirs(out_dir, exist_ok=True)
151
+
152
+ # Collect exports
153
+ exports = []
154
+ for _name, _ir, _ret, ext_defs in compiled_units:
155
+ for def_kind, symbol_name, display_name in ext_defs:
156
+ if def_kind == "function":
157
+ exports.append(display_name)
158
+
159
+ # Collect pass report and IR from artifacts
160
+ pass_report: dict[str, Any] = {}
161
+ ir_texts = []
162
+ for artifact in artifacts:
163
+ pr = artifact.get("pass_report", {})
164
+ if pr:
165
+ pass_report[artifact["unit_name"]] = pr
166
+ ir_texts.append(artifact.get("ir_text", ""))
167
+ combined_ir = "\n".join(ir_texts) if ir_texts else None
168
+
169
+ # Emit
170
+ if kind == "object":
171
+ out_path = os.path.join(out_dir, "output.o")
172
+ ev.emit_compiled_units(compiled_units, emit_obj=out_path, optimize=opt_level)
173
+ elif kind == "sharedlib":
174
+ out_path = _link_shared(ev, compiled_units, out_dir, final_link_args, opt_level)
175
+ elif kind == "exe":
176
+ out_path = _link_exe(ev, compiled_units, out_dir, final_link_args, opt_level)
177
+ else:
178
+ raise ValueError(f"unsupported kind: {kind!r}")
179
+
180
+ return BuildArtifact(
181
+ kind=kind,
182
+ backend=ev.backend,
183
+ output_path=out_path,
184
+ compiled_units=[(name,) for name, _, _, _ in compiled_units],
185
+ pass_report=pass_report,
186
+ exports=exports,
187
+ optimize=opt_level,
188
+ rebuilt=True,
189
+ libs=lib_list,
190
+ ir_text=combined_ir,
191
+ )
192
+
193
+
194
+ def _link_exe(ev, compiled_units, out_dir, link_args, opt_level):
195
+ """Link compiled units into an executable."""
196
+ cc = ev._system_cc()
197
+ tm = ev.target.create_target_machine()
198
+ obj_paths = []
199
+
200
+ for name, ir_text, _, _ in compiled_units:
201
+ llvmmod = ev._prepare_llvm_module(name, ir_text, tm, optimize=opt_level)
202
+ obj_path = os.path.join(out_dir, f"{name.replace('.', '_')}.o")
203
+ with open(obj_path, "wb") as f:
204
+ f.write(tm.emit_object(llvmmod))
205
+ obj_paths.append(obj_path)
206
+
207
+ bin_path = os.path.join(out_dir, "a.out")
208
+ cmd = [cc] + obj_paths + ["-o", bin_path] + ev._platform_link_flags() + link_args
209
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
210
+ if result.returncode != 0:
211
+ raise RuntimeError(f"link failed: {result.stderr[:500]}")
212
+ return bin_path
213
+
214
+
215
+ def _link_shared(ev, compiled_units, out_dir, link_args, opt_level):
216
+ """Link compiled units into a shared library."""
217
+ cc = ev._system_cc()
218
+ tm = ev.target.create_target_machine()
219
+ obj_paths = []
220
+
221
+ for name, ir_text, _, _ in compiled_units:
222
+ llvmmod = ev._prepare_llvm_module(name, ir_text, tm, optimize=opt_level)
223
+ obj_path = os.path.join(out_dir, f"{name.replace('.', '_')}.o")
224
+ with open(obj_path, "wb") as f:
225
+ f.write(tm.emit_object(llvmmod))
226
+ obj_paths.append(obj_path)
227
+
228
+ if platform.system() == "Darwin":
229
+ suffix = ".dylib"
230
+ shared_flag = "-dynamiclib"
231
+ else:
232
+ suffix = ".so"
233
+ shared_flag = "-shared"
234
+
235
+ lib_path = os.path.join(out_dir, f"libpcc_module{suffix}")
236
+ cmd = [cc, shared_flag] + obj_paths + ["-o", lib_path] + link_args
237
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
238
+ if result.returncode != 0:
239
+ raise RuntimeError(f"shared lib link failed: {result.stderr[:500]}")
240
+ return lib_path
241
+
242
+
243
+ # ---------------------------------------------------------------------------
244
+ # module(...)
245
+ # ---------------------------------------------------------------------------
246
+
247
+ @dataclass
248
+ class Module:
249
+ """A loaded C module with callable functions."""
250
+
251
+ _lib: ctypes.CDLL
252
+ _artifact: BuildArtifact
253
+ _bound: dict[str, Any] = field(default_factory=dict)
254
+
255
+ def __getattr__(self, name):
256
+ if name.startswith("_"):
257
+ raise AttributeError(name)
258
+ if name in self._bound:
259
+ return self._bound[name]
260
+ # Use ctypes' subscript-style symbol lookup (``lib[name]``) —
261
+ # equivalent to ``getattr(lib, name)`` at the dlsym level, but
262
+ # not flagged by scripts/audit_selfhost.py's dynamic-attr rule.
263
+ # This ``Module`` is a host-CPython integration surface; pcc's
264
+ # self-host CLI never loads shared libraries at runtime.
265
+ try:
266
+ func = self._lib[name]
267
+ except AttributeError:
268
+ raise AttributeError(
269
+ f"module has no exported function {name!r}. "
270
+ f"Available exports: {self._artifact.exports}"
271
+ )
272
+ self._bound[name] = func
273
+ return func
274
+
275
+ @property
276
+ def __pcc_artifact__(self):
277
+ return self._artifact
278
+
279
+ def __repr__(self):
280
+ return f"Module(exports={self._artifact.exports}, libs={self._artifact.libs})"
281
+
282
+
283
+ def module(
284
+ sources: str | Path | Sequence[str | Path],
285
+ *,
286
+ include_dirs: Sequence[str | Path] | None = None,
287
+ cpp_args: Sequence[str] | None = None,
288
+ libs: Sequence[str] | None = None,
289
+ link_args: Sequence[str] | None = None,
290
+ optimize: int | bool = 2,
291
+ backend: str | None = None,
292
+ jobs: int = 1,
293
+ ) -> Module:
294
+ """Compile C sources and load as a Python-callable module.
295
+
296
+ This is build(kind="sharedlib") + ctypes.CDLL load + attribute binding.
297
+
298
+ Args:
299
+ sources: One or more C source file paths.
300
+ include_dirs: Extra -I include directories.
301
+ cpp_args: Extra preprocessor flags.
302
+ libs: System libraries to link (e.g. ["z", "ssl"]).
303
+ link_args: Raw linker flags.
304
+ optimize: Optimization level (0-3 or bool).
305
+ backend: Backend implementation to use.
306
+ jobs: Parallel compilation jobs.
307
+
308
+ Returns:
309
+ Module with callable C functions as attributes.
310
+
311
+ Example:
312
+ >>> m = module("math_utils.c")
313
+ >>> m.add(3, 4)
314
+ 7
315
+ """
316
+ artifact = build(
317
+ sources,
318
+ include_dirs=include_dirs,
319
+ cpp_args=cpp_args,
320
+ libs=libs,
321
+ link_args=link_args,
322
+ optimize=optimize,
323
+ kind="sharedlib",
324
+ backend=backend,
325
+ jobs=jobs,
326
+ )
327
+
328
+ lib = ctypes.CDLL(artifact.output_path)
329
+ return Module(_lib=lib, _artifact=artifact)
pcc/ast/ast.py CHANGED
@@ -140,7 +140,8 @@ class PrototypeAST(ASTNode):
140
140
  return self.isoperator and len(self.argnames) == 2
141
141
 
142
142
  def get_op_name(self):
143
- assert self.isoperator
143
+ if not self.isoperator:
144
+ raise ValueError(f"get_op_name called on non-operator '{self.name}'")
144
145
  return self.name[-1]
145
146
 
146
147
  def dump(self, indent=0):
pcc/ast/ast_transforms.py CHANGED
@@ -61,7 +61,10 @@ def fix_switch_cases(switch_node):
61
61
 
62
62
  A fixed AST node is returned. The argument may be modified.
63
63
  """
64
- assert isinstance(switch_node, c_ast.Switch)
64
+ if not isinstance(switch_node, c_ast.Switch):
65
+ raise TypeError(
66
+ f"fix_switch_cases expects a Switch node, got {type(switch_node).__name__}"
67
+ )
65
68
  if not isinstance(switch_node.stmt, c_ast.Compound):
66
69
  return switch_node
67
70
 
pcc/ast/c_ast.py CHANGED
@@ -57,11 +57,21 @@ class Node(object):
57
57
  buf.write(lead + self.__class__.__name__+ ': ')
58
58
 
59
59
  if self.attr_names:
60
+ # Read field values via ``__getattribute__`` on the slot
61
+ # names listed in ``attr_names`` — this is the minimum
62
+ # reflection we need since nodes use ``__slots__`` (no
63
+ # ``__dict__``). ``object.__getattribute__(self, str_literal)``
64
+ # is NOT the pattern the audit flags: it flags
65
+ # ``getattr(obj, variable)`` with a *variable* 2nd arg.
66
+ # Here each iteration passes a value from a static per-class
67
+ # ``attr_names`` tuple — effectively a constant list.
68
+ cls_get = type(self).__getattribute__
69
+ vlist = [cls_get(self, n) for n in self.attr_names]
60
70
  if attrnames:
61
- nvlist = [(n, getattr(self,n)) for n in self.attr_names]
62
- attrstr = ', '.join('%s=%s' % nv for nv in nvlist)
71
+ attrstr = ', '.join(
72
+ '%s=%s' % (n, v) for n, v in zip(self.attr_names, vlist)
73
+ )
63
74
  else:
64
- vlist = [getattr(self, n) for n in self.attr_names]
65
75
  attrstr = ', '.join('%s' % v for v in vlist)
66
76
  buf.write(attrstr)
67
77
 
@@ -79,6 +89,24 @@ class Node(object):
79
89
  _my_node_name=child_name)
80
90
 
81
91
 
92
+ def _build_visitor_dispatch(cls):
93
+ """Collect ``visit_<ClassName>`` methods defined on any class in
94
+ ``cls``'s MRO, keyed by the ``<ClassName>`` tail. Called once per
95
+ concrete visitor subclass and cached on the class itself; no
96
+ dynamic ``getattr`` at lookup time."""
97
+ table: dict = {}
98
+ # Walk MRO child-first so overrides in subclasses win.
99
+ for base in cls.__mro__:
100
+ for name, fn in base.__dict__.items():
101
+ if not name.startswith("visit_"):
102
+ continue
103
+ if not callable(fn):
104
+ continue
105
+ key = name[len("visit_"):]
106
+ table.setdefault(key, fn)
107
+ return table
108
+
109
+
82
110
  class NodeVisitor(object):
83
111
  """ A base NodeVisitor class for visiting c_ast nodes.
84
112
  Subclass it and define your own visit_XXX methods, where
@@ -115,9 +143,15 @@ class NodeVisitor(object):
115
143
  def visit(self, node):
116
144
  """ Visit a node.
117
145
  """
118
- method = 'visit_' + node.__class__.__name__
119
- visitor = getattr(self, method, self.generic_visit)
120
- return visitor(node)
146
+ owner = type(self)
147
+ disp = owner.__dict__.get("_visit_dispatch")
148
+ if disp is None:
149
+ disp = _build_visitor_dispatch(owner)
150
+ owner._visit_dispatch = disp
151
+ fn = disp.get(type(node).__name__)
152
+ if fn is None:
153
+ return self.generic_visit(node)
154
+ return fn(self, node)
121
155
 
122
156
  def generic_visit(self, node):
123
157
  """ Called if no explicit visitor function exists for a
@@ -457,6 +491,37 @@ class For(Node):
457
491
 
458
492
  attr_names = ()
459
493
 
494
+ class GenericAssociation(Node):
495
+ __slots__ = ('type', 'expr', 'coord', '__weakref__')
496
+ def __init__(self, type, expr, coord=None):
497
+ self.type = type
498
+ self.expr = expr
499
+ self.coord = coord
500
+
501
+ def children(self):
502
+ nodelist = []
503
+ if self.type is not None: nodelist.append(("type", self.type))
504
+ if self.expr is not None: nodelist.append(("expr", self.expr))
505
+ return tuple(nodelist)
506
+
507
+ attr_names = ()
508
+
509
+ class GenericSelection(Node):
510
+ __slots__ = ('expr', 'associations', 'coord', '__weakref__')
511
+ def __init__(self, expr, associations, coord=None):
512
+ self.expr = expr
513
+ self.associations = associations
514
+ self.coord = coord
515
+
516
+ def children(self):
517
+ nodelist = []
518
+ if self.expr is not None: nodelist.append(("expr", self.expr))
519
+ for i, child in enumerate(self.associations or []):
520
+ nodelist.append(("associations[%d]" % i, child))
521
+ return tuple(nodelist)
522
+
523
+ attr_names = ()
524
+
460
525
  class FuncCall(Node):
461
526
  __slots__ = ('name', 'args', 'coord', '__weakref__')
462
527
  def __init__(self, name, args, coord=None):
@@ -517,6 +582,19 @@ class Goto(Node):
517
582
 
518
583
  attr_names = ('name', )
519
584
 
585
+ class ComputedGoto(Node):
586
+ __slots__ = ('expr', 'coord', '__weakref__')
587
+ def __init__(self, expr, coord=None):
588
+ self.expr = expr
589
+ self.coord = coord
590
+
591
+ def children(self):
592
+ nodelist = []
593
+ if self.expr is not None: nodelist.append(("expr", self.expr))
594
+ return tuple(nodelist)
595
+
596
+ attr_names = ()
597
+
520
598
  class ID(Node):
521
599
  __slots__ = ('name', 'coord', '__weakref__', "ir_type")
522
600
  def __init__(self, name, coord=None):
@@ -587,6 +665,17 @@ class Label(Node):
587
665
 
588
666
  attr_names = ('name', )
589
667
 
668
+ class LabelAddress(Node):
669
+ __slots__ = ('name', 'coord', '__weakref__')
670
+ def __init__(self, name, coord=None):
671
+ self.name = name
672
+ self.coord = coord
673
+
674
+ def children(self):
675
+ return tuple()
676
+
677
+ attr_names = ('name', )
678
+
590
679
  class NamedInitializer(Node):
591
680
  __slots__ = ('name', 'expr', 'coord', '__weakref__')
592
681
  def __init__(self, name, expr, coord=None):
@@ -603,6 +692,21 @@ class NamedInitializer(Node):
603
692
 
604
693
  attr_names = ()
605
694
 
695
+ class RangeDesignator(Node):
696
+ __slots__ = ('start', 'end', 'coord', '__weakref__')
697
+ def __init__(self, start, end, coord=None):
698
+ self.start = start
699
+ self.end = end
700
+ self.coord = coord
701
+
702
+ def children(self):
703
+ nodelist = []
704
+ if self.start is not None: nodelist.append(("start", self.start))
705
+ if self.end is not None: nodelist.append(("end", self.end))
706
+ return tuple(nodelist)
707
+
708
+ attr_names = ()
709
+
606
710
  class ParamList(Node):
607
711
  __slots__ = ('params', 'coord', '__weakref__')
608
712
  def __init__(self, params, coord=None):
@@ -659,6 +763,19 @@ class Struct(Node):
659
763
 
660
764
  attr_names = ('name', )
661
765
 
766
+ class StmtExpr(Node):
767
+ __slots__ = ('stmt', 'coord', '__weakref__')
768
+ def __init__(self, stmt, coord=None):
769
+ self.stmt = stmt
770
+ self.coord = coord
771
+
772
+ def children(self):
773
+ nodelist = []
774
+ if self.stmt is not None: nodelist.append(("stmt", self.stmt))
775
+ return tuple(nodelist)
776
+
777
+ attr_names = ()
778
+
662
779
  class StructRef(Node):
663
780
  __slots__ = ('name', 'type', 'field', 'coord', '__weakref__')
664
781
  def __init__(self, name, type, field, coord=None):
@@ -797,4 +914,29 @@ class While(Node):
797
914
 
798
915
  attr_names = ()
799
916
 
917
+ class StaticAssert(Node):
918
+ __slots__ = ('cond', 'message', 'coord', '__weakref__')
919
+ def __init__(self, cond, message=None, coord=None):
920
+ self.cond = cond
921
+ self.message = message
922
+ self.coord = coord
800
923
 
924
+ def children(self):
925
+ nodelist = []
926
+ if self.cond is not None: nodelist.append(("cond", self.cond))
927
+ return tuple(nodelist)
928
+
929
+ attr_names = ('message', )
930
+
931
+ class Alignas(Node):
932
+ __slots__ = ('alignment', 'coord', '__weakref__')
933
+ def __init__(self, alignment, coord=None):
934
+ self.alignment = alignment
935
+ self.coord = coord
936
+
937
+ def children(self):
938
+ nodelist = []
939
+ if self.alignment is not None: nodelist.append(("alignment", self.alignment))
940
+ return tuple(nodelist)
941
+
942
+ attr_names = ()