9
9
*/
10
10
11
11
#include " include/BufferType.hh"
12
-
12
+ # include " include/PyBytesProxyHandler.hh "
13
13
14
14
#include < jsapi.h>
15
15
#include < js/ArrayBuffer.h>
16
16
#include < js/experimental/TypedData.h>
17
17
#include < js/ScalarType.h>
18
18
19
19
20
+ // JS to Python
21
+
22
+ /* static */
23
+ const char *BufferType::_toPyBufferFormatCode (JS::Scalar::Type subtype) {
24
+ // floating point types
25
+ if (subtype == JS::Scalar::Float32) {
26
+ return " f" ;
27
+ } else if (subtype == JS::Scalar::Float64) {
28
+ return " d" ;
29
+ }
30
+
31
+ // integer types
32
+ bool isSigned = JS::Scalar::isSignedIntType (subtype);
33
+ uint8_t byteSize = JS::Scalar::byteSize (subtype);
34
+ // Python `array` type codes are strictly mapped to basic C types (e.g., `int`), widths may vary on different architectures,
35
+ // but JS TypedArray uses fixed-width integer types (e.g., `uint32_t`)
36
+ switch (byteSize) {
37
+ case sizeof (char ):
38
+ return isSigned ? " b" : " B" ;
39
+ case sizeof (short ):
40
+ return isSigned ? " h" : " H" ;
41
+ case sizeof (int ):
42
+ return isSigned ? " i" : " I" ;
43
+ // case sizeof(long): // compile error: duplicate case value
44
+ // // And this is usually where the bit widths on 32/64-bit systems don't agree,
45
+ // // see https://en.wikipedia.org/wiki/64-bit_computing#64-bit_data_models
46
+ // return isSigned ? "l" : "L";
47
+ case sizeof (long long ):
48
+ return isSigned ? " q" : " Q" ;
49
+ default : // invalid
50
+ return " x" ; // type code for pad bytes, no value
51
+ }
52
+ }
53
+
54
+ /* static */
55
+ bool BufferType::isSupportedJsTypes (JSObject *obj) {
56
+ return JS::IsArrayBufferObject (obj) || JS_IsTypedArrayObject (obj);
57
+ }
58
+
20
59
PyObject *BufferType::getPyObject (JSContext *cx, JS::HandleObject bufObj) {
21
60
PyObject *pyObject;
22
61
if (JS_IsTypedArrayObject (bufObj)) {
@@ -32,11 +71,6 @@ PyObject *BufferType::getPyObject(JSContext *cx, JS::HandleObject bufObj) {
32
71
return pyObject;
33
72
}
34
73
35
- /* static */
36
- bool BufferType::isSupportedJsTypes (JSObject *obj) {
37
- return JS::IsArrayBufferObject (obj) || JS_IsTypedArrayObject (obj);
38
- }
39
-
40
74
/* static */
41
75
PyObject *BufferType::fromJsTypedArray (JSContext *cx, JS::HandleObject typedArray) {
42
76
JS::Scalar::Type subtype = JS_GetArrayBufferViewType (typedArray);
@@ -90,16 +124,31 @@ PyObject *BufferType::fromJsArrayBuffer(JSContext *cx, JS::HandleObject arrayBuf
90
124
return PyMemoryView_FromBuffer (&bufInfo);
91
125
}
92
126
127
+
128
+ // Python to JS
129
+
130
+ static PyBytesProxyHandler pyBytesProxyHandler;
131
+
132
+
93
133
JSObject *BufferType::toJsTypedArray (JSContext *cx, PyObject *pyObject) {
94
- Py_INCREF (pyObject);
134
+ Py_INCREF (pyObject); // TODO remove
95
135
96
136
// Get the pyObject's underlying buffer pointer and size
97
137
Py_buffer *view = new Py_buffer{};
138
+ bool immutable = false ;
98
139
if (PyObject_GetBuffer (pyObject, view, PyBUF_ND | PyBUF_WRITABLE /* C-contiguous and writable */ | PyBUF_FORMAT) < 0 ) {
140
+
99
141
// the buffer is immutable (e.g., Python `bytes` type is read-only)
100
- return nullptr ; // raises a PyExc_BufferError
142
+ PyErr_Clear (); // a PyExc_BufferError was raised
143
+
144
+ if (PyObject_GetBuffer (pyObject, view, PyBUF_ND /* C-contiguous and writable */ | PyBUF_FORMAT) < 0 ) {
145
+ return nullptr ; // and a PyExc_BufferError was raised again
146
+ }
147
+
148
+ immutable = true ;
101
149
}
102
- if (view->ndim != 1 ) {
150
+
151
+ if (view->ndim != 1 && !immutable) {
103
152
PyErr_SetString (PyExc_BufferError, " multidimensional arrays are not allowed" );
104
153
BufferType::_releasePyBuffer (view);
105
154
return nullptr ;
@@ -108,7 +157,7 @@ JSObject *BufferType::toJsTypedArray(JSContext *cx, PyObject *pyObject) {
108
157
// Determine the TypedArray's subtype (Uint8Array, Float64Array, ...)
109
158
JS::Scalar::Type subtype = _getPyBufferType (view);
110
159
111
- JSObject *arrayBuffer = nullptr ;
160
+ JSObject *arrayBuffer;
112
161
if (view->len > 0 ) {
113
162
// Create a new ExternalArrayBuffer object
114
163
// Note: data will be copied instead of transferring the ownership when this external ArrayBuffer is "transferred" to a worker thread.
@@ -117,16 +166,29 @@ JSObject *BufferType::toJsTypedArray(JSContext *cx, PyObject *pyObject) {
117
166
view->buf /* data pointer */ ,
118
167
{BufferType::_releasePyBuffer, view /* the `bufView` argument to `_releasePyBuffer` */ }
119
168
);
169
+
120
170
arrayBuffer = JS::NewExternalArrayBuffer (cx,
121
171
view->len /* byteLength */ , std::move (dataPtr)
122
172
);
123
173
} else { // empty buffer
124
174
arrayBuffer = JS::NewArrayBuffer (cx, 0 );
125
175
BufferType::_releasePyBuffer (view); // the buffer is no longer needed since we are creating a brand new empty ArrayBuffer
126
176
}
127
- JS::RootedObject arrayBufferRooted (cx, arrayBuffer);
128
177
129
- return _newTypedArrayWithBuffer (cx, subtype, arrayBufferRooted);
178
+ if (!immutable) {
179
+ JS::RootedObject arrayBufferRooted (cx, arrayBuffer);
180
+ return _newTypedArrayWithBuffer (cx, subtype, arrayBufferRooted);
181
+ } else {
182
+ JS::RootedValue v (cx);
183
+ JS::RootedObject uint8ArrayPrototype (cx);
184
+ JS_GetClassPrototype (cx, JSProto_Uint8Array, &uint8ArrayPrototype); // so that instanceof will work, not that prototype methods will TEST THIS
185
+ JSObject *proxy = js::NewProxyObject (cx, &pyBytesProxyHandler, v, uint8ArrayPrototype.get ());
186
+ JS::SetReservedSlot (proxy, PyObjectSlot, JS::PrivateValue (pyObject));
187
+ JS::PersistentRootedObject *arrayBufferPointer = new JS::PersistentRootedObject (cx);
188
+ arrayBufferPointer->set (arrayBuffer);
189
+ JS::SetReservedSlot (proxy, OtherSlot, JS::PrivateValue (arrayBufferPointer));
190
+ return proxy;
191
+ }
130
192
}
131
193
132
194
/* static */
@@ -178,38 +240,6 @@ JS::Scalar::Type BufferType::_getPyBufferType(Py_buffer *bufView) {
178
240
}
179
241
}
180
242
181
- /* static */
182
- const char *BufferType::_toPyBufferFormatCode (JS::Scalar::Type subtype) {
183
- // floating point types
184
- if (subtype == JS::Scalar::Float32) {
185
- return " f" ;
186
- } else if (subtype == JS::Scalar::Float64) {
187
- return " d" ;
188
- }
189
-
190
- // integer types
191
- bool isSigned = JS::Scalar::isSignedIntType (subtype);
192
- uint8_t byteSize = JS::Scalar::byteSize (subtype);
193
- // Python `array` type codes are strictly mapped to basic C types (e.g., `int`), widths may vary on different architectures,
194
- // but JS TypedArray uses fixed-width integer types (e.g., `uint32_t`)
195
- switch (byteSize) {
196
- case sizeof (char ):
197
- return isSigned ? " b" : " B" ;
198
- case sizeof (short ):
199
- return isSigned ? " h" : " H" ;
200
- case sizeof (int ):
201
- return isSigned ? " i" : " I" ;
202
- // case sizeof(long): // compile error: duplicate case value
203
- // // And this is usually where the bit widths on 32/64-bit systems don't agree,
204
- // // see https://en.wikipedia.org/wiki/64-bit_computing#64-bit_data_models
205
- // return isSigned ? "l" : "L";
206
- case sizeof (long long ):
207
- return isSigned ? " q" : " Q" ;
208
- default : // invalid
209
- return " x" ; // type code for pad bytes, no value
210
- }
211
- }
212
-
213
243
JSObject *BufferType::_newTypedArrayWithBuffer (JSContext *cx, JS::Scalar::Type subtype, JS::HandleObject arrayBuffer) {
214
244
switch (subtype) {
215
245
#define NEW_TYPED_ARRAY_WITH_BUFFER (ExternalType, NativeType, Name ) \
0 commit comments