Commit 5d297233 authored by Michael Droettboom's avatar Michael Droettboom

Convert typedarrays to/from buffers/memoryviews

parent 9c37f4df
......@@ -129,9 +129,9 @@ EM_JS(void, hiwire_set_member_obj, (int idobj, int ididx, int idval), {
});
EM_JS(void, hiwire_delete_member_obj, (int idobj, int ididx), {
var jsobj = Module.hiwire_get_value(idobj);
var jsidx = Module.hiwire_get_value(ididx);
delete jsobj[jsidx];
var jsobj = Module.hiwire_get_value(idobj);
var jsidx = Module.hiwire_get_value(ididx);
delete jsobj[jsidx];
});
EM_JS(void, hiwire_call, (int idfunc, int idargs), {
......@@ -176,9 +176,10 @@ EM_JS(int, hiwire_typeof, (int idobj), {
return Module.hiwire_new_value(typeof Module.hiwire_get_value(idobj));
});
#define MAKE_OPERATOR(name, op) \
EM_JS(int, hiwire_##name, (int ida, int idb), { \
return (Module.hiwire_get_value(ida) op Module.hiwire_get_value(idb)) ? 1 : 0; \
#define MAKE_OPERATOR(name, op) \
EM_JS(int, hiwire_##name, (int ida, int idb), { \
return (Module.hiwire_get_value(ida) op Module.hiwire_get_value(idb)) ? 1 \
: 0; \
});
MAKE_OPERATOR(less_than, <);
......@@ -190,7 +191,9 @@ MAKE_OPERATOR(greater_than_equal, >=);
EM_JS(int, hiwire_next, (int idobj), {
var jsobj = Module.hiwire_get_value(idobj);
// clang-format off
if (jsobj.next === undefined) {
// clang-format on
return -1;
}
......@@ -201,3 +204,55 @@ EM_JS(int, hiwire_nonzero, (int idobj), {
var jsobj = Module.hiwire_get_value(idobj);
return (jsobj != 0) ? 1 : 0;
});
EM_JS(int, hiwire_is_typedarray, (int idobj), {
var jsobj = Module.hiwire_get_value(idobj);
return (jsobj['byteLength'] != = undefined) ? 1 : 0;
});
EM_JS(int, hiwire_get_byteLength, (int idobj), {
var jsobj = Module.hiwire_get_value(idobj);
return jsobj['byteLength'];
});
EM_JS(int, hiwire_copy_to_ptr, (int idobj, int ptr), {
var jsobj = Module.hiwire_get_value(idobj);
Module.HEAPU8.set(new Uint8Array(jsobj.buffer), ptr);
});
EM_JS(int, hiwire_get_dtype, (int idobj), {
var jsobj = Module.hiwire_get_value(idobj);
switch (jsobj.constructor.name) {
case 'Int8Array':
dtype = 1; // INT8_TYPE;
break;
case 'Uint8Array':
dtype = 2; // UINT8_TYPE;
break;
case 'Uint8ClampedArray':
dtype = 3; // UINT8CLAMPED_TYPE;
break;
case 'Int16Array':
dtype = 4; // INT16_TYPE;
break;
case 'Uint16Array':
dtype = 5; // UINT16_TYPE;
break;
case 'Int32Array':
dtype = 6; // INT32_TYPE;
break;
case 'Uint32Array':
dtype = 7; // UINT32_TYPE;
break;
case 'Float32Array':
dtype = 8; // FLOAT32_TYPE;
break;
case 'Float64Array':
dtype = 9; // FLOAT64_TYPE;
break;
default:
dtype = 3; // UINT8CLAMPED_TYPE;
break;
}
return dtype;
});
......@@ -340,6 +340,48 @@ hiwire_next(int idobj);
* Returns 1 if the value is non-zero.
*
*/
int hiwire_nonzero(int idobj);
int
hiwire_nonzero(int idobj);
/**
* Returns 1 if the value is a typedarray.
*/
int
hiwire_is_typedarray(int idobj);
/**
* Returns the value of obj.byteLength.
*
* There is no error checking. Caller must ensure that hiwire_is_typedarray is
* true.
*/
int
hiwire_get_byteLength(int idobj);
/**
* Copies the buffer contents of a given typed array or buffer into the memory
* at ptr.
*/
int
hiwire_copy_to_ptr(int idobj, int ptr);
#define INT8_TYPE 1
#define UINT8_TYPE 2
#define UINT8CLAMPED_TYPE 3
#define INT16_TYPE 4
#define UINT16_TYPE 5
#define INT32_TYPE 6
#define UINT32_TYPE 7
#define FLOAT32_TYPE 8
#define FLOAT64_TYPE 9
/**
* Get a data type identifier for a given typedarray.
*
* It will be one of INT8_TYPE, UINT8_TYPE, UINT8CLAMPED_TYPE, INT16_TYPE,
* UINT16_TYPE, INT32_TYPE, UINT32_TYPE, FLOAT32_TYPE, FLOAT64_TYPE.
*/
int
hiwire_get_dtype(int idobj);
#endif /* HIWIRE_H */
......@@ -49,15 +49,10 @@ _js2python_pyproxy(PyObject* val)
}
int
_js2python_init_bytes(int length)
_js2python_memoryview(int id)
{
return (int)PyBytes_FromStringAndSize(NULL, length);
}
int
_js2python_get_bytes_ptr(PyObject* val)
{
return (int)PyBytes_AsString(val);
PyObject* jsproxy = JsProxy_cnew(id);
return (int)PyMemoryView_FromObject(jsproxy);
}
int
......@@ -88,10 +83,7 @@ EM_JS(int, __js2python, (int id), {
} else if (Module.PyProxy.isPyProxy(value)) {
return __js2python_pyproxy(Module.PyProxy.getPtr(value));
} else if (value['byteLength'] !== undefined) {
var result = __js2python_init_bytes(value['byteLength']);
var ptr = __js2python_get_bytes_ptr(result);
Module.HEAPU8.set(new Uint8Array(value.buffer), ptr);
return result;
return __js2python_memoryview(id);
} else {
return __js2python_jsproxy(id);
}
......
......@@ -16,12 +16,14 @@ JsBoundMethod_cnew(int this_, const char* name);
typedef struct
{
PyObject_HEAD int js;
PyObject* bytes;
} JsProxy;
static void
JsProxy_dealloc(JsProxy* self)
{
hiwire_decref(self->js);
Py_XDECREF(self->bytes);
Py_TYPE(self)->tp_free((PyObject*)self);
}
......@@ -116,17 +118,18 @@ JsProxy_Call(PyObject* o, PyObject* args, PyObject* kwargs)
}
static PyObject*
JsProxy_RichCompare(PyObject *a, PyObject *b, int op) {
JsProxy_RichCompare(PyObject* a, PyObject* b, int op)
{
JsProxy* aproxy = (JsProxy*)a;
if (!JsProxy_Check(b)) {
switch (op) {
case Py_EQ:
Py_RETURN_FALSE;
case Py_NE:
Py_RETURN_TRUE;
default:
return Py_NotImplemented;
case Py_EQ:
Py_RETURN_FALSE;
case Py_NE:
Py_RETURN_TRUE;
default:
return Py_NotImplemented;
}
}
......@@ -134,24 +137,24 @@ JsProxy_RichCompare(PyObject *a, PyObject *b, int op) {
int ida = python2js(a);
int idb = python2js(b);
switch (op) {
case Py_LT:
result = hiwire_less_than(ida, idb);
break;
case Py_LE:
result = hiwire_less_than_equal(ida, idb);
break;
case Py_EQ:
result = hiwire_equal(ida, idb);
break;
case Py_NE:
result = hiwire_not_equal(ida, idb);
break;
case Py_GT:
result = hiwire_greater_than(ida, idb);
break;
case Py_GE:
result = hiwire_greater_than_equal(ida, idb);
break;
case Py_LT:
result = hiwire_less_than(ida, idb);
break;
case Py_LE:
result = hiwire_less_than_equal(ida, idb);
break;
case Py_EQ:
result = hiwire_equal(ida, idb);
break;
case Py_NE:
result = hiwire_not_equal(ida, idb);
break;
case Py_GT:
result = hiwire_greater_than(ida, idb);
break;
case Py_GE:
result = hiwire_greater_than_equal(ida, idb);
break;
}
hiwire_decref(ida);
......@@ -164,14 +167,14 @@ JsProxy_RichCompare(PyObject *a, PyObject *b, int op) {
}
static PyObject*
JsProxy_GetIter(PyObject *o)
JsProxy_GetIter(PyObject* o)
{
Py_INCREF(o);
return o;
}
static PyObject*
JsProxy_IterNext(PyObject *o)
JsProxy_IterNext(PyObject* o)
{
JsProxy* self = (JsProxy*)o;
......@@ -215,7 +218,7 @@ JsProxy_New(PyObject* o, PyObject* args, PyObject* kwargs)
return pyresult;
}
Py_ssize_t
static Py_ssize_t
JsProxy_length(PyObject* o)
{
JsProxy* self = (JsProxy*)o;
......@@ -223,7 +226,7 @@ JsProxy_length(PyObject* o)
return hiwire_get_length(self->js);
}
PyObject*
static PyObject*
JsProxy_subscript(PyObject* o, PyObject* pyidx)
{
JsProxy* self = (JsProxy*)o;
......@@ -236,7 +239,7 @@ JsProxy_subscript(PyObject* o, PyObject* pyidx)
return pyresult;
}
int
static int
JsProxy_ass_subscript(PyObject* o, PyObject* pyidx, PyObject* pyvalue)
{
JsProxy* self = (JsProxy*)o;
......@@ -252,19 +255,112 @@ JsProxy_ass_subscript(PyObject* o, PyObject* pyidx, PyObject* pyvalue)
return 0;
}
static int
JsProxy_GetBuffer(PyObject* o, Py_buffer* view, int flags)
{
JsProxy* self = (JsProxy*)o;
if (!hiwire_is_typedarray(self->js)) {
PyErr_SetString(PyExc_BufferError, "Can not use as buffer");
view->obj = NULL;
return -1;
}
Py_ssize_t byteLength = hiwire_get_byteLength(self->js);
if (self->bytes == NULL) {
self->bytes = PyBytes_FromStringAndSize(NULL, byteLength);
if (self->bytes == NULL) {
return -1;
}
}
void* ptr = PyBytes_AsString(self->bytes);
hiwire_copy_to_ptr(self->js, (int)ptr);
int dtype = hiwire_get_dtype(self->js);
char* format;
Py_ssize_t itemsize;
switch (dtype) {
case INT8_TYPE:
format = "b";
itemsize = 1;
break;
case UINT8_TYPE:
format = "B";
itemsize = 1;
break;
case UINT8CLAMPED_TYPE:
format = "B";
itemsize = 1;
break;
case INT16_TYPE:
format = "h";
itemsize = 2;
break;
case UINT16_TYPE:
format = "H";
itemsize = 2;
break;
case INT32_TYPE:
format = "i";
itemsize = 4;
break;
case UINT32_TYPE:
format = "I";
itemsize = 4;
break;
case FLOAT32_TYPE:
format = "f";
itemsize = 4;
break;
case FLOAT64_TYPE:
format = "d";
itemsize = 8;
break;
default:
format = "B";
itemsize = 1;
break;
}
Py_INCREF(self);
view->buf = ptr;
view->obj = (PyObject*)self;
view->len = byteLength;
view->readonly = 0;
view->itemsize = itemsize;
view->format = format;
view->ndim = 1;
view->shape = NULL;
view->strides = NULL;
view->suboffsets = NULL;
return 0;
}
// clang-format off
static PyMappingMethods JsProxy_MappingMethods = {
JsProxy_length,
JsProxy_subscript,
JsProxy_ass_subscript,
};
// clang-format on
static PyMethodDef JsProxy_Methods[] = { { "new",
(PyCFunction)JsProxy_New,
METH_VARARGS | METH_KEYWORDS,
"Construct a new instance" },
{ NULL } };
static PyBufferProcs JsProxy_BufferProcs = {
JsProxy_GetBuffer,
NULL
};
static PyMethodDef JsProxy_Methods[] = {
{ "new",
(PyCFunction)JsProxy_New,
METH_VARARGS | METH_KEYWORDS,
"Construct a new instance" },
{ NULL }
};
// clang-format on
static PyTypeObject JsProxyType = {
.tp_name = "JsProxy",
......@@ -280,7 +376,8 @@ static PyTypeObject JsProxyType = {
.tp_as_mapping = &JsProxy_MappingMethods,
.tp_iter = JsProxy_GetIter,
.tp_iternext = JsProxy_IterNext,
.tp_repr = JsProxy_Repr
.tp_repr = JsProxy_Repr,
.tp_as_buffer = &JsProxy_BufferProcs
};
PyObject*
......@@ -289,6 +386,7 @@ JsProxy_cnew(int idobj)
JsProxy* self;
self = (JsProxy*)JsProxyType.tp_alloc(&JsProxyType, 0);
self->js = hiwire_incref(idobj);
self->bytes = NULL;
return (PyObject*)self;
}
......
......@@ -6,3 +6,26 @@ def test_numpy(selenium):
assert all(len(y) == 64 for y in x)
for y in x:
assert all(z == 0 for z in y)
def test_typed_arrays(selenium):
selenium.load_package("numpy")
selenium.run("import numpy")
for (jstype, npytype) in (
('Int8Array', 'int8'),
('Uint8Array', 'uint8'),
('Uint8ClampedArray', 'uint8'),
('Int16Array', 'int16'),
('Uint16Array', 'uint16'),
('Int32Array', 'int32'),
('Uint32Array', 'uint32'),
('Float32Array', 'float32'),
('Float64Array', 'float64')):
print(jstype, npytype)
selenium.run_js(
f'window.array = new {jstype}([1, 2, 3, 4]);\n')
assert selenium.run(
'from js import array\n'
'npyarray = numpy.asarray(array)\n'
f'npyarray.dtype.name == "{npytype}" '
'and npyarray == [1, 2, 3, 4]')
......@@ -93,18 +93,43 @@ def test_js2python(selenium):
'jspython is open')
assert selenium.run(
'from js import jsbytes\n'
'jsbytes == b"\x01\x02\x03"')
'(jsbytes.tolist() == [1, 2, 3]) '
'and (jsbytes.tobytes() == b"\x01\x02\x03")')
assert selenium.run(
'from js import jsfloats\n'
'print(jsfloats)\n'
'import struct\n'
'expected = struct.pack("fff", 1, 2, 3)\n'
'jsfloats == expected')
'(jsfloats.tolist() == [1, 2, 3]) '
'and (jsfloats.tobytes() == expected)')
assert selenium.run(
'from js import jsobject\n'
'str(jsobject) == "[object XMLHttpRequest]"')
def test_typed_arrays(selenium):
for (jstype, pytype) in (
('Int8Array', 'b'),
('Uint8Array', 'B'),
('Uint8ClampedArray', 'B'),
('Int16Array', 'h'),
('Uint16Array', 'H'),
('Int32Array', 'i'),
('Uint32Array', 'I'),
('Float32Array', 'f'),
('Float64Array', 'd')):
print(jstype, pytype)
selenium.run_js(
f'window.array = new {jstype}([1, 2, 3, 4]);\n')
assert selenium.run(
'from js import array\n'
'import struct\n'
f'expected = struct.pack("{pytype*4}", 1, 2, 3, 4)\n'
'print(array.format, array.tolist(), array.tobytes())\n'
f'array.format == "{pytype}" '
'and array.tolist() == [1, 2, 3, 4] '
'and array.tobytes() == expected')
def test_import_js(selenium):
result = selenium.run(
"from js import window\nwindow.title = 'Foo'\nwindow.title")
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment