Skip to content

Add support for constant_dynamic with SymbolKey as an example. - #2461

Open
aardvark179 wants to merge 8 commits into
mozilla:masterfrom
aardvark179:aardvark179-const-dynamic
Open

aardvark179 wants to merge 8 commits into
mozilla:masterfrom
aardvark179:aardvark179-const-dynamic

Conversation

@aardvark179

Copy link
Copy Markdown
Contributor

When working on the lazy source provider API I wanted to be able to easily create a constant SourceCodeProvider and have that de-duplicated by the class writer. The class file format allows for these constant dynamics in a similar manner to invoke dynamic bootstraps.

This prototype is a little rough, Claude wrote some of it but got some of the symbol stuff very wrong so I mostly rewrote that and edited a bunch of the related tests. SymbolKey was chosen as a reasonable tractable example that we might conceivably want, but EagerSourceCodeProvider and other more complex constants would be my real uses for this.

@aardvark179
aardvark179 force-pushed the aardvark179-const-dynamic branch 2 times, most recently from 8ee5ea8 to 770d922 Compare August 6, 2026 16:39
@aardvark179
aardvark179 marked this pull request as ready for review August 6, 2026 18:01
@aardvark179

Copy link
Copy Markdown
Contributor Author

I've tried adding regexps to the literals we can handle, and the results look pretty positive. In the V8 regexp benchmark we see a good reduction in recompilations. Before:

Benchmark           (evalMethod)  Mode  Cnt       Score        Error  Units
V8Benchmark.regExp      Compiler  avgt   20  395050.021 ± 638710.572  us/op

and after:

Benchmark           (evalMethod)  Mode  Cnt       Score        Error  Units
V8Benchmark.regExp      Compiler  avgt   20  272335.450 ± 329153.448  us/op

Not quite at the level of benchmark that measures anything very useful, yet, but at least we're reducing the time and variance caused by the JIT thrashing away.

I tried looking at whether we could pull the same sort of thing for JSDescriptors, but at the moment we have to initialise the entire tree eagerly, and have to encode a number of arrays as part of the descriptor, and the descriptor would need to be fully built before we could turn it into a constant. Cutting that knot seems tricky.

@gbrail

gbrail commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

This is very interesting. I think we should merge this just because of the improvement it makes to regexp stuff. I'm not sure where else it helps since so many JavaScript constants are for things like big precomputed objects but I'm certainly willing to find out.

Any reason we shouldn't just do this?

@aardvark179 aardvark179 changed the title PoC: Add support for constant_dynamic with SymbolKey as an example. Add support for constant_dynamic with SymbolKey as an example. Aug 13, 2026
@aardvark179
aardvark179 force-pushed the aardvark179-const-dynamic branch from a93ae5f to 5341889 Compare August 15, 2026 20:06

@gbrail gbrail left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

My AI very diligently churned on this for a long time but there is a real bug here.

It turns out that this breaks "jsc" compilation for scripts with regular expressions in them. (Before, though, compilation worked but the scripts themselves did not -- we need some tests for this!)

Given a script "t.js" that has a constant regexp:

 java -classpath rhino-all/build/libs/rhino-all-2.0.0-SNAPSHOT.jar org.mozilla.javascript.tools.jsc.Main -o out t.js

will throw IllegalStateException.

(Now, prior to this PR, the same compilation of a script would throw NullPointerException, but only at runtime, but we need to fix that too!)

Thanks!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Apparently we don't get to this code any more in the PR, but this refers to the "_reInit" method which apparently was removed in this PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I've removed that code.

* regexp implementation reports anything wrong with an expression now, while there is still a
* script being compiled to report it against. If the implementation has no constant form, or
* there is no implementation at all, nothing is prepared and the literals are compiled when the
* class is defined by {@link #emitRegExpInit} instead.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Broken doc link

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

if (constant != null) {
cfw.addLoadDynamicConstant(constant);
} else {
throw new IllegalStateException("null rgexp constant.");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

typo in "rgexp", or at least according to common shortening of "regexp!"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

SYMBOL_REGULAR,
value.getName(),
CD_SYMBOL_KEY,
Integer.valueOf(System.identityHashCode(value)));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is there a theoretical possibility of a collision on "identityHashCode"? I mean not back in the days of 32-bit systems, but I think yes. How do we fall back in that case?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We'd only see a clash on heaps too large to use 32-bit references, which is okay up to a 32GB heap (because object alignment allows for compact references). We can theoretically get a clash in heaps above that, but for that to be a problem we'd need two symbols with the same name that are intended to be distinct to have an identity hash code collision.

If we're really worried then we could key a weak hash set of symbols, and if the symbol isn't in there then increment a counter and use that.

@aardvark179 aardvark179 mentioned this pull request Sep 10, 2026
@aardvark179
aardvark179 force-pushed the aardvark179-const-dynamic branch from b8a891c to cc3409a Compare September 10, 2026 13:31
@aardvark179

Copy link
Copy Markdown
Contributor Author

I've expanded our class compiler test to cover regexes and template literals, and fixed up class compilation so these pass.

@gbrail

gbrail commented Sep 19, 2026

Copy link
Copy Markdown
Collaborator

Haven't forgotten about this! These break the "threejs" test case in the "rhino-benchmarks" repo:

  1. [24] benchmarkName=threejs (org.brail.rhinobenchmarks.test.StandardBenchmarkTest)
    org.mozilla.classfile.ClassFileWriter$ClassFileFormatException: negative operand
    at org.mozilla.classfile.ClassFileWriter.add(ClassFileWriter.java:581)
    at org.mozilla.classfile.ClassFileWriter.addLoadConstant(ClassFileWriter.java:980)
    at org.mozilla.classfile.ClassFileWriter.addLoadDynamicConstant(ClassFileWriter.java:974)
    at org.mozilla.javascript.optimizer.BodyCodegen.generateExpression(BodyCodegen.java:1219)
    at org.mozilla.javascript.optimizer.BodyCodegen.visitSetVar(BodyCodegen.java:4590)
    at org.mozilla.javascript.optimizer.BodyCodegen.generateStatement(BodyCodegen.java:871)
    at org.mozilla.javascript.optimizer.BodyCodegen.generateStatement(BodyCodegen.java:682)
    at org.mozilla.javascript.optimizer.BodyCodegen.generateStatement(BodyCodegen.java:682)
    at org.mozilla.javascript.optimizer.BodyCodegen.generateBodyCode(BodyCodegen.java:58)
    at org.mozilla.javascript.optimizer.Codegen.generateCode(Codegen.java:467)
    at org.mozilla.javascript.optimizer.Codegen.compileToClassFile(Codegen.java:248)
    at org.mozilla.javascript.optimizer.Codegen.doCompileInt(Codegen.java:153)
    at org.mozilla.javascript.optimizer.Codegen.doCompile(Codegen.java:124)
    at org.mozilla.javascript.optimizer.Codegen.compileScript(Codegen.java:108)
    at org.mozilla.javascript.Context.compileImpl(Context.java:2731)
    at org.mozilla.javascript.Context.compileScriptImpl(Context.java:2655)
    at org.mozilla.javascript.Context.compileScript(Context.java:1562)
    at org.mozilla.javascript.Context.evaluateScript(Context.java:1209)
    at org.mozilla.javascript.Context.evaluateReader(Context.java:1269)
    at org.brail.rhinobenchmarks.BenchmarkRunner.loadFile(BenchmarkRunner.java:80)
    at org.brail.rhinobenchmarks.BenchmarkRunner.initialize(BenchmarkRunner.java:87)
    at org.brail.rhinobenchmarks.BenchmarkRunner.dryRun(BenchmarkRunner.java:147)
    at org.brail.rhinobenchmarks.BenchmarkDriver.dryRunOne(BenchmarkDriver.java:62)
    at org.brail.rhinobenchmarks.test.StandardBenchmarkTest.benchmark(StandardBenchmarkTest.java:28)

Other than that I think this is really close... Thanks!

@aardvark179

Copy link
Copy Markdown
Contributor Author

It's not the only thing that breaks that benchmark. 😕

@aardvark179

Copy link
Copy Markdown
Contributor Author

I found a couple of failures to deduplicate constants and failures to check for overflows in the constant pool maps when fixing things on the lexical scope branch. I think the flight planner benchmark has been mis-compiling its data file for a long time.

Things broke on my lexical scoping branch because compilation changed just enough that the error was detected during compilation.

The bug you found here is a short to int conversion issue when the constant index exceeds 0x7fff, fixing that shows we later cause the constant pool to become exhausted.

Quite a few of the those benchmarks actually overflow, and if we are checking for this properly, fall back to the interpreter. We might want to think about a strategy for splitting out functions and stuff to subsidiary class files when things get too large.

This branch has not been deployed

No deployments
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.

2 participants