Skip to content

Commit cf9f2c9

Browse files
authored
Add int64_t support to embind using BigInt support (#13889)
This PR adds int64_t and uint64_t support to embind when the -s WASM_BIGINT flag is used (see issue #11726). Emscripten has support for handling int64_t and uint64_t as JS BigInt values, but before this PR embind did not know anything about those values.
1 parent d8ec245 commit cf9f2c9

File tree

14 files changed

+408
-1
lines changed

14 files changed

+408
-1
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,7 @@ a license to everyone to use it as detailed in LICENSE.)
552552
* Sharad Saxena <sharad.saxena@autodesk.com> (copyright owned by Autodesk, Inc.)
553553
* Vasili Skurydzin <vasili.skurydzin@ibm.com>
554554
* Jakub Nowakowski <jn925+emcc@o2.pl>
555+
* Michael Taylor <mitaylor@adobe.com>
555556
* Andrew Brown <andrew.brown@intel.com> (copyright owned by Intel Corporation)
556557
* Benjamin Puzycki <bpuzycki@umich.edu>
557558
* Marco Buono <marcobuono@invisionapp.com> (copyright owned by InVisionApp, Inc.)

site/source/docs/getting_started/FAQ.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,7 @@ This is a limitation of the asm.js target in :term:`Clang`. This code is not cur
527527
How do I pass int64_t and uint64_t values from js into wasm functions?
528528
======================================================================
529529

530-
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.
530+
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.
531531

532532

533533
Can I use multiple Emscripten-compiled programs on one Web page?

src/embind/embind.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,11 @@ var LibraryEmbind = {
499499
case 2: return signed ?
500500
function readS32FromPointer(pointer) { return HEAP32[pointer >> 2]; } :
501501
function readU32FromPointer(pointer) { return HEAPU32[pointer >> 2]; };
502+
#if WASM_BIGINT
503+
case 3: return signed ?
504+
function readS64FromPointer(pointer) { return HEAP64[pointer >> 3]; } :
505+
function readU64FromPointer(pointer) { return HEAPU64[pointer >> 3]; };
506+
#endif
502507
default:
503508
throw new TypeError("Unknown integer type: " + name);
504509
}
@@ -584,6 +589,45 @@ var LibraryEmbind = {
584589
});
585590
},
586591

592+
#if WASM_BIGINT
593+
_embind_register_bigint__deps: [
594+
'embind_repr', '$readLatin1String', '$registerType', '$integerReadValueFromPointer'],
595+
_embind_register_bigint: function(primitiveType, name, size, minRange, maxRange) {
596+
name = readLatin1String(name);
597+
598+
var shift = getShiftFromSize(size);
599+
600+
var isUnsignedType = (name.indexOf('u') != -1);
601+
602+
// maxRange comes through as -1 for uint64_t (see issue 13902). Work around that temporarily
603+
if (isUnsignedType) {
604+
// Use string because acorn does recognize bigint literals
605+
maxRange = (BigInt(1) << BigInt(64)) - BigInt(1);
606+
}
607+
608+
registerType(primitiveType, {
609+
name: name,
610+
'fromWireType': function (value) {
611+
return value;
612+
},
613+
'toWireType': function (destructors, value) {
614+
if (typeof value !== "bigint") {
615+
throw new TypeError('Cannot convert "' + _embind_repr(value) + '" to ' + this.name);
616+
}
617+
if (value < minRange || value > maxRange) {
618+
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 + ']!');
619+
}
620+
return value;
621+
},
622+
'argPackAdvance': 8,
623+
'readValueFromPointer': integerReadValueFromPointer(name, shift, !isUnsignedType),
624+
destructorFunction: null, // This type does not need a destructor
625+
});
626+
},
627+
#else
628+
_embind_register_bigint__deps: [],
629+
_embind_register_bigint: function(primitiveType, name, size, minRange, maxRange) {},
630+
#endif
587631

588632
_embind_register_float__deps: [
589633
'embind_repr', '$floatReadValueFromPointer', '$getShiftFromSize',

src/embind/emval.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,20 @@ var LibraryEmVal = {
282282
return returnType['toWireType'](destructors, handle);
283283
},
284284

285+
_emval_as_int64__deps: ['$requireHandle', '$requireRegisteredType'],
286+
_emval_as_int64: function(handle, returnType, destructorsRef) {
287+
handle = requireHandle(handle);
288+
returnType = requireRegisteredType(returnType, 'emval::as');
289+
return returnType['toWireType'](null, handle);
290+
},
291+
292+
_emval_as_uint64__deps: ['$requireHandle', '$requireRegisteredType'],
293+
_emval_as_uint64: function(handle, returnType, destructorsRef) {
294+
handle = requireHandle(handle);
295+
returnType = requireRegisteredType(returnType, 'emval::as');
296+
return returnType['toWireType'](null, handle);
297+
},
298+
285299
_emval_equals__deps: ['$requireHandle'],
286300
_emval_equals: function(first, second) {
287301
first = requireHandle(first);

src/preamble.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ var HEAP_DATA_VIEW;
267267

268268
#if WASM_BIGINT
269269
var HEAP64;
270+
var HEAPU64;
270271
#endif
271272

272273
#if USE_PTHREADS
@@ -292,6 +293,7 @@ function updateGlobalBufferAndViews(buf) {
292293
Module['HEAPF64'] = HEAPF64 = new Float64Array(buf);
293294
#if WASM_BIGINT
294295
Module['HEAP64'] = HEAP64 = new BigInt64Array(buf);
296+
Module['HEAPU64'] = HEAPU64 = new BigUint64Array(buf);
295297
#endif
296298
}
297299

system/include/emscripten/bind.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,13 @@ namespace emscripten {
6262
long minRange,
6363
unsigned long maxRange);
6464

65+
void _embind_register_bigint(
66+
TYPEID integerType,
67+
const char* name,
68+
size_t size,
69+
long long minRange,
70+
unsigned long long maxRange);
71+
6572
void _embind_register_float(
6673
TYPEID floatType,
6774
const char* name,

system/include/emscripten/val.h

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ namespace emscripten {
6565
EM_VAL _emval_get_property(EM_VAL object, EM_VAL key);
6666
void _emval_set_property(EM_VAL object, EM_VAL key, EM_VAL value);
6767
EM_GENERIC_WIRE_TYPE _emval_as(EM_VAL value, TYPEID returnType, EM_DESTRUCTORS* destructors);
68+
int64_t _emval_as_int64(EM_VAL value, TYPEID returnType);
69+
uint64_t _emval_as_uint64(EM_VAL value, TYPEID returnType);
6870

6971
bool _emval_equals(EM_VAL first, EM_VAL second);
7072
bool _emval_strictly_equals(EM_VAL first, EM_VAL second);
@@ -190,6 +192,7 @@ namespace emscripten {
190192
const void* p;
191193
} w[2];
192194
double d;
195+
uint64_t u;
193196
};
194197
static_assert(sizeof(GenericWireType) == 8, "GenericWireType must be 8 bytes");
195198
static_assert(alignof(GenericWireType) == 8, "GenericWireType must be 8-byte-aligned");
@@ -204,6 +207,16 @@ namespace emscripten {
204207
++cursor;
205208
}
206209

210+
inline void writeGenericWireType(GenericWireType*& cursor, int64_t wt) {
211+
cursor->u = wt;
212+
++cursor;
213+
}
214+
215+
inline void writeGenericWireType(GenericWireType*& cursor, uint64_t wt) {
216+
cursor->u = wt;
217+
++cursor;
218+
}
219+
207220
template<typename T>
208221
void writeGenericWireType(GenericWireType*& cursor, T* wt) {
209222
cursor->w[0].p = wt;
@@ -501,6 +514,30 @@ namespace emscripten {
501514
return fromGenericWireType<T>(result);
502515
}
503516

517+
template<>
518+
int64_t as<int64_t>() const {
519+
using namespace internal;
520+
521+
typedef BindingType<int64_t> BT;
522+
typename WithPolicies<>::template ArgTypeList<int64_t> targetType;
523+
524+
return _emval_as_int64(
525+
handle,
526+
targetType.getTypes()[0]);
527+
}
528+
529+
template<>
530+
uint64_t as<uint64_t>() const {
531+
using namespace internal;
532+
533+
typedef BindingType<uint64_t> BT;
534+
typename WithPolicies<>::template ArgTypeList<uint64_t> targetType;
535+
536+
return _emval_as_uint64(
537+
handle,
538+
targetType.getTypes()[0]);
539+
}
540+
504541
// If code is not being compiled with GNU extensions enabled, typeof() is not a reserved keyword, so support that as a member function.
505542
#if __STRICT_ANSI__
506543
val typeof() const {

system/include/emscripten/wire.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,8 @@ namespace emscripten {
267267
EMSCRIPTEN_DEFINE_NATIVE_BINDING_TYPE(unsigned long);
268268
EMSCRIPTEN_DEFINE_NATIVE_BINDING_TYPE(float);
269269
EMSCRIPTEN_DEFINE_NATIVE_BINDING_TYPE(double);
270+
EMSCRIPTEN_DEFINE_NATIVE_BINDING_TYPE(int64_t);
271+
EMSCRIPTEN_DEFINE_NATIVE_BINDING_TYPE(uint64_t);
270272

271273
template<>
272274
struct BindingType<void> {

system/lib/embind/bind.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ template <typename T> static void register_integer(const char* name) {
5656
std::numeric_limits<T>::max());
5757
}
5858

59+
template <typename T> static void register_bigint(const char* name) {
60+
using namespace internal;
61+
_embind_register_bigint(TypeID<T>::get(), name, sizeof(T), std::numeric_limits<T>::min(),
62+
std::numeric_limits<T>::max());
63+
}
64+
5965
template <typename T> static void register_float(const char* name) {
6066
using namespace internal;
6167
_embind_register_float(TypeID<T>::get(), name, sizeof(T));
@@ -107,6 +113,9 @@ void EMSCRIPTEN_KEEPALIVE __embind_register_native_and_builtin_types() {
107113
register_integer<signed long>("long");
108114
register_integer<unsigned long>("unsigned long");
109115

116+
register_bigint<int64_t>("int64_t");
117+
register_bigint<uint64_t>("uint64_t");
118+
110119
register_float<float>("float");
111120
register_float<double>("double");
112121

tests/embind/test_i64_binding.cpp

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// Copyright 2021 The Emscripten Authors. All rights reserved.
2+
// Emscripten is available under two separate licenses, the MIT license and the
3+
// University of Illinois/NCSA Open Source License. Both these licenses can be
4+
// found in the LICENSE file.
5+
6+
#include <stdio.h>
7+
#include <iostream>
8+
#include <cmath>
9+
#include <emscripten/bind.h>
10+
#include <emscripten/emscripten.h>
11+
#include <emscripten/val.h>
12+
13+
using namespace emscripten;
14+
using namespace std;
15+
16+
void fail()
17+
{
18+
cout << "fail\n";
19+
}
20+
21+
void pass()
22+
{
23+
cout << "pass\n";
24+
}
25+
26+
void test(string message)
27+
{
28+
cout << "test:\n" << message << "\n";
29+
}
30+
31+
void ensure(bool value)
32+
{
33+
if (value)
34+
pass();
35+
else
36+
fail();
37+
}
38+
39+
void execute_js(string js_code)
40+
{
41+
js_code.append(";");
42+
const char* js_code_pointer = js_code.c_str();
43+
EM_ASM_INT({
44+
var js_code = UTF8ToString($0);
45+
return eval(js_code);
46+
}, js_code_pointer);
47+
}
48+
49+
void ensure_js(string js_code)
50+
{
51+
js_code.append(";");
52+
const char* js_code_pointer = js_code.c_str();
53+
ensure(EM_ASM_INT({
54+
var js_code = UTF8ToString($0);
55+
return eval(js_code);
56+
}, js_code_pointer));
57+
}
58+
59+
void ensure_js_throws(string js_code, string error_type)
60+
{
61+
js_code.append(";");
62+
const char* js_code_pointer = js_code.c_str();
63+
const char* error_type_pointer = error_type.c_str();
64+
ensure(EM_ASM_INT({
65+
var js_code = UTF8ToString($0);
66+
var error_type = UTF8ToString($1);
67+
try {
68+
eval(js_code);
69+
}
70+
catch(error_thrown)
71+
{
72+
return error_thrown.name === error_type;
73+
}
74+
return false;
75+
}, js_code_pointer, error_type_pointer));
76+
}
77+
78+
EMSCRIPTEN_BINDINGS(tests) {
79+
register_vector<int64_t>("Int64Vector");
80+
register_vector<uint64_t>("UInt64Vector");
81+
}
82+
83+
int main()
84+
{
85+
const int64_t max_int64_t = numeric_limits<int64_t>::max();
86+
const int64_t min_int64_t = numeric_limits<int64_t>::min();
87+
const uint64_t max_uint64_t = numeric_limits<uint64_t>::max();
88+
89+
printf("start\n");
90+
91+
test("vector<int64_t>");
92+
val::global().set("v64", val(vector<int64_t>{1, 2, 3, -4}));
93+
ensure_js("v64.get(0) === 1n");
94+
ensure_js("v64.get(1) === 2n");
95+
ensure_js("v64.get(2) === 3n");
96+
ensure_js("v64.get(3) === -4n");
97+
98+
execute_js("v64.push_back(1234n)");
99+
ensure_js("v64.size() === 5");
100+
ensure_js("v64.get(4) === 1234n");
101+
102+
test("vector<int64_t> Cannot convert number to int64_t");
103+
ensure_js_throws("v64.push_back(1234)", "TypeError");
104+
105+
test("vector<int64_t> Cannot convert bigint that is too big");
106+
ensure_js_throws("v64.push_back(12345678901234567890123456n)", "TypeError");
107+
108+
test("vector<uint64_t>");
109+
val::global().set("vU64", val(vector<uint64_t>{1, 2, 3, 4}));
110+
ensure_js("vU64.get(0) === 1n");
111+
ensure_js("vU64.get(1) === 2n");
112+
ensure_js("vU64.get(2) === 3n");
113+
ensure_js("vU64.get(3) === 4n");
114+
115+
execute_js("vU64.push_back(1234n)");
116+
ensure_js("vU64.size() === 5");
117+
ensure_js("vU64.get(4) === 1234n");
118+
119+
test("vector<uint64_t> Cannot convert number to uint64_t");
120+
ensure_js_throws("vU64.push_back(1234)", "TypeError");
121+
122+
test("vector<uint64_t> Cannot convert bigint that is too big");
123+
ensure_js_throws("vU64.push_back(12345678901234567890123456n)", "TypeError");
124+
125+
test("vector<uint64_t> Cannot convert bigint that is negative");
126+
ensure_js_throws("vU64.push_back(-1n)", "TypeError");
127+
128+
printf("end\n");
129+
return 0;
130+
}

0 commit comments

Comments
 (0)