Skip to content

[emscripten] Enable WASM_BIGINT by default#7984

Merged
sbc100 merged 1 commit intomainfrom
wasm_bigint
Feb 18, 2026
Merged

[emscripten] Enable WASM_BIGINT by default#7984
sbc100 merged 1 commit intomainfrom
wasm_bigint

Conversation

@sbc100
Copy link
Copy Markdown
Member

@sbc100 sbc100 commented Oct 22, 2025

This flag has been enabled by default in emscripten for a while now so I don't think we need this here anymore.

Also remove the -Wno-experimental flag which is no longer needed for -sMEMORY64.

Also remove -sMODULARIZE which is implied by -sEXPORT_ES6.

@sbc100 sbc100 requested review from dschuff and kripken October 22, 2025 22:11
@sbc100 sbc100 enabled auto-merge (squash) October 22, 2025 22:11
@sbc100 sbc100 force-pushed the wasm_bigint branch 2 times, most recently from 2484a29 to b84c9bd Compare October 22, 2025 22:14
@sbc100 sbc100 force-pushed the wasm_bigint branch 3 times, most recently from 834c3bd to aa256bb Compare October 23, 2025 23:58
@sbc100
Copy link
Copy Markdown
Member Author

sbc100 commented Oct 24, 2025

Hm.. it looks like more work is requires to make the binaryen API bigint friendly. @kripken did you take a look at this in the past?

sbc100 added a commit that referenced this pull request Oct 24, 2025
@kripken
Copy link
Copy Markdown
Member

kripken commented Oct 24, 2025

Hmm, no, I don't recall doing that.

More generally, my hope is we can move binaryen.js to embind so stuff like this is handled automatically.

@sbc100
Copy link
Copy Markdown
Member Author

sbc100 commented Oct 24, 2025

Hmm, no, I don't recall doing that.

More generally, my hope is we can move binaryen.js to embind so stuff like this is handled automatically.

It seems like there are places where the API uses 64-bit stuff and is not expecting it to simply use bigints

sbc100 added a commit that referenced this pull request Oct 24, 2025
@sbc100 sbc100 force-pushed the wasm_bigint branch 4 times, most recently from 991158b to 8d03d35 Compare October 24, 2025 20:42
@sbc100
Copy link
Copy Markdown
Member Author

sbc100 commented Oct 24, 2025

I decided to look into the changes needed to the JS API.. WDYT?

@sbc100 sbc100 changed the title [emscripten] Remove -sWASM_BIGINT flag [emscripten] Enable WASM_BIGINT by default Oct 24, 2025
Copy link
Copy Markdown
Member

@kripken kripken left a comment

Choose a reason for hiding this comment

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

Changes make sense to me, but they are breaking, so please update the changelog.

Comment thread scripts/test/shared.py Outdated
Comment thread src/js/binaryen.js-post.js Outdated
Comment thread src/js/binaryen.js-post.js Outdated
Comment thread src/js/binaryen.js-post.js Outdated
Comment thread test/binaryen.js/expressions.js Outdated
Comment thread test/binaryen.js/kitchen-sink.js Outdated
This flag has been enabled by default in emscripten for a while now
so I don't think we need this here anymore.

The main user-visible effect of this change is that it makes the
`module.i64.const` JS method now simply take a single bigint rather than
a pair numbers.
@sbc100 sbc100 merged commit 5d74c9c into main Feb 18, 2026
17 checks passed
@sbc100 sbc100 deleted the wasm_bigint branch February 18, 2026 17:54
@sbc100
Copy link
Copy Markdown
Member Author

sbc100 commented Feb 18, 2026

Oops, sorry, I didn't realize that would auto-land. Will add a Changelog entry now.

sbc100 added a commit that referenced this pull request Feb 18, 2026
sbc100 added a commit that referenced this pull request Feb 18, 2026
@guest271314
Copy link
Copy Markdown

@sbc100 How do I use this? wasm2js builds the JavaScript, though doesn't support passing BigInt to WASM.

@sbc100
Copy link
Copy Markdown
Member Author

sbc100 commented Mar 30, 2026

@guest271314 exactly is the problem you are trying to solve?

Are you building binaryen with -sWASM=0 (i.e. using wasm2js)? In this mode -sWASM_BIGINT will be disable by default.

@sbc100
Copy link
Copy Markdown
Member Author

sbc100 commented Mar 30, 2026

Oh I see, it looks like maybe its not possible to use setValueI64 in this mode, maybe we need another ifdef in there? Is that the issue you are running into?

@guest271314
Copy link
Copy Markdown

Oh I see, it looks like maybe its not possible to use setValueI64 in this mode, maybe we need another ifdef in there? Is that the issue you are running into?

I didn't build Binaryen. I have just been using the prebuilt executables from Releases.

I just thought wasm2js would/could handle BigInt. Turns out it can't.

The source code that creates the WASM is in Zig. I'm just trying to get the WASM produced by Zig, that works as WASM, to JavaScript, by any means necessary - and still work.

@sbc100
Copy link
Copy Markdown
Member Author

sbc100 commented Mar 31, 2026

Hmm.. I'm a little confused about that the problem is that you are facing.

The the prebuilt binaries for binaryen do not use wasm2js right (i.e. https://github.com/WebAssembly/binaryen/releases/download/version_128/binaryen-version_128-node.tar.gz contains both js and wasm files).

So I guess you are not referring here to running binaryen itself under wasm (i.e. not really related to this issue) but using wasm2js to convert a Wasm module to JS? What is the specific wasm2js issue you are running into? You would prefer that the resulting JS did use BigInt for i64?

@guest271314
Copy link
Copy Markdown

@sbc100

but using wasm2js to convert a Wasm module to JS? What is the specific wasm2js issue you are running into? You would prefer that the resulting JS did use BigInt for i64?

Yes.

See Required imports (env)

get_time_ns	() → i64 (BigInt)	Monotonic clock in nanoseconds

In JavaScript

  const { instance } = await WebAssembly.instantiate(wasmBytes, {
    env: {
      get_time_ns: () => BigInt(Math.round(performance.now() * 1_000_000)),
      console_log: (ptr, len) => {
        const bytes = new Uint8Array(inst.exports.memory.buffer, ptr, len);
        console.log("[wasm]", new TextDecoder().decode(bytes));
      },
      random_fill: (ptr, len) => {
        crypto.getRandomValues(
          new Uint8Array(inst.exports.memory.buffer, ptr, len),
        );
      },
    },
  });

The output of wasm2js can't handle that BigInt() in env.get_time_ns().

@sbc100
Copy link
Copy Markdown
Member Author

sbc100 commented Apr 1, 2026

Ok, so this is unrelated to this PR I believe, and the behaviour of wasm2js has simply never worked with bigint/i64.

@kripken, correct me if I'm wrong by I think the solution that emscripten depends on here is to first always run the --legalize-js-interface to convert the external API to use i32 pairs insteads of i64/bigint, before running wasm2js. Would that work for your use case @guest271314?

@kripken, how hard would it be to add i64/bigint support for wasm2js? I guess one argument against doing that is the its hard to imagine a target that is new enough to support BitInt but old enough to now support WebAssembly? @guest271314 what engine are you targetting that has BigInt support in JS but not WebAssembly support?

@kripken
Copy link
Copy Markdown
Member

kripken commented Apr 1, 2026

@sbc100

Yes, legalizing the API is a way to fix this. Pairs of i32s can then be combined on the JS side to a BigInt (and for return values there is getTempRet for the high bits).

Yes, we could support BigInts in wasm2js, but its focus is really on older browsers/VMs that lack wasm support entirely, and those places likely don't have BigInts or other modern JS features. So we have not been adding new feature support there.

@guest271314
Copy link
Copy Markdown

@sbc100 I tried --legalize-js-interface. There are other internal places in the WASM I'm using that uses i64 values besides imports from JavaScript. When I got past the timer thing there was another error reported about please use explicit conversions.

I guess my idea about wasm2js was that it was/is around just for the conversion to and from JavaScript, not expressly to support "older" browsers. The conversion itself - on "old" and "new" browsers is useful; particularly for somebody such as myself who tries to port the same algorithm to 20 different programming languages/runtimes/interpreters.

"Old" browser, at this point, could be the Chromium or Nightly TOT build from yesterday. I only use snapshots and fetch the lastest CHromium and Nightly every day or so. I'm tryint to port code from Zig to JavaScript, using WASM as the bridge. I'm trying to do that by any means necessary.

@guest271314
Copy link
Copy Markdown

@kripken

Yes, we could support BigInts in wasm2js

That would be useful.

@kripken
Copy link
Copy Markdown
Member

kripken commented Apr 2, 2026

The conversion itself - on "old" and "new" browsers is useful; particularly for somebody such as myself who tries to port the same algorithm to 20 different programming languages/runtimes/interpreters.

What is the practical motivation there? I mean, why use pure JS rather than JS+wasm, in "new" browsers that have wasm?

@guest271314
Copy link
Copy Markdown

What is the practical motivation there? I mean, why use pure JS rather than JS+wasm, in "new" browsers that have wasm?

For me, that JavaScript, including JavaScript in the browser, is on par with Rust, C++, C, Go, Python, etc. as to programming language capabilties. If it can be done in C++, it can be done in JavaScript. To see how far I can go with browser capabilities, and JavaScript capabilties, in the language itself, without importing a .node file or .wasm file.

That was my motivation for creating a WebTransport server in the browser.

@kripken
Copy link
Copy Markdown
Member

kripken commented Apr 2, 2026

I see, interesting.

In general we have prioritized allowing code to work - we want our users to be able to ship code that runs - rather than other considerations like you mention. We also focus a lot on performance, but that reaches the same conclusion here, since using wasm is generally faster than JS.

I wouldn't be opposed to a patch that improves wasm2js here, but this is not something that I would work on myself.

@guest271314
Copy link
Copy Markdown

@kripken

We also focus a lot on performance, but that reaches the same conclusion here, since using wasm is generally faster than JS.

I don't think so.

The nm_wasm is Bytecode Alliance's Javy compiled to WASM.

Same algorithm, run 10 times for each script/executable/execution by wasmtime/scripting language.

I'm gonna have to do a WASM-only table. So far I've compiled the same algorithm to WASM from JavaScript, AssemblyScript, C, C++, Go, Rust, Zig - and compare those numbers to the JavaScript engines, runtimes, interpreters.

qjs is around 1.3 MiB. wasmtime alone is 61 MiB. bun executes the same WASM file faster than wasmtime.

Static Hermes and QuickJS smoke AssemblyScript and Javy.

0	'nm_rust'	0.10895999999940395
1	'nm_go'	0.10998999999910594
2	'nm_lua'	0.12152000000029803
3	'nm_tjs'	0.13784999999701977
4	'nm_c'	0.1418199999988079
5	'nm_qjs'	0.14232000000178813
6	'nm_shermes'	0.14345000000000002
7	'nm_cpp'	0.14939999999701975
8	'nm_nodejs'	0.22531000000089407
9	'nm_typescript'	0.22576000000089405
10	'nm_wasm'	0.23064999999850988
11	'nm_assemblyscript'	0.25082000000327825
12	'nm_spidermonkey'	0.30095000000149014
13	'nm_python'	0.3032899999961257
14	'nm_ruby'	0.311790000000596
15	'nm_bash'	0.3118999999955297
16	'nm_deno'	0.338639999999106
17	'nm_bun'	0.42192000000029795
18	'nm_d8'	0.45548000000119215
19	'nm_llrt'	0.7427399999991058

There is no singular "wasm", nor a singular "JS". There are dozens of JavaScript engines, runtimes, interpreters. For example, Facebook (Meta) Static Hermes engine compiles JavaScript to native executable Ahead of Time, can emit C, and be compiled to WASM with WASI-SDK, or Emscripten. QuickJS is all over the place, Including Javy. QuickJS (NG) proper is faster than Javy compiled to WASM. Google's V8 d8 doesn't really have a way to read binary input, etc. There's no I/O specified in ECMA-262 (JavaScript)!

There's potential hazard in relying on only the tests and claims of the claimants of anything themselves. I prefer to vet, everybody.

Similarly, within the span of a decade or so "wasm" is all over the place; servers, WASI P! to WASI P3 in a short span. Great! Thanks for your consideration.

@kripken
Copy link
Copy Markdown
Member

kripken commented Apr 3, 2026

There are certainly exceptions - you can make a microbenchmark where JS is faster than C. But, here we are comparing wasm to wasm-compiled-to-JS (wasm2js), which is not ordinary JS. It won't have JS objects in the compiled code, in particular, and not benefit from JS's GC there. On that comparison, it is extremely rare to have JS be faster than wasm in my experience. I can't remember a single production use case where wasm2js made things faster for a user.

@guest271314
Copy link
Copy Markdown

There are certainly exceptions - you can make a microbenchmark where JS is faster than C. But, here we are comparing wasm to wasm-compiled-to-JS (wasm2js), which is not ordinary JS. It won't have JS objects in the compiled code, in particular, and not benefit from JS's GC there. On that comparison, it is extremely rare to have JS be faster than wasm in my experience. I can't remember a single production use case where wasm2js made things faster for a user.

I was specifically replying to the idea that WASM is faster than JavaScript in general.

The first question is which JavaScript engine or runtime or interpreter A list of JavaScript engines, runtimes, interpreters executing exactly what in what form versus which WebAssembly runtime executing exactly what in what form. The details can be tedious when we consider the number of JavaScript engines, runtimes, interpreters; and conversely the various WebAssembly implementations, modules versus components, WASI P1 cf. WASI PN. Technically ECMA-262 doesn't have I/O specified, so the question might arise about exactly how the result are even read.

For the wasm2js use case I don't care about performance at all. It's all about getting the WebTransport server code to JavaScript, based on general principles; because AFAICT nobody has implemented a WebTransport server in JavaScript that consistently works. And by works, I mean I start off with the idea that I am gonna try to break your gear. There's quico https://github.com/colocohen/quico, though I havn't got it to work. So JavaScript developers don't have to go to Node.js Addons or C, C++, Rust, Go, etc. to develop the QUIC/HTTP/3/WebTransport stack. This https://github.com/guest271314/quic-zig-wasm/tree/wasm-experiment/wasm does work. E.g., running a WebTransport server over Direct Sockets UDPSocket from Chromium, using Firefox Nightly as client.

So, I'm thinking about the general functionality of converting one code base from one programming language to another as a baked in feature of WebAssembly.

I don't think "performance" will be an issue. But we can test and verify to make sure - if we can get wasm2js to handle some BigInt imports from JavaScript.

webtransport-server-chromium-firefox-client

@guest271314
Copy link
Copy Markdown

@kripken Perhaps think: speak.js. Capability, in JavaScript. Years later, browsers still don't ship a builtin TTS or STT engine for Web Speech API. They got room for "AI" stuff, though...

@kripken
Copy link
Copy Markdown
Member

kripken commented Apr 4, 2026

@guest271314 Fair enough, those are interesting questions. It just happens that those motivations aren't shared by the majority of our users. Most of them focus on shipping code to the Web, where browser engines run it, and where wasm runs very fast. wasm2js hasn't been a priority for that reason. And even off the Web, people usually ship in Node.js - where, again, wasm is fast - or in a dedicated wasm VM.

@guest271314
Copy link
Copy Markdown

@kripken I got this working from wasm2js quic.wasm --enable-bulk-memory --legalize-js-interface -o quic-wasm-wasm2js.js

  var tempRet0 = 0;
  const env = {
    setTempRet0: (val) => {
      tempRet0 = val | 0;
    },
    getTempRet0: () => tempRet0 | 0,
    get_time_ns: () => {
      const nowNs = BigInt(Math.round(performance.now() * 1e6));
      const low = Number(nowNs & 0xFFFFFFFFn) | 0;
      tempRet0 = Number(nowNs >> 32n) | 0;
      return low;
    },
    console_log: (ptr, len) => {
      const bytes = new Uint8Array(mem.buffer, ptr, len);
      const msg = new TextDecoder().decode(bytes);
      console.log(msg);
    },
    random_fill: (ptr, len) => {
      const bytes = new Uint8Array(mem.buffer, ptr, len);
      crypto.getRandomValues(bytes);
    }
  };
  var retasmFunc = asmFunc({
    env
  });
  const mem = retasmFunc.memory;
  const instance = {
    exports: {
      ...retasmFunc,
      qz_wt_accept_session: (sessionId) => {
        const bId = BigInt(sessionId);
        const low = Number(bId & 0xFFFFFFFFn) | 0;
        const high = Number(bId >> 32n) | 0;
        return retasmFunc.qz_wt_accept_session(low, high);
      },
      qz_wt_read_stream: (streamId, outPtr, outLen) => {
        const bId = BigInt(streamId);
        const low = Number(bId & 0xFFFFFFFFn) | 0;
        const high = Number(bId >> 32n) | 0;
        return retasmFunc.qz_wt_read_stream(low, high, outPtr, outLen);
      },
      qz_wt_send_stream: (streamId, dataPtr, len) => {
        const bId = BigInt(streamId);
        const low = Number(bId & 0xFFFFFFFFn) | 0;
        const high = Number(bId >> 32n) | 0;
        return retasmFunc.qz_wt_send_stream(low, high, dataPtr, len);
      },
      qz_wt_close_stream: (streamId) => {
        const bId = BigInt(streamId);
        const low = Number(bId & 0xFFFFFFFFn) | 0;
        const high = Number(bId >> 32n) | 0;
        return retasmFunc.qz_wt_close_stream(low, high);
      },
      qz_wt_send_datagram: (sessionId, dataPtr, len) => {
        const bId = BigInt(sessionId);
        const low = Number(bId & 0xFFFFFFFFn) | 0;
        const high = Number(bId >> 32n) | 0;
        return retasmFunc.qz_wt_send_datagram(low, high, dataPtr, len);
      },
      qz_wt_close_session: (sessionId, errorCode, reasonPtr, reasonLen) => {
        const bId = BigInt(sessionId);
        const low = Number(bId & 0xFFFFFFFFn) | 0;
        const high = Number(bId >> 32n) | 0;
        return retasmFunc.qz_wt_close_session(low, high, errorCode, reasonPtr, reasonLen);
      }
    }
  };
  console.log(instance);
  const wasm = instance.exports;
  copyToWasm(wasm, new Uint8Array(certDer), (ptr, len) => wasm.qz_set_cert(ptr, len));
  copyToWasm(wasm, new Uint8Array(keyDer), (ptr, len) => wasm.qz_set_key(ptr, len));

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants