diff --git a/AUTHORS b/AUTHORS index fdabaff7970e5..bfa020e61535e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -552,6 +552,7 @@ a license to everyone to use it as detailed in LICENSE.) * Sharad Saxena (copyright owned by Autodesk, Inc.) * Vasili Skurydzin * Jakub Nowakowski +* Michael Taylor * Andrew Brown (copyright owned by Intel Corporation) * Benjamin Puzycki * Marco Buono (copyright owned by InVisionApp, Inc.) diff --git a/site/source/docs/getting_started/FAQ.rst b/site/source/docs/getting_started/FAQ.rst index 9352f895f91ac..20c559486e371 100644 --- a/site/source/docs/getting_started/FAQ.rst +++ b/site/source/docs/getting_started/FAQ.rst @@ -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? diff --git a/src/embind/embind.js b/src/embind/embind.js index 26e26878dc68e..4846dfd7c59cd 100644 --- a/src/embind/embind.js +++ b/src/embind/embind.js @@ -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); } @@ -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 + 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', diff --git a/src/embind/emval.js b/src/embind/emval.js index 8e3682ebce05b..2eaa4908e123e 100644 --- a/src/embind/emval.js +++ b/src/embind/emval.js @@ -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); diff --git a/src/preamble.js b/src/preamble.js index 47d3d11ff0a1e..8c5c422ebb410 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -267,6 +267,7 @@ var HEAP_DATA_VIEW; #if WASM_BIGINT var HEAP64; +var HEAPU64; #endif #if USE_PTHREADS @@ -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 } diff --git a/system/include/emscripten/bind.h b/system/include/emscripten/bind.h index 8f66466abad0e..2258c41762155 100644 --- a/system/include/emscripten/bind.h +++ b/system/include/emscripten/bind.h @@ -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, diff --git a/system/include/emscripten/val.h b/system/include/emscripten/val.h index 272046fad0594..4cb811eb343b3 100644 --- a/system/include/emscripten/val.h +++ b/system/include/emscripten/val.h @@ -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); @@ -190,6 +192,7 @@ namespace emscripten { const void* p; } w[2]; double d; + uint64_t u; }; static_assert(sizeof(GenericWireType) == 8, "GenericWireType must be 8 bytes"); static_assert(alignof(GenericWireType) == 8, "GenericWireType must be 8-byte-aligned"); @@ -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 void writeGenericWireType(GenericWireType*& cursor, T* wt) { cursor->w[0].p = wt; @@ -501,6 +514,30 @@ namespace emscripten { return fromGenericWireType(result); } + template<> + int64_t as() const { + using namespace internal; + + typedef BindingType BT; + typename WithPolicies<>::template ArgTypeList targetType; + + return _emval_as_int64( + handle, + targetType.getTypes()[0]); + } + + template<> + uint64_t as() const { + using namespace internal; + + typedef BindingType BT; + typename WithPolicies<>::template ArgTypeList 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 { diff --git a/system/include/emscripten/wire.h b/system/include/emscripten/wire.h index e406baa072f12..f669cf718d0ee 100644 --- a/system/include/emscripten/wire.h +++ b/system/include/emscripten/wire.h @@ -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 { diff --git a/system/lib/embind/bind.cpp b/system/lib/embind/bind.cpp index 03a900c61385e..df084a0cf5928 100644 --- a/system/lib/embind/bind.cpp +++ b/system/lib/embind/bind.cpp @@ -56,6 +56,12 @@ template static void register_integer(const char* name) { std::numeric_limits::max()); } +template static void register_bigint(const char* name) { + using namespace internal; + _embind_register_bigint(TypeID::get(), name, sizeof(T), std::numeric_limits::min(), + std::numeric_limits::max()); +} + template static void register_float(const char* name) { using namespace internal; _embind_register_float(TypeID::get(), name, sizeof(T)); @@ -107,6 +113,9 @@ void EMSCRIPTEN_KEEPALIVE __embind_register_native_and_builtin_types() { register_integer("long"); register_integer("unsigned long"); + register_bigint("int64_t"); + register_bigint("uint64_t"); + register_float("float"); register_float("double"); diff --git a/tests/embind/test_i64_binding.cpp b/tests/embind/test_i64_binding.cpp new file mode 100644 index 0000000000000..db10ff0202ab2 --- /dev/null +++ b/tests/embind/test_i64_binding.cpp @@ -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 +#include +#include +#include +#include +#include + +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("Int64Vector"); + register_vector("UInt64Vector"); +} + +int main() +{ + const int64_t max_int64_t = numeric_limits::max(); + const int64_t min_int64_t = numeric_limits::min(); + const uint64_t max_uint64_t = numeric_limits::max(); + + printf("start\n"); + + test("vector"); + val::global().set("v64", val(vector{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 Cannot convert number to int64_t"); + ensure_js_throws("v64.push_back(1234)", "TypeError"); + + test("vector Cannot convert bigint that is too big"); + ensure_js_throws("v64.push_back(12345678901234567890123456n)", "TypeError"); + + test("vector"); + val::global().set("vU64", val(vector{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 Cannot convert number to uint64_t"); + ensure_js_throws("vU64.push_back(1234)", "TypeError"); + + test("vector Cannot convert bigint that is too big"); + ensure_js_throws("vU64.push_back(12345678901234567890123456n)", "TypeError"); + + test("vector Cannot convert bigint that is negative"); + ensure_js_throws("vU64.push_back(-1n)", "TypeError"); + + printf("end\n"); + return 0; +} diff --git a/tests/embind/test_i64_binding.out b/tests/embind/test_i64_binding.out new file mode 100644 index 0000000000000..d9ebc4ecf2b6a --- /dev/null +++ b/tests/embind/test_i64_binding.out @@ -0,0 +1,33 @@ +start +test: +vector +pass +pass +pass +pass +pass +pass +test: +vector Cannot convert number to int64_t +pass +test: +vector Cannot convert bigint that is too big +pass +test: +vector +pass +pass +pass +pass +pass +pass +test: +vector Cannot convert number to uint64_t +pass +test: +vector Cannot convert bigint that is too big +pass +test: +vector Cannot convert bigint that is negative +pass +end diff --git a/tests/embind/test_i64_val.cpp b/tests/embind/test_i64_val.cpp new file mode 100644 index 0000000000000..53bd2b4462092 --- /dev/null +++ b/tests/embind/test_i64_val.cpp @@ -0,0 +1,95 @@ +// 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 +#include +#include +#include +#include +#include +#include + +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 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)); +} + +template +string compare_a_64_js(T value) { + stringstream ss; + ss << "a === " << value << "n"; + return ss.str(); +} + +int main() +{ + const int64_t max_int64_t = numeric_limits::max(); + const int64_t min_int64_t = numeric_limits::min(); + const uint64_t max_uint64_t = numeric_limits::max(); + + printf("start\n"); + + test("val(int64_t v)"); + val::global().set("a", val(int64_t(1234))); + ensure_js("a === 1234n"); + + val::global().set("a", val(int64_t(-4321))); + ensure_js("a === -4321n"); + + val::global().set("a", val(int64_t(0x12345678aabbccddL))); + ensure_js("a === 1311768467732155613n"); + ensure(val::global()["a"].as() == 0x12345678aabbccddL); + + test("val(uint64_t v)"); + val::global().set("a", val(uint64_t(1234))); + ensure_js("a === 1234n"); + + val::global().set("a", val(max_uint64_t)); + ensure_js(compare_a_64_js(max_uint64_t)); + ensure(val::global()["a"].as() == max_uint64_t); + + test("val(int64_t v)"); + val::global().set("a", val(max_int64_t)); + ensure_js(compare_a_64_js(max_int64_t)); + ensure(val::global()["a"].as() == max_int64_t); + + val::global().set("a", val(min_int64_t)); + ensure_js(compare_a_64_js(min_int64_t)); + ensure(val::global()["a"].as() == min_int64_t); + + printf("end\n"); + return 0; +} diff --git a/tests/embind/test_i64_val.out b/tests/embind/test_i64_val.out new file mode 100644 index 0000000000000..b3db521a80eb1 --- /dev/null +++ b/tests/embind/test_i64_val.out @@ -0,0 +1,19 @@ +start +test: +val(int64_t v) +pass +pass +pass +pass +test: +val(uint64_t v) +pass +pass +pass +test: +val(int64_t v) +pass +pass +pass +pass +end diff --git a/tests/test_core.py b/tests/test_core.py index 825c7d9730a28..ad1df1cbd3e9c 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -6898,6 +6898,20 @@ def test_embind_val(self): self.emcc_args += ['--bind'] self.do_run_in_out_file_test('embind', 'test_val.cpp') + @no_wasm2js('wasm_bigint') + def test_embind_i64_val(self): + self.set_setting('WASM_BIGINT') + self.emcc_args += ['--bind'] + self.node_args += ['--experimental-wasm-bigint'] + self.do_run_in_out_file_test('embind', 'test_i64_val.cpp', assert_identical=True) + + @no_wasm2js('wasm_bigint') + def test_embind_i64_binding(self): + self.set_setting('WASM_BIGINT') + self.emcc_args += ['--bind'] + self.node_args += ['--experimental-wasm-bigint'] + self.do_run_in_out_file_test('embind', 'test_i64_binding.cpp', assert_identical=True) + def test_embind_no_rtti(self): create_file('main.cpp', r''' #include