tinycwrap 0.0.6__tar.gz → 0.0.7__tar.gz
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.
- {tinycwrap-0.0.6 → tinycwrap-0.0.7}/PKG-INFO +1 -1
- {tinycwrap-0.0.6 → tinycwrap-0.0.7}/pyproject.toml +1 -1
- {tinycwrap-0.0.6 → tinycwrap-0.0.7}/tests/test_kernels.py +7 -0
- {tinycwrap-0.0.6 → tinycwrap-0.0.7}/tinycwrap/__init__.py +1 -1
- {tinycwrap-0.0.6 → tinycwrap-0.0.7}/tinycwrap/cmodule.py +128 -7
- {tinycwrap-0.0.6 → tinycwrap-0.0.7}/tinycwrap/parsing.py +4 -1
- {tinycwrap-0.0.6 → tinycwrap-0.0.7}/tinycwrap.egg-info/PKG-INFO +1 -1
- {tinycwrap-0.0.6 → tinycwrap-0.0.7}/README.md +0 -0
- {tinycwrap-0.0.6 → tinycwrap-0.0.7}/setup.cfg +0 -0
- {tinycwrap-0.0.6 → tinycwrap-0.0.7}/tests/test_geom.py +0 -0
- {tinycwrap-0.0.6 → tinycwrap-0.0.7}/tests/test_path.py +0 -0
- {tinycwrap-0.0.6 → tinycwrap-0.0.7}/tinycwrap.egg-info/SOURCES.txt +0 -0
- {tinycwrap-0.0.6 → tinycwrap-0.0.7}/tinycwrap.egg-info/dependency_links.txt +0 -0
- {tinycwrap-0.0.6 → tinycwrap-0.0.7}/tinycwrap.egg-info/requires.txt +0 -0
- {tinycwrap-0.0.6 → tinycwrap-0.0.7}/tinycwrap.egg-info/top_level.txt +0 -0
|
@@ -88,6 +88,13 @@ def test_merge_sorted(cm):
|
|
|
88
88
|
assert out_len_val == 4
|
|
89
89
|
|
|
90
90
|
|
|
91
|
+
def test_owned_array(cm):
|
|
92
|
+
arr, n = cm.alloc_random_array()
|
|
93
|
+
assert arr.shape[0] == n
|
|
94
|
+
assert n >= 3
|
|
95
|
+
assert np.all(arr[:n] >= 1.0)
|
|
96
|
+
|
|
97
|
+
|
|
91
98
|
def test_struct_output_array(cm):
|
|
92
99
|
n = 4
|
|
93
100
|
particles = cm.Particle.zeros(n)
|
|
@@ -11,6 +11,7 @@ from cffi import FFI
|
|
|
11
11
|
from .parsing import (
|
|
12
12
|
FuncSpec,
|
|
13
13
|
StructSpec,
|
|
14
|
+
base_type_from_ctype,
|
|
14
15
|
numpy_dtype_for_base_type,
|
|
15
16
|
parse_functions_from_cdef,
|
|
16
17
|
parse_structs_from_cdef,
|
|
@@ -163,6 +164,8 @@ class CModule:
|
|
|
163
164
|
|
|
164
165
|
# Re-parse cdef and attach docs from C source, then create wrappers
|
|
165
166
|
self._func_specs = parse_functions_from_cdef(self._cdef)
|
|
167
|
+
# remove helper prototypes we add for ownership
|
|
168
|
+
self._func_specs.pop("free", None)
|
|
166
169
|
self._struct_specs = parse_structs_from_cdef(self._cdef)
|
|
167
170
|
self._attach_docs_from_source()
|
|
168
171
|
self._mark_length_params_from_contracts()
|
|
@@ -287,6 +290,21 @@ class CModule:
|
|
|
287
290
|
src_wo_pp = re.sub(r"__declspec\s*\([^)]+\)", "", src_wo_pp)
|
|
288
291
|
src_wo_pp = re.sub(r"__asm__\s*\([^)]*\)", "", src_wo_pp)
|
|
289
292
|
|
|
293
|
+
# Keep a version with only top-level text (brace depth 0) to avoid
|
|
294
|
+
# picking up statements inside function bodies.
|
|
295
|
+
top_level_chars = []
|
|
296
|
+
depth = 0
|
|
297
|
+
for ch in src_wo_pp:
|
|
298
|
+
if ch == "{":
|
|
299
|
+
depth += 1
|
|
300
|
+
if depth == 0:
|
|
301
|
+
top_level_chars.append(ch)
|
|
302
|
+
if ch == "}" and depth > 0:
|
|
303
|
+
depth -= 1
|
|
304
|
+
if depth == 0:
|
|
305
|
+
top_level_chars.append("\n")
|
|
306
|
+
top_level_src = "".join(top_level_chars)
|
|
307
|
+
|
|
290
308
|
# regex for function definitions
|
|
291
309
|
func_def_re = re.compile(
|
|
292
310
|
r"""
|
|
@@ -311,11 +329,15 @@ class CModule:
|
|
|
311
329
|
r"typedef\s+struct\s+(?:\w+\s*)?{(?P<body>[^}]*)}\s*(?P<name>\w+)\s*;",
|
|
312
330
|
re.DOTALL,
|
|
313
331
|
)
|
|
332
|
+
seen_structs = set()
|
|
314
333
|
for m in struct_re.finditer(src_wo_pp):
|
|
315
|
-
struct_text = m.group(0)
|
|
316
|
-
|
|
334
|
+
struct_text = m.group(0).strip()
|
|
335
|
+
if struct_text not in seen_structs:
|
|
336
|
+
struct_defs.append(struct_text)
|
|
337
|
+
seen_structs.add(struct_text)
|
|
317
338
|
|
|
318
339
|
proto_set = set()
|
|
340
|
+
proto_name_set = set()
|
|
319
341
|
for m in func_def_re.finditer(src_wo_pp):
|
|
320
342
|
if m.group("prefix") and "static" in m.group("prefix"):
|
|
321
343
|
# ignore static functions, not exported
|
|
@@ -339,12 +361,46 @@ class CModule:
|
|
|
339
361
|
continue
|
|
340
362
|
args = " ".join(strip_restrict_keywords(m.group("args")).split())
|
|
341
363
|
proto = f"{ret} {name}({args});"
|
|
342
|
-
if
|
|
364
|
+
if name not in proto_name_set:
|
|
365
|
+
prototypes.append(proto)
|
|
366
|
+
proto_set.add(proto)
|
|
367
|
+
proto_name_set.add(name)
|
|
368
|
+
|
|
369
|
+
# also pick up function declarations (without body), e.g., from headers
|
|
370
|
+
decl_re = re.compile(r"([A-Za-z_][A-Za-z0-9_\s\*]*?)\s+([A-Za-z_]\w*)\s*\(([^)]*)\)\s*;")
|
|
371
|
+
for m in decl_re.finditer(top_level_src):
|
|
372
|
+
if "return" in m.group(0) or "=" in m.group(0):
|
|
373
|
+
continue
|
|
374
|
+
ret = " ".join(strip_restrict_keywords(m.group(1)).split())
|
|
375
|
+
if "static" in ret.split():
|
|
376
|
+
continue
|
|
377
|
+
name = m.group(2)
|
|
378
|
+
args = " ".join(strip_restrict_keywords(m.group(3)).split())
|
|
379
|
+
proto = f"{ret} {name}({args});"
|
|
380
|
+
if name not in proto_name_set:
|
|
343
381
|
prototypes.append(proto)
|
|
344
382
|
proto_set.add(proto)
|
|
383
|
+
proto_name_set.add(name)
|
|
345
384
|
|
|
346
385
|
if not prototypes and not struct_defs:
|
|
347
386
|
raise RuntimeError(f"No functions found in {self._c_path} to generate cdef")
|
|
387
|
+
# fallback: if we missed pointer-returning prototypes present as lines
|
|
388
|
+
type_prefix_re = re.compile(r"^(void|int|float|double|long|unsigned|signed|struct|char)\b")
|
|
389
|
+
for line in top_level_src.splitlines():
|
|
390
|
+
line_clean = line.strip()
|
|
391
|
+
if not line_clean or line_clean.startswith("typedef") or ":" in line_clean:
|
|
392
|
+
continue
|
|
393
|
+
if not type_prefix_re.match(line_clean):
|
|
394
|
+
continue
|
|
395
|
+
if "=" in line_clean:
|
|
396
|
+
continue
|
|
397
|
+
if "(" in line_clean and ")" in line_clean and line_clean.endswith(";"):
|
|
398
|
+
name_match = re.match(r"[A-Za-z_][A-Za-z0-9_\s\*]*?\s+([A-Za-z_]\w*)\s*\(", line_clean)
|
|
399
|
+
fname = name_match.group(1) if name_match else line_clean
|
|
400
|
+
if fname not in proto_name_set:
|
|
401
|
+
prototypes.append(line_clean)
|
|
402
|
+
proto_set.add(line_clean)
|
|
403
|
+
proto_name_set.add(fname)
|
|
348
404
|
|
|
349
405
|
cdef_parts = []
|
|
350
406
|
if struct_defs:
|
|
@@ -357,6 +413,8 @@ class CModule:
|
|
|
357
413
|
continue
|
|
358
414
|
cdef_lines.append(line)
|
|
359
415
|
cdef = "\n".join(cdef_lines)
|
|
416
|
+
if "free(" not in cdef:
|
|
417
|
+
cdef += "\nvoid free(void *);"
|
|
360
418
|
show = self._verbose if verbose is None else verbose
|
|
361
419
|
if show:
|
|
362
420
|
print("[CModule] Auto-generated cdef:\n" + cdef)
|
|
@@ -380,9 +438,16 @@ class CModule:
|
|
|
380
438
|
doc = re.sub(r"\n\s+", "\n", doc)
|
|
381
439
|
fspec.doc = doc
|
|
382
440
|
contracts = []
|
|
441
|
+
owns: list[str] = []
|
|
383
442
|
for line in doc.splitlines():
|
|
384
443
|
m_contract = re.search(r"(post-)?contract:\s*(.*)", line, flags=re.IGNORECASE)
|
|
385
444
|
if not m_contract:
|
|
445
|
+
m_own = re.search(r"own:\s*(.*)", line, flags=re.IGNORECASE)
|
|
446
|
+
if m_own:
|
|
447
|
+
for name in m_own.group(1).split(","):
|
|
448
|
+
n = name.strip()
|
|
449
|
+
if n:
|
|
450
|
+
owns.append(n)
|
|
386
451
|
continue
|
|
387
452
|
is_post = m_contract.group(1) is not None
|
|
388
453
|
after = m_contract.group(2)
|
|
@@ -398,6 +463,7 @@ class CModule:
|
|
|
398
463
|
expr = mlen.group(2).strip()
|
|
399
464
|
contracts.append((target, expr, is_post))
|
|
400
465
|
fspec.contracts = contracts or None
|
|
466
|
+
fspec.owns = owns or None
|
|
401
467
|
|
|
402
468
|
def _mark_length_params_from_contracts(self):
|
|
403
469
|
"""Mark length-like parameters based solely on explicit contracts."""
|
|
@@ -640,6 +706,7 @@ class CModule:
|
|
|
640
706
|
"_self": self,
|
|
641
707
|
"np": np,
|
|
642
708
|
"numpy_dtype_for_base_type": numpy_dtype_for_base_type,
|
|
709
|
+
"base_type_from_ctype": base_type_from_ctype,
|
|
643
710
|
"_struct_classes": self._struct_classes,
|
|
644
711
|
"_struct_dtypes": self._struct_dtypes,
|
|
645
712
|
}
|
|
@@ -665,7 +732,6 @@ class CModule:
|
|
|
665
732
|
Build the Python source string for a wrapper with an explicit signature.
|
|
666
733
|
Keeping this separate allows inspection/debugging of the generated code.
|
|
667
734
|
"""
|
|
668
|
-
src_parts: list[str] = []
|
|
669
735
|
func_text = self._function_source(fspec.name)
|
|
670
736
|
if func_text:
|
|
671
737
|
c_source_text = func_text
|
|
@@ -973,10 +1039,14 @@ class CModule:
|
|
|
973
1039
|
|
|
974
1040
|
ret_type = fspec.return_ctype.strip()
|
|
975
1041
|
call_expr = f"cfun({', '.join(arg_call_args)})"
|
|
976
|
-
|
|
977
|
-
|
|
1042
|
+
own_return = fspec.owns and "return" in fspec.owns and fspec.return_ctype.strip().endswith("*")
|
|
1043
|
+
if own_return:
|
|
1044
|
+
lines.append(f" res_ptr = {call_expr}")
|
|
978
1045
|
else:
|
|
979
|
-
|
|
1046
|
+
if ret_type == "void":
|
|
1047
|
+
lines.append(f" {call_expr}")
|
|
1048
|
+
else:
|
|
1049
|
+
lines.append(f" res = {call_expr}")
|
|
980
1050
|
|
|
981
1051
|
for name, arr_var in pointer_scalar_outputs:
|
|
982
1052
|
lines.append(f" scalar_{name} = int(np.asarray({arr_var}).ravel()[0])")
|
|
@@ -1001,6 +1071,57 @@ class CModule:
|
|
|
1001
1071
|
else:
|
|
1002
1072
|
output_vars_final.append(ov)
|
|
1003
1073
|
|
|
1074
|
+
own_return_len_expr = None
|
|
1075
|
+
if own_return and contract_map.get("return"):
|
|
1076
|
+
own_return_len_expr = _expr_py(contract_map["return"])
|
|
1077
|
+
|
|
1078
|
+
if own_return:
|
|
1079
|
+
base_ret = base_type_from_ctype(fspec.return_ctype)
|
|
1080
|
+
ret_dtype_expr = f"np.dtype('{np.dtype(numpy_dtype_for_base_type(base_ret)).name}')"
|
|
1081
|
+
lines.append(" lib_free = None")
|
|
1082
|
+
lines.append(" try:")
|
|
1083
|
+
lines.append(" lib_free = _self._lib.free")
|
|
1084
|
+
lines.append(" except AttributeError:")
|
|
1085
|
+
lines.append(" pass")
|
|
1086
|
+
lines.append(" libc_free = None")
|
|
1087
|
+
lines.append(" for _cand in (None, 'libc.so.6', 'libc.so', 'libc.dylib'):")
|
|
1088
|
+
lines.append(" if libc_free is not None:")
|
|
1089
|
+
lines.append(" break")
|
|
1090
|
+
lines.append(" try:")
|
|
1091
|
+
lines.append(" _libc = _self._ffi.dlopen(_cand)")
|
|
1092
|
+
lines.append(" libc_free = getattr(_libc, 'free', None)")
|
|
1093
|
+
lines.append(" except OSError:")
|
|
1094
|
+
lines.append(" continue")
|
|
1095
|
+
lines.append(" free_fn = lib_free or libc_free")
|
|
1096
|
+
lines.append(f" if free_fn is None: raise RuntimeError('{fspec.name}: cannot locate free() for owned return')")
|
|
1097
|
+
if own_return_len_expr is None:
|
|
1098
|
+
lines.append(f" raise ValueError('{fspec.name}: Own return requires len(return)=... Contract')")
|
|
1099
|
+
else:
|
|
1100
|
+
for name, arr_var in pointer_scalar_outputs:
|
|
1101
|
+
if f"scalar_{name}" not in [v for v in output_vars_final]:
|
|
1102
|
+
lines.append(f" scalar_{name} = int(np.asarray({arr_var}).ravel()[0])")
|
|
1103
|
+
# allow len(return)=out_len style expressions that use pointer-scalar values
|
|
1104
|
+
lines.append(f" res_buf = _self._ffi.gc(res_ptr, free_fn)")
|
|
1105
|
+
lines.append(
|
|
1106
|
+
f" arr_ret = np.frombuffer(_self._ffi.buffer(res_buf, int({own_return_len_expr}) * np.dtype({ret_dtype_expr}).itemsize), dtype={ret_dtype_expr})"
|
|
1107
|
+
)
|
|
1108
|
+
ret_parts: list[str] = ["arr_ret"]
|
|
1109
|
+
if output_vars_final:
|
|
1110
|
+
for v in output_vars_final:
|
|
1111
|
+
if v not in ret_parts:
|
|
1112
|
+
ret_parts.append(v)
|
|
1113
|
+
# append any scalar lengths not already in ret_parts
|
|
1114
|
+
for name, _arr in pointer_scalar_outputs:
|
|
1115
|
+
sval = f"scalar_{name}"
|
|
1116
|
+
if sval not in ret_parts:
|
|
1117
|
+
ret_parts.append(sval)
|
|
1118
|
+
if len(ret_parts) == 1:
|
|
1119
|
+
ret_expr = ret_parts[0]
|
|
1120
|
+
else:
|
|
1121
|
+
ret_expr = "(" + ", ".join(ret_parts) + ")"
|
|
1122
|
+
lines.append(f" return {ret_expr}")
|
|
1123
|
+
return "\n".join(lines)
|
|
1124
|
+
|
|
1004
1125
|
if output_vars_final:
|
|
1005
1126
|
if ret_type == "void":
|
|
1006
1127
|
if len(output_vars_final) == 1:
|
|
@@ -72,6 +72,7 @@ class FuncSpec:
|
|
|
72
72
|
args: list # list[ArgSpec]
|
|
73
73
|
doc: str | None = None
|
|
74
74
|
contracts: list[tuple[str, str, bool]] | None = None
|
|
75
|
+
owns: list[str] | None = None
|
|
75
76
|
|
|
76
77
|
|
|
77
78
|
@dataclass
|
|
@@ -151,7 +152,9 @@ def _parse_functions_with_pycparser(cdef: str) -> dict[str, FuncSpec]:
|
|
|
151
152
|
for ext in ast.ext:
|
|
152
153
|
if isinstance(ext, c_ast.Decl) and isinstance(ext.type, c_ast.FuncDecl):
|
|
153
154
|
fname = ext.name
|
|
154
|
-
ret_ctype,
|
|
155
|
+
ret_ctype, ret_is_ptr, _, _ = _ctype_from_decl(ext.type.type)
|
|
156
|
+
if ret_is_ptr:
|
|
157
|
+
ret_ctype = ret_ctype + " *"
|
|
155
158
|
argspecs: list[ArgSpec] = []
|
|
156
159
|
args = ext.type.args
|
|
157
160
|
if args and args.params:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|