Skip to content

Add int64_t support to embind using bigint support #13889

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
May 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,7 @@ a license to everyone to use it as detailed in LICENSE.)
* Sharad Saxena <sharad.saxena@autodesk.com> (copyright owned by Autodesk, Inc.)
* Vasili Skurydzin <vasili.skurydzin@ibm.com>
* Jakub Nowakowski <jn925+emcc@o2.pl>
* Michael Taylor <mitaylor@adobe.com>
* Andrew Brown <andrew.brown@intel.com> (copyright owned by Intel Corporation)
* Benjamin Puzycki <bpuzycki@umich.edu>
* Marco Buono <marcobuono@invisionapp.com> (copyright owned by InVisionApp, Inc.)
Expand Down
2 changes: 1 addition & 1 deletion site/source/docs/getting_started/FAQ.rst
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,7 @@ This is a limitation of the asm.js target in :term:`Clang`. This code is not cur
How do I pass int64_t and uint64_t values from js into wasm functions?
======================================================================

JS can't represent int64s, so what happens is that in exported functions (that you can call from JS) we "legalize" the types, by turning an i64 argument into two i32s (low and high bits), and an i64 return value becomes an i32, and you can access the high bits by calling a helper function called getTempRet0.
If you build using the `-s WASM_BIGINT` flag, then `int64_t` and `uint64_t` will be represented as `bigint` values in JS. Without the `-s WASM_BIGINT` flag, the values will be represented as `number` in JS which can't represent int64s, so what happens is that in exported functions (that you can call from JS) we "legalize" the types, by turning an i64 argument into two i32s (low and high bits), and an i64 return value becomes an i32, and you can access the high bits by calling a helper function called getTempRet0.


Can I use multiple Emscripten-compiled programs on one Web page?
Expand Down
44 changes: 44 additions & 0 deletions src/embind/embind.js
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,11 @@ var LibraryEmbind = {
case 2: return signed ?
function readS32FromPointer(pointer) { return HEAP32[pointer >> 2]; } :
function readU32FromPointer(pointer) { return HEAPU32[pointer >> 2]; };
#if WASM_BIGINT
case 3: return signed ?
function readS64FromPointer(pointer) { return HEAP64[pointer >> 3]; } :
function readU64FromPointer(pointer) { return HEAPU64[pointer >> 3]; };
#endif
default:
throw new TypeError("Unknown integer type: " + name);
}
Expand Down Expand Up @@ -584,6 +589,45 @@ var LibraryEmbind = {
});
},

#if WASM_BIGINT
_embind_register_bigint__deps: [
'embind_repr', '$readLatin1String', '$registerType', '$integerReadValueFromPointer'],
_embind_register_bigint: function(primitiveType, name, size, minRange, maxRange) {
name = readLatin1String(name);

var shift = getShiftFromSize(size);

var isUnsignedType = (name.indexOf('u') != -1);

// maxRange comes through as -1 for uint64_t (see issue 13902). Work around that temporarily
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See issue #13902

if (isUnsignedType) {
// Use string because acorn does recognize bigint literals
maxRange = (BigInt(1) << BigInt(64)) - BigInt(1);
}

registerType(primitiveType, {
name: name,
'fromWireType': function (value) {
return value;
},
'toWireType': function (destructors, value) {
if (typeof value !== "bigint") {
throw new TypeError('Cannot convert "' + _embind_repr(value) + '" to ' + this.name);
}
if (value < minRange || value > maxRange) {
throw new TypeError('Passing a number "' + _embind_repr(value) + '" from JS side to C/C++ side to an argument of type "' + name + '", which is outside the valid range [' + minRange + ', ' + maxRange + ']!');
}
return value;
},
'argPackAdvance': 8,
'readValueFromPointer': integerReadValueFromPointer(name, shift, !isUnsignedType),
destructorFunction: null, // This type does not need a destructor
});
},
#else
_embind_register_bigint__deps: [],
_embind_register_bigint: function(primitiveType, name, size, minRange, maxRange) {},
#endif

_embind_register_float__deps: [
'embind_repr', '$floatReadValueFromPointer', '$getShiftFromSize',
Expand Down
14 changes: 14 additions & 0 deletions src/embind/emval.js
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,20 @@ var LibraryEmVal = {
return returnType['toWireType'](destructors, handle);
},

_emval_as_int64__deps: ['$requireHandle', '$requireRegisteredType'],
_emval_as_int64: function(handle, returnType, destructorsRef) {
handle = requireHandle(handle);
returnType = requireRegisteredType(returnType, 'emval::as');
return returnType['toWireType'](null, handle);
},

_emval_as_uint64__deps: ['$requireHandle', '$requireRegisteredType'],
_emval_as_uint64: function(handle, returnType, destructorsRef) {
handle = requireHandle(handle);
returnType = requireRegisteredType(returnType, 'emval::as');
return returnType['toWireType'](null, handle);
},

_emval_equals__deps: ['$requireHandle'],
_emval_equals: function(first, second) {
first = requireHandle(first);
Expand Down
2 changes: 2 additions & 0 deletions src/preamble.js
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ var HEAP_DATA_VIEW;

#if WASM_BIGINT
var HEAP64;
var HEAPU64;
#endif

#if USE_PTHREADS
Expand All @@ -292,6 +293,7 @@ function updateGlobalBufferAndViews(buf) {
Module['HEAPF64'] = HEAPF64 = new Float64Array(buf);
#if WASM_BIGINT
Module['HEAP64'] = HEAP64 = new BigInt64Array(buf);
Module['HEAPU64'] = HEAPU64 = new BigUint64Array(buf);
#endif
}

Expand Down
7 changes: 7 additions & 0 deletions system/include/emscripten/bind.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ namespace emscripten {
long minRange,
unsigned long maxRange);

void _embind_register_bigint(
TYPEID integerType,
const char* name,
size_t size,
long long minRange,
unsigned long long maxRange);

void _embind_register_float(
TYPEID floatType,
const char* name,
Expand Down
37 changes: 37 additions & 0 deletions system/include/emscripten/val.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ namespace emscripten {
EM_VAL _emval_get_property(EM_VAL object, EM_VAL key);
void _emval_set_property(EM_VAL object, EM_VAL key, EM_VAL value);
EM_GENERIC_WIRE_TYPE _emval_as(EM_VAL value, TYPEID returnType, EM_DESTRUCTORS* destructors);
int64_t _emval_as_int64(EM_VAL value, TYPEID returnType);
uint64_t _emval_as_uint64(EM_VAL value, TYPEID returnType);

bool _emval_equals(EM_VAL first, EM_VAL second);
bool _emval_strictly_equals(EM_VAL first, EM_VAL second);
Expand Down Expand Up @@ -190,6 +192,7 @@ namespace emscripten {
const void* p;
} w[2];
double d;
uint64_t u;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need both signed and unsigned? i'd hope only unsigned is enough, as with unsigned u above for 32-bit values.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kripken You are correct, the int64_t member was redundant.

};
static_assert(sizeof(GenericWireType) == 8, "GenericWireType must be 8 bytes");
static_assert(alignof(GenericWireType) == 8, "GenericWireType must be 8-byte-aligned");
Expand All @@ -204,6 +207,16 @@ namespace emscripten {
++cursor;
}

inline void writeGenericWireType(GenericWireType*& cursor, int64_t wt) {
cursor->u = wt;
++cursor;
}

inline void writeGenericWireType(GenericWireType*& cursor, uint64_t wt) {
cursor->u = wt;
++cursor;
}

template<typename T>
void writeGenericWireType(GenericWireType*& cursor, T* wt) {
cursor->w[0].p = wt;
Expand Down Expand Up @@ -501,6 +514,30 @@ namespace emscripten {
return fromGenericWireType<T>(result);
}

template<>
int64_t as<int64_t>() const {
using namespace internal;

typedef BindingType<int64_t> BT;
typename WithPolicies<>::template ArgTypeList<int64_t> targetType;

return _emval_as_int64(
handle,
targetType.getTypes()[0]);
}

template<>
uint64_t as<uint64_t>() const {
using namespace internal;

typedef BindingType<uint64_t> BT;
typename WithPolicies<>::template ArgTypeList<uint64_t> targetType;

return _emval_as_uint64(
handle,
targetType.getTypes()[0]);
}

// If code is not being compiled with GNU extensions enabled, typeof() is not a reserved keyword, so support that as a member function.
#if __STRICT_ANSI__
val typeof() const {
Expand Down
2 changes: 2 additions & 0 deletions system/include/emscripten/wire.h
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,8 @@ namespace emscripten {
EMSCRIPTEN_DEFINE_NATIVE_BINDING_TYPE(unsigned long);
EMSCRIPTEN_DEFINE_NATIVE_BINDING_TYPE(float);
EMSCRIPTEN_DEFINE_NATIVE_BINDING_TYPE(double);
EMSCRIPTEN_DEFINE_NATIVE_BINDING_TYPE(int64_t);
EMSCRIPTEN_DEFINE_NATIVE_BINDING_TYPE(uint64_t);

template<>
struct BindingType<void> {
Expand Down
9 changes: 9 additions & 0 deletions system/lib/embind/bind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ template <typename T> static void register_integer(const char* name) {
std::numeric_limits<T>::max());
}

template <typename T> static void register_bigint(const char* name) {
using namespace internal;
_embind_register_bigint(TypeID<T>::get(), name, sizeof(T), std::numeric_limits<T>::min(),
std::numeric_limits<T>::max());
}

template <typename T> static void register_float(const char* name) {
using namespace internal;
_embind_register_float(TypeID<T>::get(), name, sizeof(T));
Expand Down Expand Up @@ -107,6 +113,9 @@ void EMSCRIPTEN_KEEPALIVE __embind_register_native_and_builtin_types() {
register_integer<signed long>("long");
register_integer<unsigned long>("unsigned long");

register_bigint<int64_t>("int64_t");
register_bigint<uint64_t>("uint64_t");

register_float<float>("float");
register_float<double>("double");

Expand Down
130 changes: 130 additions & 0 deletions tests/embind/test_i64_binding.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Copyright 2021 The Emscripten Authors. All rights reserved.
// Emscripten is available under two separate licenses, the MIT license and the
// University of Illinois/NCSA Open Source License. Both these licenses can be
// found in the LICENSE file.

#include <stdio.h>
#include <iostream>
#include <cmath>
#include <emscripten/bind.h>
#include <emscripten/emscripten.h>
#include <emscripten/val.h>

using namespace emscripten;
using namespace std;

void fail()
{
cout << "fail\n";
}

void pass()
{
cout << "pass\n";
}

void test(string message)
{
cout << "test:\n" << message << "\n";
}

void ensure(bool value)
{
if (value)
pass();
else
fail();
}

void execute_js(string js_code)
{
js_code.append(";");
const char* js_code_pointer = js_code.c_str();
EM_ASM_INT({
var js_code = UTF8ToString($0);
return eval(js_code);
}, js_code_pointer);
}

void ensure_js(string js_code)
{
js_code.append(";");
const char* js_code_pointer = js_code.c_str();
ensure(EM_ASM_INT({
var js_code = UTF8ToString($0);
return eval(js_code);
}, js_code_pointer));
}

void ensure_js_throws(string js_code, string error_type)
{
js_code.append(";");
const char* js_code_pointer = js_code.c_str();
const char* error_type_pointer = error_type.c_str();
ensure(EM_ASM_INT({
var js_code = UTF8ToString($0);
var error_type = UTF8ToString($1);
try {
eval(js_code);
}
catch(error_thrown)
{
return error_thrown.name === error_type;
}
return false;
}, js_code_pointer, error_type_pointer));
}

EMSCRIPTEN_BINDINGS(tests) {
register_vector<int64_t>("Int64Vector");
register_vector<uint64_t>("UInt64Vector");
}

int main()
{
const int64_t max_int64_t = numeric_limits<int64_t>::max();
const int64_t min_int64_t = numeric_limits<int64_t>::min();
const uint64_t max_uint64_t = numeric_limits<uint64_t>::max();

printf("start\n");

test("vector<int64_t>");
val::global().set("v64", val(vector<int64_t>{1, 2, 3, -4}));
ensure_js("v64.get(0) === 1n");
ensure_js("v64.get(1) === 2n");
ensure_js("v64.get(2) === 3n");
ensure_js("v64.get(3) === -4n");

execute_js("v64.push_back(1234n)");
ensure_js("v64.size() === 5");
ensure_js("v64.get(4) === 1234n");

test("vector<int64_t> Cannot convert number to int64_t");
ensure_js_throws("v64.push_back(1234)", "TypeError");

test("vector<int64_t> Cannot convert bigint that is too big");
ensure_js_throws("v64.push_back(12345678901234567890123456n)", "TypeError");

test("vector<uint64_t>");
val::global().set("vU64", val(vector<uint64_t>{1, 2, 3, 4}));
ensure_js("vU64.get(0) === 1n");
ensure_js("vU64.get(1) === 2n");
ensure_js("vU64.get(2) === 3n");
ensure_js("vU64.get(3) === 4n");

execute_js("vU64.push_back(1234n)");
ensure_js("vU64.size() === 5");
ensure_js("vU64.get(4) === 1234n");

test("vector<uint64_t> Cannot convert number to uint64_t");
ensure_js_throws("vU64.push_back(1234)", "TypeError");

test("vector<uint64_t> Cannot convert bigint that is too big");
ensure_js_throws("vU64.push_back(12345678901234567890123456n)", "TypeError");

test("vector<uint64_t> Cannot convert bigint that is negative");
ensure_js_throws("vU64.push_back(-1n)", "TypeError");

printf("end\n");
return 0;
}
33 changes: 33 additions & 0 deletions tests/embind/test_i64_binding.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
start
test:
vector<int64_t>
pass
pass
pass
pass
pass
pass
test:
vector<int64_t> Cannot convert number to int64_t
pass
test:
vector<int64_t> Cannot convert bigint that is too big
pass
test:
vector<uint64_t>
pass
pass
pass
pass
pass
pass
test:
vector<uint64_t> Cannot convert number to uint64_t
pass
test:
vector<uint64_t> Cannot convert bigint that is too big
pass
test:
vector<uint64_t> Cannot convert bigint that is negative
pass
end
Loading