python-cc 0.0.8__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 (211) hide show
  1. pcc/__init__.py +3 -0
  2. pcc/api.py +329 -0
  3. pcc/ast/c_ast.py +67 -6
  4. pcc/backend/__init__.py +165 -0
  5. pcc/backend/self_backend.py +1843 -0
  6. pcc/codegen/c_codegen.py +1852 -202
  7. pcc/evaluater/c_evaluator.py +1115 -88
  8. pcc/extern/__init__.py +145 -0
  9. pcc/generator/c_generator.py +60 -2
  10. pcc/ir_passes/__init__.py +252 -0
  11. pcc/ir_passes/adce.py +410 -0
  12. pcc/ir_passes/alias_analysis.py +171 -0
  13. pcc/ir_passes/alignment_from_assumptions.py +229 -0
  14. pcc/ir_passes/arg_opt.py +306 -0
  15. pcc/ir_passes/argpromotion.py +282 -0
  16. pcc/ir_passes/bdce.py +120 -0
  17. pcc/ir_passes/called_value_prop.py +166 -0
  18. pcc/ir_passes/callsite_splitting.py +336 -0
  19. pcc/ir_passes/constant_lattice.py +241 -0
  20. pcc/ir_passes/constraint_elimination.py +296 -0
  21. pcc/ir_passes/correlated_propagation.py +241 -0
  22. pcc/ir_passes/dce.py +227 -0
  23. pcc/ir_passes/dominator_tree.py +401 -0
  24. pcc/ir_passes/dse.py +340 -0
  25. pcc/ir_passes/early_cse.py +281 -0
  26. pcc/ir_passes/elim_avail_extern.py +140 -0
  27. pcc/ir_passes/float2int.py +289 -0
  28. pcc/ir_passes/function_attrs.py +498 -0
  29. pcc/ir_passes/globalopt.py +545 -0
  30. pcc/ir_passes/gvn.py +474 -0
  31. pcc/ir_passes/indvars.py +545 -0
  32. pcc/ir_passes/infer_alignment.py +207 -0
  33. pcc/ir_passes/inline.py +675 -0
  34. pcc/ir_passes/instcombine.py +2112 -0
  35. pcc/ir_passes/instsimplify.py +455 -0
  36. pcc/ir_passes/ipo_passes.py +109 -0
  37. pcc/ir_passes/ipsccp.py +296 -0
  38. pcc/ir_passes/ir_mutator.py +622 -0
  39. pcc/ir_passes/jump_threading.py +383 -0
  40. pcc/ir_passes/late_scalar.py +199 -0
  41. pcc/ir_passes/libcalls_shrinkwrap.py +46 -0
  42. pcc/ir_passes/licm.py +654 -0
  43. pcc/ir_passes/loop_deletion.py +325 -0
  44. pcc/ir_passes/loop_distribute.py +266 -0
  45. pcc/ir_passes/loop_info.py +185 -0
  46. pcc/ir_passes/loop_instsimplify.py +171 -0
  47. pcc/ir_passes/loop_load_elim.py +130 -0
  48. pcc/ir_passes/loop_passes.py +50 -0
  49. pcc/ir_passes/loop_rotate.py +232 -0
  50. pcc/ir_passes/loop_simplify.py +284 -0
  51. pcc/ir_passes/loop_simplifycfg.py +169 -0
  52. pcc/ir_passes/loop_sink.py +203 -0
  53. pcc/ir_passes/loop_unroll.py +1616 -0
  54. pcc/ir_passes/loop_vectorize.py +313 -0
  55. pcc/ir_passes/lower_constant_intrinsics.py +113 -0
  56. pcc/ir_passes/lower_expect.py +136 -0
  57. pcc/ir_passes/manager.py +308 -0
  58. pcc/ir_passes/mem2reg.py +739 -0
  59. pcc/ir_passes/memcpyopt.py +222 -0
  60. pcc/ir_passes/memory_ssa.py +174 -0
  61. pcc/ir_passes/meta_passes.py +266 -0
  62. pcc/ir_passes/mldst_motion.py +326 -0
  63. pcc/ir_passes/newgvn.py +163 -0
  64. pcc/ir_passes/parity.py +487 -0
  65. pcc/ir_passes/reassociate.py +304 -0
  66. pcc/ir_passes/sccp.py +220 -0
  67. pcc/ir_passes/simple_loop_unswitch.py +261 -0
  68. pcc/ir_passes/simplifycfg.py +1576 -0
  69. pcc/ir_passes/slp_vectorizer.py +221 -0
  70. pcc/ir_passes/speculative_execution.py +246 -0
  71. pcc/ir_passes/sroa.py +496 -0
  72. pcc/ir_passes/ssa_utils.py +213 -0
  73. pcc/ir_passes/tailcallelim.py +310 -0
  74. pcc/ir_passes/trivial_simplify.py +193 -0
  75. pcc/ir_passes/vector_combine.py +110 -0
  76. pcc/ir_passes/vectorize_passes.py +28 -0
  77. pcc/lex/c_lexer.py +29 -6
  78. pcc/llvm_capi/__init__.py +227 -0
  79. pcc/llvm_capi/binding.py +609 -0
  80. pcc/llvm_capi/compat.py +104 -0
  81. pcc/llvm_capi/ir.py +1569 -0
  82. pcc/parse/__init__.py +26 -0
  83. pcc/parse/ast_normalize.py +106 -0
  84. pcc/parse/c_lex.py +563 -0
  85. pcc/parse/c_parse_driver.py +241 -0
  86. pcc/parse/c_parser.py +64 -17
  87. pcc/parse/c_parser_actions.py +1247 -0
  88. pcc/parse/c_parsetab.py +1079 -0
  89. pcc/parse/file_parser.py +2 -1
  90. pcc/parse/py_lex.py +341 -0
  91. pcc/parse/py_lift.py +650 -0
  92. pcc/parse/py_parse.py +1542 -0
  93. pcc/passes/__init__.py +84 -0
  94. pcc/passes/alloc_decision.py +95 -0
  95. pcc/passes/ast_utils.py +552 -0
  96. pcc/passes/base.py +470 -0
  97. pcc/passes/canonicalize.py +420 -0
  98. pcc/passes/chez_transforms.py +477 -0
  99. pcc/passes/clang_compat.py +191 -0
  100. pcc/passes/context.py +333 -0
  101. pcc/passes/control_flow.py +353 -0
  102. pcc/passes/dce.py +319 -0
  103. pcc/passes/escape_analysis.py +227 -0
  104. pcc/passes/global_dce.py +99 -0
  105. pcc/passes/groups.py +188 -0
  106. pcc/passes/inline_opt.py +314 -0
  107. pcc/passes/ipo_boundary.py +80 -0
  108. pcc/passes/ir_metadata.py +290 -0
  109. pcc/passes/llvm_builtin_registry.py +237 -0
  110. pcc/passes/llvm_explicit.py +1916 -0
  111. pcc/passes/llvm_loop_explicit.py +582 -0
  112. pcc/passes/llvm_python_registry.py +1114 -0
  113. pcc/passes/llvm_text_pipeline.py +325 -0
  114. pcc/passes/loop_opt.py +372 -0
  115. pcc/passes/lower_expect.py +55 -0
  116. pcc/passes/memory_opt.py +583 -0
  117. pcc/passes/nsw_inference.py +173 -0
  118. pcc/passes/propagation.py +710 -0
  119. pcc/passes/ssa_adce.py +339 -0
  120. pcc/passes/ssa_bootstrap.py +53 -0
  121. pcc/passes/ssa_branch_prune.py +94 -0
  122. pcc/passes/ssa_dse.py +261 -0
  123. pcc/passes/ssa_gvn.py +55 -0
  124. pcc/passes/ssa_gvn_rewrite.py +285 -0
  125. pcc/passes/ssa_loop_phi.py +58 -0
  126. pcc/passes/ssa_sccp.py +64 -0
  127. pcc/passes/ssa_sccp_rewrite.py +310 -0
  128. pcc/passes/tbaa.py +129 -0
  129. pcc/passes/whole_program.py +235 -0
  130. pcc/passes/whole_program_pass.py +60 -0
  131. pcc/pcc.py +359 -36
  132. pcc/ply/ctokens.py +2 -3
  133. pcc/preprocessor.py +267 -11
  134. pcc/project.py +62 -14
  135. pcc/py_frontend/__init__.py +5 -0
  136. pcc/py_frontend/codegen/__init__.py +9 -0
  137. pcc/py_frontend/codegen/class_gen.py +1173 -0
  138. pcc/py_frontend/codegen/layer1.py +7920 -0
  139. pcc/py_frontend/codegen/marshal.py +262 -0
  140. pcc/py_frontend/codegen/runtime_abi.py +285 -0
  141. pcc/py_frontend/parser.py +1109 -0
  142. pcc/py_frontend/pipeline.py +599 -0
  143. pcc/py_frontend/py_ast.py +352 -0
  144. pcc/py_frontend/type_infer.py +1151 -0
  145. pcc/py_frontend/types.py +295 -0
  146. pcc/py_runtime/Makefile +66 -0
  147. pcc/py_runtime/include/py_runtime.h +286 -0
  148. pcc/py_runtime/libpy_runtime.a +0 -0
  149. pcc/py_runtime/src/py_class.c +472 -0
  150. pcc/py_runtime/src/py_dict.c +350 -0
  151. pcc/py_runtime/src/py_exc.c +441 -0
  152. pcc/py_runtime/src/py_int.c +1208 -0
  153. pcc/py_runtime/src/py_internal.h +556 -0
  154. pcc/py_runtime/src/py_libpython.c +455 -0
  155. pcc/py_runtime/src/py_list.c +407 -0
  156. pcc/py_runtime/src/py_obj.c +185 -0
  157. pcc/py_runtime/src/py_obj_ops.c +452 -0
  158. pcc/py_runtime/src/py_obj_stubs.c +88 -0
  159. pcc/py_runtime/src/py_print.c +167 -0
  160. pcc/py_runtime/src/py_set.c +201 -0
  161. pcc/py_runtime/src/py_str.c +905 -0
  162. pcc/py_runtime/src/py_tuple.c +77 -0
  163. pcc/py_stdlib/README.md +59 -0
  164. pcc/py_stdlib/__init__.py +11 -0
  165. pcc/py_stdlib/abc.py +21 -0
  166. pcc/py_stdlib/collections.py +133 -0
  167. pcc/py_stdlib/concurrent.py +117 -0
  168. pcc/py_stdlib/contextlib.py +46 -0
  169. pcc/py_stdlib/copy.ll +847 -0
  170. pcc/py_stdlib/copy.py +50 -0
  171. pcc/py_stdlib/ctypes.py +197 -0
  172. pcc/py_stdlib/dataclasses.py +93 -0
  173. pcc/py_stdlib/enum.py +43 -0
  174. pcc/py_stdlib/fcntl.py +31 -0
  175. pcc/py_stdlib/functools.py +70 -0
  176. pcc/py_stdlib/io.py +61 -0
  177. pcc/py_stdlib/itertools.py +82 -0
  178. pcc/py_stdlib/json.py +245 -0
  179. pcc/py_stdlib/logging.py +54 -0
  180. pcc/py_stdlib/math.py +86 -0
  181. pcc/py_stdlib/multiprocessing.py +118 -0
  182. pcc/py_stdlib/operator.py +58 -0
  183. pcc/py_stdlib/os.py +89 -0
  184. pcc/py_stdlib/pathlib.py +71 -0
  185. pcc/py_stdlib/platform.py +36 -0
  186. pcc/py_stdlib/re.py +91 -0
  187. pcc/py_stdlib/shlex.py +53 -0
  188. pcc/py_stdlib/shutil.py +38 -0
  189. pcc/py_stdlib/string.py +12 -0
  190. pcc/py_stdlib/struct.py +61 -0
  191. pcc/py_stdlib/subprocess.py +64 -0
  192. pcc/py_stdlib/sys.py +43 -0
  193. pcc/py_stdlib/tempfile.py +51 -0
  194. pcc/py_stdlib/time.py +45 -0
  195. pcc/py_stdlib/traceback.py +27 -0
  196. pcc/py_stdlib/typing.py +62 -0
  197. pcc/py_stdlib/warnings.py +37 -0
  198. pcc/ssa/__init__.py +77 -0
  199. pcc/ssa/adce.py +122 -0
  200. pcc/ssa/builder.py +4238 -0
  201. pcc/ssa/gvn.py +152 -0
  202. pcc/ssa/ir.py +293 -0
  203. pcc/ssa/loop_phi.py +279 -0
  204. pcc/ssa/sccp.py +418 -0
  205. python_cc-0.1.0.dist-info/METADATA +660 -0
  206. python_cc-0.1.0.dist-info/RECORD +329 -0
  207. python_cc-0.0.8.dist-info/METADATA +0 -429
  208. python_cc-0.0.8.dist-info/RECORD +0 -138
  209. {python_cc-0.0.8.dist-info → python_cc-0.1.0.dist-info}/WHEEL +0 -0
  210. {python_cc-0.0.8.dist-info → python_cc-0.1.0.dist-info}/entry_points.txt +0 -0
  211. {python_cc-0.0.8.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/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
@@ -879,3 +913,30 @@ class While(Node):
879
913
  return tuple(nodelist)
880
914
 
881
915
  attr_names = ()
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
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 = ()
@@ -0,0 +1,165 @@
1
+ from __future__ import annotations
2
+
3
+ """Backend selection primitives for optional back-end execution paths.
4
+
5
+ The project currently defaults to the existing LLVM/llvmlite path. This module
6
+ only introduces the contract used by task A1/A2: explicit backend choice and
7
+ cache-stable backend identity.
8
+ """
9
+
10
+ from dataclasses import dataclass
11
+
12
+ import os
13
+
14
+
15
+ _ENV_BACKEND = "PCC_BACKEND"
16
+ _DEFAULT_BACKEND = "llvm"
17
+
18
+
19
+ class BackendUnavailable(ValueError):
20
+ """Raised when a requested backend is known but not yet implemented."""
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class BackendConfig:
25
+ kind: str
26
+ semver: str
27
+ supported: bool
28
+ capabilities: tuple[str, ...]
29
+ requested: str | None = None
30
+
31
+ def cache_signature(self) -> str:
32
+ return f"{self.kind}:{self.semver}:" + \
33
+ ("support" if self.supported else "unsupported")
34
+
35
+ def capabilities_csv(self) -> str:
36
+ return ",".join(self.capabilities) if self.capabilities else ""
37
+
38
+
39
+ _BACKEND_TABLE = {
40
+ "llvm": {
41
+ "semver": "llvmlite-default",
42
+ "supported": True,
43
+ "capabilities": (
44
+ "llvm-ir",
45
+ "llvm-binding",
46
+ "mcjit",
47
+ "emit-object",
48
+ ),
49
+ },
50
+ "llvm_capi": {
51
+ "semver": "llvm-capi-wip",
52
+ # Placeholder backend in phase A/B: available path is tracked as a
53
+ # placeholder selection in the cache/config surface, but not mandatory for
54
+ # default-path execution.
55
+ "supported": True,
56
+ "capabilities": (
57
+ "llvm-ir",
58
+ "llvm-c",
59
+ "mcjit",
60
+ "emit-object",
61
+ ),
62
+ },
63
+ "self": {
64
+ # Partial asm-first bootstrap path only; execution/object emission still
65
+ # intentionally unsupported.
66
+ "semver": "self-aarch64-asm-v0",
67
+ "supported": False,
68
+ "capabilities": (
69
+ "emit-asm",
70
+ "emit-object",
71
+ "run-native-via-system-cc",
72
+ "aarch64-darwin-mvp",
73
+ ),
74
+ },
75
+ }
76
+
77
+
78
+ def _normalize_backend_name(value: str | None) -> str:
79
+ if not value:
80
+ return _DEFAULT_BACKEND
81
+ candidate = value.strip().lower()
82
+ if not candidate:
83
+ return _DEFAULT_BACKEND
84
+ if candidate == "llvmlite":
85
+ return "llvm"
86
+ if candidate == "llvm-capi":
87
+ return "llvm_capi"
88
+ return candidate
89
+
90
+
91
+ def resolve_backend(
92
+ requested: str | None = None,
93
+ *,
94
+ allow_unimplemented: bool = False,
95
+ ) -> BackendConfig:
96
+ """Resolve and return a concrete backend configuration.
97
+
98
+ Args:
99
+ requested: user-supplied backend name (`llvm`, `llvm_capi`, `self`).
100
+ allow_unimplemented: when True, return config objects even for known-but-
101
+ unimplemented backends such as `self`.
102
+ """
103
+ env_raw = os.environ.get(_ENV_BACKEND)
104
+ kind = _normalize_backend_name(requested if requested is not None else env_raw)
105
+
106
+ if kind not in _BACKEND_TABLE:
107
+ known = ", ".join(sorted(_BACKEND_TABLE))
108
+ raise ValueError(f"unknown backend {kind!r}; expected one of: {known}")
109
+
110
+ info = _BACKEND_TABLE[kind]
111
+ config = BackendConfig(
112
+ kind=kind,
113
+ semver=info["semver"],
114
+ supported=bool(info["supported"]),
115
+ capabilities=tuple(info["capabilities"]),
116
+ requested=None if requested is None else _normalize_backend_name(requested),
117
+ )
118
+
119
+ if not allow_unimplemented and not config.supported:
120
+ raise BackendUnavailable(
121
+ f"backend '{kind}' is selected but not implemented in this build"
122
+ )
123
+ return config
124
+
125
+
126
+ def resolve_backend_or_raise(requested: str | None = None) -> BackendConfig:
127
+ """Resolve backend and raise an explicit error for unsupported kinds."""
128
+ return resolve_backend(requested, allow_unimplemented=False)
129
+
130
+
131
+ def backend_signature(config: BackendConfig | str | None) -> str:
132
+ """Return a cache-safe backend string identity."""
133
+ if isinstance(config, str):
134
+ kind = _normalize_backend_name(config)
135
+ if kind in _BACKEND_TABLE:
136
+ info = _BACKEND_TABLE[kind]
137
+ return BackendConfig(
138
+ kind=kind,
139
+ semver=info["semver"],
140
+ supported=bool(info["supported"]),
141
+ capabilities=tuple(info["capabilities"]),
142
+ ).cache_signature()
143
+ return f"unknown:{kind}"
144
+ if config is None:
145
+ return backend_signature(_DEFAULT_BACKEND)
146
+ return config.cache_signature()
147
+
148
+
149
+ def backend_env_name() -> str:
150
+ return _ENV_BACKEND
151
+
152
+
153
+ def all_backend_names() -> tuple[str, ...]:
154
+ return tuple(sorted(_BACKEND_TABLE))
155
+
156
+
157
+ def backend_capabilities(config: BackendConfig | str | None) -> tuple[str, ...]:
158
+ if isinstance(config, str):
159
+ kind = _normalize_backend_name(config)
160
+ if kind in _BACKEND_TABLE:
161
+ return tuple(_BACKEND_TABLE[kind]["capabilities"])
162
+ return ()
163
+ if config is None:
164
+ return _BACKEND_TABLE[_DEFAULT_BACKEND]["capabilities"]
165
+ return config.capabilities