-
Notifications
You must be signed in to change notification settings - Fork 14
Allow passing non-bigint values to I64 parameters #12
Comments
In JavaScript a normal number is a float64 which allow up to 2**53 bits of value, which can't be used to represent properly a i64. In WebAssembly there's no implicit type conversion, I would expect the exported/imported functions to behave the same. I guess the ability to pass a float in |
I accept that there are reasons why implicit conversions from numbers to BigInt are not the best solution in JavaScript in general. In this particular example, however, I think an implicit conversion are appropriate. We can also do that in the spec here by applying the BigInt constructor on the incoming value, i.e. use ToBigInt64(BigInt(|value|)) instead of ToBigInt64(|value|). |
I agree with @gahaas in that passing a JavaScript number into a WebAssembly function expecting an i64 would be a very common pitfall, but also doesn't have the same ergonomic problems of JS numbers and BigInt in general. E.g. We could have semantics where accepting integer-valued doubles in the range -2^53 to 2^53 is OK, and non-integral and larger values throw (though details TBD). |
I don't have a strong opinion about that. The interactions in JavaScript between BigInt and Number are mostly disallowed (example Also in many cases the BigInt to i64 conversion is fast (on 64 bits just takes the first digit), while the conversion from float will probably be fast too it introduce a new bounds check. What should be the type of a I64 return? If we follow the same login: |
I think it's fine to always return an I64. |
As mentioned in #12 (comment), we could just implicitly convert a parameter to BigInt before passing it as an I64 parameter. The return value is always BigInt, because that's the only primitive type which can represent wasm.I64 bit-precisely. |
FWIW, I think this kind of API decision ideally has TC39 sign-off. Coercing Numbers to BigInts in some places and not others will catch developers by surprise. |
Implicitly coercing Numbers into BigInts is a footgun. We shouldn't make it easier to make this mistake. Developers can pass |
Even if a given Number is within the safe range, it might have been the result of a calculation that crossed the safe boundary at some point, at which point the damage has potentially already been done. Number.MAX_SAFE_INTEGER
// --> 9007199254740991
Number.MAX_SAFE_INTEGER + 2 - 2;
// --> 9007199254740990
// Within the safe range, but not the number you wanted! For this reason, I argue that throwing for all Number values is better than throwing selectively. |
Agree with @mathiasbynens. In TC39, we are trying to make a consistent call here, that using a Number in a place where BigInts are expected throws. This is hoped to encourage a pattern where developers make consistent use of types, to avoid overflow errors. We recently made a decision to affirm that this error exists in the case of 64-bit TypedArrays, which is basically the same case as this issue. |
@littledan @mathiasbynens I understand your reasoning, and I don't disagree that there are pitfalls to rampant implicit conversion of numbers to However, in this case this reduces the ergonomics of the Wasm surface, and I am not convinced this surface is the place to debug type errors in JavaScript programs. After all, arguments to Wasm functions already have an implicit Requiring |
Perhaps a reasonable compromise for now is to throw and then revisit (with TC39 folks included) if it indeed turns out to be prohibitive and cannot be optimized away? (As in, it's always easy to remove an exception, not to add one.) |
I'm happy to consider relaxing the restrictions if this turns out to be un-optimizable. But, I would prefer if programmers had to do something explicitly when doing an operation which risks losing precision, to getting it implicitly. General TAG design guidance thread: w3ctag/design-principles#115 |
I am generally in favor of clearly separating between Numbers and BigInts in JavaScript in order to avoid hard-to-detect bugs due to precision loss. However I'm not convinced that that reasoning applies here. May I humbly present the following observations for your consideration: (1) A value (2) A value (3) We're talking about the JS ↔ Wasm boundary here, not about pure JavaScript. Crossing language boundaries typically involves some amount of mapping and conversions; that is fine. (4) Wasm modules are typically compiled from C/C++. In C/C++, developers often choose int64 types on APIs when they're concerned that the int32 range might not quite be enough for some use cases. To be clear, lacking specific experience with all this, I don't feel very strongly about the ultimate outcome of this debate, I just wanted to point out that I don't find the argument presented so far particularly convincing. For returning Wasm i64 values to JavaScript, BigInts are clearly the right choice. For sending values from JavaScript to Wasm, some amount of conversion must happen anyways (as usual when crossing language boundaries) and happens already (one can initialize an i32 value from |
For this upgrade scenario: Today, you usually have to write some JavaScript glue code to access C++. In the future, we may have something higher level with host/WebIDL bindings. I think it'd be good to have a way to declare something as an "int53" and accept Number, for this sort of scenario. But I'd like to start out conservative, and consider relaxing restrictions over time. We discussed the analogous restriction recently in TC39 when it came to TypedArrays, and decided to be strict about BigInts. See this summary. The TAG guidance linked above is informed by this; the hope is that JavaScript can remain consistent across the whole platform. If this argument is unconvincing, let's discuss it in an upcoming WebAssembly CG call to get a strong conclusion. |
Closing no-change following the TAG design principles and consistency with the rest of the platform. Authors are expected to use bigint consistently when they're planning to use an i64 API. If this turns out to be problematic in practice, we can revisit; it's generally much easier to make an API stop throwing exceptions than the other way around. |
Uh oh!
There was an error while loading. Please reload this page.
At the moment, ToWebAssemblyValue(value) calls ToBigInt64(value) if the expected type is I64. ToBigInt64 throws a TypeError if value is a Number.
This behavior looks unexpected and inconsistent to me, because for all other types, ToWebAssemblyValue works differently.
I'm not convinced that we have to couple wasm.I64 so tightly to js.BigInt. BigInt is needed to allow wasm.I64 to be passed precisely to JavaScript, and also to big numbers precisely from JavaScript to WebAssembly. However, this does not mean that it should not be possible to pass normal numbers to WebAssembly as I64 parameters.
One scenario that comes to my mind is the following: if I want to pass an object to WebAssembly as an I32 parameter, I just have to add the valueOf function to the object to return a number. For an I64 parameter, however, I would have to add a toString function and avoid the valueOf function to avoid a TypeError. [edit] The valueOf function could also return a BigInt, but then it cannot be used for I32 parameters anymore.
The text was updated successfully, but these errors were encountered: