Skip to content

Commit 0237c87

Browse files
committed
variable pointers
1 parent 4df716c commit 0237c87

18 files changed

+6176
-1817
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,6 @@ vgcore.*
1313
_pointers.cpython*
1414
*.egg-info/
1515
wheelhouse/
16-
gen.py
16+
gen.py
17+
ext/
18+
*.o

docs/using_pointers.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,8 @@ However, this **does not** change the original value. To do that, see the sectio
162162
Movement is somewhat complicated. In low level languages with pointers, you can use dereferencing assignment, like so:
163163

164164
```c
165-
int* a = &1;
165+
int b = 1;
166+
int* a = &b;
166167
*a = 2;
167168
```
168169

@@ -190,6 +191,16 @@ While pointers.py does its best to try and prevent segmentation faults, data mov
190191

191192
In fact, unless you are familiar with the CPython internal machinery, I wouldn't touch movement at all.
192193

194+
### For C/C++ developers
195+
196+
Data movement would be like the following C code:
197+
198+
```c
199+
int* ptr = &1; // lets pretend this is allowed (which it is in python)
200+
*ptr = 2;
201+
assert(1 == 2);
202+
```
203+
193204
### Bypassing size limits
194205

195206
An important safety feature of movement is making sure that you can't move an object larger than the underlying value.

docs/variable_pointers.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Variable Pointers
2+
3+
## What's the difference?
4+
5+
An object pointer (created by `to_ptr` or `Pointer.make_from`) points to the actual Python object (`PyObject*`), while a variable pointer points to that actual variable.
6+
7+
```py
8+
hello = "123"
9+
ptr = to_ptr(hello)
10+
# ptr now points to "123", not "hello"
11+
```
12+
13+
## Creation
14+
15+
You can make a variable pointer through `to_var_ptr`:
16+
17+
```py
18+
from pointers import to_var_ptr
19+
20+
a = "hi"
21+
ptr = to_var_ptr(a)
22+
```
23+
24+
Note that of course you can't use a literal, because that isn't a variable:
25+
26+
```py
27+
from pointers import to_var_ptr
28+
29+
ptr = to_var_ptr(123) # ValueError
30+
```
31+
32+
## Movement
33+
34+
Moving is much different when it comes to variable pointers. With an object pointer, you are overwriting that actual value, while with variable pointers you are just changing the value of that variable.
35+
36+
You can use movement the same way you would with an object pointer:
37+
38+
```py
39+
from pointers import to_var_ptr
40+
41+
my_var = 1
42+
my_ptr = to_var_ptr(my_var)
43+
my_ptr <<= 2
44+
print(my_var, 1)
45+
# outputs 2 1, since we didnt overwrite 1 like we would with object pointers
46+
```
47+
48+
You are free to use movement however you like with variable pointers. It isn't dangerous, unlike its counterpart.
49+
50+
### For C/C++ developers
51+
52+
Movement in variable pointers is equivalent to the following C code:
53+
54+
```c
55+
int my_var = 1; // "my_var = 1"
56+
int my_ptr = &my_var; // "my_ptr = to_var_ptr(my_var)"
57+
*my_ptr = 2; // "my_ptr <<= 2"
58+
// my_var is now 2 (the actual 1 is unchanged, thankfully)
59+
```
60+
61+
## Assignment
62+
63+
Assignment works the same way as it does with object pointers:
64+
65+
```py
66+
from pointers import to_var_ptr, NULL
67+
68+
hello = "world"
69+
foo = "bar"
70+
71+
ptr = to_var_ptr(hello)
72+
print(*ptr) # world
73+
ptr >>= NULL
74+
# ...
75+
ptr >>= foo
76+
print(*ptr) # bar
77+
```

hatch.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[version]
2-
path = "src/pointers/version.py"
2+
path = "src/pointers/__init__.py"
33

44
[envs.default]
55
dependencies = [
@@ -19,4 +19,8 @@ build = "mkdocs build --clean"
1919
serve = "mkdocs serve --dev-addr localhost:8000"
2020

2121
[build.targets.wheel]
22-
packages = ["src/pointers", "_pointers"]
22+
packages = ["src/pointers"]
23+
include = ["src/pointers/*.py", "src/pointers/_pointers.pyi"]
24+
25+
[build.hooks.custom]
26+
enable-by-default = true

hatch_build.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import os
2+
import shutil
3+
import sysconfig
4+
from distutils.extension import Extension
5+
from glob import glob
6+
7+
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
8+
from hatchling.plugin import hookimpl
9+
from setuptools._distutils.ccompiler import new_compiler
10+
11+
ext_modules = [
12+
Extension(
13+
"_pointers",
14+
include_dirs=["<header-file-directory>"],
15+
sources=glob("src/_pointers/*"),
16+
),
17+
]
18+
19+
20+
class CustomBuildHook(BuildHookInterface):
21+
PLUGIN_NAME = "custom"
22+
23+
def initialize(self, version: str, data: dict):
24+
self.clean()
25+
compiler = new_compiler()
26+
ext = os.path.join(self.root, "ext")
27+
lib = os.path.join(ext, "./ext/lib")
28+
29+
compiler.add_include_dir(sysconfig.get_path("include"))
30+
compiler.define_macro("PY_SSIZE_T_CLEAN")
31+
32+
self.app.display_waiting("compiling _pointers")
33+
34+
try:
35+
compiler.compile(
36+
glob("./src/mod.c"), output_dir=ext, extra_preargs=["-fPIC"]
37+
)
38+
except Exception:
39+
self.app.abort("failed to compile _pointers")
40+
41+
self.app.display_success("successfully compiled _pointers")
42+
self.app.display_waiting("linking _pointers")
43+
44+
files = []
45+
46+
for root, _, fls in os.walk(ext):
47+
for i in fls:
48+
if i.endswith(".o"):
49+
files.append(os.path.join(root, i))
50+
51+
try:
52+
compiler.link_shared_lib(files, "_pointers", output_dir=lib)
53+
except Exception:
54+
self.app.abort("failed to link _pointers")
55+
56+
self.app.display_success("successfully linked _pointers")
57+
data["force_include"][
58+
os.path.join(lib, "lib_pointers.so")
59+
] = "src/_pointers.so"
60+
data["infer_tag"] = True
61+
data["pure_python"] = False
62+
63+
def clean(self, *_):
64+
path = os.path.join(self.root, "ext")
65+
if os.path.exists(path):
66+
shutil.rmtree(path)
67+
68+
69+
@hookimpl
70+
def hatch_register_build_hook():
71+
return CustomBuildHook

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ nav:
44
- Home: index.md
55
- Getting Started: getting_started.md
66
- Using Pointers: using_pointers.md
7+
- Variable Pointers: variable_pointers.md
78
- Allocation: allocation.md
89
- C Bindings: bindings.md
910
- CPython ABI Bindings: cpython_bindings.md

pyproject.toml

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[build-system]
2-
requires = ["setuptools"]
3-
build-backend = "setuptools.build_meta"
2+
requires = ["hatchling", "setuptools"]
3+
build-backend = "hatchling.build"
44

55
[project]
66
name = "pointers.py"
@@ -23,9 +23,17 @@ classifiers = [
2323
dependencies = [
2424
"typing_extensions",
2525
]
26-
version = "2.5.0"
26+
dynamic = ["version"]
2727

2828
[project.urls]
2929
Documentation = "https://pointers.zintensity.dev"
3030
Issues = "https://github.com/ZeroIntensity/pointers.py/issues"
3131
Source = "https://github.com/ZeroIntensity/pointers.py"
32+
33+
[tool.ward]
34+
path = ["tests"]
35+
capture-output = true
36+
order = "standard"
37+
test-output-style = "test-per-line"
38+
fail-limit = 5
39+
progress-style = ["bar"]

setup.py

Lines changed: 0 additions & 18 deletions
This file was deleted.

src/_pointers.pyi

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
1+
from types import FrameType
12
from typing import Any, Callable, Type, TypeVar
23

34
_T = TypeVar("_T")
45
_A = TypeVar("_A")
56

6-
def add_ref(obj: Any) -> None: ...
7-
def remove_ref(obj: Any) -> None: ...
8-
def force_set_attr(typ: type[Any], key: str, value: Any) -> None: ...
9-
def set_ref(obj: Any, count: int) -> None: ...
7+
def add_ref(__obj: Any) -> None: ...
8+
def remove_ref(__obj: Any) -> None: ...
9+
def force_set_attr(__typ: type[Any], __key: str, __value: Any) -> None: ...
10+
def set_ref(__obj: Any, __count: int) -> None: ...
1011
def handle(
11-
func: Callable[..., _T],
12-
args: tuple[Any, ...] | None = None,
13-
kwargs: dict[str, Any] | None = None,
12+
__func: Callable[..., _T],
13+
__args: tuple[Any, ...] | None = None,
14+
__kwargs: dict[str, Any] | None = None,
1415
) -> _T: ...
15-
def run_stack_callback(size: int, ptr: Type[_T], func: Callable[[_T], _A]) -> _A: ...
16+
def run_stack_callback(
17+
__size: int, __ptr: Type[_T], __func: Callable[[_T], _A]
18+
) -> _A: ...
19+
def force_update_locals(__f: FrameType, __key: str, __value: Any) -> None: ...

src/mod.c

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,13 +141,41 @@ static PyObject* run_stack_callback(PyObject* self, PyObject* args) {
141141
return result;
142142
}
143143

144+
static PyObject* force_update_locals(PyObject* self, PyObject* args) {
145+
PyFrameObject* f;
146+
PyObject* key;
147+
PyObject* value;
148+
149+
if (!PyArg_ParseTuple(args, "O!UO", &PyFrame_Type, &f, &key, &value))
150+
return NULL;
151+
152+
Py_INCREF(f->f_locals);
153+
PyObject* target = PyDict_GetItem(f->f_locals, key);
154+
Py_INCREF(target);
155+
156+
for (int i = 0; i < PyDict_GET_SIZE(f->f_locals); i++) {
157+
if (Py_Is(f->f_localsplus[i], target)) {
158+
Py_DECREF(f->f_localsplus[i]);
159+
f->f_localsplus[i] = value;
160+
Py_INCREF(value);
161+
break;
162+
}
163+
}
164+
165+
Py_DECREF(f->f_locals);
166+
Py_DECREF(target);
167+
168+
Py_RETURN_NONE;
169+
}
170+
144171
static PyMethodDef methods[] = {
145172
{"add_ref", add_ref, METH_VARARGS, "Increment the reference count on the target object."},
146173
{"remove_ref", remove_ref, METH_VARARGS, "Decrement the reference count on the target object."},
147174
{"force_set_attr", force_set_attr, METH_VARARGS, "Force setting an attribute on the target type."},
148175
{"set_ref", set_ref, METH_VARARGS, "Set the reference count on the target object."},
149176
{"handle", handle, METH_VARARGS, "Enable the SIGSEGV handler."},
150-
{"run_stack_callback", run_stack_callback, METH_VARARGS, "."},
177+
{"run_stack_callback", run_stack_callback, METH_VARARGS, "Run a callback with a stack allocated pointer."},
178+
{"force_update_locals", force_update_locals, METH_VARARGS, "Force update the locals of the target frame."},
151179
{NULL, NULL, 0, NULL}
152180
};
153181

0 commit comments

Comments
 (0)