Skip to content
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

ES2020 BigInt #837

Merged
merged 39 commits into from
May 4, 2021
Merged

ES2020 BigInt #837

merged 39 commits into from
May 4, 2021

Conversation

tuchida
Copy link
Contributor

@tuchida tuchida commented Feb 13, 2021

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt

Compatibility

Raw java.math.BigInteger is now handled as JavaScript BigInt.
This leads to the following problems.

final String script = "d + bi";
Utils.runWithAllOptimizationLevels(cx -> {
        cx.setLanguageVersion(Context.VERSION_ES6);

        Scriptable scope = cx.initStandardObjects();
        scope.put("d", scope, Double.valueOf(123));
        scope.put("bi", scope, new BigInteger("234"));

        Object result =  cx.evaluateString(scope, script, "", 1, null);
        Assert.assertEquals(Double.valueOf(357), result);
        return null;
});

It used to be interpreted as 123 + 234, but now it is interpreted as 123 + 234n, which results in an error. This will happen regardless of the LanguageVersion. It may be able to work around this by version checking if always pass the Context to ScriptRuntime.

Benchmark

master

SunSpiderBenchmark.accessBinaryTrees     avgt    5    7339.020 ±  163.986  us/op
SunSpiderBenchmark.accessFannkuch        avgt    5   24465.875 ±  486.237  us/op
SunSpiderBenchmark.accessNBody           avgt    5   18369.340 ±  834.781  us/op
SunSpiderBenchmark.accessNsieve          avgt    5    3967.743 ±   83.396  us/op
SunSpiderBenchmark.bitops3BitBitsInByte  avgt    5    2434.747 ±   69.952  us/op
SunSpiderBenchmark.bitopsBitsInByte      avgt    5   11837.858 ± 1274.065  us/op
SunSpiderBenchmark.bitopsBitwiseAnd      avgt    5   24415.034 ±  450.579  us/op
SunSpiderBenchmark.bitopsNsieveBits      avgt    5   19733.613 ±  825.524  us/op
SunSpiderBenchmark.controlflowRecursive  avgt    5    2924.783 ±   62.720  us/op
SunSpiderBenchmark.cryptoAes             avgt    5   10444.501 ±  305.440  us/op
SunSpiderBenchmark.cryptoMd5             avgt    5    7597.363 ±  538.611  us/op
SunSpiderBenchmark.cryptoSha1            avgt    5    5274.564 ±  555.156  us/op
SunSpiderBenchmark.dateFormatToFte       avgt    5   17574.064 ±  786.713  us/op
SunSpiderBenchmark.dateFormatXparb       avgt    5   24559.370 ± 2241.143  us/op
SunSpiderBenchmark.mathCordic            avgt    5    9568.241 ±  756.833  us/op
SunSpiderBenchmark.mathPartialSums       avgt    5   21454.715 ± 2229.847  us/op
SunSpiderBenchmark.mathSpectralNorm      avgt    5   10544.219 ±   77.957  us/op
SunSpiderBenchmark.regexpDna             avgt    5  110944.817 ± 1671.568  us/op
SunSpiderBenchmark.stringBase64          avgt    5   24295.712 ± 1267.732  us/op
SunSpiderBenchmark.stringFasta           avgt    5   29006.286 ±  533.307  us/op
SunSpiderBenchmark.stringTagcloud        avgt    5   37410.147 ±  825.179  us/op
SunSpiderBenchmark.stringUnpackCode      avgt    5   27798.423 ± 2576.532  us/op
SunSpiderBenchmark.stringValidateInput   avgt    5   16280.353 ± 1294.283  us/op
SunSpiderBenchmark.threeDCube            avgt    5   21931.051 ±  290.129  us/op
SunSpiderBenchmark.threeDMorph           avgt    5   14004.732 ±  513.980  us/op
SunSpiderBenchmark.threeDRayTrace        avgt    5   15150.070 ±  209.269  us/op

V8Benchmark.boyer          avgt    5  70481.542 ±  262.282  us/op
V8Benchmark.cryptoDecrypt  avgt    5  87530.566 ± 1282.020  us/op
V8Benchmark.cryptoEncrpyt  avgt    5   4824.302 ±   99.392  us/op
V8Benchmark.deltaBlue      avgt    5   6164.140 ±   54.848  us/op
V8Benchmark.earley         avgt    5  20443.091 ±  462.214  us/op
V8Benchmark.rayTrace       avgt    5  40234.364 ± 2703.675  us/op
V8Benchmark.richards       avgt    5   2912.571 ±   10.596  us/op
V8Benchmark.splay          avgt    5   2007.140 ±  383.734  us/op

bigint

SunSpiderBenchmark.accessBinaryTrees     avgt    5    6857.694 ±  115.087  us/op
SunSpiderBenchmark.accessFannkuch        avgt    5   25297.146 ±  358.333  us/op
SunSpiderBenchmark.accessNBody           avgt    5   17830.198 ± 1607.994  us/op
SunSpiderBenchmark.accessNsieve          avgt    5    3661.630 ±  410.265  us/op
SunSpiderBenchmark.bitops3BitBitsInByte  avgt    5    1970.694 ±   57.911  us/op
SunSpiderBenchmark.bitopsBitsInByte      avgt    5   12308.511 ±  132.202  us/op
SunSpiderBenchmark.bitopsBitwiseAnd      avgt    5   22380.243 ±  531.675  us/op
SunSpiderBenchmark.bitopsNsieveBits      avgt    5   15186.223 ±  685.243  us/op
SunSpiderBenchmark.controlflowRecursive  avgt    5    2983.580 ±   30.732  us/op
SunSpiderBenchmark.cryptoAes             avgt    5   10921.327 ±  123.993  us/op
SunSpiderBenchmark.cryptoMd5             avgt    5    8026.452 ±  691.531  us/op
SunSpiderBenchmark.cryptoSha1            avgt    5    5661.606 ±  427.463  us/op
SunSpiderBenchmark.dateFormatToFte       avgt    5   17969.765 ±  314.480  us/op
SunSpiderBenchmark.dateFormatXparb       avgt    5   25856.467 ± 2411.417  us/op
SunSpiderBenchmark.mathCordic            avgt    5   10083.659 ±  980.310  us/op
SunSpiderBenchmark.mathPartialSums       avgt    5   22052.303 ±  724.467  us/op
SunSpiderBenchmark.mathSpectralNorm      avgt    5   12104.923 ±  434.196  us/op
SunSpiderBenchmark.regexpDna             avgt    5  107707.159 ± 7605.636  us/op
SunSpiderBenchmark.stringBase64          avgt    5   23835.379 ±  535.228  us/op
SunSpiderBenchmark.stringFasta           avgt    5   29075.257 ±  546.614  us/op
SunSpiderBenchmark.stringTagcloud        avgt    5   37760.985 ±  789.787  us/op
SunSpiderBenchmark.stringUnpackCode      avgt    5   29698.743 ±  717.608  us/op
SunSpiderBenchmark.stringValidateInput   avgt    5   16533.185 ±  278.765  us/op
SunSpiderBenchmark.threeDCube            avgt    5   22863.344 ±  459.779  us/op
SunSpiderBenchmark.threeDMorph           avgt    5   13546.212 ±  167.729  us/op
SunSpiderBenchmark.threeDRayTrace        avgt    5   15353.174 ±  224.194  us/op

V8Benchmark.boyer          avgt    5   68474.731 ± 3457.683  us/op
V8Benchmark.cryptoDecrypt  avgt    5  117865.924 ± 2687.926  us/op
V8Benchmark.cryptoEncrpyt  avgt    5    4895.171 ±  371.395  us/op
V8Benchmark.deltaBlue      avgt    5    6215.524 ±  255.234  us/op
V8Benchmark.earley         avgt    5   21386.777 ±  584.409  us/op
V8Benchmark.rayTrace       avgt    5   43347.104 ±  777.577  us/op
V8Benchmark.richards       avgt    5    2849.553 ±  141.519  us/op
V8Benchmark.splay          avgt    5    2041.655 ±  248.059  us/op

These two things seem to have changed a lot.

SunSpiderBenchmark.bitopsNsieveBits: 19733.613 => 15186.223
V8Benchmark.cryptoDecrypt: 87530.566 => 117865.924

@@ -529,6 +529,7 @@ private static int findExpressionType(OptFunctionNode fn, Node n,
case Token.ARRAYCOMP:
case Token.ARRAYLIT:
case Token.OBJECTLIT:
case Token.BIGINT:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wasn't sure if BigInt should be AnyType or not.
Should these also be AnyType as the result can be BigInt?

case Token.INC:
case Token.DEC:
case Token.MUL:
case Token.DIV:
case Token.MOD:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
case Token.BITNOT:
case Token.LSH:
case Token.RSH:
case Token.URSH:
case Token.SUB:
case Token.POS:
case Token.NEG:
return Optimizer.NumberType;

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not sure how effective this part of the optimization is, but I'm sure that a BigInteger is wayy more expensive in Java than the other numeric types, and that it's going to come up a lot less often. We definitely want to avoid having all other numeric types end up going down a code path that assumes that everything might be a BigInteger. So, based on what I know about this class, I'd leave it like you have it here.

@@ -48,12 +48,12 @@ public void primitiveWrapFalse() {
test(false, new Integer(1), "number", "number", "number");
test(false, new Long(2L), "number", "number", "number");

// I want to treat BigInteger / BigDecimal as BigInteger / BigDecimal. But fails.
test(false, new BigInteger("30"), "number", "object", "object");
test(false, new BigInteger("30"), "bigint", "bigint", "bigint");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Compatibility issue.

Copy link
Collaborator

@gbrail gbrail left a comment

Choose a reason for hiding this comment

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

Thanks! This is a very thorough implementation and it seems to do all the things. I have some smaller comments, in this review. If it didn't affect performance it'd be almost ready to go.

Other than that, the performance drop-off worries me. It might be because this PR replaces some arithmetic operations that were previously a single bytecode with a ScriptRuntime invocation that first does a few instanceof checks. Those are hot code paths so it's hard to optimize, but at the same time JavaScript doesn't give us much choice but to assume that any number might be a BigInt.

@@ -138,8 +138,17 @@
Icode_GENERATOR_RETURN = -65,
Icode_YIELD_STAR = -66,

// BigInt
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is a lot of new icodes for a new feature -- could you comment here, or in the "addBigInt" code above, what these all are and why they're different?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is that okay?

// Load BigInt register to prepare for the following BigInt operation

src/org/mozilla/javascript/Interpreter.java Outdated Show resolved Hide resolved
src/org/mozilla/javascript/NativeBigInt.java Outdated Show resolved Hide resolved
*/
final class NativeBigInt extends IdScriptableObject
{
private static final long serialVersionUID = 1L;
Copy link
Collaborator

Choose a reason for hiding this comment

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

We still use serialization, so please generate a real UID using "serialver".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

$ serialver -classpath buildGradle/libs/rhino-1.7.14-SNAPSHOT.jar org.mozilla.javascript.NativeBigInt

private static final long serialVersionUID = 1335609231306775449L;

protected int findPrototypeId(String s)
{
int id;
// #generated# Last update: 2007-05-09 08:15:50 EDT
Copy link
Collaborator

Choose a reason for hiding this comment

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

I assume that you ran "IdMap" on this source recently, right? Or have you really been working on this PR for a long time? Otherwise, the tool:

org.mozilla.javascript.tools.idswitch.Main

will update this table for you.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

// #generated# Last update: 2021-03-07 11:48:35 JST

Is this really fast? I feel it would be easier for the JVM to optimize using Strings in switch in Java7.
https://docs.oracle.com/javase/8/docs/technotes/guides/language/strings-switch.html

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ref. #847

@@ -124,116 +124,116 @@
REF_MEMBER = 78, // Reference for x.@y, x..y etc.
REF_NS_MEMBER = 79, // Reference for x.ns::y, x..ns::y etc.
REF_NAME = 80, // Reference for @y, @[y] etc.
REF_NS_NAME = 81; // Reference for ns::y, @ns::y@[y] etc.
REF_NS_NAME = 81, // Reference for ns::y, @ns::y@[y] etc.
BIGINT = 82; // ES2020 BigInt
Copy link
Collaborator

Choose a reason for hiding this comment

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

We now have two PRs in the pipeline that rearrange most of this table and it's going to be painful to merge or unmerge if we have to do that someday. Wouldn't it be easier to assign the highest value (168) to this new token?

Copy link
Contributor Author

@tuchida tuchida Feb 20, 2021

Choose a reason for hiding this comment

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

Is it possible to change this to be defined enum or like iota in go?
https://golang.org/ref/spec#Iota

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I fixed.

BIGINT = 83; // ES2020 BigInt

@@ -529,6 +529,7 @@ private static int findExpressionType(OptFunctionNode fn, Node n,
case Token.ARRAYCOMP:
case Token.ARRAYLIT:
case Token.OBJECTLIT:
case Token.BIGINT:
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not sure how effective this part of the optimization is, but I'm sure that a BigInteger is wayy more expensive in Java than the other numeric types, and that it's going to come up a lot less often. We definitely want to avoid having all other numeric types end up going down a code path that assumes that everything might be a BigInteger. So, based on what I know about this class, I'd leave it like you have it here.


switch (type) {
case Token.SUB:
addScriptRuntimeInvoke("subtract", "(Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number;");
Copy link
Collaborator

Choose a reason for hiding this comment

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

I bet that this is where some of the performance regressions might be coming in -- before, in most cases (because I have rarely seen the "numeric optimization" above really work -- we convert both operands to a double and then do the operation in one bytecode. This replaces all of those with a call to a function that does a bunch of instanceof checks before invoking the instruction.

I wonder if this would benchmark better if we first called a method that checked the operand types for big-integerness in the most efficient way we can, then either used the old code path or the new one. Eventually invokedynamic could be used to make the same thing happen.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks. Sorry for the late reply.

I too have concerns about performance. However, I couldn't figure out what was slowed down by this change.

The bigint branch and master were each measured five times by ./gradlew testBenchmark.
https://docs.google.com/spreadsheets/d/1sYM7EhGONY5-oQLFjuHwJou4DZEwJ7o9KCGB3-jKisc/edit?usp=sharing

There doesn't seem to be much difference except for SunSpiderBenchmark.bitopsNsieveBits. (SunSpiderBenchmark.bitopsNsieveBits is it getting faster?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you have any data or samples that show that using invokedynamic is faster?
How does invokedynamic work on Android?

}
else {
boolean childOfArithmetic = isArithmeticNode(parent);
generateExpression(child, node);
if (!isArithmeticNode(child))
addObjectToDouble();
addObjectToNumeric();
Copy link
Contributor Author

@tuchida tuchida Feb 20, 2021

Choose a reason for hiding this comment

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

(print(1), { valueOf(){ print(2)}}) - (print(3), { valueOf(){ print(4)}})
// actual
// 1
// 2
// 3
// 4

// expect
// 1
// 3
// 2
// 4

This is correct.

generateExpression(child, node);
generateExpression(child.getNext(), node);
short reg = getNewWordLocal();
cfw.addAStore(reg);
addObjectToDouble();
cfw.addALoad(reg);
addObjectToDouble();

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We need to figure out what to do with isArithmeticNode, so we'll leave that for next time.

Copy link
Collaborator

Choose a reason for hiding this comment

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

If there are performance regressions here, then I think this is where they'd come, because there is at least a theoretical optimization here that we're partially undoing. If you have any ideas how to keep the old code path in some more cases then I'd be more comfortable. For instance, if "childOfArithmetic" is true at all, can we assume that there is no BigInteger object involved? but I might be OK just doing it this way and then trying to restore the old optimizations later.

@@ -124,116 +124,116 @@
REF_MEMBER = 78, // Reference for x.@y, x..y etc.
REF_NS_MEMBER = 79, // Reference for x.ns::y, x..ns::y etc.
REF_NAME = 80, // Reference for @y, @[y] etc.
REF_NS_NAME = 81; // Reference for ns::y, @ns::y@[y] etc.
REF_NS_NAME = 81, // Reference for ns::y, @ns::y@[y] etc.
BIGINT = 82; // ES2020 BigInt
Copy link
Contributor Author

@tuchida tuchida Feb 20, 2021

Choose a reason for hiding this comment

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

Is it possible to change this to be defined enum or like iota in go?
https://golang.org/ref/spec#Iota

@gbrail
Copy link
Collaborator

gbrail commented Mar 6, 2021 via email

@tuchida tuchida force-pushed the es2020-bigint branch 2 times, most recently from 209b7a3 to 3e5eea4 Compare March 7, 2021 11:11
@tuchida
Copy link
Contributor Author

tuchida commented Mar 7, 2021

Here's what I'm doing now.

if (val1 instanceof BigInteger && val2 instanceof BigInteger) {
  // calc
} else if (val1 instanceof Number && val2 instanceof Number) {
  // calc
}

It may be faster this way.

if (val1 instanceof Double && val2 instanceof Double) {
  // calc
} else if (val1 instanceof BigInteger && val2 instanceof BigInteger) {
  // calc
} else if (val1 instanceof Number && val2 instanceof Number) {
  // calc
}

@tuchida
Copy link
Contributor Author

tuchida commented Mar 13, 2021

SunSpiderBenchmark.bitopsNsieveBits

Benchmark                            Mode  Cnt      Score     Error  Units
SunSpiderBenchmark.bitopsNsieveBits  avgt    5  18908.779 ± 148.335  us/op

commit 5181accd8a5eeb8304c5efda7840798ac4b16f35 (HEAD)
    bit operations for BigInt
Benchmark                            Mode  Cnt      Score     Error  Units
SunSpiderBenchmark.bitopsNsieveBits  avgt    5  14542.023 ± 286.907  us/op

commit c8656ddb467a48d3a666ada3c2a9d0e9c63f97bc (HEAD)
    bitwise NOT for BigInt

c8656dd seems to have made it faster.
https://github.com/mozilla/rhino/blob/c8656ddb467a48d3a666ada3c2a9d0e9c63f97bc/testsrc/benchmarks/sunspider-0.9.1/bitops-nsieve-bits.js

@tuchida
Copy link
Contributor Author

tuchida commented Mar 13, 2021

b525566 This is faster.

Benchmark                                Mode  Cnt       Score      Error  Units
SunSpiderBenchmark.accessBinaryTrees     avgt    5    6896.580 ±  349.558  us/op
SunSpiderBenchmark.accessFannkuch        avgt    5   25249.452 ± 1179.327  us/op
SunSpiderBenchmark.accessNBody           avgt    5   18699.092 ±  727.417  us/op
SunSpiderBenchmark.accessNsieve          avgt    5    3902.934 ±  165.557  us/op
SunSpiderBenchmark.bitops3BitBitsInByte  avgt    5    2085.102 ±  200.521  us/op
SunSpiderBenchmark.bitopsBitsInByte      avgt    5   12264.252 ±  459.047  us/op
SunSpiderBenchmark.bitopsBitwiseAnd      avgt    5   22322.207 ±  842.092  us/op
SunSpiderBenchmark.bitopsNsieveBits      avgt    5   10660.768 ±  426.337  us/op
SunSpiderBenchmark.controlflowRecursive  avgt    5    2946.532 ±  204.685  us/op
SunSpiderBenchmark.cryptoAes             avgt    5   10951.822 ±  315.705  us/op
SunSpiderBenchmark.cryptoMd5             avgt    5    8082.551 ±  399.776  us/op
SunSpiderBenchmark.cryptoSha1            avgt    5    5523.482 ±   77.351  us/op
SunSpiderBenchmark.dateFormatToFte       avgt    5   18337.982 ± 2044.831  us/op
SunSpiderBenchmark.dateFormatXparb       avgt    5   25343.045 ± 3758.116  us/op
SunSpiderBenchmark.mathCordic            avgt    5   10282.664 ±  515.868  us/op
SunSpiderBenchmark.mathPartialSums       avgt    5   22594.311 ± 1290.731  us/op
SunSpiderBenchmark.mathSpectralNorm      avgt    5   11819.411 ±  874.842  us/op
SunSpiderBenchmark.regexpDna             avgt    5  111098.051 ± 7397.927  us/op
SunSpiderBenchmark.stringBase64          avgt    5   23172.763 ±  856.285  us/op
SunSpiderBenchmark.stringFasta           avgt    5   28796.824 ±  339.958  us/op
SunSpiderBenchmark.stringTagcloud        avgt    5   37317.970 ± 1359.425  us/op
SunSpiderBenchmark.stringUnpackCode      avgt    5   27426.340 ±  596.161  us/op
SunSpiderBenchmark.stringValidateInput   avgt    5   17481.931 ± 1348.579  us/op
SunSpiderBenchmark.threeDCube            avgt    5   22605.476 ± 3175.912  us/op
SunSpiderBenchmark.threeDMorph           avgt    5   11958.454 ± 1150.110  us/op
SunSpiderBenchmark.threeDRayTrace        avgt    5   16087.591 ± 1250.476  us/op

Benchmark                  Mode  Cnt      Score      Error  Units
V8Benchmark.boyer          avgt   10  70875.019 ± 1976.893  us/op
V8Benchmark.cryptoDecrypt  avgt   10  89292.307 ± 2279.035  us/op
V8Benchmark.cryptoEncrpyt  avgt   10   4724.938 ±  106.322  us/op
V8Benchmark.deltaBlue      avgt   10   6032.947 ±   78.235  us/op
V8Benchmark.earley         avgt   10  19685.939 ±  714.160  us/op
V8Benchmark.rayTrace       avgt   10  42013.522 ± 1908.857  us/op
V8Benchmark.richards       avgt   10   2985.615 ±   73.166  us/op
V8Benchmark.splay          avgt   10   2294.107 ±  194.044  us/op

It seems that bitopsNsieveBits is now about twice as fast as master.
Token.POS/Token.NEG also looks like it could be optimized.
Shall I create another Pull Request?

This was referenced Mar 20, 2021
@tuchida tuchida force-pushed the es2020-bigint branch 2 times, most recently from 9f0016a to 53f041d Compare April 10, 2021 00:13
@rbri
Copy link
Collaborator

rbri commented Apr 11, 2021

Token.POS/Token.NEG also looks like it could be optimized.

for me any optimization is welcome.

@gbrail - is there something that blocks merging this? While working on the 626 update i saw a lot of bigint stuff that might work if this is done

@tonygermano
Copy link
Contributor

@tuchida In PR #860 I made a more generalized message for when JSON.stringify is unable to serialize an object and throws a TypeError. I based it on the message that Chrome returns when trying to stringify a BigInt. Not sure if you would want to make that small adjustment to avoid having two separate messages for the same purpose (assuming my PR eventually gets merged.)

msg.toisostring.must.return.primitive =\
toISOString must return a primitive value, but instead returned "{0}"
# NativeJSON
msg.json.cant.serialize =\
Do not know how to serialize a {0}
# Parser
msg.got.syntax.errors = \

@tuchida
Copy link
Contributor Author

tuchida commented Apr 11, 2021

Thanks! If your PR is merged, I will fix the message.

Copy link
Collaborator

@gbrail gbrail left a comment

Choose a reason for hiding this comment

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

This is very complete and very close -- thanks for doing all the work.

Can you address a few of my small comments here, merge master again, and let me look at it one more time.

If I continue to be worried about bytecode performance (which will not affect Android BTW) then I might plan to make a few small changes on top of it myself, but we'll see then.

Thanks!

src/org/mozilla/javascript/Interpreter.java Outdated Show resolved Hide resolved
// #string_id_map#

@Override
protected int findPrototypeId(String s)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since you already worked to replace these bits with string switches, can you update this method as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The idswitch tool does not support the new coding style, so the code could not be generated correctly.
Do you mind if I correct it by hand?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

4900964
I did not use idswitch.

public static BigInteger toBigInt(Object val)
{
for (;;) {
if (val instanceof BigInteger)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd like to get to a point where we start to apply a formatting standard to the code. I think that one thing that we'll want to enforce is using brackets with conditional statements, even one-line ones. So this is a nit but please consider it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I too like the writing style you suggested.
I want to automatically check what kind of writing style I should use. ref. #863

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Apparently, the current coding style allows one-line. I have checked and corrected what I have added.

if (val instanceof Number) return ((Number) val).doubleValue();
if (val == null) return +0.0;
if (val == Undefined.instance) return NaN;


public static Number leftShift(Number val1, Number val2) {
if (val1 instanceof BigInteger && val2 instanceof BigInteger) {
// TODO: val2 is supported only in the range of 32-bit integer.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we need to enter a bug or something to fix this TODO? What will happen now if val2 is out of range?

Copy link
Contributor

Choose a reason for hiding this comment

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

https://docs.oracle.com/javase/8/docs/api/java/math/BigInteger.html#intValue--

if this BigInteger is too big to fit in an int, only the low-order 32 bits are returned

In Chrome when I try 1n << 2147483647n (java.lang.Integer.MAX_VALUE) I get a RangeError: Maximum BigInt size exceeded.

It looks like java also throws an error with numbers that large:

js> var b = new java.math.BigInteger(1)
js> b.shiftLeft(java.lang.Integer.MAX_VALUE)
org.mozilla.javascript.WrappedException: Wrapped java.lang.ArithmeticException: BigInteger would overflow supported range

If the BigInteger is larger than Ingeter.MAX_VALUE you can get some pretty wacky incorrect results:

js> new java.math.BigInteger(java.lang.Integer.MAX_VALUE).shiftLeft(1).intValue()
-2

It looks like you can use BigInteger.intValueExact() instead, which will throw an ArithmeticException if the number is too large to fit in an int.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you change it to throw RangeError if the size is out of range? That sounds quite reasonable, and then we're done. I think that's a lot more preferable to letting Java exceptions leak out.

Copy link
Contributor

Choose a reason for hiding this comment

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

I looked at it again, and have another suggestion.

I think you will want to check if val2.signum() == -1, return signedRightShift(val1, val2.negate()). Else if either val1.shiftLeft() or val2.intValueExact() throw an ArithmeticException, rethrow as a RangeError.

Likewise, in signedRightShift, if val2.signum() == -1, return leftShift(val1, val2.negate()). Else if val2.intValueExact() throws an ArithmeticException, return BigInteger.ZERO.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

13dcf61
I did this because not only intValueExact, but also shiftLeft, etc. sometimes threw ArithmeticException.
I'm checking for error patterns in testsrc/jstests/es2020/bigint.js, please let me know if I'm missing anything.

}
else {
boolean childOfArithmetic = isArithmeticNode(parent);
generateExpression(child, node);
if (!isArithmeticNode(child))
addObjectToDouble();
addObjectToNumeric();
Copy link
Collaborator

Choose a reason for hiding this comment

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

If there are performance regressions here, then I think this is where they'd come, because there is at least a theoretical optimization here that we're partially undoing. If you have any ideas how to keep the old code path in some more cases then I'd be more comfortable. For instance, if "childOfArithmetic" is true at all, can we assume that there is no BigInteger object involved? but I might be OK just doing it this way and then trying to restore the old optimizations later.

addScriptRuntimeInvoke("subtract", "(Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number;");
break;
case Token.MUL:
addScriptRuntimeInvoke("multiply", "(Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number;");
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we assume that the parameter types are Number objects for these methods? I'm not sure what we do elsewhere but it might be important that we assume that they are Objects instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The parameter passed here is a Number or BigInt by addObjectToNumeric. With Object, it is hard to know explicitly where to do ToNumeric. If you use ToNumeric in ScriptRuntime.multiply method, the evaluation order will change, which violates the specification. ref. #837 (comment)

@tuchida
Copy link
Contributor Author

tuchida commented Apr 12, 2021

Thank you for your review. I will fix it in the near future.

By the way, are you sure about the compatibility? java.math.BigInteger inherits from java.lang.Number. Until now, java.math.BigInteger has been treated as a Double. It's a buggy code with a potential error, but this time, TypeError will be raised.

@gbrail
Copy link
Collaborator

gbrail commented Apr 13, 2021

Thanks for the question -- no, I'm not sure about compatibility. I'd be more comfortable if the new capability only worked if the language version was >= ES6. However, I don't think it's worth putting that check into every arithmetic operation! That would be quite bad for performance.

For pure JavaScript code, there's no problem -- it's only a problem if someone is doing embedded code and creating BigInteger objects instead of Double or Integer objects, and then expecting to be able to mix them freely with other numbers. As you said that's actually incorrect code and to me it sounds like it'd also be very rare.

So unless you can think of a clever way to restrict this whole thing by language level I think that the risk of making this change is low.

@@ -530,6 +530,7 @@ private static int findExpressionType(OptFunctionNode fn, Node n,
case Token.ARRAYCOMP:
case Token.ARRAYLIT:
case Token.OBJECTLIT:
case Token.BIGINT:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It seems that there is still a problem with optimization.
I will fix it and redo the measurement.

// $ java -jar buildGradle/libs/rhino-1.7.14-SNAPSHOT.jar -version 200 -opt 9

const one = 1n;
(() => 1n - one)(); // js: uncaught JavaScript runtime exception: TypeError: Cannot convert BigInt to a number

@gbrail
Copy link
Collaborator

gbrail commented Apr 23, 2021

Thanks for continuing to iterate on this. Unfortunately it needs another merge and conflict resolution...

I'll hold off on doing anything for this until you tell me it's ready but I think it's very close now.

@tuchida tuchida force-pushed the es2020-bigint branch 2 times, most recently from 8f4c496 to 13dcf61 Compare April 23, 2021 23:31
@tuchida
Copy link
Contributor Author

tuchida commented Apr 23, 2021

Here's what the benchmark looks like.
https://docs.google.com/spreadsheets/d/17H7OstklzKuS7qBApHtpmD2IlQRUfEvhNUJqmxGIFYs/edit?usp=sharing

V8Benchmark.cryptoDecrypt is slower because it is faster when optimized by #851, which may have opened up the difference.
SunSpiderBenchmark.mathSpectralNorm seems to be caused by each operation being a little slower than the other, but I haven't looked into it too much.
Other than that, we are still investigating.

Speedup suggestions.

First of all, this did not work. ref. #837 (comment)
I haven't tried any other methods, but I can think of the following

  • Dynamic type inference using invokedynamic
  • More types of static type inference
  • I've only seen the benchmark. How much faster or slower will it be in real world code?

@gbrail
Copy link
Collaborator

gbrail commented Apr 24, 2021

Thanks -- let's keep at it. I have several experiments that have tried to use invokedynamic and none have helped yet, but with biginteger support in there it might. I'll see if I can find time next week to try them out.

@rbri
Copy link
Collaborator

rbri commented Apr 27, 2021

@tuchida sorry but i fear you have to adapt at least the TokenStream a bit because of the new numeric separator support - hope we have the bigint stuff finally in soon.

@tonygermano
Copy link
Contributor

What is the status of this? Should we put a pause on PRs that create merge conflicts until this one goes in?

@gbrail
Copy link
Collaborator

gbrail commented May 3, 2021 via email

@@ -305,6 +317,9 @@ private static Object str(Object key, Scriptable holder,
}

if (value instanceof Number) {
if (value instanceof BigInteger) {
throw ScriptRuntime.typeErrorById("msg.cant.json.serialized.from.bigint");
Copy link
Contributor

Choose a reason for hiding this comment

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

This has been merged in now if you would like to use the same message.

msg.toisostring.must.return.primitive =\
toISOString must return a primitive value, but instead returned "{0}"
# NativeJSON
msg.json.cant.serialize =\
Do not know how to serialize a {0}
# Parser
msg.got.syntax.errors = \

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks! This has been fixed.
56d3802#diff-ecefe1882290fb24e0b96edd98a83e5c7446d94a851ceeb829c94b94dc30bb10R334

I'm sorry that I rebased it so many times that it was hard to understand the additional corrections I made.

@@ -519,6 +519,9 @@ msg.destruct.default.vals =\
msg.no.old.octal.strict =\
Old octal numbers prohibited in strict mode.

msg.no.old.octal.bigint =\
Old octal numbers prohibited in BigInt.

Copy link
Contributor

@tonygermano tonygermano May 4, 2021

Choose a reason for hiding this comment

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

I don't think this message is necessary. Below is the current behavior, and it is only triggered in strict mode. I think the existing message is good enough, and you don't need a special case for bigint in the parser.

js> (function() {return 013})()
11
js> (function() {"use strict"; return 013})()
js: "<stdin>", line 24: Old octal numbers prohibited in strict mode.
js: (function() {"use strict"; return 013})()
js: .....................................^
js: "<stdin>", line 24: missing } after function body
js: (function() {"use strict"; return 013})()
js: ........................................^
js: "<stdin>", line 24: Compilation produced 2 syntax errors.

js> (function() {return 013n})() // bigint conversion to octal when not in strict mode
11
js> (function() {"use strict"; return 013n})()
js: "<stdin>", line 26: Old octal numbers prohibited in BigInt.
js: (function() {"use strict"; return 013n})()
js: ......................................^
js: "<stdin>", line 26: missing } after function body
js: (function() {"use strict"; return 013n})()
js: .........................................^
js: "<stdin>", line 26: Compilation produced 2 syntax errors.

Technically, the spec doesn't allow legacy octal notation at all, but lists it as a browser extension when not in strict mode. Chrome supports it in the same way that we do for numbers, but reports a generic Syntax Error for bigints.

Node.js reports a generic Syntax Error for both numbers and bigints, even when not in strict mode, per the spec. I think we should match this behavior for language version 200+. I would open an issue for this after merging, though. I don't think it needs to stop this PR from going in.

Copy link
Contributor

Choose a reason for hiding this comment

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

The rest of the messages look fine to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There seems to have been a mistake when I fixed the conflicts with #814.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's working well enough to merge now, and then fix after. Existing code won't break, because it previously would not parse at all.

Copy link
Collaborator

Choose a reason for hiding this comment

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

That's fine with me -- like we said the "123n" syntax didn't work at all before.

@gbrail
Copy link
Collaborator

gbrail commented May 4, 2021

This is great. Thanks for all the hard work!

@gbrail gbrail merged commit 9638cf1 into mozilla:master May 4, 2021
@rbri
Copy link
Collaborator

rbri commented May 8, 2021

Just a note about performance here. Did the merge into the HtmlUnit fork some days ago and rerun the whole HtmlUnit test suite (including various js framwork test suites. The whole test runs for > 3h and did not show any notable difference to the versions before - in terms of runtime.

Great work and thanks to @tuchida, @tonygermano and @gbrail. Nice too see all the progress here - this is really motivating.

@tuchida tuchida deleted the es2020-bigint branch May 27, 2021 08:13
@p-bakker p-bakker added this to the Release 1.7.14 milestone Oct 13, 2021
@p-bakker p-bakker added the feature Issues considered a new feature label Oct 13, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Issues considered a new feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants