RESOLVED FIXED 147167
stress/math-pow-with-constants.js fails in cloop
https://bugs.webkit.org/show_bug.cgi?id=147167
Summary stress/math-pow-with-constants.js fails in cloop
Filip Pizlo
Reported 2015-07-21 15:40:30 PDT
stress/math-pow-with-constants.js.default: Exception: Error: exponentIsIntegerConstant(2.1) should be 34135823067412.42, was = 34135823067412.465 stress/math-pow-with-constants.js.default: ERROR: Unexpected exit code: 3
Attachments
Benchmark results (52.99 KB, text/plain)
2015-07-24 11:15 PDT, Basile Clement
no flags
Patch (5.37 KB, patch)
2015-07-24 11:17 PDT, Basile Clement
ggaren: review+
Mark Lam
Comment 1 2015-07-21 22:50:27 PDT
Some facts: The CLoop and ASM llint (useJIT=0) both yield 34135823067412.465. The Mac Calculator yields 34135823067412.46. With the JIT enabled, JSC yields 34135823067412.42. Chrome and Firefox yields 34135823067412.42.
Mark Lam
Comment 2 2015-07-22 09:39:17 PDT
For the record, a C++ test function to call pow(2.1, 42) and printed with %20.3f formatting also yields 34135823067412.465.
Basile Clement
Comment 3 2015-07-22 09:45:40 PDT
(In reply to comment #1) > Some facts: > The CLoop and ASM llint (useJIT=0) both yield 34135823067412.465. > The Mac Calculator yields 34135823067412.46. > With the JIT enabled, JSC yields 34135823067412.42. > Chrome and Firefox yields 34135823067412.42. (In reply to comment #2) > For the record, a C++ test function to call pow(2.1, 42) and printed with > %20.3f formatting also yields 34135823067412.465. Could we just be doing some kind of fast path computation when JITting Math.pow that is invalid in floating point land?
Yusuke Suzuki
Comment 4 2015-07-22 09:48:11 PDT
I guess this is due to x87 floating point width (80bit). If I'm saying something wrong, please ignore it :)
Filip Pizlo
Comment 5 2015-07-22 12:44:58 PDT
(In reply to comment #4) > I guess this is due to x87 floating point width (80bit). > If I'm saying something wrong, please ignore it :) If this is 64bit, then we wouldn't be using x87. More generally, it would be a bug to implement JS floating point using x87 and I don't think we ever do that.
Filip Pizlo
Comment 6 2015-07-22 12:46:56 PDT
(In reply to comment #3) > (In reply to comment #1) > > Some facts: > > The CLoop and ASM llint (useJIT=0) both yield 34135823067412.465. > > The Mac Calculator yields 34135823067412.46. > > With the JIT enabled, JSC yields 34135823067412.42. > > Chrome and Firefox yields 34135823067412.42. > > (In reply to comment #2) > > For the record, a C++ test function to call pow(2.1, 42) and printed with > > %20.3f formatting also yields 34135823067412.465. > > Could we just be doing some kind of fast path computation when JITting > Math.pow that is invalid in floating point land? thr opposite might also be true. :-) the JIT could be calling variant of pow that is more compliant. I think that it's okto fix this by making the C++ code agree with the JIT or vice versa. The spec is very permissive about what pow returns and we really just want to have internal consistency in our engine.
Basile Clement
Comment 7 2015-07-22 13:35:39 PDT
(In reply to comment #6) > (In reply to comment #3) > > (In reply to comment #1) > > > Some facts: > > > The CLoop and ASM llint (useJIT=0) both yield 34135823067412.465. > > > The Mac Calculator yields 34135823067412.46. > > > With the JIT enabled, JSC yields 34135823067412.42. > > > Chrome and Firefox yields 34135823067412.42. > > > > (In reply to comment #2) > > > For the record, a C++ test function to call pow(2.1, 42) and printed with > > > %20.3f formatting also yields 34135823067412.465. > > > > Could we just be doing some kind of fast path computation when JITting > > Math.pow that is invalid in floating point land? > > thr opposite might also be true. :-) the JIT could be calling variant of pow > that is more compliant. > > I think that it's okto fix this by making the C++ code agree with the JIT or > vice versa. The spec is very permissive about what pow returns and we really > just want to have internal consistency in our engine. Just checked; JIT code is doing a fast exponentiation when the exponent is an integer but LLInt is not.
Mark Lam
Comment 8 2015-07-22 13:40:52 PDT
More facts: double result = 1; double v = 2.1; for (int i = 0; i < 42; i++) result *= v; printf(" 2.1 * ... * 2.1 (42 times) = %f\n", result); yields: 2.1 * ... * 2.1 (42 times) = 34135823067412.472656 The powThunkGenerator algorithm: double result = 1; double v = 2.1; int exp = 42; do { if (exp & 1) result *= v; result *= result; exp = exp >> 1; } while (exp); printf(" powThunkGenerator algo(2.1, 42) = %f\n", result); yields: powThunkGenerator algo(2.1, 42) = 34135823067412.433594 Neither of these are consistent with the 34135823067412.464844 returned by the C pow(), nor the 34135823067412.42 returned by the JITted code.
Basile Clement
Comment 9 2015-07-22 14:15:07 PDT
(In reply to comment #8) > More facts: > > double result = 1; > double v = 2.1; > for (int i = 0; i < 42; i++) > result *= v; > printf(" 2.1 * ... * 2.1 (42 times) = %f\n", result); > > yields: > 2.1 * ... * 2.1 (42 times) = 34135823067412.472656 > > The powThunkGenerator algorithm: > > double result = 1; > double v = 2.1; > int exp = 42; > do { > if (exp & 1) > result *= v; > result *= result; This should be v *= v, and just happen to (almost) work for 42 because when repeatedly divided by 2 we get 42 -> 21 -> 10 -> 5 -> 2 -> 1 which is an alternative sequence of even and odd numbers, so the odd step does the multiplication the even step should have been doing and all is good - for real numbers. Using v *= v instead gives the same result as JITted code, preparing a patch. > exp = exp >> 1; > } while (exp); > printf(" powThunkGenerator algo(2.1, 42) = %f\n", result); > > yields: > powThunkGenerator algo(2.1, 42) = 34135823067412.433594 > > Neither of these are consistent with the 34135823067412.464844 returned by > the C pow(), nor the 34135823067412.42 returned by the JITted code.
Basile Clement
Comment 10 2015-07-22 18:54:14 PDT
We currently have different ways of calling Math.pow: 1) In the LLInt when the JIT is disabled 2) In the LLInt with the JIT enabled 3) In the baseline JIT 4) Through constant folding in the DFG or FTL 5) Without constant folding in the DFG 6) Without constant folding in the FTL Cases 1 and 4 are calling operationMathPow, which is essentially a call to std::pow. Cases 2 and 3 use the powThunkGenerator from jit/ThunkGenerators.cpp, which performs fast exponentiation if the exponent is represented as a boxed int32 value, and falls back to operationMathPow otherwise. Cases 5 and 6 have their own functionally identical implementations that performs fast exponentiation if the exponent can be converted to an int32 value, and falls back to operationMathPow otherwise (case 6 falling back to the LLVM pow intrinsic). Since the fast exponentiation can yield different outputs than the standard pow() function, we can thus get different results depending on the representation of arguments and the tier we are on. I see two valid fixes here: - We don't care. Floating point arithmetic is never exact anyway, and the results are still close. In this case, we shouldn't compare the results of Math.pow for equality in our tests however, and the fix would be to change the math-pow-with-constants test to check for closeness instead of equality. - We want Math.pow to give an illusion of consistency by making Math.pow deterministic on a given platform. I think the second option is more desirable, as long as that doesn't impact too much on performances. I have a patch for always taking the fast exponentiation path when the exponent is convertible to an int32, I'll run the benchmarks tomorrow to see how it goes performance-wise.
Basile Clement
Comment 11 2015-07-24 11:15:05 PDT
Created attachment 257459 [details] Benchmark results
Basile Clement
Comment 12 2015-07-24 11:17:51 PDT
Basile Clement
Comment 13 2015-07-24 11:20:44 PDT
(In reply to comment #11) > Created attachment 257459 [details] > Benchmark results This is essentially perf neutral except for: math-partial-sums 7.0537+-0.0603 ! 7.5745+-0.0369 ! definitely 1.0738x slower math-partial-sums 696.1862+-8.3549 ! 749.4553+-3.7285 ! definitely 1.0765x slower in SunSpider and LongSpider, which is weird because all of the calls to Math.pow in that benchmark should be using the fast paths AFAICT.
Geoffrey Garen
Comment 14 2015-07-24 11:34:42 PDT
Comment on attachment 257460 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=257460&action=review > Source/JavaScriptCore/ChangeLog:12 > + Baseline JIT, DFG and FTL are using a fast exponentiation fast path > + when computing Math.pow() with an integer exponent that is not taken in > + the LLInt (or the DFG abstract interpreter). This leads to the result > + of pow changing depending on the compilation tier or the fact that > + constant propagation kicks in, which is undesirable. Why is it OK for fast exponentiation to produce a result that a different from slow exponentiation? Isn't that a bug in fast exponentiation? Under what conditions is the result different?
Basile Clement
Comment 15 2015-07-24 11:53:10 PDT
(In reply to comment #14) > Comment on attachment 257460 [details] > Patch > > View in context: > https://bugs.webkit.org/attachment.cgi?id=257460&action=review > > > Source/JavaScriptCore/ChangeLog:12 > > + Baseline JIT, DFG and FTL are using a fast exponentiation fast path > > + when computing Math.pow() with an integer exponent that is not taken in > > + the LLInt (or the DFG abstract interpreter). This leads to the result > > + of pow changing depending on the compilation tier or the fact that > > + constant propagation kicks in, which is undesirable. > > Why is it OK for fast exponentiation to produce a result that a different > from slow exponentiation? Isn't that a bug in fast exponentiation? Under > what conditions is the result different? The fast path is using fast exponentiation[0] when the exponent is an integer, which would yield the same result (except faster) than the usual std::pow() function in a perfect and ideal mathematical world. But, we are using floating point math, and thus these two ways of computing pow() yield different approximations of the exponentiation (which can't usually be represented exactly as a double anyway). Note that the ES6 spec[1] (as well as IEEE 754) only says that Math.pow(x, y) should return "an implementation-dependent approximation to the result of raising x to the power y." It is perfectly fine for us to choose that approximation to be the result of the fast exponentiation algorithm when the exponent is an integer, and the result of std::pow() otherwise - we just should be consistent in our choice. 0: https://en.wikipedia.org/wiki/Exponentiation_by_squaring 1: http://www.ecma-international.org/ecma-262/6.0/index.html#sec-math.pow
Basile Clement
Comment 16 2015-07-24 11:56:09 PDT
(In reply to comment #15) > (In reply to comment #14) > > Comment on attachment 257460 [details] > > Patch > > > > View in context: > > https://bugs.webkit.org/attachment.cgi?id=257460&action=review > > > > > Source/JavaScriptCore/ChangeLog:12 > > > + Baseline JIT, DFG and FTL are using a fast exponentiation fast path > > > + when computing Math.pow() with an integer exponent that is not taken in > > > + the LLInt (or the DFG abstract interpreter). This leads to the result > > > + of pow changing depending on the compilation tier or the fact that > > > + constant propagation kicks in, which is undesirable. > > > > Why is it OK for fast exponentiation to produce a result that a different > > from slow exponentiation? Isn't that a bug in fast exponentiation? Under > > what conditions is the result different? > > The fast path is using fast exponentiation[0] when the exponent is an > integer, which would yield the same result (except faster) than the usual > std::pow() function in a perfect and ideal mathematical world. But, we are > using floating point math, and thus these two ways of computing pow() yield > different approximations of the exponentiation (which can't usually be > represented exactly as a double anyway). > > Note that the ES6 spec[1] (as well as IEEE 754) only says that Math.pow(x, > y) should return "an implementation-dependent approximation to the result of > raising x to the power y." It is perfectly fine for us to choose that > approximation to be the result of the fast exponentiation algorithm when the > exponent is an integer, and the result of std::pow() otherwise - we just > should be consistent in our choice. Just to be clear - the spec mandates particular values for the infinity, zero and NaN cases, with which the fast exponentiation algorithm is consistent.
Filip Pizlo
Comment 17 2015-07-28 11:45:27 PDT
Comment on attachment 257460 [details] Patch LGTM.
Geoffrey Garen
Comment 18 2015-07-28 11:46:16 PDT
Comment on attachment 257460 [details] Patch r=me Thanks for the explanation.
Basile Clement
Comment 19 2015-07-28 11:52:53 PDT
Simon Fraser (smfr)
Comment 20 2015-07-28 20:18:38 PDT
Basile Clement
Comment 21 2015-07-29 10:29:38 PDT
(In reply to comment #20) > This caused 6 JSC tests to fail on Windows: > https://build.webkit.org/builders/Apple%20Win%207%20Debug%20%28Tests%29/ > builds/67036/steps/jscore-test/logs/stdio That is the test added in this patch timing out on debug builds. I'll lower the number of test iterations by 10x, hopefully that should be enough to not time out.
Basile Clement
Comment 22 2015-07-29 10:46:15 PDT
(In reply to comment #21) > (In reply to comment #20) > > This caused 6 JSC tests to fail on Windows: > > https://build.webkit.org/builders/Apple%20Win%207%20Debug%20%28Tests%29/ > > builds/67036/steps/jscore-test/logs/stdio > > That is the test added in this patch timing out on debug builds. I'll lower > the number of test iterations by 10x, hopefully that should be enough to not > time out. Hopefully fixed by http://trac.webkit.org/changeset/187548
Basile Clement
Comment 23 2015-07-29 12:23:32 PDT
This test is now failing on some platforms due to not always being compiled up to the fourth tier. I skipped it temporarily in http://trac.webkit.org/changeset/187551 while I figure out how to simplify the test while still testing what it should test.
Note You need to log in before you can comment on or make changes to this bug.