Skip to content

Commit 84d0a7e

Browse files
authored
Merge branch 'ZeroIntensity:master' into master
2 parents d43bf15 + de81d6d commit 84d0a7e

27 files changed

+1389
-288
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ vgcore.*
1212
.venv/
1313
_pointers.cpython*
1414
*.egg-info/
15-
wheelhouse/
15+
wheelhouse/
16+
gen.py

docs/allocation.md

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,99 @@ for index, ptr in enumerate(array):
9898

9999
print(ptr[1]) # prints out "1"
100100
```
101+
102+
## Stack
103+
104+
Objects can be put on the stack using `stack_alloc` or `acquire_stack_alloc`:
105+
106+
```py
107+
from pointers import stack_alloc, StackAllocatedPointer
108+
109+
@stack_alloc(28)
110+
def test(ptr: StackAllocatedPointer):
111+
ptr <<= 1
112+
print(*ptr)
113+
114+
test()
115+
```
116+
117+
The difference between `acquire_stack_alloc` and `stack_alloc` is that `acquire_stack_alloc` automatically calls the function, whereas `stack_alloc` makes you call it manually:
118+
119+
```py
120+
from pointers import stack_alloc, acquire_stack_alloc, StackAllocatedPointer
121+
122+
@stack_alloc(28)
123+
def a(ptr: StackAllocatedPointer): # you need to call a manually
124+
...
125+
126+
@acquire_stack_alloc(28)
127+
def b(ptr: StackAllocatedPointer): # this is called automatically by the decorator
128+
...
129+
```
130+
131+
Stack allocated pointers **cannot** be deallocated manually, meaning the following **will not** work:
132+
133+
```py
134+
from pointers import acquire_stack_alloc, StackAllocatedPointer, free
135+
136+
@acquire_stack_alloc(28)
137+
def test(ptr: StackAllocatedPointer):
138+
free(ptr) # ValueError is raised
139+
```
140+
141+
Instead, it will be popped off the stack at the end of the function. Read more about that below.
142+
143+
### Why not a context?
144+
145+
**Warning:** This section is for advanced users.
146+
147+
We have to go through a couple different reasons on why we need to use a decorator instead of a context for stack allocations.
148+
149+
First, we need to understand how functions are handled in ASM (at least on GNU compilers).
150+
151+
Lets take a look at this piece of x86 ASM as an example (32 bit):
152+
153+
```asm
154+
global _start
155+
156+
_start:
157+
push 123 ; 123 on stack
158+
call func ; call our function
159+
mov eax, 1 ; system exit
160+
mov ebx, 0 ; return code
161+
int 0x80 ; call kernel
162+
163+
func:
164+
push ebp ; push base pointer onto the stack
165+
mov ebp, esp ; preserve current stack top
166+
167+
mov esp, ebp
168+
pop ebp ; restore base pointer
169+
ret ; jump to return address
170+
```
171+
172+
This function does nothing, but as you can see with the `push` and `pop` instructions, we are using the stack to pass parameters and store the return address of the functions.
173+
174+
When we put something on the top of the stack in Python, we still need to follow these rules. The memory is popped off the stack at the end of the C function (in this case, its pointers.py's `run_stack_callback`), so we cannot use it elsewhere.
175+
176+
Now, how does this relate to using a context or not?
177+
178+
Python context managers are done like this:
179+
180+
```py
181+
class MyContext:
182+
def __enter__(self):
183+
...
184+
185+
def __exit__(self, *_):
186+
...
187+
188+
with MyContext() as x:
189+
...
190+
```
191+
192+
Remember, the data is popped off the stack after the end of the C function, not the Python function, meaning we can't just allocate and then store in the class. We also can't just do it all from `__enter__`, since the allocated memory will be destroyed at the end of it.
193+
194+
We also can't do it from a C based class, since that will still have to return the object.
195+
196+
Note that there's also no yielding in C, so we can't return early either.

docs/bindings.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,40 @@ signal(2, sighandler)
151151
c_raise(2) # send signal 2 to the program
152152
```
153153

154+
Alternatively, you can create a function pointer using `to_func_ptr`:
155+
156+
```py
157+
from pointers import to_func_ptr, signal, exit, c_raise
158+
159+
def test(signum: int) -> None:
160+
print('hello world')
161+
exit(0)
162+
163+
signal(2, to_func_ptr(test))
164+
c_raise(2)
165+
```
166+
167+
**Note:** `to_func_ptr` requires the function to be type hinted in order to correctly generate the signature of the C function, so the following **will not** work:
168+
169+
```py
170+
def test(signum):
171+
...
172+
173+
to_func_ptr(test)
174+
```
175+
176+
## Null Pointers
177+
178+
If you would like to pass a `NULL` pointer to a binding, you can use `pointers.NULL` or pass `None`:
179+
180+
```py
181+
from pointers import time, NULL
182+
183+
# these two do the same thing
184+
print(time(NULL))
185+
print(time(None))
186+
```
187+
154188
## Custom Bindings
155189

156190
You can create your own binding to a C function with `ctypes` and pointers.py.

docs/cpython_bindings.md

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Here's an example with `PyEval_GetFrame`:
2020
```py
2121
from pointers import PyEval
2222

23-
frame = PyEval.get_frame() # calls the c PyEval_GetFrame function
23+
frame = PyEval.get_frame() # calls PyEval_GetFrame
2424
```
2525

2626
## Casting Pointers
@@ -44,11 +44,3 @@ from pointers import PyEval, struct_cast
4444
frame = struct_cast(PyEval.get_frame())
4545
# frame is now a valid frame object!
4646
```
47-
48-
## Limited Support
49-
50-
CPython ABI bindings are still unfinished, and any method that contains one of the following remains unsupported:
51-
52-
- Uses a format string
53-
- Undocumented on [python.org](https://python.org)
54-
- Isn't documented properly

docs/reference.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Reference
22

33
<!-- prettier-ignore -->
4-
::: pointers.constants
4+
::: pointers.util
55
::: pointers.base_pointers
66
::: pointers.c_pointer
77
::: pointers.decay

docs/making_a_program.md renamed to docs/working_by_example.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
# Making a Program
1+
# Working By Example
22

3-
Now that we've learned how to use pointers.py, lets build a small script to make `1 == 2`, and then revert our changes (make `1 != 2`).
3+
Now that we've learned how to use pointers.py, lets build some small scripts to try out some of its features.
4+
5+
## Making one equal two
6+
7+
**Note:** This may not work depending on your build of CPython.
48

59
Lets start out with creating a pointer to `1`, and then moving `2` to it:
610

@@ -85,7 +89,3 @@ ptr <<= ~cache
8589
assert 1 != 2
8690
free(cache)
8791
```
88-
89-
**Note:** This may not work depending on your build of CPython.
90-
91-
Congratulations, you have written a working program with pointers.py!

0 commit comments

Comments
 (0)