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