Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
P
pyodide
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Boxiang Sun
pyodide
Commits
e2ab1ee9
Commit
e2ab1ee9
authored
Jul 09, 2018
by
Michael Droettboom
Committed by
GitHub
Jul 09, 2018
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #68 from iodide-project/typed-arrays
Convert typedarrays to/from buffers/memoryviews
parents
9c37f4df
737602b5
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
321 additions
and
68 deletions
+321
-68
docs/type_conversions.md
docs/type_conversions.md
+25
-9
src/hiwire.c
src/hiwire.c
+63
-6
src/hiwire.h
src/hiwire.h
+43
-1
src/js2python.c
src/js2python.c
+4
-12
src/jsproxy.c
src/jsproxy.c
+135
-37
test/test_numpy.py
test/test_numpy.py
+23
-0
test/test_python.py
test/test_python.py
+28
-3
No files found.
docs/type_conversions.md
View file @
e2ab1ee9
...
...
@@ -26,15 +26,31 @@ Python. The values are copied and any connection to the original object is lost.
|
`list`
,
`tuple`
|
`Array`
|
|
`dict`
|
`Object`
|
Additionally, Python
`bytes`
and
`buffer`
objects are converted to/from Javascript
`Uint8ClampedArray`
typed arrays. In this case, however, the underlying data is
not copied, and is shared between the Python and Javascript sides. This makes
passing raw memory between the languages (which in practice can be quite large)
very efficient.
Aside: This is the technology on which matplotlib images are passed to
Javascript to render in a canvas, and will be the basis of sharing Numpy arrays
with n-dimensional array data structures in Javascript.
## Typed arrays
Javascript typed arrays (Int8Array and friends) are converted to Python
`memoryviews`
. This happens with a single binary memory copy (since Python can't
access arrays on the Javascript heap), and the data type is preserved. This
makes it easy to correctly convert it to a Numpy array using
`numpy.asarray`
:
```
javascript
array
=
Float32Array
([
1
,
2
,
3
])
```
```
python
from
js
import
array
import
numpy
as
np
numpy_array
=
np
.
asarray
(
array
)
```
Python
`bytes`
and
`buffer`
objects are converted to Javascript as
`Uint8ClampedArray`
s, without any memory copy at all, and is thus very
efficient, but be aware that any changes to the buffer will be reflected in both
places.
Numpy arrays are currently converted to Javascript as nested (regular) Arrays. A
more efficient method will probably emerge as we decide on an ndarray
implementation for Javascript.
## Class instances
...
...
src/hiwire.c
View file @
e2ab1ee9
...
...
@@ -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,57 @@ 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
);
// clang-format off
return
(
jsobj
[
'
byteLength
'
]
!==
undefined
)
?
1
:
0
;
// clang-format on
});
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
;
});
src/hiwire.h
View file @
e2ab1ee9
...
...
@@ -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 */
src/js2python.c
View file @
e2ab1ee9
...
...
@@ -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
);
}
...
...
src/jsproxy.c
View file @
e2ab1ee9
...
...
@@ -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
;
}
...
...
test/test_numpy.py
View file @
e2ab1ee9
...
...
@@ -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]'
)
test/test_python.py
View file @
e2ab1ee9
...
...
@@ -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
\
n
window.title = 'Foo'
\
n
window.title"
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment