1
1
# Implementation Details
2
2
3
- ### Python Global Thread State
3
+ ## Abstract Operations on Python Objects
4
+
5
+ Many generic operations on Python objects in CPython are defined in the header
6
+ files ` object.h ` and ` abstract.h ` . These operations are widely used and their
7
+ interplay and intricacies are the cause for the conversion, error message, and
8
+ control flow bugs when not mimicked correctly. Our current approach is to
9
+ provide many of these abstract operations as part of the ` PythonObjectLibrary ` .
10
+
11
+ ### Common operations in the PythonObjectLibrary
12
+
13
+ The code has evolved over time, so not all built-in nodes are prime examples of
14
+ messages that should be used from the PythonObjectLibrary. We are refactoring
15
+ this as we go, but here are a few examples for things you can (or should soon be
16
+ able to) use the PythonObjectLibrary for:
17
+
18
+ - casting and coercion to ` java.lang.String ` , array-sized Java ` int ` , Python
19
+ index, fileno, ` double ` , filesystem path, iterator, and more
20
+ - reading the class of an object
21
+ - accessing the ` __dict__ ` attribute of an object
22
+ - hashing objects and testing for equality
23
+ - testing for truthy-ness
24
+ - getting the length
25
+ - testing for abstract types such as ` mapping ` , ` sequence ` , ` callable `
26
+ - invoking methods or executing callables
27
+ - access objects through the buffer protocol
28
+
29
+ ### PythonObjectLibrary functions with and without state
30
+
31
+ Usually, there are at least two messages for each operation - one that takes a
32
+ ` ThreadState ` argument, and one that doesn't. The intent is to allow passing of
33
+ exception state and caller information similar to how we do it with the ` PFrame `
34
+ argument even across library messages, which cannot take a VirtualFrame.
35
+
36
+ All nodes that are used in message implementations must allow uncached
37
+ usage. Often (e.g. in the case of the generic ` CallNode ` ) they offer execute
38
+ methods with and without frames. If a ` ThreadState ` was passed to the message, a
39
+ frame to pass to the node can be reconstructed using
40
+ ` PArguments.frameForCall(threadState) ` . Here's an example:
41
+
42
+ ``` java
43
+ @ExportMessage
44
+ long messageWithState(ThreadState state,
45
+ @Cached CallNode callNode) {
46
+ Object callable = ...
47
+
48
+ if (state != null ) {
49
+ return callNode. execute(PArguments . frameForCall(state), callable, arguments);
50
+ } else {
51
+ return callNode. execute(callable, arguments);
52
+ }
53
+ }
54
+ ```
55
+
56
+ * Note* : It is ** always** preferable to call an ` execute ` method with a
57
+ ` VirtualFrame ` when both one with and without exist! The reason is that this
58
+ avoids materialization of the frame state in more cases, as described on the
59
+ section on Python's global thread state above.
60
+
61
+ ### Other libraries in the codebase
62
+
63
+ Accessing hashing storages (the storage for ` dict ` , ` set ` , and ` frozenset ` )
64
+ should be done via the ` HashingStorageLibrary ` . We are in the process of
65
+ creating a ` SequenceStorageLibrary ` for sequence types (` tuple ` , ` list ` ) to
66
+ replace the ` SequenceStorageNodes ` collection of classes.
67
+
68
+ ## Python Global Thread State
4
69
5
70
In CPython, each stack frame is allocated on the heap, and there's a global
6
71
thread state holding on to the chain of currently handled exceptions (e.g. if
@@ -21,15 +86,15 @@ be forced to the heap.
21
86
In Graal Python, the implementation is thus a bit more involved. Here's how it
22
87
works.
23
88
24
- #### The PFrame.Reference
89
+ ### The PFrame.Reference
25
90
26
91
A ` PFrame.Reference ` is created when entering a Python function. By default it
27
92
only holds on to another reference, that of the Python caller. If there are
28
93
non-Python frames between the newly entered frame and the last Python frame,
29
94
those are ignored - our linked list only connects Python frames. The entry point
30
95
into the interpreter has a ` PFrame.Reference ` with no caller.
31
96
32
- ###### ExecutionContext.CallContext and ExecutionContext.CalleeContext
97
+ #### ExecutionContext.CallContext and ExecutionContext.CalleeContext
33
98
34
99
If we're only calling between Python, we pass our ` PFrame.Reference ` as implicit
35
100
argument to any callees. On entry, they will create their own ` PFrame.Reference `
@@ -60,7 +125,7 @@ ExecutionContext.CalleeContext classes. These also use profiling information to
60
125
eagerly fill in frame information if the callees actually access the stack, for
61
126
example, so that no further stack walks need to take place.
62
127
63
- ###### ExecutionContext.IndirectCallContext and ExecutionContext.IndirectCalleeContext
128
+ #### ExecutionContext.IndirectCallContext and ExecutionContext.IndirectCalleeContext
64
129
65
130
If we're mixing Python frames with non-Python frames, or if we are making calls
66
131
to methods and cannot pass the Truffle frame, we need to store the last
@@ -72,48 +137,10 @@ caller, it initially walks the stack to find it. But it will also tell the last
72
137
Python node that made a call to a "foreign" callee that it will have to store
73
138
its ` PFrame.Reference ` globally in the future for it to be available later.
74
139
75
- #### The current PException
140
+ ### The current PException
76
141
77
142
Now that we have a mechanism to lazily make available only as much frame state
78
143
as needed, we use the same mechanism to also pass the currently handled
79
144
exception. Unlike CPython we do not use a stack of currently handled exceptions,
80
145
instead we utilize the call stack of Java by always passing the current exception
81
146
and holding on to the last (if any) in a local variable.
82
-
83
- ### Abstract Operations on Python Objects
84
-
85
- Many generic operations on Python objects in CPython are defined in the header
86
- files ` abstract.c ` and ` abstract.h ` . These operations are widely used and their
87
- interplay and intricacies are the cause for the conversion, error message, and
88
- control flow bugs when not mimicked correctly. Our current approach is to
89
- provide many of these abstract operations as part of the
90
- ` PythonObjectLibrary ` . Usually, this means there are at least two messages for
91
- each operation - one that takes a ` ThreadState ` argument, and one that
92
- doesn't. The intent is to allow passing of exception state and caller
93
- information similar to how we do it with the ` PFrame ` argument even across
94
- library messages, which cannot take a VirtualFrame.
95
-
96
- All nodes that are used in message implementations must allow uncached
97
- usage. Often (e.g. in the case of the generic ` CallNode ` ) they offer execute
98
- methods with and without frames. If a ` ThreadState ` was passed to the message, a
99
- frame to pass to the node can be reconstructed using
100
- ` PArguments.frameForCall(threadState) ` . Here's an example:
101
-
102
- ``` java
103
- @ExportMessage
104
- long messageWithState(ThreadState state,
105
- @Cached CallNode callNode) {
106
- Object callable = ...
107
-
108
- if (state != null ) {
109
- return callNode. execute(PArguments . frameForCall(state), callable, arguments);
110
- } else {
111
- return callNode. execute(callable, arguments);
112
- }
113
- }
114
- ```
115
-
116
- * Note* : It is ** always** preferable to call an ` execute ` method with a
117
- ` VirtualFrame ` when both one with and without exist! The reason is that this
118
- avoids materialization of the frame state in more cases, as described on the
119
- section on Python's global thread state above.
0 commit comments