Skip to content

Commit 42cbd6c

Browse files
authored
Wasm/JS BigInt support (#10860)
This adds a flag WASM_BIGINT which enables this feature, with which we let the JS VM use a JS BigInt for a wasm i64. In that case we don't need to legalize i64s into pairs of i32s. This is fairly straightforward, but we do need to modify the JS code of each library method that receives or returns an i64. Tests verify that we can send and receive i64s from wasm to JS, and also that a dynCall works. This depends on WebAssembly/binaryen#2726 for that.
1 parent ceacba1 commit 42cbd6c

17 files changed

+213
-38
lines changed

emcc.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2019,6 +2019,9 @@ def check_human_readable_list(items):
20192019
# requires JS legalization
20202020
shared.Settings.LEGALIZE_JS_FFI = 0
20212021

2022+
if shared.Settings.WASM_BIGINT:
2023+
shared.Settings.LEGALIZE_JS_FFI = 0
2024+
20222025
if shared.Settings.WASM_BACKEND:
20232026
if shared.Settings.SIMD:
20242027
newargs.append('-msimd128')

emscripten.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2327,6 +2327,8 @@ def finalize_wasm(temp_files, infile, outfile, memfile, DEBUG):
23272327
# (which matches what llvm+lld has given us)
23282328
if shared.Settings.DEBUG_LEVEL >= 2 or shared.Settings.PROFILING_FUNCS or shared.Settings.EMIT_SYMBOL_MAP or shared.Settings.ASYNCIFY_WHITELIST or shared.Settings.ASYNCIFY_BLACKLIST:
23292329
args.append('-g')
2330+
if shared.Settings.WASM_BIGINT:
2331+
args.append('--bigint')
23302332
if shared.Settings.LEGALIZE_JS_FFI != 1:
23312333
args.append('--no-legalize-javascript-ffi')
23322334
if not shared.Settings.MEM_INIT_IN_WASM:

src/closure-externs/closure-externs.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -994,3 +994,7 @@ var threadInfoStruct;
994994
var selfThreadId;
995995
/** @suppress {duplicate} */
996996
var noExitRuntime;
997+
998+
// No BigInt in closure yet
999+
// https://github.com/google/closure-compiler/issues/3167
1000+
var BigInt;

src/library_syscall.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1390,7 +1390,9 @@ var SyscallsLibrary = {
13901390
FS.utime(path, atime, mtime);
13911391
return 0;
13921392
},
1393-
__sys_fallocate: function(fd, mode, off_low, off_high, len_low, len_high) {
1393+
__sys_fallocate: function(fd, mode, {{{ defineI64Param('off') }}}, {{{ defineI64Param('len') }}}) {
1394+
{{{ receiveI64ParamAsI32s('off') }}}
1395+
{{{ receiveI64ParamAsI32s('len') }}}
13941396
var stream = SYSCALLS.getStreamFromFD(fd)
13951397
var offset = SYSCALLS.get64(off_low, off_high);
13961398
var len = SYSCALLS.get64(len_low, len_high);
@@ -1522,7 +1524,8 @@ var SyscallsLibrary = {
15221524
return 0;
15231525
},
15241526
fd_seek__sig: 'iiiiii',
1525-
fd_seek: function(fd, offset_low, offset_high, whence, newOffset) {
1527+
fd_seek: function(fd, {{{ defineI64Param('offset') }}}, whence, newOffset) {
1528+
{{{ receiveI64ParamAsI32s('offset') }}}
15261529
var stream = SYSCALLS.getStreamFromFD(fd);
15271530
var HIGH_OFFSET = 0x100000000; // 2^32
15281531
// use an unsigned operator on low and shift high by 32-bits

src/library_wasi.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@ var WasiLibrary = {
103103
// either wait for BigInt support or to legalize on the client.
104104
clock_time_get__sig: 'iiiii',
105105
clock_time_get__deps: ['emscripten_get_now', 'emscripten_get_now_is_monotonic', '__setErrNo'],
106-
clock_time_get: function(clk_id, precision_l, precision_h, ptime) {
106+
clock_time_get: function(clk_id, {{{ defineI64Param('precision') }}}, ptime) {
107+
{{{ receiveI64ParamAsI32s('precision') }}}
107108
var now;
108109
if (clk_id === {{{ cDefine('__WASI_CLOCKID_REALTIME') }}}) {
109110
now = Date.now();

src/library_webgpu.js

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -958,7 +958,8 @@ var LibraryWebGPU = {
958958

959959
// wgpuFence
960960

961-
wgpuFenceOnCompletion: function(fenceId, completionValue_l, completionValue_h, callback, userdata) {
961+
wgpuFenceOnCompletion: function(fenceId, {{{ defineI64Param('completionValue') }}}, callback, userdata) {
962+
{{{ receiveI64ParamAsI32s('completionValue') }}}
962963
var fence = WebGPU.mgrFence.get(fenceId);
963964
var completionValue = {{{ gpu.makeU64ToNumber('completionValue_l', 'completionValue_h') }}};
964965

@@ -990,7 +991,8 @@ var LibraryWebGPU = {
990991
return WebGPU.mgrFence.create(queue.createFence(desc));
991992
},
992993

993-
wgpuQueueSignal: function(queueId, fenceId, signalValue_l, signalValue_h) {
994+
wgpuQueueSignal: function(queueId, fenceId, {{{ defineI64Param('signalValue') }}}) {
995+
{{{ receiveI64ParamAsI32s('signalValue') }}}
994996
var queue = WebGPU.mgrQueue.get(queueId);
995997
var fence = WebGPU.mgrFence.get(fenceId);
996998
var signalValue = {{{ gpu.makeU64ToNumber('signalValue_l', 'signalValue_h') }}};
@@ -1135,7 +1137,10 @@ var LibraryWebGPU = {
11351137
return WebGPU.mgrRenderPassEncoder.create(commandEncoder["beginRenderPass"](desc));
11361138
},
11371139

1138-
wgpuCommandEncoderCopyBufferToBuffer: function(encoderId, srcId, srcOffset_l, srcOffset_h, dstId, dstOffset_l, dstOffset_h, size_l, size_h) {
1140+
wgpuCommandEncoderCopyBufferToBuffer: function(encoderId, srcId, {{{ defineI64Param('srcOffset') }}}, dstId, {{{ defineI64Param('dstOffset') }}}, {{{ defineI64Param('size') }}}) {
1141+
{{{ receiveI64ParamAsI32s('srcOffset') }}}
1142+
{{{ receiveI64ParamAsI32s('dstOffset') }}}
1143+
{{{ receiveI64ParamAsI32s('size') }}}
11391144
var commandEncoder = WebGPU.mgrCommandEncoder.get(encoderId);
11401145
var src = WebGPU.mgrBuffer.get(srcId);
11411146
var dst = WebGPU.mgrBuffer.get(dstId);
@@ -1168,7 +1173,9 @@ var LibraryWebGPU = {
11681173

11691174
// wgpuBuffer
11701175

1171-
wgpuBufferSetSubData: function(bufferId, start_l, start_h, count_l, count_h, data) {
1176+
wgpuBufferSetSubData: function(bufferId, {{{ defineI64Param('start') }}}, {{{ defineI64Param('count') }}}, data) {
1177+
{{{ receiveI64ParamAsI32s('start') }}}
1178+
{{{ receiveI64ParamAsI32s('count') }}}
11721179
var buffer = WebGPU.mgrBuffer.get(bufferId);
11731180
var start = {{{ gpu.makeU64ToNumber('start_l', 'start_h') }}};
11741181
var count = {{{ gpu.makeU64ToNumber('count_l', 'count_h') }}};
@@ -1187,7 +1194,7 @@ var LibraryWebGPU = {
11871194
var dataLength_h = (mapped.byteLength / 0x100000000) | 0;
11881195
var dataLength_l = mapped.byteLength | 0;
11891196
// WGPUBufferMapAsyncStatus status, const void* data, uint64_t dataLength, void* userdata
1190-
dynCall('viiji', callback, [WEBGPU_BUFFER_MAP_ASYNC_STATUS_SUCCESS, data, dataLength_l, dataLength_h, userdata]);
1197+
dynCall('viiji', callback, [WEBGPU_BUFFER_MAP_ASYNC_STATUS_SUCCESS, data, {{{ sendI64Argument('dataLength_l', 'dataLength_h') }}}, userdata]);
11911198
}, function() {
11921199
// TODO(kainino0x): Figure out how to pick other error status values.
11931200
var WEBGPU_BUFFER_MAP_ASYNC_STATUS_ERROR = 1;
@@ -1208,7 +1215,7 @@ var LibraryWebGPU = {
12081215
var dataLength_h = (mapped.byteLength / 0x100000000) | 0;
12091216
var dataLength_l = mapped.byteLength | 0;
12101217
// WGPUBufferMapAsyncStatus status, void* data, uint64_t dataLength, void* userdata
1211-
dynCall('viiji', callback, [WEBGPU_BUFFER_MAP_ASYNC_STATUS_SUCCESS, data, dataLength_l, dataLength_h, userdata]);
1218+
dynCall('viiji', callback, [WEBGPU_BUFFER_MAP_ASYNC_STATUS_SUCCESS, data, {{{ sendI64Argument('dataLength_l', 'dataLength_h') }}}, userdata]);
12121219
}, function() {
12131220
// TODO(kainino0x): Figure out how to pick other error status values.
12141221
dynCall('viiji', callback, [WEBGPU_BUFFER_MAP_ASYNC_STATUS_ERROR, 0, 0, 0, userdata]);
@@ -1271,7 +1278,8 @@ var LibraryWebGPU = {
12711278
var pass = WebGPU.mgrComputePassEncoder.get(passId);
12721279
pass["dispatch"](x, y, z);
12731280
},
1274-
wgpuComputePassEncoderDispatchIndirect: function(passId, indirectBufferId, indirectOffset_l, indirectOffset_h) {
1281+
wgpuComputePassEncoderDispatchIndirect: function(passId, indirectBufferId, {{{ defineI64Param('indirectOffset') }}}) {
1282+
{{{ receiveI64ParamAsI32s('indirectOffset') }}}
12751283
var indirectBuffer = WebGPU.mgrBuffer.get(indirectBufferId);
12761284
var indirectOffset = {{{ gpu.makeU64ToNumber('indirectOffset_l', 'indirectOffset_h') }}};
12771285
var pass = WebGPU.mgrComputePassEncoder.get(passId);
@@ -1336,13 +1344,15 @@ var LibraryWebGPU = {
13361344
var pass = WebGPU.mgrRenderPassEncoder.get(passId);
13371345
pass["drawIndexed"](indexCount, instanceCount, firstIndex, baseVertex, firstInstance);
13381346
},
1339-
wgpuRenderPassEncoderDrawIndirect: function(passId, indirectBufferId, indirectOffset_l, indirectOffset_h) {
1347+
wgpuRenderPassEncoderDrawIndirect: function(passId, indirectBufferId, {{{ defineI64Param('indirectOffset') }}}) {
1348+
{{{ receiveI64ParamAsI32s('indirectOffset') }}}
13401349
var indirectBuffer = WebGPU.mgrBuffer.get(indirectBufferId);
13411350
var indirectOffset = {{{ gpu.makeU64ToNumber('indirectOffset_l', 'indirectOffset_h') }}};
13421351
var pass = WebGPU.mgrRenderPassEncoder.get(passId);
13431352
pass["drawIndirect"](indirectBuffer, indirectOffset);
13441353
},
1345-
wgpuRenderPassEncoderDrawIndexedIndirect: function(passId, indirectBufferId, indirectOffset_l, indirectOffset_h) {
1354+
wgpuRenderPassEncoderDrawIndexedIndirect: function(passId, indirectBufferId, {{{ defineI64Param('indirectOffset') }}}) {
1355+
{{{ receiveI64ParamAsI32s('indirectOffset') }}}
13461356
var indirectBuffer = WebGPU.mgrBuffer.get(indirectBufferId);
13471357
var indirectOffset = {{{ gpu.makeU64ToNumber('indirectOffset_l', 'indirectOffset_h') }}};
13481358
var pass = WebGPU.mgrRenderPassEncoder.get(passId);
@@ -1379,7 +1389,8 @@ var LibraryWebGPU = {
13791389
pass["setBindGroup"](groupIndex, group, offsets);
13801390
}
13811391
},
1382-
wgpuRenderBundleEncoderSetIndexBuffer: function(bundleId, bufferId, offset_l, offset_h) {
1392+
wgpuRenderBundleEncoderSetIndexBuffer: function(bundleId, bufferId, {{{ defineI64Param('offset') }}}) {
1393+
{{{ receiveI64ParamAsI32s('offset') }}}
13831394
var offset = {{{ gpu.makeU64ToNumber('offset_l', 'offset_h') }}};
13841395
var pass = WebGPU.mgrRenderBundleEncoder.get(bundleId);
13851396
var buffer = WebGPU.mgrBuffer.get(bufferId);
@@ -1390,7 +1401,8 @@ var LibraryWebGPU = {
13901401
var pipeline = WebGPU.mgrRenderPipeline.get(pipelineId);
13911402
pass["setPipeline"](pipeline);
13921403
},
1393-
wgpuRenderBundleEncoderSetVertexBuffer: function(bundleId, slot, bufferId, offset_l, offset_h) {
1404+
wgpuRenderBundleEncoderSetVertexBuffer: function(bundleId, slot, bufferId, {{{ defineI64Param('offset') }}}) {
1405+
{{{ receiveI64ParamAsI32s('offset') }}}
13941406
var offset = {{{ gpu.makeU64ToNumber('offset_l', 'offset_h') }}};
13951407
var pass = WebGPU.mgrRenderBundleEncoder.get(bundleId);
13961408
pass["setVertexBuffer"](slot, WebGPU.mgrBuffer.get(bufferId), offset);

src/parseTools.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1677,3 +1677,34 @@ function makeAsmImportsAccessInPthread(variable) {
16771677
function hasExportedFunction(func) {
16781678
return Object.keys(EXPORTED_FUNCTIONS).indexOf(func) != -1;
16791679
}
1680+
1681+
// JS API I64 param handling: if we have BigInt support, the ABI is simple,
1682+
// it is a BigInt. Otherwise, we legalize into pairs of i32s.
1683+
function defineI64Param(name) {
1684+
if (WASM_BIGINT) {
1685+
return name + '_bigint';
1686+
} else {
1687+
return name + '_low, ' + name + '_high';
1688+
}
1689+
}
1690+
1691+
function receiveI64ParamAsI32s(name) {
1692+
if (WASM_BIGINT) {
1693+
// TODO: use Xn notation when JS parsers support it (as of April 6 2020,
1694+
// * closure compiler is missing support
1695+
// https://github.com/google/closure-compiler/issues/3167
1696+
// * acorn needs to be upgraded, and to set ecmascript version >= 11
1697+
// * terser needs to be upgraded
1698+
return 'var ' + name + '_low = Number(' + name + '_bigint & BigInt(0xffffffff)) | 0, ' + name + '_high = Number(' + name + '_bigint >> BigInt(32)) | 0;';
1699+
} else {
1700+
return '';
1701+
}
1702+
}
1703+
1704+
function sendI64Argument(low, high) {
1705+
if (WASM_BIGINT) {
1706+
return 'BigInt(low) | (BigInt(high) << BigInt(32))';
1707+
} else {
1708+
return low + ', ' + high;
1709+
}
1710+
}

src/settings.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,6 +1317,11 @@ var BINARYEN_EXTRA_PASSES = "";
13171317
// (This option was formerly called BINARYEN_ASYNC_COMPILATION)
13181318
var WASM_ASYNC_COMPILATION = 1;
13191319

1320+
// WebAssembly integration with JavaScript BigInt. When enabled we don't need
1321+
// to legalize i64s into pairs of i32s, as the wasm VM will use a BigInt where
1322+
// an i64 is used.
1323+
var WASM_BIGINT = 0;
1324+
13201325
// WebAssembly defines a "producers section" which compilers and tools can
13211326
// annotate themselves in. Emscripten does not emit this by default, as it
13221327
// increases code size, and some users may not want information about their tools

src/support.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -444,9 +444,13 @@ function loadWebAssemblyModule(binary, flags) {
444444
var moduleLocal = {};
445445

446446
var resolveSymbol = function(sym, type, legalized) {
447+
#if WASM_BIGINT
448+
assert(!legalized);
449+
#else
447450
if (legalized) {
448451
sym = 'orig$' + sym;
449452
}
453+
#endif
450454

451455
var resolved = Module["asm"][sym];
452456
if (!resolved) {
@@ -460,7 +464,7 @@ function loadWebAssemblyModule(binary, flags) {
460464
#if ASSERTIONS
461465
assert(resolved, 'missing linked ' + type + ' `' + sym + '`. perhaps a side module was not linked in? if this global was expected to arrive from a system library, try to build the MAIN_MODULE with EMCC_FORCE_STDLIBS=1 in the environment');
462466
#endif
463-
}
467+
}
464468
return resolved;
465469
}
466470

@@ -510,7 +514,11 @@ function loadWebAssemblyModule(binary, flags) {
510514
assert(parts.length == 3)
511515
var name = parts[1];
512516
var sig = parts[2];
517+
#if WASM_BIGINT
518+
var legalized = false;
519+
#else
513520
var legalized = sig.indexOf('j') >= 0; // check for i64s
521+
#endif
514522
var fp = 0;
515523
return obj[prop] = function() {
516524
if (!fp) {

tests/core/test_i64_invoke_bigint.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
2+
#include <emscripten.h>
3+
#include <stdio.h>
4+
#include <stdint.h>
5+
6+
int64_t big = 0x12345678aabbccddL;
7+
8+
__attribute__((noinline))
9+
int64_t foobar(int64_t x, int y) {
10+
x += EM_ASM_INT({
11+
return 0; // prevents llvm from seeing the final value
12+
});
13+
if (x == 1337) {
14+
throw 1; // looks like we might throw
15+
}
16+
return x + y; // use the int parameter too, to show they are all handled
17+
}
18+
19+
int main() {
20+
int64_t x;
21+
try {
22+
puts("try");
23+
x = foobar(big, 1);
24+
} catch(int) {
25+
puts("caught");
26+
}
27+
printf("ok: 0x%llx.\n", x);
28+
}
29+

0 commit comments

Comments
 (0)