Incompatibilities between Go spec and WebAssembly spec

465 views
Skip to first unread message

Richard Musiol

unread,
Jan 1, 2018, 6:29:26 PM1/1/18
to golang-dev
Hi,

I am currently working on a WebAssembly backend for the Go compiler. Progress so far is quite good, however I've run into some incompatibilities between the Go spec and the WebAssembly spec in rare edge cases. Here are two examples:

Example 1:
   Dividing -1<<63 by -1. 
Go spec:
   "[...], if the dividend x is the most negative value for the int type of x, the quotient q = x / -1 is equal to x (and r = 0)."
WebAssembly spec:
   "Else if j1 divided by j2 is 2^N−1, then the result is undefined."

Example 2:
   Converting NaN to int.
Go spec:
   "In all non-constant conversions involving floating-point or complex values, if the result type cannot represent the value the conversion succeeds but the result value is implementation-dependent."
WebAssembly spec:
   "If z is a NaN, then the result is undefined." [hint: "undefined" means it crashes on V8, so the operation does not succeed]

I am not yet sure how to resolve those situations. Adding checks to every occurrence of those operations would probably affect performance and since those special cases seem like they almost never happen in real world code, it feels to me like the performance impact would not be justified.

Are there any opinions or ideas on this? I could create actual performance benchmarks if desired.

Best,
Richard

Rob Pike

unread,
Jan 1, 2018, 6:36:05 PM1/1/18
to Richard Musiol, golang-dev
Neither of your issues is a problem. When a spec says something is undefined, as WebAssembly does in both cases, you are free to do what you want. People who depend on the particular implementation's behavior for an officially undefined situation deserve whatever opprobrium, censure and bugs they reap.

-rob


--
You received this message because you are subscribed to the Google Groups "golang-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-dev+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Brad Fitzpatrick

unread,
Jan 1, 2018, 6:46:37 PM1/1/18
to Rob Pike, Richard Musiol, golang-dev
I think he's concerned about Example 1 and the cost of implementing Go's semantics ("Dividing -1<<63 by -1") properly on wasm.

At least that's how I read it.

Rob Pike

unread,
Jan 1, 2018, 6:47:11 PM1/1/18
to Richard Musiol, golang-dev
Also, when a spec writer makes something undefined, it's often because of the difficulty of providing consistent behavior for corner cases across implementations

-rob

Rob Pike

unread,
Jan 1, 2018, 6:48:33 PM1/1/18
to Brad Fitzpatrick, Richard Musiol, golang-dev
OK but the NaN case is not a problem.

And doesn't JS use floats for numbers? Because that would make any big integers require callouts anyway, and the extra check for a particular large value won't be a significant cost.

-rob

Richard Musiol

unread,
Jan 1, 2018, 6:50:13 PM1/1/18
to golang-dev
WebAssembly is my target, so whenever the WebAssembly spec says something is "undefined", then I have to make sure that Go code that compiles to it does not rely on this behavior. The only exception is in cases where the Go spec itself already says that the behavior is "undefined". Mapping undefined Go behavior to undefined WebAssembly behavior is not a problem. However, this is not the case in the two examples above. For example if I map an int64 division to an i64.div_s instruction in WebAssembly, then it would be correct for all cases except this one special case mentioned above where the result of the division is not representable in two's complement. So the only solution that I see right now that is true to both specs is to add an "if x == -1<<63 { return -1<<63 }" in front of each and every 64-bit int division.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-dev+...@googlegroups.com.

Richard Musiol

unread,
Jan 1, 2018, 6:52:57 PM1/1/18
to golang-dev
The NaN case is a problem, since the Go spec explicitly says that the operation "succeeds", but you can not depend on the result. The corresponding WebAssembly instruction is allowed to crash. This is incompatible as far as I can see.

JS only has floats, but WebAssembly has true 64 bit integers.

Richard Musiol

unread,
Jan 1, 2018, 7:02:20 PM1/1/18
to golang-dev
Also, when a spec writer makes something undefined, it's often because of the difficulty of providing consistent behavior for corner cases across implementations

Exactly. What I'm getting at is that the Go spec is still too strict in those corner cases to be properly implemented in WebAssembly.

Brad Fitzpatrick

unread,
Jan 1, 2018, 7:08:16 PM1/1/18
to Richard Musiol, golang-dev
On Mon, Jan 1, 2018 at 3:50 PM, Richard Musiol <neel...@gmail.com> wrote:
WebAssembly is my target, so whenever the WebAssembly spec says something is "undefined", then I have to make sure that Go code that compiles to it does not rely on this behavior. The only exception is in cases where the Go spec itself already says that the behavior is "undefined". Mapping undefined Go behavior to undefined WebAssembly behavior is not a problem. However, this is not the case in the two examples above. For example if I map an int64 division to an i64.div_s instruction in WebAssembly, then it would be correct for all cases except this one special case mentioned above where the result of the division is not representable in two's complement. So the only solution that I see right now that is true to both specs is to add an "if x == -1<<63 { return -1<<63 }" in front of each and every 64-bit int division.

You don't need to add the conditional if you can statically prove it's unnecessary.

e.g, this code doesn't need it:

         x := y / 2

What do all the other backends do? I suspect wasm isn't entirely unique here and the compiler is already smart enough to know when the checks aren't needed.


Richard Musiol

unread,
Jan 1, 2018, 7:11:21 PM1/1/18
to Brad Fitzpatrick, golang-dev
Good point. I'll take a look at the other backends.

Richard Musiol

unread,
Jan 1, 2018, 7:28:57 PM1/1/18
to Brad Fitzpatrick, golang-dev
You are right, thanks! The amd64 backend emits bytecode to check for divisions by -1. However, it does so unconditionally. I guess this is because the rules in generic.rules already turn most static divisions into other instructions. I'm okay with doing the same for now and adding optimizations later. I only wanted to bring it up to get some opinions.

The case with converting floats to ints is a bit more involved, since it not only applies to NaN, but also Inf and floats that are out of range for the integer. This would be quite a number of checks I would need to add. Maybe the performance impact is not too bad, since conversions are not done that often. Again, I just wanted to bring it up to know if this is something that implementations have to just live with.

Uli Kunitz

unread,
Jan 2, 2018, 4:09:25 AM1/2/18
to golang-dev
Interestingly the WebAssembly Design document says:

Truncation from floating point to integer where IEEE 754-2008 would specify an invalid operator exception (e.g. when the floating point value is NaN or outside the range which rounds to an integer in range) traps.


Apparently this didn't make into the Webassembly specification, but was implemented by the V8 engine.


Uli Kunitz

unread,
Jan 2, 2018, 5:06:13 AM1/2/18
to golang-dev
I raised the issue https://github.com/WebAssembly/design/issues/1169 for clarification. 

Uli Kunitz

unread,
Jan 2, 2018, 5:53:08 AM1/2/18
to golang-dev
I got an answer: The conversion/truncation is a so called "partial operator" that is required to trap if the result is undefined. I closed the issue.

There appears to be an initiative to introduce non-trapping float-to-int conversions here: https://github.com/WebAssembly/nontrapping-float-to-int-conversions/blob/master/proposals/nontrapping-float-to-int-conversion/Overview.md

Michael Jones

unread,
Jan 2, 2018, 9:26:07 AM1/2/18
to Uli Kunitz, golang-dev
So the only solution that I see right now that is true to both specs is to add an "if x == -1<<63 { return -1<<63 }" in front of each and every 64-bit int division.

For what it's worth...you may find that code scheduling can be helpful:

r1 = div operation
test for == -1<<63
return -1<<63 if equal
return r1

depends on particulars




--
You received this message because you are subscribed to the Google Groups "golang-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-dev+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.



--
Michael T. Jones
michae...@gmail.com

Russ Cox

unread,
Jan 4, 2018, 10:48:05 AM1/4/18
to Richard Musiol, golang-dev
Yes, you should add checks. Note that -1<<63 / -1 traps on real hardware too (at least, x86 processors), and the Go compilers nonetheless provide a useful result, by adding exactly the checks you are afraid of adding. Given the choice between correct and fast, always choose correct.

Russ
Reply all
Reply to author
Forward
0 new messages