design icon indicating copy to clipboard operation
design copied to clipboard

Problematic JS BigInt coercion

Open dcodeIO opened this issue 4 years ago • 5 comments

It has recently been reported to me that the JS API for BigInt integration can cause unexpected problems when omitting arguments. Say one has the following Wasm module

export function test(a: i64 = 0): void { }

and wants to call exports.test() externally, with the a argument omitted for some reason, then the call will fail with

Uncaught TypeError: Cannot convert undefined to a BigInt

Without some sort of bindings generation, the JS module does not know however that a is of type i64 and should be passed a 0n default value. Something similar also happens when the argument is 0, or another number:

Uncaught TypeError: Cannot convert number to a BigInt

So I wonder: Would it be useful to define a coercion from undefined to 0n when calling Wasm exports, and perhaps also a coercion from number? I guess this is not a V8 bug?

dcodeIO avatar Feb 28 '21 11:02 dcodeIO

Spec context: https://webassembly.github.io/spec/js-api/#call-an-exported-function calls ToWebAssemblyValue → ToBigInt64 → ToBigInt which throws on undefined. (i32 gets zero, the floats get NaN.)

export function test(a: i64 = 0): void { }

The = 0 here doesn't refer to anything in WebAssembly, does it? You mean a function with the type [i64] → []?

Ms2ger avatar Mar 02 '21 14:03 Ms2ger

Yes, in Wasm this would be an [i64] -> [] function, called externally with the argument omitted where a compiler allows to do this. AssemblyScript for example has a mechanism for filling in default values of omitted arguments post-call, by hinting arguments.length pre-call.

dcodeIO avatar Mar 02 '21 14:03 dcodeIO

I guess better address this issue to this discussion: https://es.discourse.group/t/why-is-bigint-broken/567/18

MaxGraey avatar Mar 09 '21 12:03 MaxGraey

I don't think that this issue is as specific as the issue OP implies:

For example:

export function test(a: i64 = 3): void { }

or for that matter,

export function test(a: f64 = 0): void { } // will be NaN if you call test() in js, not 0

devsnek avatar Mar 16 '21 20:03 devsnek

Thanks for noting, I overlooked that, since in the specific use case I am describing the value does not matter, as it is only important that the call succeeds (the default value replaces what's is passed). I agree that there is more to think about here.

Also, in https://github.com/WebAssembly/design/issues/1402#issuecomment-789758084 it was noted that Wasm could allow duplicate exports, which seems like a preferable solution over what I suggested here, at least for that specific use case. With duplicate exports, I'd expect the JS/Wasm binding to pick the export with matching arity (not sure if that's a realistic expectation?), so the problem can be worked around fairly easily by exporting both a test() and a test(a: T).

dcodeIO avatar Mar 26 '21 17:03 dcodeIO