Skip to content

Fix #4034: Encode -x as -1.0 * x instead of 0.0 - x. #4040

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 11, 2020

Conversation

sjrd
Copy link
Member

@sjrd sjrd commented May 11, 2020

The result of -x is not the same as 0.0 - x when x is 0.0: it is -0.0 instead of +0.0. Therefore, encoding -x as 0.0 - x was incorrect, as well as link-time optimizations that interpreted 0.0 - x as -x.

We now encode -x as -1.0 * x instead, which always give the correct result. We adapt the rules in the optimizer and in the emitter to recognize -1.0 * x as -x, instead of 0.0 - x.

In addition, we remove some wrong simplifications in the optimizer, that were assuming that + is associative for doubles, whereas it is not.

All of the above also applies to floats.

Only code compiled with the newer version of the compiler will be fixed.

Attention! Code doing -x in source code and compiled with an earlier version of the compiler will be broken by this change, since it was encoded as 0.0 - x in the IR, which used to be emitted as -x (fixing the wrong encoding) and now emitted as 0.0 - x (which is correct given the IR, but incorrect given the original source code). An alternative would be to add a deserialization hack to deserialize 0.0 - x as -1.0 * x.

@sjrd sjrd requested a review from gzm0 May 11, 2020 13:20
@sjrd
Copy link
Member Author

sjrd commented May 11, 2020

@gzm0 Please pay particular attention to the last paragraph of the PR description. WDYT, should we use the alternative instead? (that would break code doing 0.0 - x in source code instead of breaking code doing -x in source code)

Copy link
Contributor

@gzm0 gzm0 left a comment

Choose a reason for hiding this comment

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

Please pay particular attention to the last paragraph of the PR description. WDYT, should we use the alternative instead? (that would break code doing 0.0 - x in source code instead of breaking code doing -x in source code)

My understanding is that because the linker (optimizer and emitter) freely translated between 0.0 - x and -x likely we can find instances of both being wrong with SJS 1.0.1 and maybe even the exact semantic depending on whether the optimizer was on or not. So breaking both of them differently in 1.1.0 is IMO not a problem.

Therefore, I think we should do what is simplest, which is this PR.

case (PreTransLit(FloatLiteral(0)), _) =>
rhs
case (_, PreTransLit(FloatLiteral(_))) =>
foldBinaryOp(Float_+, rhs, lhs)
Copy link
Contributor

Choose a reason for hiding this comment

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

These two are also incorrect? Or just not worth it anymore? (similar comments below).

Copy link
Member Author

Choose a reason for hiding this comment

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

The first one is incorrect : +0.0f + -0.0f gives +0.0f, not rhs which is -0.0f.

The second one is not worth it anymore.

@sjrd
Copy link
Member Author

sjrd commented May 11, 2020

My understanding is that because the linker (optimizer and emitter) freely translated between 0.0 - x and -x likely we can find instances of both being wrong with SJS 1.0.1 and maybe even the exact semantic depending on whether the optimizer was on or not. So breaking both of them differently in 1.1.0 is IMO not a problem.

AFAICT, the previous linker always treated 0.0 - x with the semantics of -x (reliably), including whether or not the optimizer was enabled. I can't think of a scenario where even the optimizer "disconnects" 0.0 - x into something else, other than by treating it as -x, and it was the emitter that compiled 0.0 - x down to -x in JS.

So at least I'm pretty sure that all uses of -x in the source code of 0.6.32/1.0.1 currently do the right thing.

@gzm0
Copy link
Contributor

gzm0 commented May 11, 2020

So at least I'm pretty sure that all uses of -x in the source code of 0.6.32/1.0.1 currently do the right thing.

Hmmm... But then why is it observable (as stated in the bug report)? At least when constant folding, it seems the linker didn't treat 0.0 - x with the semantics of -x (i.e. if x was a known constant).

Or am I misunderstanding what is happening here completely?

@sjrd
Copy link
Member Author

sjrd commented May 11, 2020

Oh, you're right. The optimizer could indeed incorrectly constant-fold -x. Because if x is the constant 0.0, then the optimizer would see a 0.0 - 0.0 and constant-fold it on the JVM to 0.0, instead of the correct result -0.0. The constant-folding indeed bypasses the identification as -x and its semantics.

So yes, you're right, the optimizer has an impact on the validity of -x in 0.6.32/1.0.1. So indeed it's probably best to stick to the simplest fix, which is this PR as you said.

The result of `-x` is not the same as `0.0 - x` when `x` is `0.0`:
it is -0.0 instead of +0.0. Therefore, encoding `-x` as `0.0 - x`
was incorrect, as well as link-time optimizations that interpreted
`0.0 - x` as `-x`.

We now encode `-x` as `-1.0 * x` instead, which always give the
correct result. We adapt the rules in the optimizer and in the
emitter to recognize `-1.0 * x` as `-x`, instead of `0.0 - x`.

In addition, we remove some wrong simplifications in the optimizer,
that were assuming that `+` is associative for doubles, whereas it
is not.

All of the above also applies to floats.

Only code compiled with the newer version of the compiler will be
fixed.

Attention! Code doing `-x` in source code and compiled with an
earlier version of the compiler will be *broken* by this change,
since it was encoded as `0.0 - x` in the IR, which used to be
emitted as `-x` (fixing the wrong encoding) and now emitted as
`0.0 - x` (which is correct given the IR, but incorrect given the
original source code). An alternative would be to add a
deserialization hack to deserialize `0.0 - x` as `-1.0 * x`.
@sjrd sjrd force-pushed the fix-double-negate-zero branch from 0d1aff3 to 40e323a Compare May 11, 2020 15:39
@sjrd
Copy link
Member Author

sjrd commented May 11, 2020

Added an assumeFalse(executingInRhino), because Rhino computes completely wrong results.

@plokhotnyuk
Copy link

plokhotnyuk commented May 11, 2020

Why -1.0 * x? Is -0.0 - x not an option?

$ node
Welcome to Node.js v14.2.0.
Type ".help" for more information.
> -0 - (-0)
0
> -0 - 0
-0
> -0 - (-1)
1
> -0 - 1
-1

@sjrd
Copy link
Member Author

sjrd commented May 11, 2020

Well, that would work too, apparently. It's clearly non-obvious, though (much less so than -1.0 * x), so why -0.0 - x? (instead of "why not?")

@plokhotnyuk
Copy link

Usually, FADD/FSUB have fewer latency on contemporary CPUs than FMUL: https://www.agner.org/optimize/instruction_tables.pdf

@sjrd
Copy link
Member Author

sjrd commented May 11, 2020

There's no need to care about that. It is compiled into -x in JS at the end of the day (see the changes in FunctionEmitter in this PR). The efficiency of the encoding if it was compiled as is is completely irrelevant, because it's not compiled as is.

@sjrd sjrd merged commit ad06498 into scala-js:0.6.x May 11, 2020
@sjrd sjrd deleted the fix-double-negate-zero branch May 11, 2020 18:16
FabioPinheiro added a commit to FabioPinheiro/dotty that referenced this pull request Jun 10, 2020
Copy fix for scala-js/scala-js#4043 to successfully execute the tests.
JSCodeGen was changed as in scala-js/scala-js#4040
FabioPinheiro added a commit to FabioPinheiro/dotty that referenced this pull request Jun 10, 2020
Copy fix for scala-js/scala-js#4043 to successfully execute the tests.
JSCodeGen was changed as in scala-js/scala-js#4040
FabioPinheiro added a commit to FabioPinheiro/dotty that referenced this pull request Jun 10, 2020
JSCodeGen was changed according to scala-js/scala-js#4040
FabioPinheiro added a commit to FabioPinheiro/dotty that referenced this pull request Jun 10, 2020
JSCodeGen was changed according to scala-js/scala-js#4040
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