Use Uint32 counter in hiwire (#1016)

Also replaces JsERROR instances with NULL
This commit is contained in:
Hood Chatham 2021-01-04 14:07:40 -08:00 committed by GitHub
parent 9eaf376b96
commit f43fc42f35
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 88 additions and 70 deletions

View File

@ -2,12 +2,6 @@
#include "hiwire.h" #include "hiwire.h"
JsRef
hiwire_error()
{
return Js_ERROR;
}
JsRef JsRef
hiwire_undefined() hiwire_undefined()
{ {
@ -15,7 +9,7 @@ hiwire_undefined()
} }
JsRef JsRef
hiwire_null() hiwire_jsnull()
{ {
return Js_NULL; return Js_NULL;
} }
@ -39,16 +33,28 @@ hiwire_bool(bool boolean)
} }
EM_JS(int, hiwire_init, (), { EM_JS(int, hiwire_init, (), {
let _hiwire = { objects : new Map(), counter : 1 }; let _hiwire = {
objects : new Map(),
// counter is used to allocate keys for the objects map.
// We use even integers to represent singleton constants which we won't
// reference count. We only want to allocate odd keys so we start at 1 and
// step by 2. We use a native uint32 for our counter, so counter
// automatically overflows back to 1 if it ever gets up to the max u32 =
// 2^{31} - 1. This ensures we can keep recycling keys even for very long
// sessions. (Also the native u32 is faster since javascript won't convert
// it to a float.)
// 0 == C NULL is an error code for compatibility with Python calling
// conventions.
counter : new Uint32Array([1])
};
Module.hiwire = {}; Module.hiwire = {};
Module.hiwire.ERROR = _hiwire_error();
Module.hiwire.UNDEFINED = _hiwire_undefined(); Module.hiwire.UNDEFINED = _hiwire_undefined();
Module.hiwire.NULL = _hiwire_null(); Module.hiwire.JSNULL = _hiwire_jsnull();
Module.hiwire.TRUE = _hiwire_true(); Module.hiwire.TRUE = _hiwire_true();
Module.hiwire.FALSE = _hiwire_false(); Module.hiwire.FALSE = _hiwire_false();
_hiwire.objects.set(Module.hiwire.UNDEFINED, undefined); _hiwire.objects.set(Module.hiwire.UNDEFINED, undefined);
_hiwire.objects.set(Module.hiwire.NULL, null); _hiwire.objects.set(Module.hiwire.JSNULL, null);
_hiwire.objects.set(Module.hiwire.TRUE, true); _hiwire.objects.set(Module.hiwire.TRUE, true);
_hiwire.objects.set(Module.hiwire.FALSE, false); _hiwire.objects.set(Module.hiwire.FALSE, false);
@ -58,12 +64,14 @@ EM_JS(int, hiwire_init, (), {
// Probably not worth it for performance: it's harmless to ocassionally // Probably not worth it for performance: it's harmless to ocassionally
// duplicate. Maybe in test builds we could raise if jsval is a standard // duplicate. Maybe in test builds we could raise if jsval is a standard
// value? // value?
while (_hiwire.objects.has(_hiwire.counter)) { while (_hiwire.objects.has(_hiwire.counter[0])) {
_hiwire.counter = (_hiwire.counter + 1) & 0x7fffffff; // Increment by two here (and below) because even integers are reserved
// for singleton constants
_hiwire.counter[0] += 2;
} }
let idval = _hiwire.counter; let idval = _hiwire.counter[0];
_hiwire.objects.set(idval, jsval); _hiwire.objects.set(idval, jsval);
_hiwire.counter = (_hiwire.counter + 1) & 0x7fffffff; _hiwire.counter[0] += 2;
return idval; return idval;
}; };
@ -73,14 +81,20 @@ EM_JS(int, hiwire_init, (), {
throw new Error("Argument to hiwire.get_value is undefined"); throw new Error("Argument to hiwire.get_value is undefined");
} }
if (!_hiwire.objects.has(idval)) { if (!_hiwire.objects.has(idval)) {
// clang-format off
throw new Error(`Undefined id ${ idval }`); throw new Error(`Undefined id ${ idval }`);
// clang-format on
} }
return _hiwire.objects.get(idval); return _hiwire.objects.get(idval);
}; };
Module.hiwire.decref = function(idval) Module.hiwire.decref = function(idval)
{ {
if (idval < 0) { // clang-format off
if ((idval & 1) === 0) {
// least significant bit unset ==> idval is a singleton.
// We don't reference count singletons.
// clang-format on
return; return;
} }
_hiwire.objects.delete(idval); _hiwire.objects.delete(idval);
@ -89,7 +103,11 @@ EM_JS(int, hiwire_init, (), {
}); });
EM_JS(JsRef, hiwire_incref, (JsRef idval), { EM_JS(JsRef, hiwire_incref, (JsRef idval), {
if (idval < 0) { // clang-format off
if ((idval & 1) === 0) {
// least significant bit unset ==> idval is a singleton.
// We don't reference count singletons.
// clang-format on
return; return;
} }
return Module.hiwire.new_value(Module.hiwire.get_value(idval)); return Module.hiwire.new_value(Module.hiwire.get_value(idval));

View File

@ -32,13 +32,13 @@ struct _JsRefStruct
typedef struct _JsRefStruct* JsRef; typedef struct _JsRefStruct* JsRef;
// Define special ids for singleton constants. These must be negative to // Special JsRefs for singleton constants.
// avoid being reused for other values. // (These must be even because the least significance bit is set to 0 for
#define Js_ERROR ((JsRef)(-1)) // singleton constants.)
#define Js_UNDEFINED ((JsRef)(-2)) #define Js_UNDEFINED ((JsRef)(2))
#define Js_TRUE ((JsRef)(-3)) #define Js_TRUE ((JsRef)(4))
#define Js_FALSE ((JsRef)(-4)) #define Js_FALSE ((JsRef)(6))
#define Js_NULL ((JsRef)(-5)) #define Js_NULL ((JsRef)(8))
/** /**
* Initialize the variables and functions required for hiwire. * Initialize the variables and functions required for hiwire.

View File

@ -15,7 +15,7 @@ JsImport_GetAttr(PyObject* self, PyObject* attr)
return NULL; return NULL;
} }
JsRef idval = hiwire_get_global(c); JsRef idval = hiwire_get_global(c);
if (idval == Js_ERROR) { if (idval == NULL) {
PyErr_Format(PyExc_AttributeError, "Unknown attribute '%s'", c); PyErr_Format(PyExc_AttributeError, "Unknown attribute '%s'", c);
return NULL; return NULL;
} }

View File

@ -70,7 +70,7 @@ JsProxy_GetAttr(PyObject* o, PyObject* attr_name)
JsRef idresult = hiwire_get_member_string(self->js, key); JsRef idresult = hiwire_get_member_string(self->js, key);
Py_DECREF(str); Py_DECREF(str);
if (idresult == Js_ERROR) { if (idresult == NULL) {
PyErr_SetString(PyExc_AttributeError, key); PyErr_SetString(PyExc_AttributeError, key);
return NULL; return NULL;
} }
@ -192,7 +192,7 @@ JsProxy_GetIter(PyObject* o)
JsRef iditer = hiwire_get_iterator(self->js); JsRef iditer = hiwire_get_iterator(self->js);
if (iditer == Js_ERROR) { if (iditer == NULL) {
PyErr_SetString(PyExc_TypeError, "Object is not iterable"); PyErr_SetString(PyExc_TypeError, "Object is not iterable");
return NULL; return NULL;
} }
@ -206,7 +206,7 @@ JsProxy_IterNext(PyObject* o)
JsProxy* self = (JsProxy*)o; JsProxy* self = (JsProxy*)o;
JsRef idresult = hiwire_next(self->js); JsRef idresult = hiwire_next(self->js);
if (idresult == Js_ERROR) { if (idresult == NULL) {
return NULL; return NULL;
} }
@ -263,7 +263,7 @@ JsProxy_subscript(PyObject* o, PyObject* pyidx)
JsRef ididx = python2js(pyidx); JsRef ididx = python2js(pyidx);
JsRef idresult = hiwire_get_member_obj(self->js, ididx); JsRef idresult = hiwire_get_member_obj(self->js, ididx);
hiwire_decref(ididx); hiwire_decref(ididx);
if (idresult == Js_ERROR) { if (idresult == NULL) {
PyErr_SetObject(PyExc_KeyError, pyidx); PyErr_SetObject(PyExc_KeyError, pyidx);
return NULL; return NULL;
} }

View File

@ -58,7 +58,7 @@ _pyproxy_set(PyObject* pyobj, JsRef idkey, JsRef idval)
if (result) { if (result) {
pythonexc2js(); pythonexc2js();
return Js_ERROR; return NULL;
} }
return idval; return idval;
} }
@ -78,7 +78,7 @@ _pyproxy_deleteProperty(PyObject* pyobj, JsRef idkey)
if (ret) { if (ret) {
pythonexc2js(); pythonexc2js();
return Js_ERROR; return NULL;
} }
return hiwire_undefined(); return hiwire_undefined();
@ -91,7 +91,7 @@ _pyproxy_ownKeys(PyObject* pyobj)
if (pydir == NULL) { if (pydir == NULL) {
pythonexc2js(); pythonexc2js();
return Js_ERROR; return NULL;
} }
JsRef iddir = hiwire_array(); JsRef iddir = hiwire_array();
@ -128,7 +128,7 @@ _pyproxy_apply(PyObject* pyobj, JsRef idargs)
if (pyresult == NULL) { if (pyresult == NULL) {
Py_DECREF(pyargs); Py_DECREF(pyargs);
pythonexc2js(); pythonexc2js();
return Js_ERROR; return NULL;
} }
JsRef idresult = python2js(pyresult); JsRef idresult = python2js(pyresult);
Py_DECREF(pyresult); Py_DECREF(pyresult);

View File

@ -24,7 +24,7 @@ pythonexc2js()
PyErr_Fetch(&type, &value, &traceback); PyErr_Fetch(&type, &value, &traceback);
PyErr_NormalizeException(&type, &value, &traceback); PyErr_NormalizeException(&type, &value, &traceback);
JsRef excval = Js_ERROR; JsRef excval = NULL;
int exc; int exc;
if (type == NULL || type == Py_None || value == NULL || value == Py_None) { if (type == NULL || type == Py_None || value == NULL || value == Py_None) {
@ -103,7 +103,7 @@ _python2js_float(PyObject* x)
{ {
double x_double = PyFloat_AsDouble(x); double x_double = PyFloat_AsDouble(x);
if (x_double == -1.0 && PyErr_Occurred()) { if (x_double == -1.0 && PyErr_Occurred()) {
return Js_ERROR; return NULL;
} }
return hiwire_double(x_double); return hiwire_double(x_double);
} }
@ -117,11 +117,11 @@ _python2js_long(PyObject* x)
if (overflow) { if (overflow) {
PyObject* py_float = PyNumber_Float(x); PyObject* py_float = PyNumber_Float(x);
if (py_float == NULL) { if (py_float == NULL) {
return Js_ERROR; return NULL;
} }
return _python2js_float(py_float); return _python2js_float(py_float);
} else if (PyErr_Occurred()) { } else if (PyErr_Occurred()) {
return Js_ERROR; return NULL;
} }
} }
return hiwire_int(x_long); return hiwire_int(x_long);
@ -142,7 +142,7 @@ _python2js_unicode(PyObject* x)
return hiwire_string_ucs4(data, length); return hiwire_string_ucs4(data, length);
default: default:
PyErr_SetString(PyExc_ValueError, "Unknown Unicode KIND"); PyErr_SetString(PyExc_ValueError, "Unknown Unicode KIND");
return Js_ERROR; return NULL;
} }
} }
@ -152,7 +152,7 @@ _python2js_bytes(PyObject* x)
char* x_buff; char* x_buff;
Py_ssize_t length; Py_ssize_t length;
if (PyBytes_AsStringAndSize(x, &x_buff, &length)) { if (PyBytes_AsStringAndSize(x, &x_buff, &length)) {
return Js_ERROR; return NULL;
} }
return hiwire_bytes(x_buff, length); return hiwire_bytes(x_buff, length);
} }
@ -163,7 +163,7 @@ _python2js_sequence(PyObject* x, PyObject* map)
JsRef jsarray = hiwire_array(); JsRef jsarray = hiwire_array();
if (_python2js_add_to_cache(map, x, jsarray)) { if (_python2js_add_to_cache(map, x, jsarray)) {
hiwire_decref(jsarray); hiwire_decref(jsarray);
return Js_ERROR; return NULL;
} }
size_t length = PySequence_Size(x); size_t length = PySequence_Size(x);
for (size_t i = 0; i < length; ++i) { for (size_t i = 0; i < length; ++i) {
@ -178,11 +178,11 @@ _python2js_sequence(PyObject* x, PyObject* map)
return pyproxy_new(x); return pyproxy_new(x);
} }
JsRef jsitem = _python2js_cache(pyitem, map); JsRef jsitem = _python2js_cache(pyitem, map);
if (jsitem == Js_ERROR) { if (jsitem == NULL) {
_python2js_remove_from_cache(map, x); _python2js_remove_from_cache(map, x);
Py_DECREF(pyitem); Py_DECREF(pyitem);
hiwire_decref(jsarray); hiwire_decref(jsarray);
return Js_ERROR; return NULL;
} }
Py_DECREF(pyitem); Py_DECREF(pyitem);
hiwire_push_array(jsarray, jsitem); hiwire_push_array(jsarray, jsitem);
@ -190,7 +190,7 @@ _python2js_sequence(PyObject* x, PyObject* map)
} }
if (_python2js_remove_from_cache(map, x)) { if (_python2js_remove_from_cache(map, x)) {
hiwire_decref(jsarray); hiwire_decref(jsarray);
return Js_ERROR; return NULL;
} }
return jsarray; return jsarray;
} }
@ -201,23 +201,23 @@ _python2js_dict(PyObject* x, PyObject* map)
JsRef jsdict = hiwire_object(); JsRef jsdict = hiwire_object();
if (_python2js_add_to_cache(map, x, jsdict)) { if (_python2js_add_to_cache(map, x, jsdict)) {
hiwire_decref(jsdict); hiwire_decref(jsdict);
return Js_ERROR; return NULL;
} }
PyObject *pykey, *pyval; PyObject *pykey, *pyval;
Py_ssize_t pos = 0; Py_ssize_t pos = 0;
while (PyDict_Next(x, &pos, &pykey, &pyval)) { while (PyDict_Next(x, &pos, &pykey, &pyval)) {
JsRef jskey = _python2js_cache(pykey, map); JsRef jskey = _python2js_cache(pykey, map);
if (jskey == Js_ERROR) { if (jskey == NULL) {
_python2js_remove_from_cache(map, x); _python2js_remove_from_cache(map, x);
hiwire_decref(jsdict); hiwire_decref(jsdict);
return Js_ERROR; return NULL;
} }
JsRef jsval = _python2js_cache(pyval, map); JsRef jsval = _python2js_cache(pyval, map);
if (jsval == Js_ERROR) { if (jsval == NULL) {
_python2js_remove_from_cache(map, x); _python2js_remove_from_cache(map, x);
hiwire_decref(jskey); hiwire_decref(jskey);
hiwire_decref(jsdict); hiwire_decref(jsdict);
return Js_ERROR; return NULL;
} }
hiwire_push_object_pair(jsdict, jskey, jsval); hiwire_push_object_pair(jsdict, jskey, jsval);
hiwire_decref(jskey); hiwire_decref(jskey);
@ -225,7 +225,7 @@ _python2js_dict(PyObject* x, PyObject* map)
} }
if (_python2js_remove_from_cache(map, x)) { if (_python2js_remove_from_cache(map, x)) {
hiwire_decref(jsdict); hiwire_decref(jsdict);
return Js_ERROR; return NULL;
} }
return jsdict; return jsdict;
} }
@ -258,7 +258,7 @@ _python2js(PyObject* x, PyObject* map)
} else { } else {
JsRef ret = _python2js_buffer(x); JsRef ret = _python2js_buffer(x);
if (ret != Js_ERROR) { if (ret != NULL) {
return ret; return ret;
} }
if (PySequence_Check(x)) { if (PySequence_Check(x)) {
@ -269,7 +269,7 @@ _python2js(PyObject* x, PyObject* map)
// same object on the Python side is always the same object on the // same object on the Python side is always the same object on the
// Javascript side. // Javascript side.
ret = pyproxy_use(x); ret = pyproxy_use(x);
if (ret != Js_ERROR) { if (ret != NULL) {
return ret; return ret;
} }
@ -322,7 +322,7 @@ _python2js_cache(PyObject* x, PyObject* map)
JsRef result; JsRef result;
if (val) { if (val) {
result = (JsRef)PyLong_AsLong(val); result = (JsRef)PyLong_AsLong(val);
if (result != Js_ERROR) { if (result != NULL) {
result = hiwire_incref(result); result = hiwire_incref(result);
} }
} else { } else {
@ -339,7 +339,7 @@ python2js(PyObject* x)
JsRef result = _python2js_cache(x, map); JsRef result = _python2js_cache(x, map);
Py_DECREF(map); Py_DECREF(map);
if (result == Js_ERROR) { if (result == NULL) {
pythonexc2js(); pythonexc2js();
} }

View File

@ -294,9 +294,9 @@ _python2js_buffer_recursive(Py_buffer* buff,
for (i = 0; i < n; ++i) { for (i = 0; i < n; ++i) {
jsitem = _python2js_buffer_recursive(buff, ptr, dim + 1, convert); jsitem = _python2js_buffer_recursive(buff, ptr, dim + 1, convert);
if (jsitem == Js_ERROR) { if (jsitem == NULL) {
hiwire_decref(jsarray); hiwire_decref(jsarray);
return Js_ERROR; return NULL;
} }
hiwire_push_array(jsarray, jsitem); hiwire_push_array(jsarray, jsitem);
hiwire_decref(jsitem); hiwire_decref(jsitem);
@ -321,7 +321,7 @@ _python2js_buffer_to_typed_array(Py_buffer* buff)
case '>': case '>':
case '!': case '!':
// This path can't handle byte-swapping // This path can't handle byte-swapping
return Js_ERROR; return NULL;
case '=': case '=':
case '<': case '<':
case '@': case '@':
@ -339,7 +339,7 @@ _python2js_buffer_to_typed_array(Py_buffer* buff)
case 'B': case 'B':
return hiwire_uint8array((u8*)buff->buf, buff->len); return hiwire_uint8array((u8*)buff->buf, buff->len);
case '?': case '?':
return Js_ERROR; return NULL;
case 'h': case 'h':
return hiwire_int16array((i16*)buff->buf, buff->len); return hiwire_int16array((i16*)buff->buf, buff->len);
case 'H': case 'H':
@ -354,13 +354,13 @@ _python2js_buffer_to_typed_array(Py_buffer* buff)
return hiwire_uint32array((u32*)buff->buf, buff->len); return hiwire_uint32array((u32*)buff->buf, buff->len);
case 'q': case 'q':
case 'Q': case 'Q':
return Js_ERROR; return NULL;
case 'f': case 'f':
return hiwire_float32array((f32*)buff->buf, buff->len); return hiwire_float32array((f32*)buff->buf, buff->len);
case 'd': case 'd':
return hiwire_float64array((f64*)buff->buf, buff->len); return hiwire_float64array((f64*)buff->buf, buff->len);
default: default:
return Js_ERROR; return NULL;
} }
} }
@ -408,9 +408,9 @@ _python2js_shareable_buffer_recursive(Py_buffer* buff,
for (i = 0; i < n; ++i) { for (i = 0; i < n; ++i) {
jsitem = _python2js_shareable_buffer_recursive( jsitem = _python2js_shareable_buffer_recursive(
buff, shareable, idarr, ptr, dim + 1); buff, shareable, idarr, ptr, dim + 1);
if (jsitem == Js_ERROR) { if (jsitem == NULL) {
hiwire_decref(jsarray); hiwire_decref(jsarray);
return Js_ERROR; return NULL;
} }
hiwire_push_array(jsarray, jsitem); hiwire_push_array(jsarray, jsitem);
hiwire_decref(jsitem); hiwire_decref(jsitem);
@ -457,7 +457,7 @@ _python2js_buffer(PyObject* x)
PyObject* memoryview = PyMemoryView_FromObject(x); PyObject* memoryview = PyMemoryView_FromObject(x);
if (memoryview == NULL) { if (memoryview == NULL) {
PyErr_Clear(); PyErr_Clear();
return Js_ERROR; return NULL;
} }
Py_buffer* buff; Py_buffer* buff;
@ -468,11 +468,11 @@ _python2js_buffer(PyObject* x)
if (shareable != NOT_SHAREABLE) { if (shareable != NOT_SHAREABLE) {
JsRef idarr = _python2js_buffer_to_typed_array(buff); JsRef idarr = _python2js_buffer_to_typed_array(buff);
if (idarr == Js_ERROR) { if (idarr == NULL) {
PyErr_SetString( PyErr_SetString(
PyExc_TypeError, PyExc_TypeError,
"Internal error: Invalid type to convert to array buffer."); "Internal error: Invalid type to convert to array buffer.");
return Js_ERROR; return NULL;
} }
result = result =
@ -481,7 +481,7 @@ _python2js_buffer(PyObject* x)
scalar_converter* convert = _python2js_buffer_get_converter(buff); scalar_converter* convert = _python2js_buffer_get_converter(buff);
if (convert == NULL) { if (convert == NULL) {
Py_DECREF(memoryview); Py_DECREF(memoryview);
return Js_ERROR; return NULL;
} }
result = _python2js_buffer_recursive(buff, buff->buf, 0, convert); result = _python2js_buffer_recursive(buff, buff->buf, 0, convert);

View File

@ -74,8 +74,8 @@ int
runpython_init() runpython_init()
{ {
bool success = false; bool success = false;
JsRef pyodide_py_proxy = Js_ERROR; JsRef pyodide_py_proxy = NULL;
JsRef globals_proxy = Js_ERROR; JsRef globals_proxy = NULL;
// borrowed // borrowed
PyObject* builtins = PyImport_AddModule("builtins"); PyObject* builtins = PyImport_AddModule("builtins");
@ -101,7 +101,7 @@ runpython_init()
QUIT_IF_NULL(pyodide_py); QUIT_IF_NULL(pyodide_py);
pyodide_py_proxy = python2js(pyodide_py); pyodide_py_proxy = python2js(pyodide_py);
if (pyodide_py_proxy == Js_ERROR) { if (pyodide_py_proxy == NULL) {
goto fail; goto fail;
} }
// Currently by default, python2js copies dicts into objects. // Currently by default, python2js copies dicts into objects.
@ -114,7 +114,7 @@ runpython_init()
// modifications. // modifications.
Py_INCREF(globals); // pyproxy_new steals argument Py_INCREF(globals); // pyproxy_new steals argument
globals_proxy = pyproxy_new(globals); globals_proxy = pyproxy_new(globals);
if (globals_proxy == Js_ERROR) { if (globals_proxy == NULL) {
goto fail; goto fail;
} }
QUIT_IF_NZ(runpython_init_js(pyodide_py_proxy, globals_proxy)); QUIT_IF_NZ(runpython_init_js(pyodide_py_proxy, globals_proxy));