This commit is contained in:
Lana Steuck 2014-10-23 13:45:22 -07:00
commit 4b4e46f1fb
118 changed files with 3122 additions and 1668 deletions

View File

@ -69,7 +69,6 @@ ENABLE_ASSERTIONS_FLAGS="-ea -esa"
if [ -z $JFR_FILENAME ]; then
JFR_FILENAME="./nashorn_$(date|sed "s/ /_/g"|sed "s/:/_/g").jfr"
echo "Using default JFR filename: ${JFR_FILENAME}..."
fi
# Flight recorder

View File

@ -25,6 +25,14 @@ Example:
> java -Dnashorn.args="--lazy-complation --log=compiler" large-java-app-with-nashorn.jar
> ant -Dnashorn.args="--log=codegen" antjob
SYSTEM PROPERTY: -Dnashorn.args.prepend=<string>
This property behaves like nashorn.args, but adds the given arguments
before the existing ones instead of after them. Later arguments will
overwrite earlier ones, so this is useful for setting default arguments
that can be overwritten.
SYSTEM PROPERTY: -Dnashorn.unstable.relink.threshold=x
This property controls how many call site misses are allowed before a
@ -42,533 +50,38 @@ be split into several classes in order not to run out of bytecode space.
The default value is 0x8000 (32768).
SYSTEM PROPERTY: -Dnashorn.compiler.intarithmetic
SYSTEM PROPERTY: -Dnashorn.serialize.compression=<x>
(and integer arithmetic in general)
<currently disabled - this is being refactored for update releases>
Arithmetic operations in Nashorn (except bitwise ones) typically
coerce the operands to doubles (as per the JavaScript spec). To switch
this off and remain in integer mode, for example for "var x = a&b; var
y = c&d; var z = x*y;", use this flag. This will force the
multiplication of variables that are ints to be done with the IMUL
bytecode and the result "z" to become an int.
WARNING: Note that is is experimental only to ensure that type support
exists for all primitive types. The generated code is unsound. This
will be the case until we do optimizations based on it. There is a CR
in Nashorn to do better range analysis, and ensure that this is only
done where the operation can't overflow into a wider type. Currently
no overflow checking is done, so at the moment, until range analysis
has been completed, this option is turned off.
We've experimented by using int arithmetic for everything and putting
overflow checks afterwards, which would recompute the operation with
the correct precision, but have yet to find a configuration where this
is faster than just using doubles directly, even if the int operation
does not overflow. Getting access to a JVM intrinsic that does branch
on overflow would probably alleviate this.
The future:
We are transitioning to an optimistic type system that uses int
arithmetic everywhere until proven wrong. The problem here is mostly
catch an overflow exception and rolling back the state to a new method
with less optimistic assumptions for an operation at a particular
program point. This will most likely not be in the Java 8.0 release
but likely end up in an update release
For Java 8, several java.lang.Math methods like addExact, subExact and
mulExact are available to help us. Experiments intrinsifying these
show a lot of promise, and we have devised a system that basically
does on stack replacement with exceptions in bytecode to revert
erroneous assumptions. An explanation of how this works and what we
are doing can be found here:
http://www.slideshare.net/lagergren/lagergren-jvmls2013final
Experiments with this show significant ~x2-3 performance increases on
pretty much everything, provided that optimistic assumptions don't
fail much. It will affect warmup time negatively, depending on how
many erroneous too optimistic assumptions are placed in the code at
compile time. We don't think this will be much of an issue.
For example for a small benchmark that repeatedly executes this
method taken from the Crypto Octane benchmark
function am3(i,x,w,j,c,n) {
var this_array = this.array;
var w_array = w.array;
var xl = x&0x3fff, xh = x>>14;
while(--n >= 0) {
var l = this_array[i]&0x3fff;
var h = this_array[i++]>>14;
var m = xh*l+h*xl;
l = xl*l+((m&0x3fff)<<14)+w_array[j]+c;
c = (l>>28)+(m>>14)+xh*h;
w_array[j++] = l&0xfffffff;
}
return c;
}
The performance increase more than doubles. We are also working hard
with the code generation team in the Java Virtual Machine to fix
things that are lacking in invokedynamic performance, which is another
area where a lot of ongoing performance work takes place
"Pessimistic" bytecode for am3, guaranteed to be semantically correct:
// access flags 0x9
public static am3(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
L0
LINENUMBER 12 L0
ALOAD 0
INVOKEDYNAMIC dyn:getProp|getElem|getMethod:array(Ljava/lang/Object;)Ljava/lang/Object; [
// handle kind 0x6 : INVOKESTATIC
jdk/nashorn/internal/runtime/linker/Bootstrap.bootstrap((Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;I)Ljava/lang/invoke/CallSite;)
// arguments:
0
]
ASTORE 8
L1
LINENUMBER 13 L1
ALOAD 3
INVOKEDYNAMIC dyn:getProp|getElem|getMethod:array(Ljava/lang/Object;)Ljava/lang/Object; [
// handle kind 0x6 : INVOKESTATIC
jdk/nashorn/internal/runtime/linker/Bootstrap.bootstrap((Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;I)Ljava/lang/invoke/CallSite;)
// arguments:
0
]
ASTORE 9
L2
LINENUMBER 14 L2
ALOAD 2
INVOKESTATIC jdk/nashorn/internal/runtime/JSType.toInt32 (Ljava/lang/Object;)I
SIPUSH 16383
IAND
ISTORE 10
ALOAD 2
INVOKESTATIC jdk/nashorn/internal/runtime/JSType.toInt32 (Ljava/lang/Object;)I
BIPUSH 14
ISHR
ISTORE 11
L3
LINENUMBER 15 L3
GOTO L4
L5
LINENUMBER 16 L5
FRAME FULL [java/lang/Object java/lang/Object java/lang/Object java/lang/Object java/lang/Object java/lang/Object java/lang/Double T java/lang/Object java/lang/Object I I] []
ALOAD 8
ALOAD 1
INVOKEDYNAMIC dyn:getElem|getProp|getMethod(Ljava/lang/Object;Ljava/lang/Object;)I [
// handle kind 0x6 : INVOKESTATIC
jdk/nashorn/internal/runtime/linker/Bootstrap.bootstrap((Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;I)Ljava/lang/invoke/CallSite;)
// arguments:
0
]
SIPUSH 16383
IAND
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
ASTORE 12
L6
LINENUMBER 17 L6
ALOAD 8
ALOAD 1
INVOKESTATIC jdk/nashorn/internal/runtime/JSType.toNumber (Ljava/lang/Object;)D
DUP2
DCONST_1
DADD
INVOKESTATIC java/lang/Double.valueOf (D)Ljava/lang/Double;
ASTORE 1
INVOKEDYNAMIC dyn:getElem|getProp|getMethod(Ljava/lang/Object;D)I [
// handle kind 0x6 : INVOKESTATIC
jdk/nashorn/internal/runtime/linker/Bootstrap.bootstrap((Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;I)Ljava/lang/invoke/CallSite;)
// arguments:
0
]
BIPUSH 14
ISHR
ISTORE 13
L7
LINENUMBER 18 L7
ILOAD 11
I2D
ALOAD 12
INVOKESTATIC jdk/nashorn/internal/runtime/JSType.toNumber (Ljava/lang/Object;)D
DMUL
ILOAD 13
I2D
ILOAD 10
I2D
DMUL
DADD
DSTORE 14
L8
LINENUMBER 19 L8
ILOAD 10
I2D
ALOAD 12
INVOKESTATIC jdk/nashorn/internal/runtime/JSType.toNumber (Ljava/lang/Object;)D
DMUL
DLOAD 14
INVOKESTATIC jdk/nashorn/internal/runtime/JSType.toInt32 (D)I
SIPUSH 16383
IAND
BIPUSH 14
ISHL
I2D
DADD
ALOAD 9
ALOAD 4
INVOKEDYNAMIC dyn:getElem|getProp|getMethod(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; [
// handle kind 0x6 : INVOKESTATIC
jdk/nashorn/internal/runtime/linker/Bootstrap.bootstrap((Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;I)Ljava/lang/invoke/CallSite;)
// arguments:
0
]
INVOKEDYNAMIC ADD:ODO_D(DLjava/lang/Object;)Ljava/lang/Object; [
// handle kind 0x6 : INVOKESTATIC
jdk/nashorn/internal/runtime/linker/Bootstrap.runtimeBootstrap((Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;)
// arguments: none
]
ALOAD 5
INVOKEDYNAMIC ADD:OOO_I(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; [
// handle kind 0x6 : INVOKESTATIC
jdk/nashorn/internal/runtime/linker/Bootstrap.runtimeBootstrap((Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;)
// arguments: none
]
ASTORE 12
L9
LINENUMBER 20 L9
ALOAD 12
INVOKESTATIC jdk/nashorn/internal/runtime/JSType.toInt32 (Ljava/lang/Object;)I
BIPUSH 28
ISHR
I2D
DLOAD 14
INVOKESTATIC jdk/nashorn/internal/runtime/JSType.toInt32 (D)I
BIPUSH 14
ISHR
I2D
DADD
ILOAD 11
I2D
ILOAD 13
I2D
DMUL
DADD
INVOKESTATIC java/lang/Double.valueOf (D)Ljava/lang/Double;
ASTORE 5
L10
LINENUMBER 21 L10
ALOAD 9
ALOAD 4
INVOKESTATIC jdk/nashorn/internal/runtime/JSType.toNumber (Ljava/lang/Object;)D
DUP2
DCONST_1
DADD
INVOKESTATIC java/lang/Double.valueOf (D)Ljava/lang/Double;
ASTORE 4
ALOAD 12
INVOKESTATIC jdk/nashorn/internal/runtime/JSType.toInt32 (Ljava/lang/Object;)I
LDC 268435455
IAND
INVOKEDYNAMIC dyn:setElem|setProp(Ljava/lang/Object;DI)V [
// handle kind 0x6 : INVOKESTATIC
jdk/nashorn/internal/runtime/linker/Bootstrap.bootstrap((Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;I)Ljava/lang/invoke/CallSite;)
// arguments:
0
]
L4
FRAME FULL [java/lang/Object java/lang/Object java/lang/Object java/lang/Object java/lang/Object java/lang/Object java/lang/Object T java/lang/Object java/lang/Object I I] []
ALOAD 6
INVOKESTATIC jdk/nashorn/internal/runtime/JSType.toNumber (Ljava/lang/Object;)D
LDC -1.0
DADD
DUP2
INVOKESTATIC java/lang/Double.valueOf (D)Ljava/lang/Double;
ASTORE 6
DCONST_0
DCMPL
IFGE L5
L11
LINENUMBER 24 L11
ALOAD 5
ARETURN
"Optimistic" bytecode that requires invalidation on e.g overflow. Factor
x2-3 speedup:
public static am3(Ljava/lang/Object;IILjava/lang/Object;III)I
L0
LINENUMBER 12 L0
ALOAD 0
INVOKEDYNAMIC dyn:getProp|getElem|getMethod:array(Ljava/lang/Object;)Ljava/lang/Object; [
// handle kind 0x6 : INVOKESTATIC
jdk/nashorn/internal/runtime/linker/Bootstrap.bootstrap((Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;I)Ljava/lang/invoke/CallSite;)
// arguments:
0
]
ASTORE 8
L1
LINENUMBER 13 L1
ALOAD 3
INVOKEDYNAMIC dyn:getProp|getElem|getMethod:array(Ljava/lang/Object;)Ljava/lang/Object; [
// handle kind 0x6 : INVOKESTATIC
jdk/nashorn/internal/runtime/linker/Bootstrap.bootstrap((Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;I)Ljava/lang/invoke/CallSite;)
// arguments:
0
]
ASTORE 9
L2
LINENUMBER 14 L2
ILOAD 2
SIPUSH 16383
IAND
ISTORE 10
ILOAD 2
BIPUSH 14
ISHR
ISTORE 11
L3
LINENUMBER 15 L3
GOTO L4
L5
LINENUMBER 16 L5
FRAME FULL [java/lang/Object I I java/lang/Object I I I T java/lang/Object java/lang/Object I I] []
ALOAD 8
ILOAD 1
INVOKEDYNAMIC dyn:getElem|getProp|getMethod(Ljava/lang/Object;I)I [
// handle kind 0x6 : INVOKESTATIC
jdk/nashorn/internal/runtime/linker/Bootstrap.bootstrap((Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;I)Ljava/lang/invoke/CallSite;)
// arguments:
0
]
SIPUSH 16383
IAND
ISTORE 12
L6
LINENUMBER 17 L6
ALOAD 8
ILOAD 1
DUP
ICONST_1
IADD
ISTORE 1
INVOKEDYNAMIC dyn:getElem|getProp|getMethod(Ljava/lang/Object;I)I [
// handle kind 0x6 : INVOKESTATIC
jdk/nashorn/internal/runtime/linker/Bootstrap.bootstrap((Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;I)Ljava/lang/invoke/CallSite;)
// arguments:
0
]
BIPUSH 14
ISHR
ISTORE 13
L7
LINENUMBER 18 L7
ILOAD 11
ILOAD 12
BIPUSH 8
INVOKESTATIC jdk/nashorn/internal/runtime/JSType.mulExact (III)I
ILOAD 13
ILOAD 10
BIPUSH 9
INVOKESTATIC jdk/nashorn/internal/runtime/JSType.mulExact (III)I
IADD
ISTORE 14
L8
LINENUMBER 19 L8
ILOAD 10
ILOAD 12
BIPUSH 11
INVOKESTATIC jdk/nashorn/internal/runtime/JSType.mulExact (III)I
ILOAD 14
SIPUSH 16383
IAND
BIPUSH 14
ISHL
IADD
ALOAD 9
ILOAD 4
INVOKEDYNAMIC dyn:getElem|getProp|getMethod(Ljava/lang/Object;I)I [
// handle kind 0x6 : INVOKESTATIC
jdk/nashorn/internal/runtime/linker/Bootstrap.bootstrap((Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;I)Ljava/lang/invoke/CallSite;)
// arguments:
0
]
IADD
ILOAD 5
IADD
ISTORE 12
L9
LINENUMBER 20 L9
ILOAD 12
BIPUSH 28
ISHR
ILOAD 14
BIPUSH 14
ISHR
IADD
ILOAD 11
ILOAD 13
BIPUSH 21
INVOKESTATIC jdk/nashorn/internal/runtime/JSType.mulExact (III)I
IADD
ISTORE 5
L10
LINENUMBER 21 L10
ALOAD 9
ILOAD 4
DUP
ICONST_1
IADD
ISTORE 4
ILOAD 12
LDC 268435455
IAND
INVOKEDYNAMIC dyn:setElem|setProp(Ljava/lang/Object;II)V [
// handle kind 0x6 : INVOKESTATIC
jdk/nashorn/internal/runtime/linker/Bootstrap.bootstrap((Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;I)Ljava/lang/invoke/CallSite;)
// arguments:
0
]
L4
FRAME SAME
ILOAD 6
ICONST_M1
IADD
DUP
ISTORE 6
ICONST_0
IF_ICMPGE L5
L11
LINENUMBER 24 L11
ILOAD 5
IRETURN
This property sets the compression level used when deflating serialized
AST structures of anonymous split functions. Valid values range from 0 to 9,
the default value is 4. Higher values will reduce memory size of serialized
AST but increase CPU usage required for compression.
SYSTEM PROPERTY: -Dnashorn.codegen.debug, -Dnashorn.codegen.debug.trace=<x>
SYSTEM PROPERTY: -Dnashorn.codegen.debug.trace=<x>
See the description of the codegen logger below.
SYSTEM_PROPERTY: -Dnashorn.fields.debug
SYSTEM PROPERTY: -Dnashorn.fields.objects
See the description on the fields logger below.
When this property is true, Nashorn will only use object fields for
AccessorProperties. This means that primitive values must be boxed
when stored in a field, which is significantly slower than using
primitive fields.
SYSTEM PROPERTY: -Dnashorn.fields.dual
When this property is true, Nashorn will attempt to use primitive
fields for AccessorProperties (currently just AccessorProperties, not
spill properties). Memory footprint for script objects will increase,
as we need to maintain both a primitive field (a long) as well as an
Object field for the property value. Ints are represented as the 32
low bits of the long fields. Doubles are represented as the
doubleToLongBits of their value. This way a single field can be used
for all primitive types. Packing and unpacking doubles to their bit
representation is intrinsified by the JVM and extremely fast.
While dual fields in theory runs significantly faster than Object
fields due to reduction of boxing and memory allocation overhead,
there is still work to be done to make this a general purpose
solution. Research is ongoing.
By default, Nashorn uses dual object and long fields. Ints are
represented as the 32 low bits of the long fields. Doubles are
represented as the doubleToLongBits of their value. This way a
single field can be used for all primitive types. Packing and
unpacking doubles to their bit representation is intrinsified by
the JVM and extremely fast.
In the future, this might complement or be replaced by experimental
feature sun.misc.TaggedArray, which has been discussed on the mlvm
mailing list. TaggedArrays are basically a way to share data space
between primitives and references, and have the GC understand this.
As long as only primitive values are written to the fields and enough
type information exists to make sure that any reads don't have to be
uselessly boxed and unboxed, this is significantly faster than the
standard "Objects only" approach that currently is the default. See
test/examples/dual-fields-micro.js for an example that runs twice as
fast with dual fields as without them. Here, the compiler, can
determine that we are dealing with numbers only throughout the entire
property life span of the properties involved.
If a "real" object (not a boxed primitive) is written to a field that
has a primitive representation, its callsite is relinked and an Object
field is used forevermore for that particular field in that
PropertyMap and its children, even if primitives are later assigned to
it.
As the amount of compile time type information is very small in a
dynamic language like JavaScript, it is frequently the case that
something has to be treated as an object, because we don't know any
better. In reality though, it is often a boxed primitive is stored to
an AccessorProperty. The fastest way to handle this soundly is to use
a callsite typecheck and avoid blowing the field up to an Object. We
never revert object fields to primitives. Ping-pong:ing back and forth
between primitive representation and Object representation would cause
fatal performance overhead, so this is not an option.
For a general application the dual fields approach is still slower
than objects only fields in some places, about the same in most cases,
and significantly faster in very few. This is due the program using
primitives, but we still can't prove it. For example "local_var a =
call(); field = a;" may very well write a double to the field, but the
compiler dare not guess a double type if field is a local variable,
due to bytecode variables being strongly typed and later non
interchangeable. To get around this, the entire method would have to
be replaced and a continuation retained to restart from. We believe
that the next steps we should go through are instead:
1) Implement method specialization based on callsite, as it's quite
frequently the case that numbers are passed around, but currently our
function nodes just have object types visible to the compiler. For
example "var b = 17; func(a,b,17)" is an example where two parameters
can be specialized, but the main version of func might also be called
from another callsite with func(x,y,"string").
2) This requires lazy jitting as the functions have to be specialized
per callsite.
Even though "function square(x) { return x*x }" might look like a
trivial function that can always only take doubles, this is not
true. Someone might have overridden the valueOf for x so that the
toNumber coercion has side effects. To fulfil JavaScript semantics,
the coercion has to run twice for both terms of the multiplication
even if they are the same object. This means that call site
specialization is necessary, not parameter specialization on the form
"function square(x) { var xd = (double)x; return xd*xd; }", as one
might first think.
Generating a method specialization for any variant of a function that
we can determine by types at compile time is a combinatorial explosion
of byte code (try it e.g. on all the variants of am3 in the Octane
benchmark crypto.js). Thus, this needs to be lazy
3) Optimistic callsite writes, something on the form
x = y; //x is a field known to be a primitive. y is only an object as
far as we can tell
turns into
try {
x = (int)y;
} catch (X is not an integer field right now | ClassCastException e) {
x = y;
}
Mini POC shows that this is the key to a lot of dual field performance
in seemingly trivial micros where one unknown object, in reality
actually a primitive, foils it for us. Very common pattern. Once we
are "all primitives", dual fields runs a lot faster than Object fields
only.
We still have to deal with objects vs primitives for local bytecode
slots, possibly through code copying and versioning.
The Future:
We expect the usefulness of dual fields to increase significantly
after the optimistic type system described in the section on
integer arithmetic above is implemented.
SYSTEM PROPERTY: -Dnashorn.compiler.symbol.trace=[<x>[,*]],
-Dnashorn.compiler.symbol.stacktrace=[<x>[,*]]
@ -628,6 +141,9 @@ for invoking a particular script function.
"identical" - this method compares two script objects for reference
equality. It is a == Java comparison
"equals" - Returns true if two objects are either referentially
identical or equal as defined by java.lang.Object.equals.
"dumpCounters" - will dump the debug counters' current values to
stdout.
@ -648,66 +164,66 @@ Finally we count callsite misses on a per callsite bases, which occur
when a callsite has to be relinked, due to a previous assumption of
object layout being invalidated.
"getContext" - return the current Nashorn context.
SYSTEM PROPERTY: -Dnashorn.methodhandles.debug,
-Dnashorn.methodhandles.debug=create
"equalWithoutType" - Returns true if if the two objects are both
property maps, and they have identical properties in the same order,
but allows the properties to differ in their types.
If this property is enabled, each MethodHandle related call that uses
the java.lang.invoke package gets its MethodHandle intercepted and an
instrumentation printout of arguments and return value appended to
it. This shows exactly which method handles are executed and from
where. (Also MethodTypes and SwitchPoints). This can be augmented with
more information, for example, instance count, by subclassing or
further extending the TraceMethodHandleFactory implementation in
MethodHandleFactory.java.
"diffPropertyMaps" Returns a diagnostic string representing the difference
of two property maps.
If the property is specialized with "=create" as its option,
instrumentation will be shown for method handles upon creation time
rather than at runtime usage.
"getClass" - Returns the Java class of an object, or undefined if null.
"toJavaString" - Returns the Java toString representation of an object.
"toIdentString" - Returns a string representation of an object consisting
of its java class name and hash code.
"getListenerCount" - Return the number of property listeners for a
script object.
"getEventQueueCapacity" - Get the capacity of the event queue.
"setEventQueueCapacity" - Set the event queue capacity.
"addRuntimeEvent" - Add a runtime event to the runtime event queue.
The queue has a fixed size (see -Dnashorn.runtime.event.queue.size)
and the oldest entry will be thrown out of the queue is about to overflow.
"expandEventQueueCapacity" - Expands the event queue capacity,
or truncates if capacity is lower than current capacity. Then only
the newest entries are kept.
"clearRuntimeEvents" - Clear the runtime event queue.
"removeRuntimeEvent" - Remove a specific runtime event from the event queue.
"getRuntimeEvents" - Return all runtime events in the queue as an array.
"getLastRuntimeEvent" - Return the last runtime event in the queue.
SYSTEM PROPERTY: -Dnashorn.methodhandles.debug.stacktrace
This does the same as nashorn.methodhandles.debug, but when enabled
also dumps the stack trace for every instrumented method handle
operation. Warning: This is enormously verbose, but provides a pretty
This enhances methodhandles logging (see below) to also dump the
stack trace for every instrumented method handle operation.
Warning: This is enormously verbose, but provides a pretty
decent "grep:able" picture of where the calls are coming from.
See the description of the codegen logger below for a more verbose
description of this option
SYSTEM PROPERTY: -Dnashorn.cce
Setting this system property causes the Nashorn linker to rely on
ClassCastExceptions for triggering a callsite relink. If not set, the linker
will add an explicit instanceof guard.
SYSTEM PROPERTY: -Dnashorn.scriptfunction.specialization.disable
SYSTEM PROPERTY: -Dnashorn.spill.threshold=<x>
There are several "fast path" implementations of constructors and
functions in the NativeObject classes that, in their original form,
take a variable amount of arguments. Said functions are also declared
to take Object parameters in their original form, as this is what the
JavaScript specification mandates.
However, we often know quite a lot more at a callsite of one of these
functions. For example, Math.min is called with a fixed number (2) of
integer arguments. The overhead of boxing these ints to Objects and
folding them into an Object array for the generic varargs Math.min
function is an order of magnitude slower than calling a specialized
implementation of Math.min that takes two integers. Specialized
functions and constructors are identified by the tag
@SpecializedFunction and @SpecializedConstructor in the Nashorn
code. The linker will link in the most appropriate (narrowest types,
right number of types and least number of arguments) specialization if
specializations are available.
Every ScriptFunction may carry specializations that the linker can
choose from. This framework will likely be extended for user defined
functions. The compiler can often infer enough parameter type info
from callsites for in order to generate simpler versions with less
generic Object types. This feature depends on future lazy jitting, as
there tend to be many calls to user defined functions, some where the
callsite can be specialized, some where we mostly see object
parameters even at the callsite.
If this system property is set to true, the linker will not attempt to
use any specialized function or constructor for native objects, but
just call the generic one.
This property sets the number of fields in an object from which to use
generic array based spill storage instead of Java fields. The default value
is 256.
SYSTEM PROPERTY: -Dnashorn.tcs.miss.samplePercent=<x>
@ -719,8 +235,47 @@ system property can be used to constrain the percentage of misses that
should be logged. Typically this is set to 1 or 5 (percent). 1% is the
default value.
SYSTEM PROPERTY: -Dnashorn.persistent.code.cache
SYSTEM_PROPERTY: -Dnashorn.profilefile=<filename>
This property can be used to set the directory where Nashorn stores
serialized script classes generated with the -pcc/--persistent-code-cache
option. The default directory name is "nashorn_code_cache".
SYSTEM PROPERTY: -Dnashorn.typeInfo.maxFiles
Maximum number of files to store in the type info cache. The type info cache
is used to cache type data of JavaScript functions when running with
optimistic types (-ot/--optimistic-types). There is one file per JavaScript
function in the cache.
The default value is 0 which means the feature is disabled. Setting this
to something like 20000 is probably good enough for most applications and
will usually cap the cache directory to about 80MB presuming a 4kB
filesystem allocation unit. Set this to "unlimited" to run without limit.
If the value is not 0 or "unlimited", Nashorn will spawn a cleanup thread
that makes sure the number of files in the cache does not exceed the given
value by deleting the least recently modified files.
SYSTEM PROPERTY: -Dnashorn.typeInfo.cacheDir
This property can be used to set the directory where Nashorn stores the
type info cache when -Dnashorn.typeInfo.maxFiles is set to a nonzero
value. The default location is platform specific. On Windows, it is
"${java.io.tmpdir}\com.oracle.java.NashornTypeInfo". On Linux and
Solaris it is "~/.cache/com.oracle.java.NashornTypeInfo". On Mac OS X,
it is "~/Library/Caches/com.oracle.java.NashornTypeInfo".
SYSTEM PROPERTY: -Dnashorn.typeInfo.cleanupDelaySeconds=<value>
This sets the delay between cleanups of the typeInfo cache, in seconds.
The default delay is 20 seconds.
SYSTEM PROPERTY: -Dnashorn.profilefile=<filename>
When running with the profile callsite options (-pcs), Nashorn will
dump profiling data for all callsites to stderr as a shutdown hook. To
@ -736,6 +291,11 @@ JDK's java.util.regex package. Set this property to "joni" to install
an implementation based on Joni, the regular expression engine used by
the JRuby project. The default value for this flag is "joni"
SYSTEM PROPERTY: -Dnashorn.runtime.event.queue.size=<value>
Nashorn provides a fixed sized runtime event queue for debugging purposes.
See -Dnashorn.debug for methods to access the event queue.
The default value is 1024.
===============
2. The loggers.
@ -767,7 +327,9 @@ times on the same command line, with the same effect.
For example: --log=codegen,fields:finest is equivalent to
--log=codegen:info --log=fields:finest
The subsystems that currently support logging are:
The following is an incomplete list of subsystems that currently
support logging. Look for classes implementing
jdk.nashorn.internal.runtime.logging.Loggable for more loggers.
* compiler
@ -780,6 +342,14 @@ settings that all the tiers of codegen (e.g. Lower and CodeGenerator)
use.s
* recompile
This logger shows information about recompilation of scripts and
functions at runtime. Recompilation may happen because a function
was called with different parameter types, or because an optimistic
assumption failed while executing a function with -ot/--optimistic-types.
* codegen
The code generator is the emitter stage of the code pipeline, and
@ -836,25 +406,13 @@ and inlining finally blocks.
Lower is also responsible for determining control flow information
like end points.
* symbols
* attr
The symbols logger tracks the assignment os symbols to identifiers.
The lowering annotates a FunctionNode with symbols for each identifier
and transforms high level constructs into lower level ones, that the
CodeGenerator consumes.
Lower logging typically outputs things like post pass actions,
insertions of casts because symbol types have been changed and type
specialization information. Currently very little info is generated by
this logger. This will probably change.
* finalize
This --log=finalize log option outputs information for type finalization,
the third tier of the compiler. This means things like placement of
specialized scope nodes or explicit conversions.
* scopedepths
This logs the calculation of scope depths for non-local symbols.
* fields
@ -896,6 +454,21 @@ Here is an example:
[time]
[time] Total runtime: 11994 ms (Non-runtime: 11027 ms [91%])
* methodhandles
If this logger is enabled, each MethodHandle related call that uses
the java.lang.invoke package gets its MethodHandle intercepted and an
instrumentation printout of arguments and return value appended to
it. This shows exactly which method handles are executed and from
where. (Also MethodTypes and SwitchPoints).
* classcache
This logger shows information about reusing code classes using the
in-memory class cache. Nashorn will try to avoid compilation of
scripts by using existing classes. This can significantly improve
performance when repeatedly evaluating the same script.
=======================
3. Undocumented options
=======================

View File

@ -25,7 +25,7 @@
<description>Builds and runs nasgen.</description>
<import file="build.xml"/>
<target name="build-nasgen" depends="compile-asm">
<target name="build-nasgen" depends="prepare">
<ant inheritAll="false" dir="${basedir}/buildtools/nasgen"
antfile="build.xml" target="jar"/>
</target>

View File

@ -49,8 +49,6 @@
<condition property="git.executable" value="/usr/local/bin/git" else="git">
<available file="/usr/local/bin/git"/>
</condition>
<!-- check if JDK already has ASM classes -->
<available property="asm.available" classname="jdk.internal.org.objectweb.asm.Type"/>
<!-- check if testng.jar is avaiable -->
<available property="testng.available" file="${file.reference.testng.jar}"/>
<!-- check if Jemmy ang testng.jar are avaiable -->
@ -78,8 +76,31 @@
<istrue value="${jfr}"/>
</condition>
</target>
<!-- check minimum ant version required to be 1.8.4 -->
<target name="check-ant-version">
<property name="ant.version.required" value="1.8.4"/>
<antversion property="ant.current.version" />
<fail message="The current ant version, ${ant.current.version}, is too old. Please use 1.8.4 or above.">
<condition>
<not>
<antversion atleast="${ant.version.required}"/>
</not>
</condition>
</fail>
</target>
<target name="check-java-version">
<!-- look for a Class that is available only in jdk1.8 or above -->
<!-- core/exposed API class is better than an implementation class -->
<available property="jdk1.8+" classname="java.util.stream.Stream"/>
<!-- need jdk1.8 or above -->
<fail message="Unsupported Java version: ${ant.java.version}. Please use Java version 1.8 or greater." unless="jdk1.8+">
</fail>
</target>
<target name="init" depends="init-conditions, init-cc">
<target name="init" depends="check-ant-version, check-java-version, init-conditions, init-cc">
<!-- extends jvm args -->
<property name="run.test.jvmargs" value="${run.test.jvmargs.main} ${run.test.cc.jvmargs} ${jfr.options}"/>
<property name="run.test.jvmargs.octane" value="${run.test.jvmargs.octane.main} ${run.test.cc.jvmargs} ${jfr.options}"/>
@ -107,19 +128,7 @@
<delete dir="${dist.dir}"/>
</target>
<!-- do it only if ASM is not available -->
<target name="compile-asm" depends="prepare" unless="asm.available">
<javac srcdir="${jdk.asm.src.dir}"
destdir="${build.classes.dir}"
excludes="**/optimizer/* **/xml/* **/attrs/*"
source="${javac.source}"
target="${javac.target}"
debug="${javac.debug}"
encoding="${javac.encoding}"
includeantruntime="false"/>
</target>
<target name="compile" depends="compile-asm" description="Compiles nashorn">
<target name="compile" depends="prepare" description="Compiles nashorn">
<javac srcdir="${src.dir}"
destdir="${build.classes.dir}"
classpath="${javac.classpath}"

View File

@ -27,6 +27,7 @@ package jdk.nashorn.internal.codegen;
import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS_VAR;
import static jdk.nashorn.internal.codegen.CompilerConstants.EXPLODED_ARGUMENT_PREFIX;
import java.lang.invoke.MethodType;
import java.util.ArrayDeque;
import java.util.ArrayList;
@ -38,6 +39,7 @@ import jdk.nashorn.internal.ir.AccessNode;
import jdk.nashorn.internal.ir.CallNode;
import jdk.nashorn.internal.ir.Expression;
import jdk.nashorn.internal.ir.FunctionNode;
import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
import jdk.nashorn.internal.ir.IdentNode;
import jdk.nashorn.internal.ir.LexicalContext;
import jdk.nashorn.internal.ir.Node;
@ -321,7 +323,7 @@ public final class ApplySpecialization extends NodeVisitor<LexicalContext> imple
explodedArguments.pop();
return newFunctionNode;
return newFunctionNode.setState(lc, CompilationState.BUILTINS_TRANSFORMED);
}
private static boolean isApply(final CallNode callNode) {

View File

@ -76,7 +76,6 @@ import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode.ArrayUnit;
import jdk.nashorn.internal.ir.Node;
import jdk.nashorn.internal.ir.RuntimeNode;
import jdk.nashorn.internal.ir.RuntimeNode.Request;
import jdk.nashorn.internal.ir.SplitNode;
import jdk.nashorn.internal.ir.Statement;
import jdk.nashorn.internal.ir.SwitchNode;
import jdk.nashorn.internal.ir.Symbol;
@ -135,9 +134,6 @@ final class AssignSymbols extends NodeVisitor<LexicalContext> implements Loggabl
if (!(functionNode.hasScopeBlock() || functionNode.needsParentScope())) {
functionNode.compilerConstant(SCOPE).setNeedsSlot(false);
}
if (!functionNode.usesReturnSymbol()) {
functionNode.compilerConstant(RETURN).setNeedsSlot(false);
}
// Named function expressions that end up not referencing themselves won't need a local slot for the self symbol.
if(!functionNode.isDeclared() && !functionNode.usesSelfSymbol() && !functionNode.isAnonymous()) {
final Symbol selfSymbol = functionNode.getBody().getExistingSymbol(functionNode.getIdent().getName());
@ -1014,7 +1010,7 @@ final class AssignSymbols extends NodeVisitor<LexicalContext> implements Loggabl
boolean previousWasBlock = false;
for (final Iterator<LexicalContextNode> it = lc.getAllNodes(); it.hasNext();) {
final LexicalContextNode node = it.next();
if (node instanceof FunctionNode || node instanceof SplitNode || isSplitArray(node)) {
if (node instanceof FunctionNode || isSplitArray(node)) {
// We reached the function boundary or a splitting boundary without seeing a definition for the symbol.
// It needs to be in scope.
return true;

View File

@ -0,0 +1,71 @@
/*
* Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.nashorn.internal.codegen;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Collections;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import jdk.nashorn.internal.ir.Block;
import jdk.nashorn.internal.ir.FunctionNode;
import jdk.nashorn.internal.ir.LexicalContext;
import jdk.nashorn.internal.ir.Node;
import jdk.nashorn.internal.ir.Statement;
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
import jdk.nashorn.internal.runtime.options.Options;
/**
* This static utility class performs serialization of FunctionNode ASTs to a byte array.
* The format is a standard Java serialization stream, deflated.
*/
final class AstSerializer {
// Experimentally, we concluded that compression level 4 gives a good tradeoff between serialization speed
// and size.
private static final int COMPRESSION_LEVEL = Options.getIntProperty("nashorn.serialize.compression", 4);
static byte[] serialize(final FunctionNode fn) {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
try (final ObjectOutputStream oout = new ObjectOutputStream(new DeflaterOutputStream(out,
new Deflater(COMPRESSION_LEVEL)))) {
oout.writeObject(removeInnerFunctionBodies(fn));
} catch (final IOException e) {
throw new AssertionError("Unexpected exception serializing function", e);
}
return out.toByteArray();
}
private static FunctionNode removeInnerFunctionBodies(final FunctionNode fn) {
return (FunctionNode)fn.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
@Override
public Node leaveBlock(final Block block) {
if (lc.isFunctionBody() && lc.getFunction(block) != lc.getOutermostFunction()) {
return block.setStatements(lc, Collections.<Statement>emptyList());
}
return super.leaveBlock(block);
}
});
}
}

View File

@ -51,6 +51,7 @@ import static jdk.nashorn.internal.codegen.CompilerConstants.className;
import static jdk.nashorn.internal.codegen.CompilerConstants.methodDescriptor;
import static jdk.nashorn.internal.codegen.CompilerConstants.typeDescriptor;
import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCallNoLookup;
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import java.security.AccessController;
@ -64,7 +65,6 @@ import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.util.TraceClassVisitor;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.ir.FunctionNode;
import jdk.nashorn.internal.ir.SplitNode;
import jdk.nashorn.internal.ir.debug.NashornClassReader;
import jdk.nashorn.internal.ir.debug.NashornTextifier;
import jdk.nashorn.internal.runtime.Context;
@ -476,12 +476,6 @@ public class ClassEmitter implements Emitter {
methodsStarted.remove(method);
}
SplitMethodEmitter method(final SplitNode splitNode, final String methodName, final Class<?> rtype, final Class<?>... ptypes) {
methodCount++;
methodNames.add(methodName);
return new SplitMethodEmitter(this, methodVisitor(EnumSet.of(Flag.PUBLIC, Flag.STATIC), methodName, rtype, ptypes), splitNode);
}
/**
* Add a new method to the class - defaults to public method
*

View File

@ -34,9 +34,7 @@ import static jdk.nashorn.internal.codegen.CompilerConstants.GET_MAP;
import static jdk.nashorn.internal.codegen.CompilerConstants.GET_STRING;
import static jdk.nashorn.internal.codegen.CompilerConstants.QUICK_PREFIX;
import static jdk.nashorn.internal.codegen.CompilerConstants.REGEX_PREFIX;
import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN;
import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE;
import static jdk.nashorn.internal.codegen.CompilerConstants.SPLIT_ARRAY_ARG;
import static jdk.nashorn.internal.codegen.CompilerConstants.SPLIT_PREFIX;
import static jdk.nashorn.internal.codegen.CompilerConstants.THIS;
import static jdk.nashorn.internal.codegen.CompilerConstants.VARARGS;
@ -99,10 +97,10 @@ import jdk.nashorn.internal.ir.ExpressionStatement;
import jdk.nashorn.internal.ir.ForNode;
import jdk.nashorn.internal.ir.FunctionNode;
import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
import jdk.nashorn.internal.ir.GetSplitState;
import jdk.nashorn.internal.ir.IdentNode;
import jdk.nashorn.internal.ir.IfNode;
import jdk.nashorn.internal.ir.IndexNode;
import jdk.nashorn.internal.ir.JoinPredecessor;
import jdk.nashorn.internal.ir.JoinPredecessorExpression;
import jdk.nashorn.internal.ir.JumpStatement;
import jdk.nashorn.internal.ir.LabelNode;
@ -121,7 +119,8 @@ import jdk.nashorn.internal.ir.PropertyNode;
import jdk.nashorn.internal.ir.ReturnNode;
import jdk.nashorn.internal.ir.RuntimeNode;
import jdk.nashorn.internal.ir.RuntimeNode.Request;
import jdk.nashorn.internal.ir.SplitNode;
import jdk.nashorn.internal.ir.SetSplitState;
import jdk.nashorn.internal.ir.SplitReturn;
import jdk.nashorn.internal.ir.Statement;
import jdk.nashorn.internal.ir.SwitchNode;
import jdk.nashorn.internal.ir.Symbol;
@ -493,8 +492,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
//walk up the chain from starting block and when we bump into the current function boundary, add the external
//information.
final FunctionNode fn = lc.getCurrentFunction();
final int fnId = fn.getId();
final int externalDepth = compiler.getScriptFunctionData(fnId).getExternalSymbolDepth(symbol.getName());
final int externalDepth = compiler.getScriptFunctionData(fn.getId()).getExternalSymbolDepth(symbol.getName());
//count the number of scopes from this place to the start of the function
@ -554,10 +552,10 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
}
MethodEmitter loadBinaryOperands(final BinaryNode binaryNode) {
return loadBinaryOperands(binaryNode.lhs(), binaryNode.rhs(), TypeBounds.UNBOUNDED.notWiderThan(binaryNode.getWidestOperandType()), false);
return loadBinaryOperands(binaryNode.lhs(), binaryNode.rhs(), TypeBounds.UNBOUNDED.notWiderThan(binaryNode.getWidestOperandType()), false, false);
}
private MethodEmitter loadBinaryOperands(final Expression lhs, final Expression rhs, final TypeBounds explicitOperandBounds, final boolean baseAlreadyOnStack) {
private MethodEmitter loadBinaryOperands(final Expression lhs, final Expression rhs, final TypeBounds explicitOperandBounds, final boolean baseAlreadyOnStack, final boolean forceConversionSeparation) {
// ECMAScript 5.1 specification (sections 11.5-11.11 and 11.13) prescribes that when evaluating a binary
// expression "LEFT op RIGHT", the order of operations must be: LOAD LEFT, LOAD RIGHT, CONVERT LEFT, CONVERT
// RIGHT, EXECUTE OP. Unfortunately, doing it in this order defeats potential optimizations that arise when we
@ -574,15 +572,34 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
final Type narrowestOperandType = Type.narrowest(Type.widest(lhs.getType(), rhs.getType()), explicitOperandBounds.widest);
final TypeBounds operandBounds = explicitOperandBounds.notNarrowerThan(narrowestOperandType);
if (noToPrimitiveConversion(lhs.getType(), explicitOperandBounds.widest) || rhs.isLocal()) {
// Can reorder. Combine load and convert into single operations.
loadExpression(lhs, operandBounds, baseAlreadyOnStack);
loadExpression(rhs, operandBounds, false);
// Can reorder. We might still need to separate conversion, but at least we can do it with reordering
if (forceConversionSeparation) {
// Can reorder, but can't move conversion into the operand as the operation depends on operands
// exact types for its overflow guarantees. E.g. with {L}{%I}expr1 {L}* {L}{%I}expr2 we are not allowed
// to merge {L}{%I} into {%L}, as that can cause subsequent overflows; test for JDK-8058610 contains
// concrete cases where this could happen.
final TypeBounds safeConvertBounds = TypeBounds.UNBOUNDED.notNarrowerThan(narrowestOperandType);
loadExpression(lhs, safeConvertBounds, baseAlreadyOnStack);
method.convert(operandBounds.within(method.peekType()));
loadExpression(rhs, safeConvertBounds, false);
method.convert(operandBounds.within(method.peekType()));
} else {
// Can reorder and move conversion into the operand. Combine load and convert into single operations.
loadExpression(lhs, operandBounds, baseAlreadyOnStack);
loadExpression(rhs, operandBounds, false);
}
} else {
// Can't reorder. Load and convert separately.
final TypeBounds safeConvertBounds = TypeBounds.UNBOUNDED.notNarrowerThan(narrowestOperandType);
loadExpression(lhs, safeConvertBounds, baseAlreadyOnStack);
final Type lhsType = method.peekType();
loadExpression(rhs, safeConvertBounds, false);
method.swap().convert(operandBounds.within(method.peekType())).swap().convert(operandBounds.within(method.peekType()));
final Type convertedLhsType = operandBounds.within(method.peekType());
if (convertedLhsType != lhsType) {
// Do it conditionally, so that if conversion is a no-op we don't introduce a SWAP, SWAP.
method.swap().convert(convertedLhsType).swap();
}
method.convert(operandBounds.within(method.peekType()));
}
assert Type.generic(method.peekType()) == operandBounds.narrowest;
assert Type.generic(method.peekType(1)) == operandBounds.narrowest;
@ -633,19 +650,11 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
}
TypeBounds booleanToInt() {
return maybeNew(booleanToInt(narrowest), booleanToInt(widest));
return maybeNew(CodeGenerator.booleanToInt(narrowest), CodeGenerator.booleanToInt(widest));
}
TypeBounds objectToNumber() {
return maybeNew(objectToNumber(narrowest), objectToNumber(widest));
}
private static Type booleanToInt(final Type t) {
return t == Type.BOOLEAN ? Type.INT : t;
}
private static Type objectToNumber(final Type t) {
return t.isObject() ? Type.NUMBER : t;
return maybeNew(CodeGenerator.objectToNumber(narrowest), CodeGenerator.objectToNumber(widest));
}
Type within(final Type type) {
@ -664,6 +673,14 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
}
}
private static Type booleanToInt(final Type t) {
return t == Type.BOOLEAN ? Type.INT : t;
}
private static Type objectToNumber(final Type t) {
return t.isObject() ? Type.NUMBER : t;
}
MethodEmitter loadExpressionAsType(final Expression expr, final Type type) {
if(type == Type.BOOLEAN) {
return loadExpressionAsBoolean(expr);
@ -1047,6 +1064,13 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
return false;
}
@Override
public boolean enterGetSplitState(final GetSplitState getSplitState) {
method.loadScope();
method.invoke(Scope.GET_SPLIT_STATE);
return false;
}
@Override
public boolean enterDefault(final Node otherNode) {
// Must have handled all expressions that can legally be encountered.
@ -1219,7 +1243,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
popScopesUntil(target);
final Label targetLabel = jump.getTargetLabel(target);
targetLabel.markAsBreakTarget();
method.splitAwareGoto(lc, targetLabel, target);
method._goto(targetLabel);
return false;
}
@ -2029,10 +2053,10 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
}
private void lineNumber(final int lineNumber) {
if (lineNumber != lastLineNumber) {
if (lineNumber != lastLineNumber && lineNumber != Node.NO_LINE_NUMBER) {
method.lineNumber(lineNumber);
lastLineNumber = lineNumber;
}
lastLineNumber = lineNumber;
}
int getLastLineNumber() {
@ -2079,13 +2103,14 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
method.begin();
defineCommonSplitMethodParameters();
defineSplitMethodParameter(3, arrayType);
defineSplitMethodParameter(CompilerConstants.SPLIT_ARRAY_ARG.slot(), arrayType);
fixScopeSlot(currentFunction);
// NOTE: when this is no longer needed, SplitIntoFunctions will no longer have to add IS_SPLIT
// to synthetic functions, and FunctionNode.needsCallee() will no longer need to test for isSplit().
final int arraySlot = fixScopeSlot(currentFunction, 3);
lc.enterSplitNode();
final int arraySlot = SPLIT_ARRAY_ARG.slot();
for (int i = arrayUnit.getLo(); i < arrayUnit.getHi(); i++) {
method.load(arrayType, arraySlot);
storeElement(nodes, elementType, postsets[i]);
@ -2700,73 +2725,6 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
method.convert(newRuntimeNode.getType());
}
@Override
public boolean enterSplitNode(final SplitNode splitNode) {
if(!method.isReachable()) {
return false;
}
final CompileUnit splitCompileUnit = splitNode.getCompileUnit();
final FunctionNode fn = lc.getCurrentFunction();
final String className = splitCompileUnit.getUnitClassName();
final String name = splitNode.getName();
final Type returnType = fn.getReturnType();
final Class<?> rtype = fn.getReturnType().getTypeClass();
final boolean needsArguments = fn.needsArguments();
final Class<?>[] ptypes = needsArguments ?
new Class<?>[] {ScriptFunction.class, Object.class, ScriptObject.class, ScriptObject.class} :
new Class<?>[] {ScriptFunction.class, Object.class, ScriptObject.class};
final MethodEmitter caller = method;
unit = lc.pushCompileUnit(splitCompileUnit);
final Call splitCall = staticCallNoLookup(
className,
name,
methodDescriptor(rtype, ptypes));
final MethodEmitter splitEmitter =
splitCompileUnit.getClassEmitter().method(
splitNode,
name,
rtype,
ptypes);
pushMethodEmitter(splitEmitter);
method.setFunctionNode(fn);
assert fn.needsCallee() : "split function should require callee";
caller.loadCompilerConstant(CALLEE);
caller.loadCompilerConstant(THIS);
caller.loadCompilerConstant(SCOPE);
if (needsArguments) {
caller.loadCompilerConstant(ARGUMENTS);
}
caller.invoke(splitCall);
caller.storeCompilerConstant(RETURN, returnType);
method.begin();
defineCommonSplitMethodParameters();
if(needsArguments) {
defineSplitMethodParameter(3, ARGUMENTS);
}
// Copy scope to its target slot as first thing because the original slot could be used by return symbol.
fixScopeSlot(fn);
final int returnSlot = fn.compilerConstant(RETURN).getSlot(returnType);
method.defineBlockLocalVariable(returnSlot, returnSlot + returnType.getSlots());
method.loadUndefined(returnType);
method.storeCompilerConstant(RETURN, returnType);
lc.enterSplitNode();
return true;
}
private void defineCommonSplitMethodParameters() {
defineSplitMethodParameter(0, CALLEE);
defineSplitMethodParameter(1, THIS);
@ -2782,114 +2740,40 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
method.onLocalStore(type, slot);
}
private void fixScopeSlot(final FunctionNode functionNode) {
private int fixScopeSlot(final FunctionNode functionNode, final int extraSlot) {
// TODO hack to move the scope to the expected slot (needed because split methods reuse the same slots as the root method)
final int actualScopeSlot = functionNode.compilerConstant(SCOPE).getSlot(SCOPE_TYPE);
final int defaultScopeSlot = SCOPE.slot();
int newExtraSlot = extraSlot;
if (actualScopeSlot != defaultScopeSlot) {
method.defineBlockLocalVariable(actualScopeSlot, actualScopeSlot + 1);
if (actualScopeSlot == extraSlot) {
newExtraSlot = extraSlot + 1;
method.defineBlockLocalVariable(newExtraSlot, newExtraSlot + 1);
method.load(Type.OBJECT, extraSlot);
method.storeHidden(Type.OBJECT, newExtraSlot);
} else {
method.defineBlockLocalVariable(actualScopeSlot, actualScopeSlot + 1);
}
method.load(SCOPE_TYPE, defaultScopeSlot);
method.storeCompilerConstant(SCOPE);
}
return newExtraSlot;
}
@Override
public Node leaveSplitNode(final SplitNode splitNode) {
assert method instanceof SplitMethodEmitter;
lc.exitSplitNode();
final boolean hasReturn = method.hasReturn();
final SplitMethodEmitter splitMethod = ((SplitMethodEmitter)method);
final List<Label> targets = splitMethod.getExternalTargets();
final boolean hasControlFlow = hasReturn || !targets.isEmpty();
final List<BreakableNode> targetNodes = splitMethod.getExternalTargetNodes();
final Type returnType = lc.getCurrentFunction().getReturnType();
try {
// Wrap up this method.
if(method.isReachable()) {
if (hasControlFlow) {
method.setSplitState(-1);
}
method.loadCompilerConstant(RETURN, returnType);
method._return(returnType);
}
method.end();
lc.releaseSlots();
unit = lc.popCompileUnit(splitNode.getCompileUnit());
popMethodEmitter();
} catch (final Throwable t) {
Context.printStackTrace(t);
final VerifyError e = new VerifyError("Code generation bug in \"" + splitNode.getName() + "\": likely stack misaligned: " + t + " " + getCurrentSource().getName());
e.initCause(t);
throw e;
public boolean enterSplitReturn(final SplitReturn splitReturn) {
if (method.isReachable()) {
method.loadUndefined(lc.getCurrentFunction().getReturnType())._return();
}
return false;
}
//no external jump targets or return in switch node
if (!hasControlFlow) {
return splitNode;
@Override
public boolean enterSetSplitState(final SetSplitState setSplitState) {
if (method.isReachable()) {
method.setSplitState(setSplitState.getState());
}
// Handle return from split method if there was one.
final MethodEmitter caller = method;
final int targetCount = targets.size();
caller.loadScope();
caller.invoke(Scope.GET_SPLIT_STATE);
final Label breakLabel = new Label("no_split_state");
// Split state is -1 for no split state, 0 for return, 1..n+1 for break/continue
//the common case is that we don't need a switch
if (targetCount == 0) {
assert hasReturn;
caller.ifne(breakLabel);
//has to be zero
caller.label(new Label("split_return"));
caller.loadCompilerConstant(RETURN, returnType);
caller._return(returnType);
caller.label(breakLabel);
} else {
assert !targets.isEmpty();
final int low = hasReturn ? 0 : 1;
final int labelCount = targetCount + 1 - low;
final Label[] labels = new Label[labelCount];
for (int i = 0; i < labelCount; i++) {
labels[i] = new Label(i == 0 ? "split_return" : "split_" + targets.get(i - 1));
}
caller.tableswitch(low, targetCount, breakLabel, labels);
for (int i = low; i <= targetCount; i++) {
caller.label(labels[i - low]);
if (i == 0) {
caller.loadCompilerConstant(RETURN, returnType);
caller._return(returnType);
} else {
final BreakableNode targetNode = targetNodes.get(i - 1);
final Label label = targets.get(i - 1);
if (!lc.isExternalTarget(splitNode, targetNode)) {
final JoinPredecessor jumpOrigin = splitNode.getJumpOrigin(label);
if(jumpOrigin != null) {
method.beforeJoinPoint(jumpOrigin);
}
popScopesUntil(targetNode);
}
caller.splitAwareGoto(lc, label, targetNode);
}
}
caller.label(breakLabel);
}
// If split has a return and caller is itself a split method it needs to propagate the return.
if (hasReturn) {
caller.setHasReturn();
}
return splitNode;
return false;
}
@Override
@ -3678,13 +3562,15 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
void loadStack() {
final TypeBounds operandBounds;
final boolean isOptimistic = isValid(getProgramPoint());
boolean forceConversionSeparation = false;
if(isOptimistic) {
operandBounds = new TypeBounds(binaryNode.getType(), Type.OBJECT);
} else {
// Non-optimistic, non-FP +. Allow it to overflow.
operandBounds = new TypeBounds(binaryNode.getWidestOperandType(), Type.OBJECT);
forceConversionSeparation = binaryNode.getWidestOperationType().narrowerThan(resultBounds.widest);
}
loadBinaryOperands(binaryNode.lhs(), binaryNode.rhs(), operandBounds, false);
loadBinaryOperands(binaryNode.lhs(), binaryNode.rhs(), operandBounds, false, forceConversionSeparation);
}
@Override
@ -3795,12 +3681,21 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
@Override
protected void evaluate() {
final Expression lhs = assignNode.lhs();
final Type widest = assignNode.isTokenType(TokenType.ASSIGN_ADD) ? Type.OBJECT : assignNode.getWidestOperationType();
final Expression rhs = assignNode.rhs();
final Type widestOperationType = assignNode.getWidestOperationType();
final Type widest = assignNode.isTokenType(TokenType.ASSIGN_ADD) ? Type.OBJECT : widestOperationType;
final TypeBounds bounds = new TypeBounds(assignNode.getType(), widest);
new OptimisticOperation(assignNode, bounds) {
@Override
void loadStack() {
loadBinaryOperands(lhs, assignNode.rhs(), bounds, true);
final boolean forceConversionSeparation;
if (isValid(getProgramPoint()) || widestOperationType == Type.NUMBER) {
forceConversionSeparation = false;
} else {
final Type operandType = Type.widest(booleanToInt(objectToNumber(lhs.getType())), booleanToInt(objectToNumber(rhs.getType())));
forceConversionSeparation = operandType.narrowerThan(widestOperationType);
}
loadBinaryOperands(lhs, rhs, bounds, true, forceConversionSeparation);
}
@Override
void consumeStack() {
@ -3823,7 +3718,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
@Override
protected void evaluate() {
loadBinaryOperands(assignNode.lhs(), assignNode.rhs(), TypeBounds.UNBOUNDED.notWiderThan(assignNode.getWidestOperandType()), true);
loadBinaryOperands(assignNode.lhs(), assignNode.rhs(), TypeBounds.UNBOUNDED.notWiderThan(assignNode.getWidestOperandType()), true, false);
op();
}
}
@ -3946,6 +3841,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
@Override
void loadStack() {
final TypeBounds operandBounds;
boolean forceConversionSeparation = false;
if(numericBounds.narrowest == Type.NUMBER) {
// Result should be double always. Propagate it into the operands so we don't have lots of I2D
// and L2D after operand evaluation.
@ -3963,9 +3859,10 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
// Non-optimistic, non-FP subtraction or multiplication. Allow them to overflow.
operandBounds = new TypeBounds(Type.narrowest(node.getWidestOperandType(),
numericBounds.widest), Type.NUMBER);
forceConversionSeparation = node.getWidestOperationType().narrowerThan(numericBounds.widest);
}
}
loadBinaryOperands(node.lhs(), node.rhs(), operandBounds, false);
loadBinaryOperands(node.lhs(), node.rhs(), operandBounds, false, forceConversionSeparation);
}
@Override
@ -4379,11 +4276,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
private void newFunctionObject(final FunctionNode functionNode, final boolean addInitializer) {
assert lc.peek() == functionNode;
final int fnId = functionNode.getId();
final RecompilableScriptFunctionData data = compiler.getScriptFunctionData(fnId);
assert data != null : functionNode.getName() + " has no data";
final RecompilableScriptFunctionData data = compiler.getScriptFunctionData(functionNode.getId());
if (functionNode.isProgram() && !compiler.isOnDemandCompilation()) {
final CompileUnit fnUnit = functionNode.getCompileUnit();

View File

@ -38,12 +38,11 @@ import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.SCOPE_DEPTHS
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.SPLIT;
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.SYMBOLS_ASSIGNED;
import static jdk.nashorn.internal.runtime.logging.DebugLogger.quote;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
@ -53,10 +52,7 @@ import jdk.nashorn.internal.ir.FunctionNode;
import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
import jdk.nashorn.internal.ir.LexicalContext;
import jdk.nashorn.internal.ir.LiteralNode;
import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode;
import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode.ArrayUnit;
import jdk.nashorn.internal.ir.Node;
import jdk.nashorn.internal.ir.SplitNode;
import jdk.nashorn.internal.ir.debug.ASTWriter;
import jdk.nashorn.internal.ir.debug.PrintVisitor;
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
@ -81,7 +77,7 @@ enum CompilationPhase {
PARSED)) {
@Override
FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
return (FunctionNode)fn.accept(new FoldConstants(compiler));
return transformFunction(fn, new FoldConstants(compiler));
}
@Override
@ -104,7 +100,7 @@ enum CompilationPhase {
CONSTANT_FOLDED)) {
@Override
FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
return (FunctionNode)fn.accept(new Lower(compiler));
return transformFunction(fn, new Lower(compiler));
}
@Override
@ -118,23 +114,6 @@ enum CompilationPhase {
* optimistic ops a program point so that an UnwarrantedException knows from where
* a guess went wrong when creating the continuation to roll back this execution
*/
PROGRAM_POINT_PHASE(
EnumSet.of(
INITIALIZED,
PARSED,
CONSTANT_FOLDED,
LOWERED)) {
@Override
FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
return (FunctionNode)fn.accept(new ProgramPoints());
}
@Override
public String toString() {
return "'Program Point Calculation'";
}
},
TRANSFORM_BUILTINS_PHASE(
EnumSet.of(
INITIALIZED,
@ -144,13 +123,7 @@ enum CompilationPhase {
//we only do this if we have a param type map, otherwise this is not a specialized recompile
@Override
FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
final FunctionNode newFunctionNode = (FunctionNode)fn.accept(new ApplySpecialization(compiler));
return (FunctionNode)newFunctionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
@Override
public Node leaveFunctionNode(final FunctionNode node) {
return node.setState(lc, BUILTINS_TRANSFORMED);
}
});
return setStates(transformFunction(fn, new ApplySpecialization(compiler)), BUILTINS_TRANSFORMED);
}
@Override
@ -177,7 +150,7 @@ enum CompilationPhase {
FunctionNode newFunctionNode;
//ensure elementTypes, postsets and presets exist for splitter and arraynodes
newFunctionNode = (FunctionNode)fn.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
newFunctionNode = transformFunction(fn, new NodeVisitor<LexicalContext>(new LexicalContext()) {
@Override
public LiteralNode<?> leaveLiteralNode(final LiteralNode<?> literalNode) {
return literalNode.initialize(lc);
@ -185,7 +158,7 @@ enum CompilationPhase {
});
newFunctionNode = new Splitter(compiler, newFunctionNode, outermostCompileUnit).split(newFunctionNode, true);
newFunctionNode = transformFunction(newFunctionNode, new SplitIntoFunctions(compiler));
assert newFunctionNode.getCompileUnit() == outermostCompileUnit : "fn=" + fn.getName() + ", fn.compileUnit (" + newFunctionNode.getCompileUnit() + ") != " + outermostCompileUnit;
assert newFunctionNode.isStrict() == compiler.isStrict() : "functionNode.isStrict() != compiler.isStrict() for " + quote(newFunctionNode.getName());
@ -198,6 +171,52 @@ enum CompilationPhase {
}
},
PROGRAM_POINT_PHASE(
EnumSet.of(
INITIALIZED,
PARSED,
CONSTANT_FOLDED,
LOWERED,
BUILTINS_TRANSFORMED,
SPLIT)) {
@Override
FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
return transformFunction(fn, new ProgramPoints());
}
@Override
public String toString() {
return "'Program Point Calculation'";
}
},
SERIALIZE_SPLIT_PHASE(
EnumSet.of(
INITIALIZED,
PARSED,
CONSTANT_FOLDED,
LOWERED,
BUILTINS_TRANSFORMED,
SPLIT)) {
@Override
FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
return transformFunction(fn, new NodeVisitor<LexicalContext>(new LexicalContext()) {
@Override
public boolean enterFunctionNode(final FunctionNode functionNode) {
if (functionNode.isSplit()) {
compiler.serializeAst(functionNode);
}
return true;
}
});
}
@Override
public String toString() {
return "'Serialize Split Functions'";
}
},
SYMBOL_ASSIGNMENT_PHASE(
EnumSet.of(
INITIALIZED,
@ -208,7 +227,7 @@ enum CompilationPhase {
SPLIT)) {
@Override
FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
return (FunctionNode)fn.accept(new AssignSymbols(compiler));
return transformFunction(fn, new AssignSymbols(compiler));
}
@Override
@ -228,7 +247,7 @@ enum CompilationPhase {
SYMBOLS_ASSIGNED)) {
@Override
FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
return (FunctionNode)fn.accept(new FindScopeDepths(compiler));
return transformFunction(fn, new FindScopeDepths(compiler));
}
@Override
@ -250,7 +269,7 @@ enum CompilationPhase {
@Override
FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
if (compiler.useOptimisticTypes()) {
return (FunctionNode)fn.accept(new OptimisticTypesCalculator(compiler));
return transformFunction(fn, new OptimisticTypesCalculator(compiler));
}
return setStates(fn, OPTIMISTIC_TYPES_ASSIGNED);
}
@ -274,8 +293,7 @@ enum CompilationPhase {
OPTIMISTIC_TYPES_ASSIGNED)) {
@Override
FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
final FunctionNode newFunctionNode = (FunctionNode)fn.accept(new LocalVariableTypesCalculator(compiler));
final FunctionNode newFunctionNode = transformFunction(fn, new LocalVariableTypesCalculator(compiler));
final ScriptEnvironment senv = compiler.getScriptEnvironment();
final PrintWriter err = senv.getErr();
@ -330,13 +348,7 @@ enum CompilationPhase {
for (final CompileUnit oldUnit : compiler.getCompileUnits()) {
assert map.get(oldUnit) == null;
final StringBuilder sb = new StringBuilder(compiler.nextCompileUnitName());
if (phases.isRestOfCompilation()) {
sb.append("$restOf");
}
//it's ok to not copy the initCount, methodCount and clinitCount here, as codegen is what
//fills those out anyway. Thus no need for a copy constructor
final CompileUnit newUnit = compiler.createCompileUnit(sb.toString(), oldUnit.getWeight());
final CompileUnit newUnit = createNewCompileUnit(compiler, phases);
log.fine("Creating new compile unit ", oldUnit, " => ", newUnit);
map.put(oldUnit, newUnit);
assert newUnit != null;
@ -350,47 +362,10 @@ enum CompilationPhase {
//replace old compile units in function nodes, if any are assigned,
//for example by running the splitter on this function node in a previous
//partial code generation
final FunctionNode newFunctionNode = (FunctionNode)fn.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
final FunctionNode newFunctionNode = transformFunction(fn, new ReplaceCompileUnits() {
@Override
public Node leaveFunctionNode(final FunctionNode node) {
final CompileUnit oldUnit = node.getCompileUnit();
assert oldUnit != null : "no compile unit in function node";
final CompileUnit newUnit = map.get(oldUnit);
assert newUnit != null : "old unit has no mapping to new unit " + oldUnit;
log.fine("Replacing compile unit: ", oldUnit, " => ", newUnit, " in ", quote(node.getName()));
return node.setCompileUnit(lc, newUnit).setState(lc, CompilationState.COMPILE_UNITS_REUSED);
}
@Override
public Node leaveSplitNode(final SplitNode node) {
final CompileUnit oldUnit = node.getCompileUnit();
assert oldUnit != null : "no compile unit in function node";
final CompileUnit newUnit = map.get(oldUnit);
assert newUnit != null : "old unit has no mapping to new unit " + oldUnit;
log.fine("Replacing compile unit: ", oldUnit, " => ", newUnit, " in ", quote(node.getName()));
return node.setCompileUnit(lc, newUnit);
}
@Override
public Node leaveLiteralNode(final LiteralNode<?> node) {
if (node instanceof ArrayLiteralNode) {
final ArrayLiteralNode aln = (ArrayLiteralNode)node;
if (aln.getUnits() == null) {
return node;
}
final List<ArrayUnit> newArrayUnits = new ArrayList<>();
for (final ArrayUnit au : aln.getUnits()) {
final CompileUnit newUnit = map.get(au.getCompileUnit());
assert newUnit != null;
newArrayUnits.add(new ArrayUnit(newUnit, au.getLo(), au.getHi()));
}
return aln.setUnits(lc, newArrayUnits);
}
return node;
CompileUnit getReplacement(CompileUnit original) {
return map.get(original);
}
@Override
@ -408,7 +383,59 @@ enum CompilationPhase {
}
},
/**
REINITIALIZE_SERIALIZED(
EnumSet.of(
INITIALIZED,
PARSED,
CONSTANT_FOLDED,
LOWERED,
BUILTINS_TRANSFORMED,
SPLIT)) {
@Override
FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
final Set<CompileUnit> unitSet = CompileUnit.createCompileUnitSet();
final Map<CompileUnit, CompileUnit> unitMap = new HashMap<>();
// Ensure that the FunctionNode's compile unit is the first in the list of new units. Install phase
// will use that as the root class.
createCompileUnit(fn.getCompileUnit(), unitSet, unitMap, compiler, phases);
final FunctionNode newFn = transformFunction(fn, new ReplaceCompileUnits() {
@Override
CompileUnit getReplacement(final CompileUnit oldUnit) {
final CompileUnit existing = unitMap.get(oldUnit);
if (existing != null) {
return existing;
}
return createCompileUnit(oldUnit, unitSet, unitMap, compiler, phases);
}
@Override
public Node leaveFunctionNode(final FunctionNode fn2) {
return super.leaveFunctionNode(
// restore flags for deserialized nested function nodes
compiler.getScriptFunctionData(fn2.getId()).restoreFlags(lc, fn2));
};
});
compiler.replaceCompileUnits(unitSet);
return newFn;
}
private CompileUnit createCompileUnit(final CompileUnit oldUnit, final Set<CompileUnit> unitSet,
final Map<CompileUnit, CompileUnit> unitMap, final Compiler compiler, final CompilationPhases phases) {
final CompileUnit newUnit = createNewCompileUnit(compiler, phases);
unitMap.put(oldUnit, newUnit);
unitSet.add(newUnit);
return newUnit;
}
@Override
public String toString() {
return "'Deserialize'";
}
},
/**
* Bytecode generation:
*
* Generate the byte code class(es) resulting from the compiled FunctionNode
@ -443,7 +470,7 @@ enum CompilationPhase {
try {
// Explicitly set BYTECODE_GENERATED here; it can not be set in case of skipping codegen for :program
// in the lazy + optimistic world. See CodeGenerator.skipFunction().
newFunctionNode = ((FunctionNode)newFunctionNode.accept(codegen)).setState(null, BYTECODE_GENERATED);
newFunctionNode = transformFunction(newFunctionNode, codegen).setState(null, BYTECODE_GENERATED);
codegen.generateScopeCalls();
} catch (final VerifyError e) {
if (senv._verify_code || senv._print_code) {
@ -615,7 +642,7 @@ enum CompilationPhase {
if (!AssertsEnabled.assertsEnabled()) {
return functionNode;
}
return (FunctionNode)functionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
return transformFunction(functionNode, new NodeVisitor<LexicalContext>(new LexicalContext()) {
@Override
public Node leaveFunctionNode(final FunctionNode fn) {
return fn.setState(lc, state);
@ -701,4 +728,17 @@ enum CompilationPhase {
return end(compiler, transform(compiler, phases, begin(compiler, functionNode)));
}
private static FunctionNode transformFunction(final FunctionNode fn, final NodeVisitor<?> visitor) {
return (FunctionNode) fn.accept(visitor);
}
private static CompileUnit createNewCompileUnit(final Compiler compiler, final CompilationPhases phases) {
final StringBuilder sb = new StringBuilder(compiler.nextCompileUnitName());
if (phases.isRestOfCompilation()) {
sb.append("$restOf");
}
//it's ok to not copy the initCount, methodCount and clinitCount here, as codegen is what
//fills those out anyway. Thus no need for a copy constructor
return compiler.createCompileUnit(sb.toString(), 0);
}
}

View File

@ -25,24 +25,31 @@
package jdk.nashorn.internal.codegen;
import java.io.Serializable;
import java.util.Set;
import java.util.TreeSet;
import jdk.nashorn.internal.ir.CompileUnitHolder;
/**
* Used to track split class compilation.
*/
public final class CompileUnit implements Comparable<CompileUnit> {
* Used to track split class compilation. Note that instances of the class are serializable, but all fields are
* transient, making the serialized version of the class only useful for tracking the referential topology of other
* AST nodes referencing the same or different compile units. We do want to preserve this topology though as
* {@link CompileUnitHolder}s in a deserialized AST will undergo reinitialization.
*/
public final class CompileUnit implements Comparable<CompileUnit>, Serializable {
private static final long serialVersionUID = 1L;
/** Current class name */
private final String className;
private transient final String className;
/** Current class generator */
private ClassEmitter classEmitter;
private transient ClassEmitter classEmitter;
private long weight;
private transient long weight;
private Class<?> clazz;
private transient Class<?> clazz;
private boolean isUsed;
private transient boolean isUsed;
private static int emittedUnitCount;
@ -121,14 +128,6 @@ public final class CompileUnit implements Comparable<CompileUnit> {
this.weight += w;
}
/**
* Get the current weight of the compile unit.
* @return the unit's weight
*/
long getWeight() {
return weight;
}
/**
* Check if this compile unit can hold {@code weight} more units of weight
* @param w weight to check if can be added
@ -155,7 +154,7 @@ public final class CompileUnit implements Comparable<CompileUnit> {
}
private static String shortName(final String name) {
return name.lastIndexOf('/') == -1 ? name : name.substring(name.lastIndexOf('/') + 1);
return name == null ? null : name.lastIndexOf('/') == -1 ? name : name.substring(name.lastIndexOf('/') + 1);
}
@Override

View File

@ -32,15 +32,16 @@ import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE;
import static jdk.nashorn.internal.codegen.CompilerConstants.THIS;
import static jdk.nashorn.internal.codegen.CompilerConstants.VARARGS;
import static jdk.nashorn.internal.runtime.logging.DebugLogger.quote;
import java.io.File;
import java.lang.invoke.MethodType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -159,75 +160,142 @@ public final class Compiler implements Loggable {
*/
private static final int COMPILE_UNIT_NAME_BUFFER_SIZE = 32;
private final Map<Integer, byte[]> serializedAsts = new HashMap<>();
/**
* Compilation phases that a compilation goes through
*/
public static class CompilationPhases implements Iterable<CompilationPhase> {
/** Singleton that describes a standard eager compilation - this includes code installation */
public final static CompilationPhases COMPILE_ALL = new CompilationPhases(
"Compile all",
new CompilationPhase[] {
CompilationPhase.CONSTANT_FOLDING_PHASE,
CompilationPhase.LOWERING_PHASE,
CompilationPhase.PROGRAM_POINT_PHASE,
CompilationPhase.TRANSFORM_BUILTINS_PHASE,
CompilationPhase.SPLITTING_PHASE,
CompilationPhase.SYMBOL_ASSIGNMENT_PHASE,
CompilationPhase.SCOPE_DEPTH_COMPUTATION_PHASE,
CompilationPhase.OPTIMISTIC_TYPE_ASSIGNMENT_PHASE,
CompilationPhase.LOCAL_VARIABLE_TYPE_CALCULATION_PHASE,
CompilationPhase.BYTECODE_GENERATION_PHASE,
CompilationPhase.INSTALL_PHASE
});
/**
* Singleton that describes compilation up to the phase where a function can be serialized.
*/
private final static CompilationPhases COMPILE_UPTO_SERIALIZABLE = new CompilationPhases(
"Common initial phases",
CompilationPhase.CONSTANT_FOLDING_PHASE,
CompilationPhase.LOWERING_PHASE,
CompilationPhase.TRANSFORM_BUILTINS_PHASE,
CompilationPhase.SPLITTING_PHASE,
CompilationPhase.PROGRAM_POINT_PHASE,
CompilationPhase.SERIALIZE_SPLIT_PHASE
);
/** Compile all for a rest of method */
public final static CompilationPhases COMPILE_ALL_RESTOF =
COMPILE_ALL.setDescription("Compile all, rest of").addAfter(CompilationPhase.LOCAL_VARIABLE_TYPE_CALCULATION_PHASE, CompilationPhase.REUSE_COMPILE_UNITS_PHASE);
private final static CompilationPhases COMPILE_SERIALIZABLE_UPTO_BYTECODE = new CompilationPhases(
"After common phases, before bytecode generator",
CompilationPhase.SYMBOL_ASSIGNMENT_PHASE,
CompilationPhase.SCOPE_DEPTH_COMPUTATION_PHASE,
CompilationPhase.OPTIMISTIC_TYPE_ASSIGNMENT_PHASE,
CompilationPhase.LOCAL_VARIABLE_TYPE_CALCULATION_PHASE
);
/** Singleton that describes a standard eager compilation, but no installation, for example used by --compile-only */
public final static CompilationPhases COMPILE_ALL_NO_INSTALL =
COMPILE_ALL.
removeLast().
setDescription("Compile without install");
/** Singleton that describes compilation up to the CodeGenerator, but not actually generating code */
public final static CompilationPhases COMPILE_UPTO_BYTECODE =
COMPILE_ALL.
removeLast().
removeLast().
setDescription("Compile upto bytecode");
/**
* Singleton that describes additional steps to be taken after deserializing, all the way up to (but not
* including) generating and installing code.
*/
public final static CompilationPhases RECOMPILE_SERIALIZED_UPTO_BYTECODE = new CompilationPhases(
"Recompile serialized function up to bytecode",
CompilationPhase.REINITIALIZE_SERIALIZED,
COMPILE_SERIALIZABLE_UPTO_BYTECODE
);
/**
* Singleton that describes back end of method generation, given that we have generated the normal
* method up to CodeGenerator as in {@link CompilationPhases#COMPILE_UPTO_BYTECODE}
*/
public final static CompilationPhases COMPILE_FROM_BYTECODE = new CompilationPhases(
public final static CompilationPhases GENERATE_BYTECODE_AND_INSTALL = new CompilationPhases(
"Generate bytecode and install",
new CompilationPhase[] {
CompilationPhase.BYTECODE_GENERATION_PHASE,
CompilationPhase.INSTALL_PHASE
});
CompilationPhase.BYTECODE_GENERATION_PHASE,
CompilationPhase.INSTALL_PHASE
);
/** Singleton that describes compilation up to the CodeGenerator, but not actually generating code */
public final static CompilationPhases COMPILE_UPTO_BYTECODE = new CompilationPhases(
"Compile upto bytecode",
COMPILE_UPTO_SERIALIZABLE,
COMPILE_SERIALIZABLE_UPTO_BYTECODE);
/** Singleton that describes a standard eager compilation, but no installation, for example used by --compile-only */
public final static CompilationPhases COMPILE_ALL_NO_INSTALL = new CompilationPhases(
"Compile without install",
COMPILE_UPTO_BYTECODE,
CompilationPhase.BYTECODE_GENERATION_PHASE);
/** Singleton that describes a standard eager compilation - this includes code installation */
public final static CompilationPhases COMPILE_ALL = new CompilationPhases(
"Full eager compilation",
COMPILE_UPTO_BYTECODE,
GENERATE_BYTECODE_AND_INSTALL);
/** Singleton that describes a full compilation - this includes code installation - from serialized state*/
public final static CompilationPhases COMPILE_ALL_SERIALIZED = new CompilationPhases(
"Eager compilation from serializaed state",
RECOMPILE_SERIALIZED_UPTO_BYTECODE,
GENERATE_BYTECODE_AND_INSTALL);
/**
* Singleton that describes restOf method generation, given that we have generated the normal
* method up to CodeGenerator as in {@link CompilationPhases#COMPILE_UPTO_BYTECODE}
*/
public final static CompilationPhases COMPILE_FROM_BYTECODE_RESTOF =
COMPILE_FROM_BYTECODE.
addFirst(CompilationPhase.REUSE_COMPILE_UNITS_PHASE).
setDescription("Generate bytecode and install - RestOf method");
public final static CompilationPhases GENERATE_BYTECODE_AND_INSTALL_RESTOF = new CompilationPhases(
"Generate bytecode and install - RestOf method",
CompilationPhase.REUSE_COMPILE_UNITS_PHASE,
GENERATE_BYTECODE_AND_INSTALL);
/** Compile all for a rest of method */
public final static CompilationPhases COMPILE_ALL_RESTOF = new CompilationPhases(
"Compile all, rest of",
COMPILE_UPTO_BYTECODE,
GENERATE_BYTECODE_AND_INSTALL_RESTOF);
/** Compile from serialized for a rest of method */
public final static CompilationPhases COMPILE_SERIALIZED_RESTOF = new CompilationPhases(
"Compile serialized, rest of",
RECOMPILE_SERIALIZED_UPTO_BYTECODE,
GENERATE_BYTECODE_AND_INSTALL_RESTOF);
private final List<CompilationPhase> phases;
private final String desc;
private CompilationPhases(final String desc, final CompilationPhase... phases) {
this.desc = desc;
this(desc, Arrays.asList(phases));
}
final List<CompilationPhase> newPhases = new LinkedList<>();
newPhases.addAll(Arrays.asList(phases));
this.phases = Collections.unmodifiableList(newPhases);
private CompilationPhases(final String desc, final CompilationPhases base, final CompilationPhase... phases) {
this(desc, concat(base.phases, Arrays.asList(phases)));
}
private CompilationPhases(final String desc, final CompilationPhase first, final CompilationPhases rest) {
this(desc, concat(Collections.singletonList(first), rest.phases));
}
private CompilationPhases(final String desc, final CompilationPhases base) {
this(desc, base.phases);
}
private CompilationPhases(final String desc, final CompilationPhases... bases) {
this(desc, concatPhases(bases));
}
private CompilationPhases(final String desc, final List<CompilationPhase> phases) {
this.desc = desc;
this.phases = phases;
}
private static List<CompilationPhase> concatPhases(final CompilationPhases[] bases) {
final ArrayList<CompilationPhase> l = new ArrayList<>();
for(final CompilationPhases base: bases) {
l.addAll(base.phases);
}
l.trimToSize();
return l;
}
private static <T> List<T> concat(final List<T> l1, final List<T> l2) {
final ArrayList<T> l = new ArrayList<>(l1);
l.addAll(l2);
l.trimToSize();
return l;
}
@Override
@ -235,45 +303,6 @@ public final class Compiler implements Loggable {
return "'" + desc + "' " + phases.toString();
}
private CompilationPhases setDescription(final String desc) {
return new CompilationPhases(desc, phases.toArray(new CompilationPhase[phases.size()]));
}
private CompilationPhases removeLast() {
final LinkedList<CompilationPhase> list = new LinkedList<>(phases);
list.removeLast();
return new CompilationPhases(desc, list.toArray(new CompilationPhase[list.size()]));
}
private CompilationPhases addFirst(final CompilationPhase phase) {
if (phases.contains(phase)) {
return this;
}
final LinkedList<CompilationPhase> list = new LinkedList<>(phases);
list.addFirst(phase);
return new CompilationPhases(desc, list.toArray(new CompilationPhase[list.size()]));
}
@SuppressWarnings("unused") //TODO I'll use this soon
private CompilationPhases replace(final CompilationPhase phase, final CompilationPhase newPhase) {
final LinkedList<CompilationPhase> list = new LinkedList<>();
for (final CompilationPhase p : phases) {
list.add(p == phase ? newPhase : p);
}
return new CompilationPhases(desc, list.toArray(new CompilationPhase[list.size()]));
}
private CompilationPhases addAfter(final CompilationPhase phase, final CompilationPhase newPhase) {
final LinkedList<CompilationPhase> list = new LinkedList<>();
for (final CompilationPhase p : phases) {
list.add(p);
if (p == phase) {
list.add(newPhase);
}
}
return new CompilationPhases(desc, list.toArray(new CompilationPhase[list.size()]));
}
boolean contains(final CompilationPhase phase) {
return phases.contains(phase);
}
@ -284,7 +313,7 @@ public final class Compiler implements Loggable {
}
boolean isRestOfCompilation() {
return this == COMPILE_ALL_RESTOF || this == COMPILE_FROM_BYTECODE_RESTOF;
return this == COMPILE_ALL_RESTOF || this == GENERATE_BYTECODE_AND_INSTALL_RESTOF || this == COMPILE_SERIALIZED_RESTOF;
}
String getDesc() {
@ -749,6 +778,14 @@ public final class Compiler implements Loggable {
compileUnits.addAll(newUnits);
}
void serializeAst(final FunctionNode fn) {
serializedAsts.put(fn.getId(), AstSerializer.serialize(fn));
}
byte[] removeSerializedAst(final int fnId) {
return serializedAsts.remove(fnId);
}
CompileUnit findUnit(final long weight) {
for (final CompileUnit unit : compileUnits) {
if (unit.canHold(weight)) {
@ -771,7 +808,10 @@ public final class Compiler implements Loggable {
}
RecompilableScriptFunctionData getScriptFunctionData(final int functionId) {
return compiledFunction == null ? null : compiledFunction.getScriptFunctionData(functionId);
assert compiledFunction != null;
final RecompilableScriptFunctionData fn = compiledFunction.getScriptFunctionData(functionId);
assert fn != null : functionId;
return fn;
}
boolean isGlobalSymbol(final FunctionNode fn, final String name) {

View File

@ -187,7 +187,6 @@ final class FindScopeDepths extends NodeVisitor<LexicalContext> implements Logga
if (compiler.isOnDemandCompilation()) {
final RecompilableScriptFunctionData data = compiler.getScriptFunctionData(newFunctionNode.getId());
assert data != null : newFunctionNode.getName() + " lacks data";
if (data.inDynamicContext()) {
log.fine("Reviving scriptfunction ", quote(name), " as defined in previous (now lost) dynamic scope.");
newFunctionNode = newFunctionNode.setInDynamicContext(lc);
@ -202,7 +201,7 @@ final class FindScopeDepths extends NodeVisitor<LexicalContext> implements Logga
//create recompilable scriptfunctiondata
final int fnId = newFunctionNode.getId();
final Map<Integer, RecompilableScriptFunctionData> nestedFunctions = fnIdToNestedFunctions.get(fnId);
final Map<Integer, RecompilableScriptFunctionData> nestedFunctions = fnIdToNestedFunctions.remove(fnId);
assert nestedFunctions != null;
// Generate the object class and property map in case this function is ever used as constructor
@ -212,8 +211,8 @@ final class FindScopeDepths extends NodeVisitor<LexicalContext> implements Logga
new AllocatorDescriptor(newFunctionNode.getThisProperties()),
nestedFunctions,
externalSymbolDepths.get(fnId),
internalSymbols.get(fnId)
);
internalSymbols.get(fnId),
compiler.removeSerializedAst(fnId));
if (lc.getOutermostFunction() != newFunctionNode) {
final FunctionNode parentFn = lc.getParentFunction(newFunctionNode);

View File

@ -24,6 +24,7 @@
*/
package jdk.nashorn.internal.codegen;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
@ -39,7 +40,9 @@ import jdk.nashorn.internal.codegen.types.Type;
*
* see -Dnashorn.codegen.debug, --log=codegen
*/
public final class Label {
public final class Label implements Serializable {
private static final long serialVersionUID = 1L;
//byte code generation evaluation type stack for consistency check
//and correct opcode selection. one per label as a label may be a
//join point
@ -491,7 +494,7 @@ public final class Label {
private final String name;
/** Type stack at this label */
private Label.Stack stack;
private transient Label.Stack stack;
/** ASM representation of this label */
private jdk.internal.org.objectweb.asm.Label label;
@ -500,9 +503,9 @@ public final class Label {
private final int id;
/** Is this label reachable (anything ever jumped to it)? */
private boolean reachable;
private transient boolean reachable;
private boolean breakTarget;
private transient boolean breakTarget;
/**
* Constructor

View File

@ -72,7 +72,7 @@ import jdk.nashorn.internal.ir.PropertyNode;
import jdk.nashorn.internal.ir.ReturnNode;
import jdk.nashorn.internal.ir.RuntimeNode;
import jdk.nashorn.internal.ir.RuntimeNode.Request;
import jdk.nashorn.internal.ir.SplitNode;
import jdk.nashorn.internal.ir.SplitReturn;
import jdk.nashorn.internal.ir.Statement;
import jdk.nashorn.internal.ir.SwitchNode;
import jdk.nashorn.internal.ir.Symbol;
@ -361,10 +361,6 @@ final class LocalVariableTypesCalculator extends NodeVisitor<LexicalContext>{
// Synthetic return node that we must insert at the end of the function if it's end is reachable.
private ReturnNode syntheticReturn;
// Topmost current split node (if any)
private SplitNode topSplit;
private boolean split;
private boolean alreadyEnteredTopLevelFunction;
// LvarType and conversion information gathered during the top-down pass; applied to nodes in the bottom-up pass.
@ -477,22 +473,7 @@ final class LocalVariableTypesCalculator extends NodeVisitor<LexicalContext>{
return false;
}
final BreakableNode target = jump.getTarget(lc);
return splitAwareJumpToLabel(jump, target, jump.getTargetLabel(target));
}
private boolean splitAwareJumpToLabel(final JumpStatement jumpStatement, final BreakableNode target, final Label targetLabel) {
final JoinPredecessor jumpOrigin;
if(topSplit != null && lc.isExternalTarget(topSplit, target)) {
// If the jump target is outside the topmost split node, then we'll create a synthetic jump origin in the
// split node.
jumpOrigin = new JoinPredecessorExpression();
topSplit.addJump(jumpOrigin, targetLabel);
} else {
// Otherwise, the original jump statement is the jump origin
jumpOrigin = jumpStatement;
}
jumpToLabel(jumpOrigin, targetLabel, getBreakTargetTypes(target));
jumpToLabel(jump, jump.getTargetLabel(target), getBreakTargetTypes(target));
doesNotContinueSequentially();
return false;
}
@ -703,18 +684,9 @@ final class LocalVariableTypesCalculator extends NodeVisitor<LexicalContext>{
}
@Override
public boolean enterSplitNode(final SplitNode splitNode) {
if(!reachable) {
return false;
}
// Need to visit inside of split nodes. While it's true that they don't have local variables, we need to visit
// breaks, continues, and returns in them.
if(topSplit == null) {
topSplit = splitNode;
}
split = true;
setType(getCompilerConstantSymbol(lc.getCurrentFunction(), CompilerConstants.RETURN), LvarType.UNDEFINED);
return true;
public boolean enterSplitReturn(final SplitReturn splitReturn) {
doesNotContinueSequentially();
return false;
}
@Override
@ -1116,15 +1088,6 @@ final class LocalVariableTypesCalculator extends NodeVisitor<LexicalContext>{
if(returnType.isUnknown()) {
returnType = Type.OBJECT;
}
if(split) {
// If the function is split, the ":return" symbol is used and needs a slot. Note we can't mark the return
// symbol as used in enterSplitNode, as we don't know the final return type of the function earlier than
// here.
final Symbol retSymbol = getCompilerConstantSymbol(lc.getCurrentFunction(), CompilerConstants.RETURN);
retSymbol.setHasSlotFor(returnType);
retSymbol.setNeedsSlot(true);
}
}
private void createSyntheticReturn(final Block body) {

View File

@ -352,8 +352,6 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> implements Lo
private Node spliceFinally(final TryNode tryNode, final List<ThrowNode> rethrows, final Block finallyBody) {
assert tryNode.getFinallyBody() == null;
final LexicalContext lowerLc = lc;
final TryNode newTryNode = (TryNode)tryNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
final List<Node> insideTry = new ArrayList<>();
@ -406,7 +404,6 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> implements Lo
//still in the try block, store it in a result value and return it afterwards
resultNode = new IdentNode(Lower.this.compilerConstant(RETURN));
newStatements.add(new ExpressionStatement(returnNode.getLineNumber(), returnNode.getToken(), returnNode.getFinish(), new BinaryNode(Token.recast(returnNode.getToken(), TokenType.ASSIGN), resultNode, expr)));
lowerLc.setFlag(lowerLc.getCurrentFunction(), FunctionNode.USES_RETURN_SYMBOL);
} else {
resultNode = null;
}

View File

@ -71,6 +71,7 @@ import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCallNoLookup
import static jdk.nashorn.internal.codegen.ObjectClassGenerator.PRIMITIVE_FIELD_TYPE;
import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_OPTIMISTIC;
import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_PROGRAM_POINT_SHIFT;
import java.io.PrintStream;
import java.lang.reflect.Array;
import java.util.Collection;
@ -88,11 +89,9 @@ import jdk.nashorn.internal.codegen.types.ArrayType;
import jdk.nashorn.internal.codegen.types.BitwiseType;
import jdk.nashorn.internal.codegen.types.NumericType;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.ir.BreakableNode;
import jdk.nashorn.internal.ir.FunctionNode;
import jdk.nashorn.internal.ir.IdentNode;
import jdk.nashorn.internal.ir.JoinPredecessor;
import jdk.nashorn.internal.ir.LexicalContext;
import jdk.nashorn.internal.ir.LiteralNode;
import jdk.nashorn.internal.ir.LocalVariableConversion;
import jdk.nashorn.internal.ir.RuntimeNode;
@ -1662,19 +1661,6 @@ public class MethodEmitter implements Emitter {
doesNotContinueSequentially();
}
/**
* Goto, possibly when splitting is taking place. If
* a splitNode exists, we need to handle the case that the
* jump target is another method
*
* @param label destination label
* @param targetNode the node to which the destination label belongs (the label is normally a break or continue
* label)
*/
void splitAwareGoto(final LexicalContext lc, final Label label, final BreakableNode targetNode) {
_goto(label);
}
/**
* Perform a comparison of two number types that are popped from the stack
*

View File

@ -30,7 +30,6 @@ import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.isValid;
import java.util.ArrayDeque;
import java.util.BitSet;
import java.util.Deque;
import jdk.nashorn.internal.IntDeque;
import jdk.nashorn.internal.ir.AccessNode;
import jdk.nashorn.internal.ir.BinaryNode;
import jdk.nashorn.internal.ir.CallNode;
@ -49,7 +48,6 @@ import jdk.nashorn.internal.ir.LoopNode;
import jdk.nashorn.internal.ir.Node;
import jdk.nashorn.internal.ir.Optimistic;
import jdk.nashorn.internal.ir.PropertyNode;
import jdk.nashorn.internal.ir.SplitNode;
import jdk.nashorn.internal.ir.Symbol;
import jdk.nashorn.internal.ir.TernaryNode;
import jdk.nashorn.internal.ir.UnaryNode;
@ -70,8 +68,6 @@ final class OptimisticTypesCalculator extends NodeVisitor<LexicalContext> {
// Per-function bit set of program points that must never be optimistic.
final Deque<BitSet> neverOptimistic = new ArrayDeque<>();
// Per-function depth of split nodes
final IntDeque splitDepth = new IntDeque();
OptimisticTypesCalculator(final Compiler compiler) {
super(new LexicalContext());
@ -155,7 +151,6 @@ final class OptimisticTypesCalculator extends NodeVisitor<LexicalContext> {
return false;
}
neverOptimistic.push(new BitSet());
splitDepth.push(0);
return true;
}
@ -189,19 +184,6 @@ final class OptimisticTypesCalculator extends NodeVisitor<LexicalContext> {
return true;
}
@Override
public boolean enterSplitNode(final SplitNode splitNode) {
splitDepth.getAndIncrement();
return true;
}
@Override
public Node leaveSplitNode(final SplitNode splitNode) {
final int depth = splitDepth.decrementAndGet();
assert depth >= 0;
return splitNode;
}
@Override
public boolean enterVarNode(final VarNode varNode) {
tagNeverOptimistic(varNode.getName());
@ -226,16 +208,11 @@ final class OptimisticTypesCalculator extends NodeVisitor<LexicalContext> {
@Override
public Node leaveFunctionNode(final FunctionNode functionNode) {
neverOptimistic.pop();
final int lastSplitDepth = splitDepth.pop();
assert lastSplitDepth == 0;
return functionNode.setState(lc, CompilationState.OPTIMISTIC_TYPES_ASSIGNED);
}
@Override
public Node leaveIdentNode(final IdentNode identNode) {
if(inSplitNode()) {
return identNode;
}
final Symbol symbol = identNode.getSymbol();
if(symbol == null) {
assert identNode.isPropertyName();
@ -256,7 +233,7 @@ final class OptimisticTypesCalculator extends NodeVisitor<LexicalContext> {
private Expression leaveOptimistic(final Optimistic opt) {
final int pp = opt.getProgramPoint();
if(isValid(pp) && !inSplitNode() && !neverOptimistic.peek().get(pp)) {
if(isValid(pp) && !neverOptimistic.peek().get(pp)) {
return (Expression)opt.setType(compiler.getOptimisticType(opt));
}
return (Expression)opt;
@ -277,8 +254,4 @@ final class OptimisticTypesCalculator extends NodeVisitor<LexicalContext> {
tagNeverOptimistic(test.getExpression());
}
}
private boolean inSplitNode() {
return splitDepth.peek() > 0;
}
}

View File

@ -85,7 +85,7 @@ class ProgramPoints extends NodeVisitor<LexicalContext> {
@Override
public boolean enterVarNode(final VarNode varNode) {
noProgramPoint.add(varNode.getAssignmentDest());
noProgramPoint.add(varNode.getName());
return true;
}

View File

@ -0,0 +1,85 @@
/*
* Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.nashorn.internal.codegen;
import java.util.ArrayList;
import java.util.List;
import jdk.nashorn.internal.ir.CompileUnitHolder;
import jdk.nashorn.internal.ir.FunctionNode;
import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
import jdk.nashorn.internal.ir.LexicalContext;
import jdk.nashorn.internal.ir.LiteralNode;
import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode;
import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode.ArrayUnit;
import jdk.nashorn.internal.ir.Node;
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
/**
* Base class for a node visitor that replaces {@link CompileUnit}s in {@link CompileUnitHolder}s.
*/
abstract class ReplaceCompileUnits extends NodeVisitor<LexicalContext> {
ReplaceCompileUnits() {
super(new LexicalContext());
}
/**
* Override to provide a replacement for an old compile unit.
* @param oldUnit the old compile unit to replace
* @return the compile unit's replacement.
*/
abstract CompileUnit getReplacement(final CompileUnit oldUnit);
CompileUnit getExistingReplacement(final CompileUnitHolder node) {
final CompileUnit oldUnit = node.getCompileUnit();
assert oldUnit != null;
final CompileUnit newUnit = getReplacement(oldUnit);
assert newUnit != null;
return newUnit;
}
@Override
public Node leaveFunctionNode(final FunctionNode node) {
return node.setCompileUnit(lc, getExistingReplacement(node)).setState(lc, CompilationState.COMPILE_UNITS_REUSED);
}
@Override
public Node leaveLiteralNode(final LiteralNode<?> node) {
if (node instanceof ArrayLiteralNode) {
final ArrayLiteralNode aln = (ArrayLiteralNode)node;
if (aln.getUnits() == null) {
return node;
}
final List<ArrayUnit> newArrayUnits = new ArrayList<>();
for (final ArrayUnit au : aln.getUnits()) {
newArrayUnits.add(new ArrayUnit(getExistingReplacement(au), au.getLo(), au.getHi()));
}
return aln.setUnits(lc, newArrayUnits);
}
return node;
}
}

View File

@ -0,0 +1,450 @@
/*
* Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.nashorn.internal.codegen;
import static jdk.nashorn.internal.ir.Node.NO_FINISH;
import static jdk.nashorn.internal.ir.Node.NO_LINE_NUMBER;
import static jdk.nashorn.internal.ir.Node.NO_TOKEN;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import java.util.Objects;
import jdk.nashorn.internal.ir.AccessNode;
import jdk.nashorn.internal.ir.BinaryNode;
import jdk.nashorn.internal.ir.Block;
import jdk.nashorn.internal.ir.BlockLexicalContext;
import jdk.nashorn.internal.ir.BreakNode;
import jdk.nashorn.internal.ir.CallNode;
import jdk.nashorn.internal.ir.CaseNode;
import jdk.nashorn.internal.ir.ContinueNode;
import jdk.nashorn.internal.ir.Expression;
import jdk.nashorn.internal.ir.ExpressionStatement;
import jdk.nashorn.internal.ir.FunctionNode;
import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
import jdk.nashorn.internal.ir.GetSplitState;
import jdk.nashorn.internal.ir.IdentNode;
import jdk.nashorn.internal.ir.IfNode;
import jdk.nashorn.internal.ir.JumpStatement;
import jdk.nashorn.internal.ir.LiteralNode;
import jdk.nashorn.internal.ir.Node;
import jdk.nashorn.internal.ir.ReturnNode;
import jdk.nashorn.internal.ir.SetSplitState;
import jdk.nashorn.internal.ir.SplitNode;
import jdk.nashorn.internal.ir.SplitReturn;
import jdk.nashorn.internal.ir.Statement;
import jdk.nashorn.internal.ir.SwitchNode;
import jdk.nashorn.internal.ir.VarNode;
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
import jdk.nashorn.internal.parser.Token;
import jdk.nashorn.internal.parser.TokenType;
/**
* A node visitor that replaces {@link SplitNode}s with anonymous function invocations and some additional constructs
* to support control flow across splits. By using this transformation, split functions are translated into ordinary
* JavaScript functions with nested anonymous functions. The transformations however introduce several AST nodes that
* have no JavaScript source representations ({@link GetSplitState}, {@link SetSplitState}, and {@link SplitReturn}),
* and therefore such function is no longer reparseable from its source. For that reason, split functions and their
* fragments are serialized in-memory and deserialized when they need to be recompiled either for deoptimization or
* for type specialization.
* NOTE: all {@code leave*()} methods for statements are returning their input nodes. That way, they will not mutate
* the original statement list in the block containing the statement, which is fine, as it'll be replaced by the
* lexical context when the block is left. If we returned something else (e.g. null), we'd cause a mutation in the
* enclosing block's statement list that is otherwise overwritten later anyway.
*/
final class SplitIntoFunctions extends NodeVisitor<BlockLexicalContext> {
private static final int FALLTHROUGH_STATE = -1;
private static final int RETURN_STATE = 0;
private static final int BREAK_STATE = 1;
private static final int FIRST_JUMP_STATE = 2;
private static final String THIS_NAME = CompilerConstants.THIS.symbolName();
private static final String RETURN_NAME = CompilerConstants.RETURN.symbolName();
// Used as the name of the formal parameter for passing the current value of :return symbol into a split fragment.
private static final String RETURN_PARAM_NAME = RETURN_NAME + "-in";
private final Deque<FunctionState> functionStates = new ArrayDeque<>();
private final Deque<SplitState> splitStates = new ArrayDeque<>();
private final Namespace namespace;
private boolean artificialBlock = false;
// -1 is program; we need to use negative ones
private int nextFunctionId = -2;
public SplitIntoFunctions(final Compiler compiler) {
super(new BlockLexicalContext() {
@Override
protected Block afterSetStatements(Block block) {
for(Statement stmt: block.getStatements()) {
assert !(stmt instanceof SplitNode);
}
return block;
}
});
namespace = new Namespace(compiler.getScriptEnvironment().getNamespace());
}
@Override
public boolean enterFunctionNode(final FunctionNode functionNode) {
functionStates.push(new FunctionState(functionNode));
return true;
}
@Override
public Node leaveFunctionNode(final FunctionNode functionNode) {
functionStates.pop();
return functionNode;
}
@Override
protected Node leaveDefault(final Node node) {
if (node instanceof Statement) {
appendStatement((Statement)node);
}
return node;
}
@Override
public boolean enterSplitNode(final SplitNode splitNode) {
getCurrentFunctionState().splitDepth++;
splitStates.push(new SplitState(splitNode));
return true;
}
@Override
public Node leaveSplitNode(final SplitNode splitNode) {
// Replace the split node with an anonymous function expression call.
final FunctionState fnState = getCurrentFunctionState();
final String name = splitNode.getName();
Block body = splitNode.getBody();
final int firstLineNumber = body.getFirstStatementLineNumber();
final long token = body.getToken();
final int finish = body.getFinish();
final FunctionNode originalFn = fnState.fn;
assert originalFn == lc.getCurrentFunction();
final boolean isProgram = originalFn.isProgram();
// Change SplitNode({...}) into "function () { ... }", or "function (:return-in) () { ... }" (for program)
final long newFnToken = Token.toDesc(TokenType.FUNCTION, nextFunctionId--, 0);
final FunctionNode fn = new FunctionNode(
originalFn.getSource(),
body.getFirstStatementLineNumber(),
newFnToken,
finish,
newFnToken,
NO_TOKEN,
namespace,
createIdent(name),
originalFn.getName() + "$" + name,
isProgram ? Collections.singletonList(createReturnParamIdent()) : Collections.<IdentNode>emptyList(),
FunctionNode.Kind.NORMAL,
// We only need IS_SPLIT conservatively, in case it contains any array units so that we force
// the :callee's existence, to force :scope to never be in a slot lower than 2. This is actually
// quite a horrible hack to do with CodeGenerator.fixScopeSlot not trampling other parameters
// and should go away once we no longer have array unit handling in codegen. Note however that
// we still use IS_SPLIT as the criteria in CompilationPhase.SERIALIZE_SPLIT_PHASE.
FunctionNode.IS_ANONYMOUS | FunctionNode.USES_ANCESTOR_SCOPE | FunctionNode.IS_SPLIT,
body,
CompilationState.INITIALIZED,
null
)
.setCompileUnit(lc, splitNode.getCompileUnit())
.copyCompilationState(lc, originalFn);
// Call the function:
// either "(function () { ... }).call(this)"
// or "(function (:return-in) { ... }).call(this, :return)"
// NOTE: Function.call() has optimized linking that basically does a pass-through to the function being invoked.
// NOTE: CompilationPhase.PROGRAM_POINT_PHASE happens after this, so these calls are subject to optimistic
// assumptions on their return value (when they return a value), as they should be.
final IdentNode thisIdent = createIdent(THIS_NAME);
final CallNode callNode = new CallNode(firstLineNumber, token, finish, new AccessNode(NO_TOKEN, NO_FINISH, fn, "call"),
isProgram ? Arrays.<Expression>asList(thisIdent, createReturnIdent())
: Collections.<Expression>singletonList(thisIdent),
false);
final SplitState splitState = splitStates.pop();
fnState.splitDepth--;
final Expression callWithReturn;
final boolean hasReturn = splitState.hasReturn;
if (hasReturn && fnState.splitDepth > 0) {
final SplitState parentSplit = splitStates.peek();
if (parentSplit != null) {
// Propagate hasReturn to parent split
parentSplit.hasReturn = true;
}
}
if (hasReturn || isProgram) {
// capture return value: ":return = (function () { ... })();"
callWithReturn = new BinaryNode(Token.recast(token, TokenType.ASSIGN), createReturnIdent(), callNode);
} else {
// no return value, just call : "(function () { ... })();"
callWithReturn = callNode;
}
appendStatement(new ExpressionStatement(firstLineNumber, token, finish, callWithReturn));
Statement splitStateHandler;
final List<JumpStatement> jumpStatements = splitState.jumpStatements;
final int jumpCount = jumpStatements.size();
// There are jumps (breaks or continues) that need to be propagated outside the split node. We need to
// set up a switch statement for them:
// switch(:scope.getScopeState()) { ... }
if (jumpCount > 0) {
final List<CaseNode> cases = new ArrayList<>(jumpCount + (hasReturn ? 1 : 0));
if (hasReturn) {
// If the split node also contained a return, we'll slip it as a case in the switch statement
addCase(cases, RETURN_STATE, createReturnFromSplit());
}
int i = FIRST_JUMP_STATE;
for (final JumpStatement jump: jumpStatements) {
addCase(cases, i++, enblockAndVisit(jump));
}
splitStateHandler = new SwitchNode(NO_LINE_NUMBER, token, finish, GetSplitState.INSTANCE, cases, null);
} else {
splitStateHandler = null;
}
// As the switch statement itself is breakable, an unlabelled break can't be in the switch statement,
// so we need to test for it separately.
if (splitState.hasBreak) {
// if(:scope.getScopeState() == Scope.BREAK) { break; }
splitStateHandler = makeIfStateEquals(firstLineNumber, token, finish, BREAK_STATE,
enblockAndVisit(new BreakNode(NO_LINE_NUMBER, token, finish, null)), splitStateHandler);
}
// Finally, if the split node had a return statement, but there were no external jumps, we didn't have
// the switch statement to handle the return, so we need a separate if for it.
if (hasReturn && jumpCount == 0) {
// if (:scope.getScopeState() == Scope.RETURN) { return :return; }
splitStateHandler = makeIfStateEquals(NO_LINE_NUMBER, token, finish, RETURN_STATE,
createReturnFromSplit(), splitStateHandler);
}
if (splitStateHandler != null) {
appendStatement(splitStateHandler);
}
return splitNode;
}
private static void addCase(final List<CaseNode> cases, final int i, final Block body) {
cases.add(new CaseNode(NO_TOKEN, NO_FINISH, intLiteral(i), body));
}
private static LiteralNode<Number> intLiteral(final int i) {
return LiteralNode.newInstance(NO_TOKEN, NO_FINISH, i);
}
private static Block createReturnFromSplit() {
return new Block(NO_TOKEN, NO_FINISH, createReturnReturn());
}
private static ReturnNode createReturnReturn() {
return new ReturnNode(NO_LINE_NUMBER, NO_TOKEN, NO_FINISH, createReturnIdent());
}
private static IdentNode createReturnIdent() {
return createIdent(RETURN_NAME);
}
private static IdentNode createReturnParamIdent() {
return createIdent(RETURN_PARAM_NAME);
}
private static IdentNode createIdent(final String name) {
return new IdentNode(NO_TOKEN, NO_FINISH, name);
}
private Block enblockAndVisit(final JumpStatement jump) {
artificialBlock = true;
final Block block = (Block)new Block(NO_TOKEN, NO_FINISH, jump).accept(this);
artificialBlock = false;
return block;
}
private static IfNode makeIfStateEquals(final int lineNumber, final long token, final int finish,
final int value, final Block pass, final Statement fail) {
return new IfNode(lineNumber, token, finish,
new BinaryNode(Token.recast(token, TokenType.EQ_STRICT),
GetSplitState.INSTANCE, intLiteral(value)),
pass,
fail == null ? null : new Block(NO_TOKEN, NO_FINISH, fail));
}
@Override
public boolean enterVarNode(VarNode varNode) {
if (!inSplitNode()) {
return super.enterVarNode(varNode);
}
assert !varNode.isBlockScoped(); //TODO: we must handle these too, but we currently don't
final Expression init = varNode.getInit();
if (varNode.isAnonymousFunctionDeclaration()) {
// We ain't moving anonymous function declarations.
return super.enterVarNode(varNode);
}
// Move a declaration-only var statement to the top of the outermost function.
getCurrentFunctionState().varStatements.add(varNode.setInit(null));
// If it had an initializer, replace it with an assignment expression statement. Note that "var" is a
// statement, so it doesn't contribute to :return of the programs, therefore we are _not_ adding a
// ":return = ..." assignment around the original assignment.
if (init != null) {
final long token = Token.recast(varNode.getToken(), TokenType.ASSIGN);
new ExpressionStatement(varNode.getLineNumber(), token, varNode.getFinish(),
new BinaryNode(token, varNode.getName(), varNode.getInit())).accept(this);
}
return false;
}
@Override
public Node leaveBlock(final Block block) {
if (!artificialBlock) {
if (lc.isFunctionBody()) {
// Prepend declaration-only var statements to the top of the statement list.
lc.prependStatements(getCurrentFunctionState().varStatements);
} else if (lc.isSplitBody()) {
appendSplitReturn(FALLTHROUGH_STATE, NO_LINE_NUMBER);
if (getCurrentFunctionState().fn.isProgram()) {
// If we're splitting the program, make sure every shard ends with "return :return" and
// begins with ":return = :return-in;".
lc.prependStatement(new ExpressionStatement(NO_LINE_NUMBER, NO_TOKEN, NO_FINISH,
new BinaryNode(Token.toDesc(TokenType.ASSIGN, 0, 0), createReturnIdent(), createReturnParamIdent())));
}
}
}
return block;
}
@Override
public Node leaveBreakNode(final BreakNode breakNode) {
return leaveJumpNode(breakNode);
}
@Override
public Node leaveContinueNode(final ContinueNode continueNode) {
return leaveJumpNode(continueNode);
}
private JumpStatement leaveJumpNode(final JumpStatement jump) {
if (inSplitNode()) {
final SplitState splitState = getCurrentSplitState();
final SplitNode splitNode = splitState.splitNode;
if (lc.isExternalTarget(splitNode, jump.getTarget(lc))) {
appendSplitReturn(splitState.getSplitStateIndex(jump), jump.getLineNumber());
return jump;
}
}
appendStatement(jump);
return jump;
}
private void appendSplitReturn(final int splitState, final int lineNumber) {
appendStatement(new SetSplitState(splitState, lineNumber));
if (getCurrentFunctionState().fn.isProgram()) {
// If we're splitting the program, make sure every fragment passes back :return
appendStatement(createReturnReturn());
} else {
appendStatement(SplitReturn.INSTANCE);
}
}
@Override
public Node leaveReturnNode(final ReturnNode returnNode) {
if(inSplitNode()) {
appendStatement(new SetSplitState(RETURN_STATE, returnNode.getLineNumber()));
getCurrentSplitState().hasReturn = true;
}
appendStatement(returnNode);
return returnNode;
}
private void appendStatement(final Statement statement) {
lc.appendStatement(statement);
}
private boolean inSplitNode() {
return getCurrentFunctionState().splitDepth > 0;
}
private FunctionState getCurrentFunctionState() {
return functionStates.peek();
}
private SplitState getCurrentSplitState() {
return splitStates.peek();
}
private static class FunctionState {
final FunctionNode fn;
final List<Statement> varStatements = new ArrayList<>();
int splitDepth;
FunctionState(final FunctionNode fn) {
this.fn = fn;
}
}
private static class SplitState {
final SplitNode splitNode;
boolean hasReturn;
boolean hasBreak;
final List<JumpStatement> jumpStatements = new ArrayList<>();
int getSplitStateIndex(final JumpStatement jump) {
if (jump instanceof BreakNode && jump.getLabelName() == null) {
// Unlabelled break is a special case
hasBreak = true;
return BREAK_STATE;
}
int i = 0;
for(final JumpStatement exJump: jumpStatements) {
if (jump.getClass() == exJump.getClass() && Objects.equals(jump.getLabelName(), exJump.getLabelName())) {
return i + FIRST_JUMP_STATE;
}
++i;
}
jumpStatements.add(jump);
return i + FIRST_JUMP_STATE;
}
SplitState(final SplitNode splitNode) {
this.splitNode = splitNode;
}
}
}

View File

@ -1,102 +0,0 @@
/*
* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.nashorn.internal.codegen;
import java.util.ArrayList;
import java.util.List;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.ir.BreakableNode;
import jdk.nashorn.internal.ir.LexicalContext;
import jdk.nashorn.internal.ir.SplitNode;
/**
* Emitter used for splitting methods. Needs to keep track of if there are jump targets
* outside the current split node. All external jump targets encountered at method
* emission are logged, and {@code CodeGenerator#leaveSplitNode(SplitNode)} creates
* an appropriate jump table when the SplitNode has been iterated through
*/
public class SplitMethodEmitter extends MethodEmitter {
private final SplitNode splitNode;
private final List<Label> externalTargets = new ArrayList<>();
/**
* In addition to external target labels, we need to track the target breakables too as the code generator needs to
* be able to correctly pop the scopes to the target, see {@link CodeGenerator#leaveSplitNode(SplitNode)}. Note that
* this is only used within CodeGenerator, which doesn't mutate the AST, so keeping pointers to other nodes is not
* incorrect.
*/
private final List<BreakableNode> externalTargetNodes = new ArrayList<>();
SplitMethodEmitter(final ClassEmitter classEmitter, final MethodVisitor mv, final SplitNode splitNode) {
super(classEmitter, mv);
this.splitNode = splitNode;
}
@Override
void splitAwareGoto(final LexicalContext lc, final Label label, final BreakableNode targetNode) {
assert splitNode != null;
final int index = findExternalTarget(lc, label, targetNode);
if (index >= 0) {
setSplitState(index + 1); // 0 is ordinary return
final Type retType = functionNode.getReturnType();
loadUndefined(retType);
_return(retType);
} else {
super.splitAwareGoto(lc, label, targetNode);
}
}
private int findExternalTarget(final LexicalContext lc, final Label label, final BreakableNode targetNode) {
final int index = externalTargets.indexOf(label);
if (index >= 0) {
return index;
}
if (lc.isExternalTarget(splitNode, targetNode)) {
externalTargets.add(label);
externalTargetNodes.add(targetNode);
return externalTargets.size() - 1;
}
return -1;
}
@Override
MethodEmitter registerReturn() {
setHasReturn();
return setSplitState(0);
}
final List<Label> getExternalTargets() {
return externalTargets;
}
final List<BreakableNode> getExternalTargetNodes() {
return externalTargetNodes;
}
}

View File

@ -37,6 +37,7 @@ import jdk.internal.org.objectweb.asm.MethodVisitor;
* This is an array type, i.e. OBJECT_ARRAY, NUMBER_ARRAY.
*/
public class ArrayType extends ObjectType implements BytecodeArrayOps {
private static final long serialVersionUID = 1L;
/**
* Constructor

View File

@ -29,6 +29,7 @@ package jdk.nashorn.internal.codegen.types;
* This class represents a numeric type that can be used for bit operations.
*/
public abstract class BitwiseType extends NumericType implements BytecodeBitwiseOps {
private static final long serialVersionUID = 1L;
/**
* Constructor

View File

@ -69,6 +69,7 @@ import jdk.nashorn.internal.codegen.CompilerConstants;
* The boolean type class
*/
public final class BooleanType extends Type {
private static final long serialVersionUID = 1L;
private static final CompilerConstants.Call VALUE_OF = staticCallNoLookup(Boolean.class, "valueOf", Boolean.class, boolean.class);
private static final CompilerConstants.Call TO_STRING = staticCallNoLookup(Boolean.class, "toString", String.class, boolean.class);

View File

@ -60,6 +60,7 @@ import jdk.nashorn.internal.codegen.CompilerConstants;
* Type class: INT
*/
class IntType extends BitwiseType {
private static final long serialVersionUID = 1L;
private static final CompilerConstants.Call TO_STRING = staticCallNoLookup(Integer.class, "toString", String.class, int.class);
private static final CompilerConstants.Call VALUE_OF = staticCallNoLookup(Integer.class, "valueOf", Integer.class, int.class);

View File

@ -54,6 +54,7 @@ import jdk.nashorn.internal.runtime.JSType;
* Type class: LONG
*/
class LongType extends BitwiseType {
private static final long serialVersionUID = 1L;
private static final CompilerConstants.Call VALUE_OF = staticCallNoLookup(Long.class, "valueOf", Long.class, long.class);

View File

@ -46,6 +46,7 @@ import jdk.nashorn.internal.codegen.CompilerConstants;
import jdk.nashorn.internal.runtime.JSType;
class NumberType extends NumericType {
private static final long serialVersionUID = 1L;
private static final CompilerConstants.Call VALUE_OF = staticCallNoLookup(Double.class, "valueOf", Double.class, double.class);

View File

@ -29,6 +29,8 @@ package jdk.nashorn.internal.codegen.types;
* This is a numeric type, i.e. NUMBER, LONG, INT, INT32.
*/
public abstract class NumericType extends Type implements BytecodeNumericOps {
private static final long serialVersionUID = 1L;
/**
* Constructor
*

View File

@ -47,6 +47,7 @@ import jdk.nashorn.internal.runtime.Undefined;
* contain a class that is a more specialized object
*/
class ObjectType extends Type {
private static final long serialVersionUID = 1L;
protected ObjectType() {
this(Object.class);

View File

@ -47,9 +47,11 @@ import static jdk.internal.org.objectweb.asm.Opcodes.T_DOUBLE;
import static jdk.internal.org.objectweb.asm.Opcodes.T_INT;
import static jdk.internal.org.objectweb.asm.Opcodes.T_LONG;
import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.Serializable;
import java.lang.invoke.CallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
@ -88,19 +90,20 @@ import jdk.nashorn.internal.runtime.linker.Bootstrap;
* INTs rather than OBJECTs
*/
public abstract class Type implements Comparable<Type>, BytecodeOps {
public abstract class Type implements Comparable<Type>, BytecodeOps, Serializable {
private static final long serialVersionUID = 1L;
/** Human readable name for type */
private final String name;
private transient final String name;
/** Descriptor for type */
private final String descriptor;
private transient final String descriptor;
/** The "weight" of the type. Used for picking widest/least specific common type */
private final int weight;
private transient final int weight;
/** How many bytecode slots does this type occupy */
private final int slots;
private transient final int slots;
/** The class for this type */
private final Class<?> clazz;
@ -113,7 +116,7 @@ public abstract class Type implements Comparable<Type>, BytecodeOps {
Collections.synchronizedMap(new WeakHashMap<Class<?>, jdk.internal.org.objectweb.asm.Type>());
/** Internal ASM type for this Type - computed once at construction */
private final jdk.internal.org.objectweb.asm.Type internalType;
private transient final jdk.internal.org.objectweb.asm.Type internalType;
/** Weights are used to decide which types are "wider" than other types */
protected static final int MIN_WEIGHT = -1;
@ -583,6 +586,7 @@ public abstract class Type implements Comparable<Type>, BytecodeOps {
public int getSlots() {
return slots;
}
/**
* Returns the widest or most common of two types
*
@ -605,6 +609,18 @@ public abstract class Type implements Comparable<Type>, BytecodeOps {
return type0.weight() > type1.weight() ? type0 : type1;
}
/**
* Returns the widest or most common of two types, given as classes
*
* @param type0 type one
* @param type1 type two
*
* @return the widest type
*/
public static Class<?> widest(final Class<?> type0, final Class<?> type1) {
return widest(Type.typeFor(type0), Type.typeFor(type1)).getTypeClass();
}
/**
* When doing widening for return types of a function or a ternary operator, it is not valid to widen a boolean to
* anything other than object. Note that this wouldn't be necessary if {@code Type.widest} did not allow
@ -934,6 +950,8 @@ public abstract class Type implements Comparable<Type>, BytecodeOps {
* This is the singleton for integer arrays
*/
public static final ArrayType INT_ARRAY = new ArrayType(int[].class) {
private static final long serialVersionUID = 1L;
@Override
public void astore(final MethodVisitor method) {
method.visitInsn(IASTORE);
@ -961,6 +979,8 @@ public abstract class Type implements Comparable<Type>, BytecodeOps {
* This is the singleton for long arrays
*/
public static final ArrayType LONG_ARRAY = new ArrayType(long[].class) {
private static final long serialVersionUID = 1L;
@Override
public void astore(final MethodVisitor method) {
method.visitInsn(LASTORE);
@ -988,6 +1008,8 @@ public abstract class Type implements Comparable<Type>, BytecodeOps {
* This is the singleton for numeric arrays
*/
public static final ArrayType NUMBER_ARRAY = new ArrayType(double[].class) {
private static final long serialVersionUID = 1L;
@Override
public void astore(final MethodVisitor method) {
method.visitInsn(DASTORE);
@ -1022,6 +1044,8 @@ public abstract class Type implements Comparable<Type>, BytecodeOps {
/** This type, always an object type, just a toString override */
public static final Type THIS = new ObjectType() {
private static final long serialVersionUID = 1L;
@Override
public String toString() {
return "this";
@ -1030,6 +1054,8 @@ public abstract class Type implements Comparable<Type>, BytecodeOps {
/** Scope type, always an object type, just a toString override */
public static final Type SCOPE = new ObjectType() {
private static final long serialVersionUID = 1L;
@Override
public String toString() {
return "scope";
@ -1041,6 +1067,7 @@ public abstract class Type implements Comparable<Type>, BytecodeOps {
}
private abstract static class ValueLessType extends Type {
private static final long serialVersionUID = 1L;
ValueLessType(final String name) {
super(name, Unknown.class, MIN_WEIGHT, 1);
@ -1092,6 +1119,8 @@ public abstract class Type implements Comparable<Type>, BytecodeOps {
* inference. It has the minimum type width
*/
public static final Type UNKNOWN = new ValueLessType("<unknown>") {
private static final long serialVersionUID = 1L;
@Override
public String getDescriptor() {
return "<unknown>";
@ -1108,6 +1137,7 @@ public abstract class Type implements Comparable<Type>, BytecodeOps {
* inference. It has the minimum type width
*/
public static final Type SLOT_2 = new ValueLessType("<slot_2>") {
private static final long serialVersionUID = 1L;
@Override
public String getDescriptor() {
@ -1124,4 +1154,8 @@ public abstract class Type implements Comparable<Type>, BytecodeOps {
cache.put(type.getTypeClass(), type);
return type;
}
protected final Object readResolve() {
return Type.typeFor(clazz);
}
}

View File

@ -34,6 +34,8 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
*/
@Immutable
public final class AccessNode extends BaseNode {
private static final long serialVersionUID = 1L;
/** Property name. */
private final String property;

View File

@ -39,6 +39,7 @@ import jdk.nashorn.internal.ir.annotations.Immutable;
*/
@Immutable
public abstract class BaseNode extends Expression implements FunctionCall, Optimistic {
private static final long serialVersionUID = 1L;
/** Base Node. */
protected final Expression base;

View File

@ -43,6 +43,8 @@ import jdk.nashorn.internal.parser.TokenType;
*/
@Immutable
public final class BinaryNode extends Expression implements Assignment<Expression>, Optimistic {
private static final long serialVersionUID = 1L;
// Placeholder for "undecided optimistic ADD type". Unfortunately, we can't decide the type of ADD during optimistic
// type calculation as it can have local variables as its operands that will decide its ultimate type.
private static final Type OPTIMISTIC_UNDECIDED_TYPE = Type.typeFor(new Object(){/*empty*/}.getClass());
@ -56,8 +58,8 @@ public final class BinaryNode extends Expression implements Assignment<Expressio
private final Type type;
private Type cachedType;
private Object cachedTypeFunction;
private transient Type cachedType;
private transient Object cachedTypeFunction;
@Ignore
private static final Set<TokenType> CAN_OVERFLOW =

View File

@ -42,6 +42,8 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
*/
@Immutable
public class Block extends Node implements BreakableNode, Terminal, Flags<Block> {
private static final long serialVersionUID = 1L;
/** List of statements */
protected final List<Statement> statements;

View File

@ -108,6 +108,16 @@ public class BlockLexicalContext extends LexicalContext {
return statement;
}
/**
* Prepend a list of statement to the block being generated
* @param statements a list of statements to prepend
*/
public void prependStatements(final List<Statement> statements) {
assert statements != null;
sstack.peek().addAll(0, statements);
}
/**
* Get the last statement that was emitted into a block
* @return the last statement emitted

View File

@ -32,6 +32,8 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
* Represents a block used as a statement.
*/
public class BlockStatement extends Statement {
private static final long serialVersionUID = 1L;
/** Block to execute. */
private final Block block;

View File

@ -34,6 +34,7 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
*/
@Immutable
public final class BreakNode extends JumpStatement {
private static final long serialVersionUID = 1L;
/**
* Constructor

View File

@ -32,6 +32,7 @@ import jdk.nashorn.internal.ir.annotations.Immutable;
@Immutable
abstract class BreakableStatement extends LexicalContextStatement implements BreakableNode {
private static final long serialVersionUID = 1L;
/** break label. */
protected final Label breakLabel;

View File

@ -27,6 +27,7 @@ package jdk.nashorn.internal.ir;
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
import java.io.Serializable;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
@ -40,6 +41,7 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
*/
@Immutable
public final class CallNode extends LexicalContextExpression implements Optimistic {
private static final long serialVersionUID = 1L;
/** Function identifier or function body. */
private final Expression function;
@ -64,7 +66,8 @@ public final class CallNode extends LexicalContextExpression implements Optimist
/**
* Arguments to be passed to builtin {@code eval} function
*/
public static class EvalArgs {
public static class EvalArgs implements Serializable {
private static final long serialVersionUID = 1L;
private final List<Expression> args;
/** location string for the eval call */

View File

@ -37,6 +37,8 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
*/
@Immutable
public final class CaseNode extends Node implements JoinPredecessor, Labels, Terminal {
private static final long serialVersionUID = 1L;
/** Test expression. */
private final Expression test;

View File

@ -33,6 +33,8 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
*/
@Immutable
public final class CatchNode extends Statement {
private static final long serialVersionUID = 1L;
/** Exception identifier. */
private final IdentNode exception;

View File

@ -34,6 +34,8 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
*/
@Immutable
public class ContinueNode extends JumpStatement {
private static final long serialVersionUID = 1L;
/**
* Constructor
*
@ -80,4 +82,3 @@ public class ContinueNode extends JumpStatement {
return ((LoopNode)target).getContinueLabel();
}
}

View File

@ -33,6 +33,7 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
*/
@Immutable
public final class EmptyNode extends Statement {
private static final long serialVersionUID = 1L;
/**
* Constructor

View File

@ -35,6 +35,8 @@ import jdk.nashorn.internal.runtime.UnwarrantedOptimismException;
*
*/
public abstract class Expression extends Node {
private static final long serialVersionUID = 1L;
static final String OPT_IDENTIFIER = "%";
private static final Function<Symbol, Type> UNKNOWN_LOCALS = new Function<Symbol, Type>() {

View File

@ -35,6 +35,8 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
*/
@Immutable
public final class ExpressionStatement extends Statement {
private static final long serialVersionUID = 1L;
/** Expression to execute. */
private final Expression expression;

View File

@ -33,6 +33,8 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
*/
@Immutable
public final class ForNode extends LoopNode {
private static final long serialVersionUID = 1L;
/** Initialize expression for an ordinary for statement, or the LHS expression receiving iterated-over values in a
* for-in statement. */
private final Expression init;

View File

@ -31,6 +31,7 @@ import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALL
import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_TRACE_ENTEREXIT;
import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_TRACE_MISSES;
import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_TRACE_VALUES;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Iterator;
@ -57,6 +58,8 @@ import jdk.nashorn.internal.runtime.linker.LinkerCallSite;
*/
@Immutable
public final class FunctionNode extends LexicalContextExpression implements Flags<FunctionNode>, CompileUnitHolder {
private static final long serialVersionUID = 1L;
/** Type used for all FunctionNodes */
public static final Type FUNCTION_TYPE = Type.typeFor(ScriptFunction.class);
@ -107,7 +110,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
}
/** Source of entity. */
private final Source source;
private transient final Source source;
/**
* Opaque object representing parser state at the end of the function. Used when reparsing outer functions
@ -141,7 +144,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
private final long lastToken;
/** Method's namespace. */
private final Namespace namespace;
private transient final Namespace namespace;
/** Current compilation state */
@Ignore
@ -207,31 +210,23 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
/** Are we vararg, but do we just pass the arguments along to apply or call */
public static final int HAS_APPLY_TO_CALL_SPECIALIZATION = 1 << 12;
/** Does this function explicitly use the {@link CompilerConstants#RETURN} symbol? Some functions are known to
* always use the return symbol, namely a function that is a program (as it must track its last executed expression
* statement's value) as well as functions that are split (to communicate return values from inner to outer
* partitions). Other functions normally don't use the return symbol (so we optimize away its slot), except in some
* very special cases, e.g. when containing a return statement in a finally block. These special cases set this
* flag. */
public static final int USES_RETURN_SYMBOL = 1 << 13;
/**
* Is this function the top-level program?
*/
public static final int IS_PROGRAM = 1 << 14;
public static final int IS_PROGRAM = 1 << 13;
/**
* Flag indicating whether this function uses the local variable symbol for itself. Only named function expressions
* can have this flag set if they reference themselves (e.g. "(function f() { return f })". Declared functions will
* use the symbol in their parent scope instead when they reference themselves by name.
*/
public static final int USES_SELF_SYMBOL = 1 << 15;
public static final int USES_SELF_SYMBOL = 1 << 14;
/** Does this function use the "this" keyword? */
public static final int USES_THIS = 1 << 16;
public static final int USES_THIS = 1 << 15;
/** Is this declared in a dynamic context */
public static final int IN_DYNAMIC_CONTEXT = 1 << 17;
public static final int IN_DYNAMIC_CONTEXT = 1 << 16;
/**
* The following flags are derived from directive comments within this function.
@ -239,28 +234,28 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
*/
/** parser, print parse tree */
public static final int IS_PRINT_PARSE = 1 << 18;
public static final int IS_PRINT_PARSE = 1 << 17;
/** parser, print lower parse tree */
public static final int IS_PRINT_LOWER_PARSE = 1 << 19;
public static final int IS_PRINT_LOWER_PARSE = 1 << 18;
/** parser, print AST */
public static final int IS_PRINT_AST = 1 << 20;
public static final int IS_PRINT_AST = 1 << 19;
/** parser, print lower AST */
public static final int IS_PRINT_LOWER_AST = 1 << 21;
public static final int IS_PRINT_LOWER_AST = 1 << 20;
/** parser, print symbols */
public static final int IS_PRINT_SYMBOLS = 1 << 22;
public static final int IS_PRINT_SYMBOLS = 1 << 21;
// callsite tracing, profiling within this function
/** profile callsites in this function? */
public static final int IS_PROFILE = 1 << 23;
public static final int IS_PROFILE = 1 << 22;
/** trace callsite enterexit in this function? */
public static final int IS_TRACE_ENTEREXIT = 1 << 24;
public static final int IS_TRACE_ENTEREXIT = 1 << 23;
/** trace callsite misses in this function? */
public static final int IS_TRACE_MISSES = 1 << 25;
public static final int IS_TRACE_MISSES = 1 << 24;
/** trace callsite values in this function? */
public static final int IS_TRACE_VALUES = 1 << 26;
public static final int IS_TRACE_VALUES = 1 << 25;
/**
* Whether this function needs the callee {@link ScriptFunction} instance passed to its code as a
@ -268,7 +263,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
* Rather, it is always calculated (see {@link #needsCallee()}). {@link RecompilableScriptFunctionData}
* will, however, cache the value of this flag.
*/
public static final int NEEDS_CALLEE = 1 << 27;
public static final int NEEDS_CALLEE = 1 << 26;
/** extension callsite flags mask */
public static final int EXTENSION_CALLSITE_FLAGS = IS_PRINT_PARSE |
@ -285,8 +280,8 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
/** Does this function potentially need "arguments"? Note that this is not a full test, as further negative check of REDEFINES_ARGS is needed. */
private static final int MAYBE_NEEDS_ARGUMENTS = USES_ARGUMENTS | HAS_EVAL;
/** Does this function need the parent scope? It needs it if either it or its descendants use variables from it, or have a deep eval. */
private static final int NEEDS_PARENT_SCOPE = USES_ANCESTOR_SCOPE | HAS_DEEP_EVAL;
/** Does this function need the parent scope? It needs it if either it or its descendants use variables from it, or have a deep eval, or it's the program. */
public static final int NEEDS_PARENT_SCOPE = USES_ANCESTOR_SCOPE | HAS_DEEP_EVAL | IS_PROGRAM;
/** What is the return type of this function? */
private Type returnType = Type.UNKNOWN;
@ -358,7 +353,8 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
final Block body,
final List<IdentNode> parameters,
final int thisProperties,
final Class<?> rootClass) {
final Class<?> rootClass,
final Source source, Namespace namespace) {
super(functionNode);
this.endParserState = endParserState;
@ -373,11 +369,11 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
this.parameters = parameters;
this.thisProperties = thisProperties;
this.rootClass = rootClass;
this.source = source;
this.namespace = namespace;
// the fields below never change - they are final and assigned in constructor
this.source = functionNode.source;
this.ident = functionNode.ident;
this.namespace = functionNode.namespace;
this.kind = functionNode.kind;
this.firstToken = functionNode.firstToken;
}
@ -442,6 +438,39 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
return source;
}
/**
* Sets the source and namespace for this function. It can only set a non-null source and namespace for a function
* that currently has both a null source and a null namespace. This is used to re-set the source and namespace for
* a deserialized function node.
* @param source the source for the function.
* @param namespace the namespace for the function
* @return a new function node with the set source and namespace
* @throws IllegalArgumentException if the specified source or namespace is null
* @throws IllegalStateException if the function already has either a source or namespace set.
*/
public FunctionNode initializeDeserialized(final Source source, final Namespace namespace) {
if (source == null || namespace == null) {
throw new IllegalArgumentException();
} else if (this.source == source && this.namespace == namespace) {
return this;
} else if (this.source != null || this.namespace != null) {
throw new IllegalStateException();
}
return new FunctionNode(
this,
lastToken,
endParserState,
flags,
name,
returnType,
compileUnit,
compilationState,
body,
parameters,
thisProperties,
rootClass, source, namespace);
}
/**
* Get the unique ID for this function within the script file.
* @return the id
@ -543,6 +572,28 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
}
final EnumSet<CompilationState> newState = EnumSet.copyOf(this.compilationState);
newState.add(state);
return setCompilationState(lc, newState);
}
/**
* Copy a compilation state from an original function to this function. Used when creating synthetic
* function nodes by the splitter.
*
* @param lc lexical context
* @param original the original function node to copy compilation state from
* @return function node or a new one if state was changed
*/
public FunctionNode copyCompilationState(final LexicalContext lc, final FunctionNode original) {
final EnumSet<CompilationState> origState = original.compilationState;
if (!AssertsEnabled.assertsEnabled() || this.compilationState.containsAll(origState)) {
return this;
}
final EnumSet<CompilationState> newState = EnumSet.copyOf(this.compilationState);
newState.addAll(origState);
return setCompilationState(lc, newState);
}
private FunctionNode setCompilationState(final LexicalContext lc, final EnumSet<CompilationState> compilationState) {
return Node.replaceInLexicalContext(
lc,
this,
@ -554,13 +605,14 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
name,
returnType,
compileUnit,
newState,
compilationState,
body,
parameters,
thisProperties,
rootClass));
rootClass, source, namespace));
}
/**
* Create a unique name in the namespace of this FunctionNode
* @param base prefix for name
@ -630,7 +682,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
body,
parameters,
thisProperties,
rootClass));
rootClass, source, namespace));
}
@Override
@ -705,17 +757,10 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
* @return true if the function's generated Java method needs a {@code callee} parameter.
*/
public boolean needsCallee() {
// NOTE: we only need isSplit() here to ensure that :scope can never drop below slot 2 for splitting array units.
return needsParentScope() || usesSelfSymbol() || isSplit() || (needsArguments() && !isStrict()) || hasOptimisticApplyToCall();
}
/**
* Check if this function uses the return symbol
* @return true if uses the return symbol
*/
public boolean usesReturnSymbol() {
return isProgram() || isSplit() || getFlag(USES_RETURN_SYMBOL);
}
/**
* Return {@code true} if this function makes use of the {@code this} object.
*
@ -778,7 +823,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
body,
parameters,
thisProperties,
rootClass));
rootClass, source, namespace));
}
/**
@ -846,7 +891,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
* @return true if the function needs parent scope.
*/
public boolean needsParentScope() {
return getFlag(NEEDS_PARENT_SCOPE) || isProgram();
return getFlag(NEEDS_PARENT_SCOPE);
}
/**
@ -874,7 +919,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
body,
parameters,
thisProperties,
rootClass));
rootClass, source, namespace));
}
/**
@ -951,7 +996,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
body,
parameters,
thisProperties,
rootClass));
rootClass, source, namespace));
}
/**
@ -965,9 +1010,9 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
}
/**
* Checks if this function is a sub-function generated by splitting a larger one
* Checks if this function is split into several smaller fragments.
*
* @return true if this function is split from a larger one
* @return true if this function is split into several smaller fragments.
*/
public boolean isSplit() {
return getFlag(IS_SPLIT);
@ -1017,7 +1062,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
body,
parameters,
thisProperties,
rootClass));
rootClass, source, namespace));
}
/**
@ -1096,7 +1141,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
body,
parameters,
thisProperties,
rootClass
rootClass, source, namespace
));
}
@ -1144,7 +1189,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
body,
parameters,
thisProperties,
rootClass));
rootClass, source, namespace));
}
/**
@ -1200,6 +1245,6 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
body,
parameters,
thisProperties,
rootClass));
rootClass, source, namespace));
}
}

View File

@ -0,0 +1,70 @@
/*
* Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.nashorn.internal.ir;
import java.util.function.Function;
import jdk.nashorn.internal.codegen.CompilerConstants;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
import jdk.nashorn.internal.runtime.Scope;
/**
* Synthetic AST node that represents loading of the scope object and invocation of the {@link Scope#getSplitState()}
* method on it. It has no JavaScript source representation and only occurs in synthetic functions created by
* the split-into-functions transformation.
*/
public final class GetSplitState extends Expression {
private static final long serialVersionUID = 1L;
/** The sole instance of this AST node. */
public final static GetSplitState INSTANCE = new GetSplitState();
private GetSplitState() {
super(NO_TOKEN, NO_FINISH);
}
@Override
public Type getType(final Function<Symbol, Type> localVariableTypes) {
return Type.INT;
}
@Override
public Node accept(final NodeVisitor<? extends LexicalContext> visitor) {
return visitor.enterGetSplitState(this) ? visitor.leaveGetSplitState(this) : this;
}
@Override
public void toString(final StringBuilder sb, final boolean printType) {
if (printType) {
sb.append("{I}");
}
sb.append(CompilerConstants.SCOPE.symbolName()).append('.').append(Scope.GET_SPLIT_STATE.name()).append("()");
}
private Object readResolve() {
return INSTANCE;
}
}

View File

@ -42,6 +42,8 @@ import jdk.nashorn.internal.parser.TokenType;
*/
@Immutable
public final class IdentNode extends Expression implements PropertyKey, FunctionCall, Optimistic, JoinPredecessor {
private static final long serialVersionUID = 1L;
private static final int PROPERTY_NAME = 1 << 0;
private static final int INITIALIZED_HERE = 1 << 1;
private static final int FUNCTION = 1 << 2;

View File

@ -33,6 +33,8 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
*/
@Immutable
public final class IfNode extends Statement implements JoinPredecessor {
private static final long serialVersionUID = 1L;
/** Test expression. */
private final Expression test;

View File

@ -33,6 +33,8 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
*/
@Immutable
public final class IndexNode extends BaseNode {
private static final long serialVersionUID = 1L;
/** Property index. */
private final Expression index;

View File

@ -33,6 +33,7 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
* A wrapper for an expression that is in a position to be a join predecessor.
*/
public class JoinPredecessorExpression extends Expression implements JoinPredecessor {
private static final long serialVersionUID = 1L;
private final Expression expression;
private final LocalVariableConversion conversion;

View File

@ -31,6 +31,7 @@ import jdk.nashorn.internal.codegen.Label;
* Common base class for jump statements (e.g. {@code break} and {@code continue}).
*/
public abstract class JumpStatement extends Statement implements JoinPredecessor {
private static final long serialVersionUID = 1L;
private final String labelName;
private final LocalVariableConversion conversion;

View File

@ -35,6 +35,8 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
*/
@Immutable
public final class LabelNode extends LexicalContextStatement implements JoinPredecessor {
private static final long serialVersionUID = 1L;
/** Label ident. */
private final String labelName;

View File

@ -439,6 +439,14 @@ public class LexicalContext {
return getParentBlock() == null;
}
/**
* Is the topmost lexical context element body of a SplitNode?
* @return true if it's the body of a split node.
*/
public boolean isSplitBody() {
return sp >= 2 && stack[sp - 1] instanceof Block && stack[sp - 2] instanceof SplitNode;
}
/**
* Get the parent function for a function in the lexical context
* @param functionNode function for which to get parent
@ -472,9 +480,6 @@ public class LexicalContext {
final LexicalContextNode node = iter.next();
if (node == until) {
break;
} else if (node instanceof SplitNode) {
// Don't bother popping scopes if we're going to do a return from a split method anyway.
return 0;
}
assert !(node instanceof FunctionNode); // Can't go outside current function
if (node instanceof WithNode || node instanceof Block && ((Block)node).needsScope()) {

View File

@ -28,6 +28,7 @@ package jdk.nashorn.internal.ir;
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
abstract class LexicalContextExpression extends Expression implements LexicalContextNode {
private static final long serialVersionUID = 1L;
LexicalContextExpression(final LexicalContextExpression expr) {
super(expr);

View File

@ -28,6 +28,8 @@ package jdk.nashorn.internal.ir;
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
abstract class LexicalContextStatement extends Statement implements LexicalContextNode {
private static final long serialVersionUID = 1L;
/**
* Constructor
*

View File

@ -25,6 +25,7 @@
package jdk.nashorn.internal.ir;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@ -49,6 +50,8 @@ import jdk.nashorn.internal.runtime.Undefined;
*/
@Immutable
public abstract class LiteralNode<T> extends Expression implements PropertyKey {
private static final long serialVersionUID = 1L;
/** Literal value */
protected final T value;
@ -270,6 +273,8 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
* @param <T> the literal type
*/
public static class PrimitiveLiteralNode<T> extends LiteralNode<T> {
private static final long serialVersionUID = 1L;
private PrimitiveLiteralNode(final long token, final int finish, final T value) {
super(token, finish, value);
}
@ -304,6 +309,7 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
@Immutable
private static final class BooleanLiteralNode extends PrimitiveLiteralNode<Boolean> {
private static final long serialVersionUID = 1L;
private BooleanLiteralNode(final long token, final int finish, final boolean value) {
super(Token.recast(token, value ? TokenType.TRUE : TokenType.FALSE), finish, value);
@ -356,6 +362,7 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
@Immutable
private static final class NumberLiteralNode extends PrimitiveLiteralNode<Number> {
private static final long serialVersionUID = 1L;
private final Type type = numberGetType(value);
@ -418,6 +425,8 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
}
private static class UndefinedLiteralNode extends PrimitiveLiteralNode<Undefined> {
private static final long serialVersionUID = 1L;
private UndefinedLiteralNode(final long token, final int finish) {
super(Token.recast(token, TokenType.OBJECT), finish, ScriptRuntime.UNDEFINED);
}
@ -454,6 +463,8 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
@Immutable
private static class StringLiteralNode extends PrimitiveLiteralNode<String> {
private static final long serialVersionUID = 1L;
private StringLiteralNode(final long token, final int finish, final String value) {
super(Token.recast(token, TokenType.STRING), finish, value);
}
@ -497,6 +508,8 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
@Immutable
private static class LexerTokenLiteralNode extends LiteralNode<LexerToken> {
private static final long serialVersionUID = 1L;
private LexerTokenLiteralNode(final long token, final int finish, final LexerToken value) {
super(Token.recast(token, TokenType.STRING), finish, value); //TODO is string the correct token type here?
}
@ -560,6 +573,7 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
}
private static final class NullLiteralNode extends PrimitiveLiteralNode<Object> {
private static final long serialVersionUID = 1L;
private NullLiteralNode(final long token, final int finish) {
super(Token.recast(token, TokenType.OBJECT), finish, null);
@ -590,6 +604,7 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
*/
@Immutable
public static final class ArrayLiteralNode extends LiteralNode<Expression[]> implements LexicalContextNode {
private static final long serialVersionUID = 1L;
/** Array element type. */
private final Type elementType;
@ -607,7 +622,9 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
* An ArrayUnit is a range in an ArrayLiteral. ArrayLiterals can
* be split if they are too large, for bytecode generation reasons
*/
public static final class ArrayUnit implements CompileUnitHolder {
public static final class ArrayUnit implements CompileUnitHolder, Serializable {
private static final long serialVersionUID = 1L;
/** Compile unit associated with the postsets range. */
private final CompileUnit compileUnit;
@ -655,13 +672,13 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
private static final class ArrayLiteralInitializer {
static ArrayLiteralNode initialize(final ArrayLiteralNode node) {
final Type elementType = computeElementType(node.value, node.elementType);
final Type elementType = computeElementType(node.value);
final int[] postsets = computePostsets(node.value);
final Object presets = computePresets(node.value, elementType, postsets);
return new ArrayLiteralNode(node, node.value, elementType, postsets, presets, node.units);
}
private static Type computeElementType(final Expression[] value, final Type elementType) {
private static Type computeElementType(final Expression[] value) {
Type widestElementType = Type.INT;
for (final Expression elem : value) {

View File

@ -34,6 +34,8 @@ import jdk.nashorn.internal.codegen.Label;
* A loop node, for example a while node, do while node or for node
*/
public abstract class LoopNode extends BreakableStatement {
private static final long serialVersionUID = 1L;
/** loop continue label. */
protected final Label continueLabel;

View File

@ -25,6 +25,7 @@
package jdk.nashorn.internal.ir;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
@ -34,7 +35,18 @@ import jdk.nashorn.internal.parser.TokenType;
/**
* Nodes are used to compose Abstract Syntax Trees.
*/
public abstract class Node implements Cloneable {
public abstract class Node implements Cloneable, Serializable {
private static final long serialVersionUID = 1L;
/** Constant used for synthetic AST nodes that have no line number. */
public static final int NO_LINE_NUMBER = -1;
/** Constant used for synthetic AST nodes that have no token. */
public static final long NO_TOKEN = 0L;
/** Constant used for synthetic AST nodes that have no finish. */
public static final int NO_FINISH = 0;
/** Start of source range. */
protected final int start;

View File

@ -37,6 +37,7 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
*/
@Immutable
public final class ObjectNode extends Expression {
private static final long serialVersionUID = 1L;
/** Literal elements. */
private final List<PropertyNode> elements;

View File

@ -33,6 +33,7 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
*/
@Immutable
public final class PropertyNode extends Node {
private static final long serialVersionUID = 1L;
/** Property key. */
private final PropertyKey key;

View File

@ -36,6 +36,8 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
*/
@Immutable
public class ReturnNode extends Statement {
private static final long serialVersionUID = 1L;
/** Optional expression. */
private final Expression expression;

View File

@ -42,6 +42,7 @@ import jdk.nashorn.internal.parser.TokenType;
*/
@Immutable
public class RuntimeNode extends Expression implements Optimistic {
private static final long serialVersionUID = 1L;
/**
* Request enum used for meta-information about the runtime request

View File

@ -0,0 +1,70 @@
/*
* Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.nashorn.internal.ir;
import jdk.nashorn.internal.codegen.CompilerConstants;
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
import jdk.nashorn.internal.runtime.Scope;
/**
* Synthetic AST node that represents loading of the scope object and invocation of the {@link Scope#setSplitState(int)}
* method on it. It has no JavaScript source representation and only occurs in synthetic functions created by
* the split-into-functions transformation.
*/
public final class SetSplitState extends Statement {
private static final long serialVersionUID = 1L;
private final int state;
/**
* Creates a new split state setter
* @param state the state to set
* @param lineNumber the line number where it is inserted
*/
public SetSplitState(final int state, final int lineNumber) {
super(lineNumber, NO_TOKEN, NO_FINISH);
this.state = state;
}
/**
* Returns the state this setter sets.
* @return the state this setter sets.
*/
public int getState() {
return state;
}
@Override
public Node accept(final NodeVisitor<? extends LexicalContext> visitor) {
return visitor.enterSetSplitState(this) ? visitor.leaveSetSplitState(this) : this;
}
@Override
public void toString(final StringBuilder sb, final boolean printType) {
sb.append(CompilerConstants.SCOPE.symbolName()).append('.').append(Scope.SET_SPLIT_STATE.name())
.append('(').append(state).append(");");
}
}

View File

@ -25,13 +25,10 @@
package jdk.nashorn.internal.ir;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectOutputStream;
import jdk.nashorn.internal.codegen.CompileUnit;
import jdk.nashorn.internal.codegen.Label;
import jdk.nashorn.internal.ir.annotations.Immutable;
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
@ -39,7 +36,9 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
* Node indicating code is split across classes.
*/
@Immutable
public class SplitNode extends LexicalContextStatement implements Labels, CompileUnitHolder {
public class SplitNode extends LexicalContextStatement implements CompileUnitHolder {
private static final long serialVersionUID = 1L;
/** Split node method name. */
private final String name;
@ -49,8 +48,6 @@ public class SplitNode extends LexicalContextStatement implements Labels, Compil
/** Body of split code. */
private final Block body;
private Map<Label, JoinPredecessor> jumps;
/**
* Constructor
*
@ -65,19 +62,18 @@ public class SplitNode extends LexicalContextStatement implements Labels, Compil
this.compileUnit = compileUnit;
}
private SplitNode(final SplitNode splitNode, final Block body, final CompileUnit compileUnit, final Map<Label, JoinPredecessor> jumps) {
private SplitNode(final SplitNode splitNode, final Block body, final CompileUnit compileUnit) {
super(splitNode);
this.name = splitNode.name;
this.body = body;
this.compileUnit = compileUnit;
this.jumps = jumps;
}
/**
* Get the body for this split node - i.e. the actual code it encloses
* @return body for split node
*/
public Node getBody() {
public Block getBody() {
return body;
}
@ -85,7 +81,7 @@ public class SplitNode extends LexicalContextStatement implements Labels, Compil
if (this.body == body) {
return this;
}
return Node.replaceInLexicalContext(lc, this, new SplitNode(this, body, compileUnit, jumps));
return Node.replaceInLexicalContext(lc, this, new SplitNode(this, body, compileUnit));
}
@Override
@ -131,33 +127,12 @@ public class SplitNode extends LexicalContextStatement implements Labels, Compil
if (this.compileUnit == compileUnit) {
return this;
}
return Node.replaceInLexicalContext(lc, this, new SplitNode(this, body, compileUnit, jumps));
return Node.replaceInLexicalContext(lc, this, new SplitNode(this, body, compileUnit));
}
/**
* Adds a jump that crosses this split node's boundary (it originates within the split node, and goes to a target
* outside of it).
* @param jumpOrigin the join predecessor that's the origin of the jump
* @param targetLabel the label that's the target of the jump.
*/
public void addJump(final JoinPredecessor jumpOrigin, final Label targetLabel) {
if (jumps == null) {
jumps = new HashMap<>();
}
jumps.put(targetLabel, jumpOrigin);
}
/**
* Returns the jump origin within this split node for a target.
* @param targetLabel the target for which a jump origin is sought.
* @return the jump origin, or null.
*/
public JoinPredecessor getJumpOrigin(final Label targetLabel) {
return jumps == null ? null : jumps.get(targetLabel);
}
@Override
public List<Label> getLabels() {
return Collections.unmodifiableList(new ArrayList<>(jumps.keySet()));
private void writeObject(final ObjectOutputStream out) throws IOException {
// We are only serializing the AST after we run SplitIntoFunctions; no SplitNodes can remain for the
// serialization.
throw new NotSerializableException(getClass().getName());
}
}

View File

@ -0,0 +1,64 @@
/*
* Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.nashorn.internal.ir;
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
/**
* Synthetic AST node that represents return from a split fragment of a split function for control flow reasons (break
* or continue into a target outside the current fragment). It has no JavaScript source representation and only occurs
* in synthetic functions created by the split-into-functions transformation. It is different from a return node in
* that the return value is irrelevant, and doesn't affect the function's return type calculation.
*/
public final class SplitReturn extends Statement {
private static final long serialVersionUID = 1L;
/** The sole instance of this AST node. */
public static final SplitReturn INSTANCE = new SplitReturn();
private SplitReturn() {
super(NO_LINE_NUMBER, NO_TOKEN, NO_FINISH);
}
@Override
public boolean isTerminal() {
return true;
}
@Override
public Node accept(final NodeVisitor<? extends LexicalContext> visitor) {
return visitor.enterSplitReturn(this) ? visitor.leaveSplitReturn(this) : this;
}
@Override
public void toString(StringBuilder sb, boolean printType) {
sb.append(":splitreturn;");
}
private Object readResolve() {
return INSTANCE;
}
}

View File

@ -31,6 +31,7 @@ package jdk.nashorn.internal.ir;
* location information is the Statement
*/
public abstract class Statement extends Node implements Terminal {
private static final long serialVersionUID = 1L;
private final int lineNumber;

View File

@ -37,6 +37,8 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
*/
@Immutable
public final class SwitchNode extends BreakableStatement {
private static final long serialVersionUID = 1L;
/** Switch expression. */
private final Expression expression;

View File

@ -97,7 +97,7 @@ public final class Symbol implements Comparable<Symbol> {
private int firstSlot = -1;
/** Field number in scope or property; array index in varargs when not using arguments object. */
private int fieldIndex;
private int fieldIndex = -1;
/** Number of times this symbol is used in code */
private int useCount;
@ -135,28 +135,15 @@ public final class Symbol implements Comparable<Symbol> {
*
* @param name name of symbol
* @param flags symbol flags
* @param slot bytecode slot for this symbol
*/
protected Symbol(final String name, final int flags, final int slot) {
public Symbol(final String name, final int flags) {
this.name = name;
this.flags = flags;
this.firstSlot = slot;
this.fieldIndex = -1;
if(shouldTrace()) {
trace("CREATE SYMBOL " + name);
}
}
/**
* Constructor
*
* @param name name of symbol
* @param flags symbol flags
*/
public Symbol(final String name, final int flags) {
this(name, flags, -1);
}
private static String align(final String string, final int max) {
final StringBuilder sb = new StringBuilder();
sb.append(string.substring(0, Math.min(string.length(), max)));

View File

@ -37,6 +37,8 @@ import jdk.nashorn.internal.parser.TokenType;
*/
@Immutable
public final class TernaryNode extends Expression {
private static final long serialVersionUID = 1L;
private final Expression test;
private final JoinPredecessorExpression trueExpr;
private final JoinPredecessorExpression falseExpr;

View File

@ -33,6 +33,8 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
*/
@Immutable
public final class ThrowNode extends Statement implements JoinPredecessor {
private static final long serialVersionUID = 1L;
/** Exception expression. */
private final Expression expression;

View File

@ -36,6 +36,8 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
*/
@Immutable
public final class TryNode extends Statement implements JoinPredecessor {
private static final long serialVersionUID = 1L;
/** Try statements. */
private final Block body;

View File

@ -46,6 +46,8 @@ import jdk.nashorn.internal.parser.TokenType;
*/
@Immutable
public final class UnaryNode extends Expression implements Assignment<Expression>, Optimistic {
private static final long serialVersionUID = 1L;
/** Right hand side argument. */
private final Expression expression;

View File

@ -34,6 +34,8 @@ import jdk.nashorn.internal.parser.Token;
*/
@Immutable
public final class VarNode extends Statement implements Assignment<IdentNode> {
private static final long serialVersionUID = 1L;
/** Var name. */
private final IdentNode name;

View File

@ -34,6 +34,8 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
*/
@Immutable
public final class WhileNode extends LoopNode {
private static final long serialVersionUID = 1L;
/** is this a do while node ? */
private final boolean isDoWhile;

View File

@ -33,6 +33,8 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
*/
@Immutable
public final class WithNode extends LexicalContextStatement {
private static final long serialVersionUID = 1L;
/** This expression. */
private final Expression expression;

View File

@ -38,6 +38,7 @@ import jdk.nashorn.internal.ir.EmptyNode;
import jdk.nashorn.internal.ir.ExpressionStatement;
import jdk.nashorn.internal.ir.ForNode;
import jdk.nashorn.internal.ir.FunctionNode;
import jdk.nashorn.internal.ir.GetSplitState;
import jdk.nashorn.internal.ir.IdentNode;
import jdk.nashorn.internal.ir.IfNode;
import jdk.nashorn.internal.ir.IndexNode;
@ -50,7 +51,9 @@ import jdk.nashorn.internal.ir.ObjectNode;
import jdk.nashorn.internal.ir.PropertyNode;
import jdk.nashorn.internal.ir.ReturnNode;
import jdk.nashorn.internal.ir.RuntimeNode;
import jdk.nashorn.internal.ir.SetSplitState;
import jdk.nashorn.internal.ir.SplitNode;
import jdk.nashorn.internal.ir.SplitReturn;
import jdk.nashorn.internal.ir.SwitchNode;
import jdk.nashorn.internal.ir.TernaryNode;
import jdk.nashorn.internal.ir.ThrowNode;
@ -389,6 +392,26 @@ public abstract class NodeVisitor<T extends LexicalContext> {
return leaveDefault(functionNode);
}
/**
* Callback for entering a {@link GetSplitState}.
*
* @param getSplitState the get split state expression
* @return true if traversal should continue and node children be traversed, false otherwise
*/
public boolean enterGetSplitState(final GetSplitState getSplitState) {
return enterDefault(getSplitState);
}
/**
* Callback for leaving a {@link GetSplitState}.
*
* @param getSplitState the get split state expression
* @return processed node, which will replace the original one, or the original node
*/
public Node leaveGetSplitState(final GetSplitState getSplitState) {
return leaveDefault(getSplitState);
}
/**
* Callback for entering an IdentNode
*
@ -569,6 +592,26 @@ public abstract class NodeVisitor<T extends LexicalContext> {
return leaveDefault(runtimeNode);
}
/**
* Callback for entering a {@link SetSplitState}.
*
* @param setSplitState the set split state statement
* @return true if traversal should continue and node children be traversed, false otherwise
*/
public boolean enterSetSplitState(final SetSplitState setSplitState) {
return enterDefault(setSplitState);
}
/**
* Callback for leaving a {@link SetSplitState}.
*
* @param setSplitState the set split state expression
* @return processed node, which will replace the original one, or the original node
*/
public Node leaveSetSplitState(final SetSplitState setSplitState) {
return leaveDefault(setSplitState);
}
/**
* Callback for entering a SplitNode
*
@ -589,6 +632,26 @@ public abstract class NodeVisitor<T extends LexicalContext> {
return leaveDefault(splitNode);
}
/**
* Callback for entering a SplitReturn
*
* @param splitReturn the node
* @return true if traversal should continue and node children be traversed, false otherwise
*/
public boolean enterSplitReturn(final SplitReturn splitReturn) {
return enterDefault(splitReturn);
}
/**
* Callback for leaving a SplitReturn
*
* @param splitReturn the node
* @return processed node, which will replace the original one, or the original node
*/
public Node leaveSplitReturn(final SplitReturn splitReturn) {
return leaveDefault(splitReturn);
}
/**
* Callback for entering a SwitchNode
*

View File

@ -48,7 +48,7 @@ import jdk.nashorn.internal.runtime.options.Options;
/**
* This class is abstraction for all method handle, switchpoint and method type
* operations. This enables the functionality interface to be subclassed and
* intrumensted, as it has been proven vital to keep the number of method
* instrumented, as it has been proven vital to keep the number of method
* handles in the system down.
*
* All operations of the above type should go through this class, and not

View File

@ -92,9 +92,10 @@ public final class NativeArray extends ScriptObject implements OptimisticBuiltin
private static final Object CALL_CMP = new Object();
private static final Object TO_LOCALE_STRING = new Object();
private SwitchPoint lengthMadeNotWritableSwitchPoint;
private PushLinkLogic pushLinkLogic;
private PopLinkLogic popLinkLogic;
private SwitchPoint lengthMadeNotWritableSwitchPoint;
private PushLinkLogic pushLinkLogic;
private PopLinkLogic popLinkLogic;
private ConcatLinkLogic concatLinkLogic;
/**
* Index for the modification SwitchPoint that triggers when length
@ -130,7 +131,9 @@ public final class NativeArray extends ScriptObject implements OptimisticBuiltin
this(ArrayData.allocate(array.length));
ArrayData arrayData = this.getArray();
arrayData.ensure(array.length - 1);
if (array.length > 0) {
arrayData.ensure(array.length - 1);
}
for (int index = 0; index < array.length; index++) {
final Object value = array[index];
@ -753,6 +756,79 @@ public final class NativeArray extends ScriptObject implements OptimisticBuiltin
return construct(newObj, self, new Object[]{length});
}
/**
* ECMA 15.4.4.4 Array.prototype.concat ( [ item1 [ , item2 [ , ... ] ] ] )
*
* @param self self reference
* @param arg argument
* @return resulting NativeArray
*/
@SpecializedFunction(linkLogic=ConcatLinkLogic.class)
public static NativeArray concat(final Object self, final int arg) {
final ContinuousArrayData newData = getContinuousArrayDataCCE(self, Integer.class).copy(); //get at least an integer data copy of this data
newData.fastPush(arg); //add an integer to its end
return new NativeArray(newData);
}
/**
* ECMA 15.4.4.4 Array.prototype.concat ( [ item1 [ , item2 [ , ... ] ] ] )
*
* @param self self reference
* @param arg argument
* @return resulting NativeArray
*/
@SpecializedFunction(linkLogic=ConcatLinkLogic.class)
public static NativeArray concat(final Object self, final long arg) {
final ContinuousArrayData newData = getContinuousArrayDataCCE(self, Long.class).copy(); //get at least a long array data copy of this data
newData.fastPush(arg); //add a long at the end
return new NativeArray(newData);
}
/**
* ECMA 15.4.4.4 Array.prototype.concat ( [ item1 [ , item2 [ , ... ] ] ] )
*
* @param self self reference
* @param arg argument
* @return resulting NativeArray
*/
@SpecializedFunction(linkLogic=ConcatLinkLogic.class)
public static NativeArray concat(final Object self, final double arg) {
final ContinuousArrayData newData = getContinuousArrayDataCCE(self, Double.class).copy(); //get at least a number array data copy of this data
newData.fastPush(arg); //add a double at the end
return new NativeArray(newData);
}
/**
* ECMA 15.4.4.4 Array.prototype.concat ( [ item1 [ , item2 [ , ... ] ] ] )
*
* @param self self reference
* @param arg argument
* @return resulting NativeArray
*/
@SpecializedFunction(linkLogic=ConcatLinkLogic.class)
public static NativeArray concat(final Object self, final Object arg) {
//arg is [NativeArray] of same type.
final ContinuousArrayData selfData = getContinuousArrayDataCCE(self);
final ContinuousArrayData newData;
if (arg instanceof NativeArray) {
final ContinuousArrayData argData = (ContinuousArrayData)((NativeArray)arg).getArray();
if (argData.isEmpty()) {
newData = selfData.copy();
} else if (selfData.isEmpty()) {
newData = argData.copy();
} else {
final Class<?> widestElementType = selfData.widest(argData).getBoxedElementType();
newData = ((ContinuousArrayData)selfData.convert(widestElementType)).fastConcat((ContinuousArrayData)argData.convert(widestElementType));
}
} else {
newData = getContinuousArrayDataCCE(self, Object.class).copy();
newData.fastPush(arg);
}
return new NativeArray(newData);
}
/**
* ECMA 15.4.4.4 Array.prototype.concat ( [ item1 [ , item2 [ , ... ] ] ] )
*
@ -763,6 +839,7 @@ public final class NativeArray extends ScriptObject implements OptimisticBuiltin
@Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
public static NativeArray concat(final Object self, final Object... args) {
final ArrayList<Object> list = new ArrayList<>();
concatToList(list, Global.toObject(self));
for (final Object obj : args) {
@ -1692,13 +1769,15 @@ public final class NativeArray extends ScriptObject implements OptimisticBuiltin
return pushLinkLogic == null ? new PushLinkLogic(this) : pushLinkLogic;
} else if (clazz == PopLinkLogic.class) {
return popLinkLogic == null ? new PopLinkLogic(this) : pushLinkLogic;
} else if (clazz == ConcatLinkLogic.class) {
return concatLinkLogic == null ? new ConcatLinkLogic(this) : concatLinkLogic;
}
return null;
}
@Override
public boolean hasPerInstanceAssumptions() {
return true; //length switchpoint
return true; //length writable switchpoint
}
/**
@ -1797,6 +1876,40 @@ public final class NativeArray extends ScriptObject implements OptimisticBuiltin
}
}
/**
* This is linker logic for optimistic concatenations
*/
private static final class ConcatLinkLogic extends ArrayLinkLogic {
private ConcatLinkLogic(final NativeArray array) {
super(array);
}
@Override
public boolean canLink(final Object self, final CallSiteDescriptor desc, final LinkRequest request) {
final Object[] args = request.getArguments();
if (args.length != 3) { //single argument check
return false;
}
final ContinuousArrayData selfData = getContinuousArrayData(self);
if (selfData == null) {
return false;
}
final Object arg = args[2];
//args[2] continuousarray or non arraydata, let past non array datas
if (arg instanceof NativeArray) {
final ContinuousArrayData argData = getContinuousArrayData(arg);
if (argData == null) {
return false;
}
}
return true;
}
}
/**
* This is linker logic for optimistic pushes
*/
@ -1864,6 +1977,14 @@ public final class NativeArray extends ScriptObject implements OptimisticBuiltin
throw new ClassCastException();
}
private static final ContinuousArrayData getContinuousArrayDataCCE(final Object self) {
try {
return (ContinuousArrayData)((NativeArray)self).getArray();
} catch (final NullPointerException e) {
throw new ClassCastException();
}
}
private static final ContinuousArrayData getContinuousArrayDataCCE(final Object self, final Class<?> elementType) {
try {
return (ContinuousArrayData)((NativeArray)self).getArray(elementType); //ensure element type can fit "elementType"

View File

@ -89,6 +89,11 @@ public final class NativeFloat32Array extends ArrayBufferView {
return double.class;
}
@Override
public Class<?> getBoxedElementType() {
return Double.class;
}
@Override
protected MethodHandle getGetElem() {
return GET_ELEM;

View File

@ -99,6 +99,11 @@ public final class NativeFloat64Array extends ArrayBufferView {
return double.class;
}
@Override
public Class<?> getBoxedElementType() {
return Double.class;
}
private double getElem(final int index) {
try {
return nb.get(index);

View File

@ -100,6 +100,11 @@ public final class NativeInt16Array extends ArrayBufferView {
return int.class;
}
@Override
public Class<?> getBoxedElementType() {
return Integer.class;
}
private int getElem(final int index) {
try {
return nb.get(index);

View File

@ -117,6 +117,11 @@ public final class NativeInt32Array extends ArrayBufferView {
return int.class;
}
@Override
public Class<?> getBoxedElementType() {
return Integer.class;
}
@Override
public int getInt(final int index) {
return getElem(index);

View File

@ -98,6 +98,11 @@ public final class NativeInt8Array extends ArrayBufferView {
return int.class;
}
@Override
public Class<?> getBoxedElementType() {
return Integer.class;
}
private int getElem(final int index) {
try {
return nb.get(index);

View File

@ -28,7 +28,6 @@ package jdk.nashorn.internal.objects;
import static jdk.nashorn.internal.lookup.Lookup.MH;
import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;

View File

@ -123,6 +123,11 @@ public final class NativeUint16Array extends ArrayBufferView {
return int.class;
}
@Override
public Class<?> getBoxedElementType() {
return Integer.class;
}
@Override
public int getInt(final int index) {
return getElem(index);

View File

@ -132,6 +132,11 @@ public final class NativeUint32Array extends ArrayBufferView {
return long.class;
}
@Override
public Class<?> getBoxedElementType() {
return Integer.class;
}
@Override
public int getInt(final int index) {
return (int)getLong(index);

View File

@ -123,6 +123,11 @@ public final class NativeUint8Array extends ArrayBufferView {
return int.class;
}
@Override
public Class<?> getBoxedElementType() {
return Integer.class;
}
@Override
public int getInt(final int index) {
return getElem(index);

View File

@ -103,6 +103,11 @@ public final class NativeUint8ClampedArray extends ArrayBufferView {
return int.class;
}
@Override
public Class<?> getBoxedElementType() {
return int.class;
}
private int getElem(final int index) {
try {
return nb.get(index) & 0xff;

View File

@ -32,7 +32,6 @@ import static jdk.nashorn.internal.parser.TokenType.ESCSTRING;
import static jdk.nashorn.internal.parser.TokenType.RBRACE;
import static jdk.nashorn.internal.parser.TokenType.RBRACKET;
import static jdk.nashorn.internal.parser.TokenType.STRING;
import java.util.ArrayList;
import java.util.List;
import jdk.nashorn.internal.ir.Expression;

View File

@ -46,6 +46,7 @@ import static jdk.nashorn.internal.parser.TokenType.RPAREN;
import static jdk.nashorn.internal.parser.TokenType.STRING;
import static jdk.nashorn.internal.parser.TokenType.XML;
import java.io.Serializable;
import jdk.nashorn.internal.runtime.ECMAErrors;
import jdk.nashorn.internal.runtime.ErrorManager;
import jdk.nashorn.internal.runtime.JSErrorType;
@ -1717,7 +1718,9 @@ public class Lexer extends Scanner {
* Helper class for Lexer tokens, e.g XML or RegExp tokens.
* This is the abstract superclass
*/
public static abstract class LexerToken {
public static abstract class LexerToken implements Serializable {
private static final long serialVersionUID = 1L;
private final String expression;
/**
@ -1741,6 +1744,8 @@ public class Lexer extends Scanner {
* Temporary container for regular expressions.
*/
public static class RegexToken extends LexerToken {
private static final long serialVersionUID = 1L;
/** Options. */
private final String options;
@ -1773,6 +1778,7 @@ public class Lexer extends Scanner {
* Temporary container for XML expression.
*/
public static class XMLToken extends LexerToken {
private static final long serialVersionUID = 1L;
/**
* Constructor.

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.nashorn.internal.runtime;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.zip.InflaterInputStream;
import jdk.nashorn.internal.ir.FunctionNode;
/**
* This static utility class performs deserialization of FunctionNode ASTs from a byte array.
* The format is a standard Java serialization stream, deflated.
*/
final class AstDeserializer {
static FunctionNode deserialize(final byte[] serializedAst) {
try {
return (FunctionNode)new ObjectInputStream(new InflaterInputStream(new ByteArrayInputStream(
serializedAst))).readObject();
} catch (final ClassNotFoundException | IOException e) {
// This is internal, can't happen
throw new AssertionError("Unexpected exception deserializing function", e);
}
}
}

View File

@ -27,6 +27,7 @@ package jdk.nashorn.internal.runtime;
import static jdk.nashorn.internal.lookup.Lookup.MH;
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.isValid;
import java.lang.invoke.CallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
@ -786,6 +787,7 @@ final class CompiledFunction {
// isn't available, we'll use the old one bound into the call site.
final OptimismInfo effectiveOptInfo = currentOptInfo != null ? currentOptInfo : oldOptInfo;
FunctionNode fn = effectiveOptInfo.reparse();
final boolean serialized = effectiveOptInfo.isSerialized();
final Compiler compiler = effectiveOptInfo.getCompiler(fn, callSiteType, re); //set to non rest-of
if (!shouldRecompile) {
@ -793,17 +795,17 @@ final class CompiledFunction {
// recompiled a deoptimized version for an inner invocation.
// We still need to do the rest of from the beginning
logRecompile("Rest-of compilation [STANDALONE] ", fn, callSiteType, effectiveOptInfo.invalidatedProgramPoints);
return restOfHandle(effectiveOptInfo, compiler.compile(fn, CompilationPhases.COMPILE_ALL_RESTOF), currentOptInfo != null);
return restOfHandle(effectiveOptInfo, compiler.compile(fn, serialized ? CompilationPhases.COMPILE_SERIALIZED_RESTOF : CompilationPhases.COMPILE_ALL_RESTOF), currentOptInfo != null);
}
logRecompile("Deoptimizing recompilation (up to bytecode) ", fn, callSiteType, effectiveOptInfo.invalidatedProgramPoints);
fn = compiler.compile(fn, CompilationPhases.COMPILE_UPTO_BYTECODE);
fn = compiler.compile(fn, serialized ? CompilationPhases.RECOMPILE_SERIALIZED_UPTO_BYTECODE : CompilationPhases.COMPILE_UPTO_BYTECODE);
log.info("Reusable IR generated");
// compile the rest of the function, and install it
log.info("Generating and installing bytecode from reusable IR...");
logRecompile("Rest-of compilation [CODE PIPELINE REUSE] ", fn, callSiteType, effectiveOptInfo.invalidatedProgramPoints);
final FunctionNode normalFn = compiler.compile(fn, CompilationPhases.COMPILE_FROM_BYTECODE);
final FunctionNode normalFn = compiler.compile(fn, CompilationPhases.GENERATE_BYTECODE_AND_INSTALL);
if (effectiveOptInfo.data.usePersistentCodeCache()) {
final RecompilableScriptFunctionData data = effectiveOptInfo.data;
@ -829,7 +831,7 @@ final class CompiledFunction {
constructor = null; // Will be regenerated when needed
log.info("Done: ", invoker);
final MethodHandle restOf = restOfHandle(effectiveOptInfo, compiler.compile(fn, CompilationPhases.COMPILE_FROM_BYTECODE_RESTOF), canBeDeoptimized);
final MethodHandle restOf = restOfHandle(effectiveOptInfo, compiler.compile(fn, CompilationPhases.GENERATE_BYTECODE_AND_INSTALL_RESTOF), canBeDeoptimized);
// Note that we only adjust the switch point after we set the invoker/constructor. This is important.
if (canBeDeoptimized) {
@ -921,6 +923,10 @@ final class CompiledFunction {
FunctionNode reparse() {
return data.reparse();
}
boolean isSerialized() {
return data.isSerialized();
}
}
@SuppressWarnings("unused")

View File

@ -150,6 +150,13 @@ public final class Context {
private final Context context;
private final ScriptLoader loader;
private final CodeSource codeSource;
private int usageCount = 0;
private int bytesDefined = 0;
// We reuse this installer for 10 compilations or 200000 defined bytes. Usually the first condition
// will occur much earlier, the second is a safety measure for very large scripts/functions.
private final static int MAX_USAGES = 10;
private final static int MAX_BYTES_DEFINED = 200_000;
private ContextCodeInstaller(final Context context, final ScriptLoader loader, final CodeSource codeSource) {
this.context = context;
@ -168,6 +175,8 @@ public final class Context {
@Override
public Class<?> install(final String className, final byte[] bytecode) {
usageCount++;
bytesDefined += bytecode.length;
final String binaryName = Compiler.binaryName(className);
return loader.installClass(binaryName, bytecode, codeSource);
}
@ -225,6 +234,10 @@ public final class Context {
@Override
public CodeInstaller<ScriptEnvironment> withNewLoader() {
// Reuse this installer if we're within our limits.
if (usageCount < MAX_USAGES && bytesDefined < MAX_BYTES_DEFINED) {
return this;
}
return new ContextCodeInstaller(context, context.createNewLoader(), codeSource);
}

View File

@ -29,7 +29,6 @@ import static jdk.nashorn.internal.codegen.CompilerConstants.staticCall;
import static jdk.nashorn.internal.codegen.ObjectClassGenerator.OBJECT_FIELDS_ONLY;
import static jdk.nashorn.internal.lookup.Lookup.MH;
import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Array;
@ -1775,6 +1774,23 @@ public enum JSType {
}
}
/**
* Returns the boxed version of a primitive class
* @param clazz the class
* @return the boxed type of clazz, or unchanged if not primitive
*/
public static Class<?> getBoxedClass(final Class<?> clazz) {
if (clazz == int.class) {
return Integer.class;
} else if (clazz == long.class) {
return Long.class;
} else if (clazz == double.class) {
return Double.class;
}
assert !clazz.isPrimitive();
return clazz;
}
/**
* Create a method handle constant of the correct primitive type
* for a constant object

View File

@ -43,6 +43,7 @@ import jdk.nashorn.internal.codegen.Compiler;
import jdk.nashorn.internal.codegen.Compiler.CompilationPhases;
import jdk.nashorn.internal.codegen.CompilerConstants;
import jdk.nashorn.internal.codegen.FunctionSignature;
import jdk.nashorn.internal.codegen.Namespace;
import jdk.nashorn.internal.codegen.ObjectClassGenerator.AllocatorDescriptor;
import jdk.nashorn.internal.codegen.OptimisticTypesPersistence;
import jdk.nashorn.internal.codegen.TypeMap;
@ -79,6 +80,9 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
/** Source from which FunctionNode was parsed. */
private transient Source source;
/** Serialized, compressed form of the AST. Used by split functions as they can't be reparsed from source. */
private final byte[] serializedAst;
/** Token of this function within the source. */
private final long token;
@ -127,6 +131,7 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
* @param nestedFunctions nested function map
* @param externalScopeDepths external scope depths
* @param internalSymbols internal symbols to method, defined in its scope
* @param serializedAst a serialized AST representation. Normally only used for split functions.
*/
public RecompilableScriptFunctionData(
final FunctionNode functionNode,
@ -134,7 +139,8 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
final AllocatorDescriptor allocationDescriptor,
final Map<Integer, RecompilableScriptFunctionData> nestedFunctions,
final Map<String, Integer> externalScopeDepths,
final Set<String> internalSymbols) {
final Set<String> internalSymbols,
final byte[] serializedAst) {
super(functionName(functionNode),
Math.min(functionNode.getParameters().size(), MAX_ARITY),
@ -158,6 +164,7 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
nfn.setParent(this);
}
this.serializedAst = serializedAst;
createLogger();
}
@ -212,10 +219,7 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
*/
public int getExternalSymbolDepth(final String symbolName) {
final Integer depth = externalScopeDepths.get(symbolName);
if (depth == null) {
return -1;
}
return depth;
return depth == null ? -1 : depth;
}
/**
@ -354,8 +358,15 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
return allocationStrategy.allocate(map);
}
boolean isSerialized() {
return serializedAst != null;
}
FunctionNode reparse() {
// NOTE: If we aren't recompiling the top-level program, we decrease functionNodeId 'cause we'll have a synthetic program node
if (isSerialized()) {
return deserialize();
}
final int descPosition = Token.descPosition(token);
final Context context = Context.getContextTrusted();
final Parser parser = new Parser(
@ -363,8 +374,10 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
source,
new Context.ThrowErrorManager(),
isStrict(),
// source starts at line 0, so even though lineNumber is the correct declaration line, back off
// one to make it exclusive
lineNumber - 1,
context.getLogger(Parser.class)); // source starts at line 0, so even though lineNumber is the correct declaration line, back off one to make it exclusive
context.getLogger(Parser.class));
if (getFunctionFlag(FunctionNode.IS_ANONYMOUS)) {
parser.setFunctionName(functionName);
@ -378,6 +391,17 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
return (isProgram() ? program : extractFunctionFromScript(program)).setName(null, functionName);
}
private FunctionNode deserialize() {
final ScriptEnvironment env = installer.getOwner();
final Timing timing = env._timing;
final long t1 = System.nanoTime();
try {
return AstDeserializer.deserialize(serializedAst).initializeDeserialized(source, new Namespace(env.getNamespace()));
} finally {
timing.accumulateTime("'Deserialize'", System.nanoTime() - t1);
}
}
private boolean getFunctionFlag(final int flag) {
return (functionFlags & flag) != 0;
}
@ -486,7 +510,8 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
final FunctionNode fn = reparse();
final Compiler compiler = getCompiler(fn, actualCallSiteType, runtimeScope);
final FunctionNode compiledFn = compiler.compile(fn, CompilationPhases.COMPILE_ALL);
final FunctionNode compiledFn = compiler.compile(fn,
isSerialized() ? CompilationPhases.COMPILE_ALL_SERIALIZED : CompilationPhases.COMPILE_ALL);
if (persist && !compiledFn.getFlag(FunctionNode.HAS_APPLY_TO_CALL_SPECIALIZATION)) {
compiler.persistClassInfo(cacheKey, compiledFn);
@ -606,7 +631,7 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
MethodHandle lookupCodeMethod(final Class<?> codeClass, final MethodType targetType) {
if (log.isEnabled()) {
log.info("Looking up ", DebugLogger.quote(name), " type=", targetType);
log.info("Looking up ", DebugLogger.quote(functionName), " type=", targetType);
}
return MH.findStatic(LOOKUP, codeClass, functionName, targetType);
}
@ -817,6 +842,26 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
return true;
}
/**
* Restores the {@link #getFunctionFlags()} flags to a function node. During on-demand compilation, we might need
* to restore flags to a function node that was otherwise not subjected to a full compile pipeline (e.g. its parse
* was skipped, or it's a nested function of a deserialized function.
* @param lc current lexical context
* @param fn the function node to restore flags onto
* @return the transformed function node
*/
public FunctionNode restoreFlags(final LexicalContext lc, final FunctionNode fn) {
assert fn.getId() == functionNodeId;
FunctionNode newFn = fn.setFlags(lc, functionFlags);
// This compensates for missing markEval() in case the function contains an inner function
// that contains eval(), that now we didn't discover since we skipped the inner function.
if (newFn.hasNestedEval()) {
assert newFn.hasScopeBlock();
newFn = newFn.setBody(lc, newFn.getBody().setNeedsScope(null));
}
return newFn;
}
private void readObject(final java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
createLogger();

View File

@ -692,8 +692,7 @@ public abstract class ScriptObject implements PropertyAccess {
assert isValidArrayIndex(index) : "invalid array index";
final long longIndex = ArrayIndex.toLongIndex(index);
doesNotHaveEnsureDelete(longIndex, getArray().length(), false);
setArray(getArray().ensure(longIndex));
setArray(getArray().set(index, value, false));
setArray(getArray().ensure(longIndex).set(index,value, false));
}
private void checkIntegerKey(final String key) {
@ -1462,9 +1461,8 @@ public abstract class ScriptObject implements PropertyAccess {
//invalidate any fast array setters
final ArrayData array = getArray();
if (array != null) {
array.invalidateSetters();
}
assert array != null;
setArray(ArrayData.preventExtension(array));
return this;
}
@ -2645,20 +2643,22 @@ public abstract class ScriptObject implements PropertyAccess {
* @param newLength new length to set
*/
public final void setLength(final long newLength) {
final long arrayLength = getArray().length();
if (newLength == arrayLength) {
return;
}
ArrayData data = getArray();
final long arrayLength = data.length();
if (newLength == arrayLength) {
return;
}
if (newLength > arrayLength) {
setArray(getArray().ensure(newLength - 1));
if (getArray().canDelete(arrayLength, newLength - 1, false)) {
setArray(getArray().delete(arrayLength, newLength - 1));
}
return;
}
if (newLength > arrayLength) {
data = data.ensure(newLength - 1);
if (data.canDelete(arrayLength, newLength - 1, false)) {
data = data.delete(arrayLength, newLength - 1);
}
setArray(data);
return;
}
if (newLength < arrayLength) {
if (newLength < arrayLength) {
long actualLength = newLength;
// Check for numeric keys in property map and delete them or adjust length, depending on whether
@ -2680,8 +2680,8 @@ public abstract class ScriptObject implements PropertyAccess {
}
}
setArray(getArray().shrink(actualLength));
getArray().setLength(actualLength);
setArray(data.shrink(actualLength));
data.setLength(actualLength);
}
}
@ -3194,8 +3194,9 @@ public abstract class ScriptObject implements PropertyAccess {
final int index = getArrayIndex(primitiveKey);
if (isValidArrayIndex(index)) {
if (getArray().has(index)) {
setArray(getArray().set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
final ArrayData data = getArray();
if (data.has(index)) {
setArray(data.set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
} else {
doesNotHave(index, value, callSiteFlags);
}
@ -3213,8 +3214,9 @@ public abstract class ScriptObject implements PropertyAccess {
final int index = getArrayIndex(primitiveKey);
if (isValidArrayIndex(index)) {
if (getArray().has(index)) {
setArray(getArray().set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
final ArrayData data = getArray();
if (data.has(index)) {
setArray(data.set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
} else {
doesNotHave(index, value, callSiteFlags);
}
@ -3232,8 +3234,9 @@ public abstract class ScriptObject implements PropertyAccess {
final int index = getArrayIndex(primitiveKey);
if (isValidArrayIndex(index)) {
if (getArray().has(index)) {
setArray(getArray().set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
final ArrayData data = getArray();
if (data.has(index)) {
setArray(data.set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
} else {
doesNotHave(index, value, callSiteFlags);
}
@ -3251,8 +3254,9 @@ public abstract class ScriptObject implements PropertyAccess {
final int index = getArrayIndex(primitiveKey);
if (isValidArrayIndex(index)) {
if (getArray().has(index)) {
setArray(getArray().set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
final ArrayData data = getArray();
if (data.has(index)) {
setArray(data.set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
} else {
doesNotHave(index, value, callSiteFlags);
}
@ -3269,8 +3273,9 @@ public abstract class ScriptObject implements PropertyAccess {
final int index = getArrayIndex(key);
if (isValidArrayIndex(index)) {
if (getArray().has(index)) {
setArray(getArray().set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
final ArrayData data = getArray();
if (data.has(index)) {
setArray(data.set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
} else {
doesNotHave(index, value, callSiteFlags);
}
@ -3287,8 +3292,9 @@ public abstract class ScriptObject implements PropertyAccess {
final int index = getArrayIndex(key);
if (isValidArrayIndex(index)) {
if (getArray().has(index)) {
setArray(getArray().set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
final ArrayData data = getArray();
if (data.has(index)) {
setArray(data.set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
} else {
doesNotHave(index, value, callSiteFlags);
}
@ -3305,8 +3311,9 @@ public abstract class ScriptObject implements PropertyAccess {
final int index = getArrayIndex(key);
if (isValidArrayIndex(index)) {
if (getArray().has(index)) {
setArray(getArray().set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
final ArrayData data = getArray();
if (data.has(index)) {
setArray(data.set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
} else {
doesNotHave(index, value, callSiteFlags);
}
@ -3323,8 +3330,9 @@ public abstract class ScriptObject implements PropertyAccess {
final int index = getArrayIndex(key);
if (isValidArrayIndex(index)) {
if (getArray().has(index)) {
setArray(getArray().set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
final ArrayData data = getArray();
if (data.has(index)) {
setArray(data.set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
} else {
doesNotHave(index, value, callSiteFlags);
}
@ -3341,8 +3349,9 @@ public abstract class ScriptObject implements PropertyAccess {
final int index = getArrayIndex(key);
if (isValidArrayIndex(index)) {
if (getArray().has(index)) {
setArray(getArray().set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
final ArrayData data = getArray();
if (data.has(index)) {
setArray(data.set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
} else {
doesNotHave(index, value, callSiteFlags);
}
@ -3359,8 +3368,9 @@ public abstract class ScriptObject implements PropertyAccess {
final int index = getArrayIndex(key);
if (isValidArrayIndex(index)) {
if (getArray().has(index)) {
setArray(getArray().set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
final ArrayData data = getArray();
if (data.has(index)) {
setArray(data.set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
} else {
doesNotHave(index, value, callSiteFlags);
}
@ -3377,8 +3387,9 @@ public abstract class ScriptObject implements PropertyAccess {
final int index = getArrayIndex(key);
if (isValidArrayIndex(index)) {
if (getArray().has(index)) {
setArray(getArray().set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
final ArrayData data = getArray();
if (data.has(index)) {
setArray(data.set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
} else {
doesNotHave(index, value, callSiteFlags);
}
@ -3395,8 +3406,9 @@ public abstract class ScriptObject implements PropertyAccess {
final int index = getArrayIndex(key);
if (isValidArrayIndex(index)) {
if (getArray().has(index)) {
setArray(getArray().set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
final ArrayData data = getArray();
if (data.has(index)) {
setArray(data.set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
} else {
doesNotHave(index, value, callSiteFlags);
}
@ -3413,7 +3425,8 @@ public abstract class ScriptObject implements PropertyAccess {
final int index = getArrayIndex(key);
if (isValidArrayIndex(index)) {
if (getArray().has(index)) {
setArray(getArray().set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
final ArrayData data = getArray();
setArray(data.set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
} else {
doesNotHave(index, value, callSiteFlags);
}
@ -3429,8 +3442,9 @@ public abstract class ScriptObject implements PropertyAccess {
final int index = getArrayIndex(key);
if (isValidArrayIndex(index)) {
if (getArray().has(index)) {
setArray(getArray().set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
final ArrayData data = getArray();
if (data.has(index)) {
setArray(data.set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
} else {
doesNotHave(index, value, callSiteFlags);
}
@ -3447,8 +3461,9 @@ public abstract class ScriptObject implements PropertyAccess {
final int index = getArrayIndex(key);
if (isValidArrayIndex(index)) {
if (getArray().has(index)) {
setArray(getArray().set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
final ArrayData data = getArray();
if (data.has(index)) {
setArray(data.set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
} else {
doesNotHave(index, value, callSiteFlags);
}
@ -3465,8 +3480,9 @@ public abstract class ScriptObject implements PropertyAccess {
final int index = getArrayIndex(key);
if (isValidArrayIndex(index)) {
if (getArray().has(index)) {
setArray(getArray().set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
final ArrayData data = getArray();
if (data.has(index)) {
setArray(data.set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
} else {
doesNotHave(index, value, callSiteFlags);
}

View File

@ -0,0 +1,38 @@
/*
* Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.nashorn.internal.runtime.arrays;
/**
* Marker interface for any ContinuousArray with any elements
* Used for type checks that throw ClassCastExceptions and force relinks
* for fast NativeArray specializations of builtin methods
*/
public interface AnyElements {
/**
* Return a numeric weight of the element type - wider is higher
* @return element type weight
*/
public int getElementWeight();
}

Some files were not shown because too many files have changed in this diff Show More