diff --git a/nashorn/bin/runopt.sh b/nashorn/bin/runopt.sh index 0a61491e624..5ec9046bb1a 100644 --- a/nashorn/bin/runopt.sh +++ b/nashorn/bin/runopt.sh @@ -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 diff --git a/nashorn/docs/DEVELOPER_README b/nashorn/docs/DEVELOPER_README index fe140954c1a..5550b903b1d 100644 --- a/nashorn/docs/DEVELOPER_README +++ b/nashorn/docs/DEVELOPER_README @@ -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= + +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= -(and integer arithmetic in general) - - - -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= +SYSTEM PROPERTY: -Dnashorn.codegen.debug.trace= 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=[[,*]], -Dnashorn.compiler.symbol.stacktrace=[[,*]] @@ -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= -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= @@ -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= +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= + +This sets the delay between cleanups of the typeInfo cache, in seconds. +The default delay is 20 seconds. + + +SYSTEM PROPERTY: -Dnashorn.profilefile= 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= + +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 ======================= diff --git a/nashorn/make/build-nasgen.xml b/nashorn/make/build-nasgen.xml index 16094cc964b..862a1999ba0 100644 --- a/nashorn/make/build-nasgen.xml +++ b/nashorn/make/build-nasgen.xml @@ -25,7 +25,7 @@ Builds and runs nasgen. - + diff --git a/nashorn/make/build.xml b/nashorn/make/build.xml index 9e6f5b5558e..1b7670e9e95 100644 --- a/nashorn/make/build.xml +++ b/nashorn/make/build.xml @@ -49,8 +49,6 @@ - - @@ -78,8 +76,31 @@ + + + + + + + + + + + + + + + + + + + + + + + - + @@ -107,19 +128,7 @@ - - - - - - + imple explodedArguments.pop(); - return newFunctionNode; + return newFunctionNode.setState(lc, CompilationState.BUILTINS_TRANSFORMED); } private static boolean isApply(final CallNode callNode) { diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AssignSymbols.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AssignSymbols.java index f83dc9ccdd1..88fd89bba18 100644 --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AssignSymbols.java +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AssignSymbols.java @@ -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 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 implements Loggabl boolean previousWasBlock = false; for (final Iterator 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; diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AstSerializer.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AstSerializer.java new file mode 100644 index 00000000000..19197a26a0b --- /dev/null +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AstSerializer.java @@ -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(new LexicalContext()) { + @Override + public Node leaveBlock(final Block block) { + if (lc.isFunctionBody() && lc.getFunction(block) != lc.getOutermostFunction()) { + return block.setStatements(lc, Collections.emptyList()); + } + return super.leaveBlock(block); + } + }); + } +} diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/ClassEmitter.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/ClassEmitter.java index 7a89e490508..80bb61dd080 100644 --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/ClassEmitter.java +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/ClassEmitter.java @@ -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 * diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java index ae3ee6947e0..d6743631749 100644 --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java @@ -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 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 targets = splitMethod.getExternalTargets(); - final boolean hasControlFlow = hasReturn || !targets.isEmpty(); - final List 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(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(new LexicalContext()) { + newFunctionNode = transformFunction(fn, new NodeVisitor(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(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(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 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 unitSet = CompileUnit.createCompileUnitSet(); + final Map 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 unitSet, + final Map 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(new LexicalContext()) { + return transformFunction(functionNode, new NodeVisitor(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); + } } diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CompileUnit.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CompileUnit.java index 2d3cd2bed25..13e9d7168cb 100644 --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CompileUnit.java +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CompileUnit.java @@ -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 { + * 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, 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 { 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 { } 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 diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Compiler.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Compiler.java index 80846e139dd..196862f0699 100644 --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Compiler.java +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Compiler.java @@ -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 serializedAsts = new HashMap<>(); + /** * Compilation phases that a compilation goes through */ public static class CompilationPhases implements Iterable { - /** 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 phases; private final String desc; private CompilationPhases(final String desc, final CompilationPhase... phases) { - this.desc = desc; + this(desc, Arrays.asList(phases)); + } - final List 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 phases) { + this.desc = desc; + this.phases = phases; + } + + private static List concatPhases(final CompilationPhases[] bases) { + final ArrayList l = new ArrayList<>(); + for(final CompilationPhases base: bases) { + l.addAll(base.phases); + } + l.trimToSize(); + return l; + } + + private static List concat(final List l1, final List l2) { + final ArrayList 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 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 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 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 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) { diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/FindScopeDepths.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/FindScopeDepths.java index 4c1db02ba93..431244dc3c6 100644 --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/FindScopeDepths.java +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/FindScopeDepths.java @@ -187,7 +187,6 @@ final class FindScopeDepths extends NodeVisitor 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 implements Logga //create recompilable scriptfunctiondata final int fnId = newFunctionNode.getId(); - final Map nestedFunctions = fnIdToNestedFunctions.get(fnId); + final Map 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 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); diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Label.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Label.java index 7c86abf0eb4..e4b024989b4 100644 --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Label.java +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Label.java @@ -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 diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java index 491ef2caf13..ea073623c9a 100644 --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java @@ -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{ // 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{ 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{ } @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{ 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) { diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Lower.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Lower.java index dddf6976891..724c6f1e531 100644 --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Lower.java +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Lower.java @@ -352,8 +352,6 @@ final class Lower extends NodeOperatorVisitor implements Lo private Node spliceFinally(final TryNode tryNode, final List rethrows, final Block finallyBody) { assert tryNode.getFinallyBody() == null; - final LexicalContext lowerLc = lc; - final TryNode newTryNode = (TryNode)tryNode.accept(new NodeVisitor(new LexicalContext()) { final List insideTry = new ArrayList<>(); @@ -406,7 +404,6 @@ final class Lower extends NodeOperatorVisitor 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; } diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/MethodEmitter.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/MethodEmitter.java index 2df1ae90b88..c6b473f3e54 100644 --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/MethodEmitter.java +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/MethodEmitter.java @@ -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 * diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/OptimisticTypesCalculator.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/OptimisticTypesCalculator.java index ac402e935bc..c8029070a8a 100644 --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/OptimisticTypesCalculator.java +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/OptimisticTypesCalculator.java @@ -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 { // Per-function bit set of program points that must never be optimistic. final Deque 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 { return false; } neverOptimistic.push(new BitSet()); - splitDepth.push(0); return true; } @@ -189,19 +184,6 @@ final class OptimisticTypesCalculator extends NodeVisitor { 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 { @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 { 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 { tagNeverOptimistic(test.getExpression()); } } - - private boolean inSplitNode() { - return splitDepth.peek() > 0; - } } diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/ProgramPoints.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/ProgramPoints.java index c04546b25f2..4606989e49c 100644 --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/ProgramPoints.java +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/ProgramPoints.java @@ -85,7 +85,7 @@ class ProgramPoints extends NodeVisitor { @Override public boolean enterVarNode(final VarNode varNode) { - noProgramPoint.add(varNode.getAssignmentDest()); + noProgramPoint.add(varNode.getName()); return true; } diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/ReplaceCompileUnits.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/ReplaceCompileUnits.java new file mode 100644 index 00000000000..57f9e681dc5 --- /dev/null +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/ReplaceCompileUnits.java @@ -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 { + 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 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; + } +} diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/SplitIntoFunctions.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/SplitIntoFunctions.java new file mode 100644 index 00000000000..9b980b5d4c9 --- /dev/null +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/SplitIntoFunctions.java @@ -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 { + 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 functionStates = new ArrayDeque<>(); + private final Deque 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.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.asList(thisIdent, createReturnIdent()) + : Collections.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 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 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 cases, final int i, final Block body) { + cases.add(new CaseNode(NO_TOKEN, NO_FINISH, intLiteral(i), body)); + } + + private static LiteralNode 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 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 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; + } + } +} diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/SplitMethodEmitter.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/SplitMethodEmitter.java deleted file mode 100644 index 792a6255859..00000000000 --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/SplitMethodEmitter.java +++ /dev/null @@ -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