99 */
1010
1111#include " include/BufferType.hh"
12-
12+ # include " include/PyBytesProxyHandler.hh "
1313
1414#include < jsapi.h>
1515#include < js/ArrayBuffer.h>
1616#include < js/experimental/TypedData.h>
1717#include < js/ScalarType.h>
1818
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+ }
1962
2063PyObject *BufferType::getPyObject (JSContext *cx, JS::HandleObject bufObj) {
2164 PyObject *pyObject;
@@ -32,11 +75,6 @@ PyObject *BufferType::getPyObject(JSContext *cx, JS::HandleObject bufObj) {
3275 return pyObject;
3376}
3477
35- /* static */
36- bool BufferType::isSupportedJsTypes (JSObject *obj) {
37- return JS::IsArrayBufferObject (obj) || JS_IsTypedArrayObject (obj);
38- }
39-
4078/* static */
4179PyObject *BufferType::fromJsTypedArray (JSContext *cx, JS::HandleObject typedArray) {
4280 JS::Scalar::Type subtype = JS_GetArrayBufferViewType (typedArray);
@@ -90,15 +128,29 @@ PyObject *BufferType::fromJsArrayBuffer(JSContext *cx, JS::HandleObject arrayBuf
90128 return PyMemoryView_FromBuffer (&bufInfo);
91129}
92130
131+
132+ // Python to JS
133+
134+ static PyBytesProxyHandler pyBytesProxyHandler;
135+
136+
93137JSObject *BufferType::toJsTypedArray (JSContext *cx, PyObject *pyObject) {
94138 Py_INCREF (pyObject);
95139
96140 // Get the pyObject's underlying buffer pointer and size
97141 Py_buffer *view = new Py_buffer{};
142+ bool immutable = false ;
98143 if (PyObject_GetBuffer (pyObject, view, PyBUF_ND | PyBUF_WRITABLE /* C-contiguous and writable */ | PyBUF_FORMAT) < 0 ) {
99144 // 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 ;
101152 }
153+
102154 if (view->ndim != 1 ) {
103155 PyErr_SetString (PyExc_BufferError, " multidimensional arrays are not allowed" );
104156 BufferType::_releasePyBuffer (view);
@@ -108,7 +160,7 @@ JSObject *BufferType::toJsTypedArray(JSContext *cx, PyObject *pyObject) {
108160 // Determine the TypedArray's subtype (Uint8Array, Float64Array, ...)
109161 JS::Scalar::Type subtype = _getPyBufferType (view);
110162
111- JSObject *arrayBuffer = nullptr ;
163+ JSObject *arrayBuffer;
112164 if (view->len > 0 ) {
113165 // Create a new ExternalArrayBuffer object
114166 // 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) {
117169 view->buf /* data pointer */ ,
118170 {BufferType::_releasePyBuffer, view /* the `bufView` argument to `_releasePyBuffer` */ }
119171 );
172+
120173 arrayBuffer = JS::NewExternalArrayBuffer (cx,
121174 view->len /* byteLength */ , std::move (dataPtr)
122175 );
123176 } else { // empty buffer
124177 arrayBuffer = JS::NewArrayBuffer (cx, 0 );
125178 BufferType::_releasePyBuffer (view); // the buffer is no longer needed since we are creating a brand new empty ArrayBuffer
126179 }
127- JS::RootedObject arrayBufferRooted (cx, arrayBuffer);
128180
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+ }
130195}
131196
132197/* static */
@@ -155,8 +220,11 @@ JS::Scalar::Type BufferType::_getPyBufferType(Py_buffer *bufView) {
155220 return JS::Scalar::Float32;
156221 } else if (typeCode == ' d' ) {
157222 return JS::Scalar::Float64;
223+ } else if (typeCode == ' e' ) {
224+ return JS::Scalar::Float16;
158225 }
159226
227+
160228 // integer types
161229 // 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
162230 // see https://docs.python.org/3.9/library/array.html#module-array
@@ -178,38 +246,6 @@ JS::Scalar::Type BufferType::_getPyBufferType(Py_buffer *bufView) {
178246 }
179247}
180248
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-
213249JSObject *BufferType::_newTypedArrayWithBuffer (JSContext *cx, JS::Scalar::Type subtype, JS::HandleObject arrayBuffer) {
214250 switch (subtype) {
215251#define NEW_TYPED_ARRAY_WITH_BUFFER (ExternalType, NativeType, Name ) \
0 commit comments