Closed Bug 198086 Opened 22 years ago Closed 21 years ago

optimizer enhancement: generate only single class per script and all its functions

Categories

(Rhino Graveyard :: Compiler, enhancement)

enhancement
Not set
normal

Tracking

(Not tracked)

VERIFIED FIXED

People

(Reporter: igor, Assigned: norrisboyd)

Details

Attachments

(5 files, 7 obsolete files)

69.06 KB, patch
Details | Diff | Splinter Review
954 bytes, patch
Details | Diff | Splinter Review
23.97 KB, patch
Details | Diff | Splinter Review
39.73 KB, patch
Details | Diff | Splinter Review
17.94 KB, patch
Details | Diff | Splinter Review
Currently Rhino optimizer generates one Java class per JavaScript function which
may lead to significant runtime overhead. I think it would be worth to try to
replace that by a single class with a switch dispatch to select code for a
particular function. I open this report to record work on it.
This is a small step to address the issue, but it makes sense on its own since
currently script containing

function f() { return 1; }
function g() { return 1; }

print(f()+g()+Math.sqrt(1));

would create 3 classes each contain a code to generate a wrapper Number object
to represent 1 and with the patch the single constant from the class for the
main script will be used.
This is an extension of the previous patch to place code implementing functions
bodies in single class. 

With the new patch classes implementing functions contain only constructor and
a call method that calls a static method 

c<function-index>(<FunctionClass> function, Context cx. Scriptable scope,
Scriptable thisObj, <possible-direct-call-params>, Object[] args)

In this way almost all strings representing JS names and strings are stored in
the main class reducing total amount of memory that generated classes occupy.
With this patch the first generated class will contain all code to initialize
and execute script and functions. The rest of generated classes contain
effectively 3 functions that looks like:

void constructor(Context cx, Scriptable scope, int id)
{
    store id
    static call to firestGeneratedClass._i<id>(this, cx, scope);
}

Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args)
{
    static call to firestGeneratedClass._c<id>(
	signature-with-possible-direct-call-optimization);
}

Object getEncodedSource()
{
    static call to firestGeneratedClass.getEncodedSourceImpl(id);
}

So only the first class contains all script-specific strings and functions, the
rest of the generated classes looks almost identical. To make them identical,
it would be necessary to replace 
    static call to firestGeneratedClass._i<id>(this, cx, scope);
    static call to firestGeneratedClass._c<id>(this, cx, scope);
by a switch like

switch (id) {
  case 0: static call to firestGeneratedClass._i0(this, cx, scope);
  case 1: static call to firestGeneratedClass._i1(this, cx, scope);
  ...
}

and 
switch (id) {
  case 0: static call to firestGeneratedClass._c0(...);
  case 1: static call to firestGeneratedClass._c1(...);
  ...
}

The patch touches org/mozilla/classfile/ClassFileWriter to add a support for
switch generation which patch uses to generate switch in getEncodedSourceImpl
and adds public methods to NativeFunction to initialize protected fields which
are not accessible otherwise for code in static functions in
firestGeneratedClass.
Attachment #130279 - Attachment is obsolete: true
With the above patch the optimizer produces at most 2 classes for a script with
arbitrary number of possibly nested functions.

The patch passes the test suite with optimization set to 0 and 9 but since it
is very intrusive, I will try to test it before committing. 

In principle it is possible to make optimizer to generate exactly one class
since a subclass of omj.NativeScript is not necessary and can be replaced by a
omj.NativeFunction implementing some interface and hence the same class that
patch uses to generate omj.NativeFunction subclass can also implement that
interface. But such change would probably require non-trivial modifications in
omj.NativeScript.
I committed the changes non-optimizer related changes that are necessary for
the patch like support for tableswitch in org/mozilla/classfile/ClassFileWriter
and making BaseFunction.createObject public instead of protected so it can be
called from after changes to the optimizer.
Attachment #130412 - Attachment is obsolete: true
I removed changes to OptNameHelper since they were too intrusive and changed
naming of generated files which I would prefer to keep as compatible with the
current naming as possible.

The patch passes the test suite and few my own stress tests. In addition the JS
compiler is working fine as well.
Attachment #130422 - Attachment is obsolete: true
I benchmarked the patch against BenchPress.js and from func_bench.js
mozilla/js/benchmarks. As far as I can tell an additional overhead switch/static
method call to implement Function.call does not change the running time in any
significant way. Such overhead is present only when direct call optimization is
not available since direct calls invoke the corresponding static method directly
and their timing should be the same as before the patch. 

The benchmarks confirmed this as I could not see a statistically meaningful
difference between running time when using -opt 9. 

With -opt 0 under JDK 1.1 there is not change in timing, under JDK 1.3 the patch
makes benchmarks slower by 1-2% while under JDK 1.4 they are faster by 1-2%.
This is under Linux.

Now regarding the generated class files size. Using JS compiler for
examples/NervousText.js as described at the bottom of
http://mozilla.org/rhino/jsc.html gives without the patch 8 class files with
total size 21321 and corresponding compressed jar gives 9842 bytes. 

After the patch the compiler produces only 3 class files with the total size
12851 and the jar size 5569 bytes.


Attachment #130119 - Attachment is obsolete: true
Attachment #130216 - Attachment is obsolete: true
Attachment #130359 - Attachment is obsolete: true
Attachment #130464 - Attachment is obsolete: true
Due to limitations of JVM class format, the patch clearly adds restrictions to
complexity of script code that optimizer can handle since now all the constants
and strings goes into single file. But such additional restrictions are not
that bad since the patch does not affect the major restriction of restricting
byte code size for methods to 64K.

The test case generates a JS source of the form

function f1(n) { return n; }
function f2(n) { return f1(n); }
function f3(n) { return f2(n); }
...
function f<N>(n) { return f<N-1>(n); }

f<N>(<some-value>);

and then compile/execute it via "var code = new Script(); code()" since eval
can not be used as Rhino uses interpreter mode for it.

It turned out that before patch the maximum value for N with optimization level
0 was 2847 before JVM refused to load the file and after it actually increased
to 2853. The limit came from the code for script body that has to instantiate N
different functions and the patch made that slightly more compact that explains
the reason for the limit increase. Of cause a mixture of top-level functions
and nested ones would show the patch limitations since without the patch nested
functions will be initialized in a separated classes, but still a limit of 2500
functions per script seems reasonable.
I tagged Rhino sources with before_less_classes_optimizer tag and committed the
patch. I do not mark the bug as fixed since I would prefer to try to change
optimizer to produce exactly one class for both script and all the functions it
contains. It will allow to make all internal fields and methods private which
would allow for better optimization of generated code by JVM.
The patch changes omj.NativeScript to represent only instances of Script object
in scripts and removes its second role to server as a superclass for compiled
forms of JS scripts. In is not only allow to make optimizer to generate only
single class but also would make instances of Script generated by optimizer not
to depend on the scope. The later is essential for Script object reuse to
execute it against different scopes.

Currently the optimizer sets the prototype of the compiled script to
Script.prototype from the scope but this wrong since the compiled script is
never exported to script. The interpreter never did it so compiled script
produced in this mode were almost scope independent (they currently hold a
scope reference through instantiated regular expressions while scripts
generated by the compiler instantiate the regexps during eval call).
Patch makes optimizer to generate exactly one class to represent a script and
all functions. That class implements Script and initializes in the default
constructor and Script.eval implementation script support while using a
separated constructor to initialize the function objects.
I committed the last patch which finally allows to mark the bug as fixed.
Status: NEW → RESOLVED
Closed: 21 years ago
Resolution: --- → FIXED
Rubber-stamp vrfy -
Status: RESOLVED → VERIFIED
Without this fix which I committed Script instances can not be executed twice
since it would reinitialize NativeFunction each time the script is executed. 

The fix also moves initialization of regular expression for scripts (and only
for scripts) directly into the call method implementation where they are stored
into a local variable. Previously regexps would be stored in a class field
which the call method accessed thus making Script.exec non-reentrant and thread
unsafe.

The last part would probably should go as a fix for bug 218440 but it is too
complex to separate the patches.

Note that the test case from attachment 130962 [details] from that bug can be used to
check this regression since the test calls Script.exec twice.
Trageting as resolved against 1.5R5
Target Milestone: --- → 1.5R5
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: