8040089: Apply to call transform was incomplete. Now passes all tests and performance is back
authorlagergren
Thu, 17 Apr 2014 20:01:19 +0200
changeset 24740 26791be09688
parent 24739 d79f0ae9392c
child 24741 4232289c3235
8040089: Apply to call transform was incomplete. Now passes all tests and performance is back Reviewed-by: hannesw, attila, sundar, jlaskey
nashorn/bin/fixwhitespace.sh
nashorn/bin/runoptdualcatch.sh
nashorn/src/jdk/nashorn/internal/codegen/ApplySpecialization.java
nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java
nashorn/src/jdk/nashorn/internal/codegen/ParamTypeMap.java
nashorn/src/jdk/nashorn/internal/codegen/ProgramPoints.java
nashorn/src/jdk/nashorn/internal/ir/CallNode.java
nashorn/src/jdk/nashorn/internal/ir/FunctionNode.java
nashorn/src/jdk/nashorn/internal/ir/IdentNode.java
nashorn/src/jdk/nashorn/internal/runtime/CompiledFunction.java
nashorn/src/jdk/nashorn/internal/runtime/CompiledFunctions.java
nashorn/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java
nashorn/test/script/basic/apply_to_call/apply_to_call1.js
nashorn/test/script/basic/apply_to_call/apply_to_call2.js
nashorn/test/script/basic/apply_to_call/apply_to_call3.js
nashorn/test/script/basic/apply_to_call/apply_to_call4.js
nashorn/test/script/basic/apply_to_call/apply_to_call_bench.js
nashorn/test/script/basic/apply_to_call/apply_to_call_recompile.js
nashorn/test/script/basic/apply_to_call/apply_to_call_recompile.js.EXPECTED
nashorn/test/script/basic/apply_to_call/apply_to_call_varargs.js
nashorn/test/script/basic/apply_to_call/apply_to_call_varargs.js.EXPECTED
nashorn/test/script/basic/run-octane.js
--- a/nashorn/bin/fixwhitespace.sh	Fri Apr 11 16:52:56 2014 +0200
+++ b/nashorn/bin/fixwhitespace.sh	Thu Apr 17 20:01:19 2014 +0200
@@ -28,3 +28,9 @@
 #remove trailing whitespace
 find . -name "*.java" -exec sed -i "" 's/[ 	]*$//' \{} \;
 
+#convert tabs to spaces
+find . -name "*.js" -exec sed -i "" 's/	/    /g' {} \;
+
+#remove trailing whitespace
+find . -name "*.js" -exec sed -i "" 's/[ 	]*$//' \{} \;
+
--- a/nashorn/bin/runoptdualcatch.sh	Fri Apr 11 16:52:56 2014 +0200
+++ b/nashorn/bin/runoptdualcatch.sh	Thu Apr 17 20:01:19 2014 +0200
@@ -6,7 +6,8 @@
 FILENAME="./optimistic_dual_catch_$(date|sed "s/ /_/g"|sed "s/:/_/g").jfr"
 
 DIR=..
-FAST_CATCH_COMBINATOR=$DIR/bin/fastCatchCombinator.jar
+FAST_CATCH_COMBINATOR=
+#$DIR/bin/fastCatchCombinator.jar
 NASHORN_JAR=$DIR/dist/nashorn.jar
 
 $JAVA_HOME/bin/java \
--- a/nashorn/src/jdk/nashorn/internal/codegen/ApplySpecialization.java	Fri Apr 11 16:52:56 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/ApplySpecialization.java	Thu Apr 17 20:01:19 2014 +0200
@@ -47,6 +47,7 @@
 import jdk.nashorn.internal.objects.Global;
 import jdk.nashorn.internal.runtime.DebugLogger;
 import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData;
+import jdk.nashorn.internal.runtime.options.Options;
 
 /**
  * An optimization that attempts to turn applies into calls. This pattern
@@ -79,7 +80,9 @@
  * </pre>
  */
 
-public class ApplySpecialization {
+public final class ApplySpecialization {
+
+    private static final boolean USE_APPLY2CALL = Options.getBooleanProperty("nashorn.apply2call", true);
 
     private final RecompilableScriptFunctionData data;
 
@@ -95,10 +98,8 @@
 
     private boolean finished;
 
-    private final boolean isRestOf;
-
     /**
-     * Return the apply to call specialization logger
+     * Return the apply to call specialization logger g
      * @return the logger
      */
     public static DebugLogger getLogger() {
@@ -113,13 +114,11 @@
      * @param data                recompilable script function data, which contains e.g. needs callee information
      * @param functionNode        functionNode
      * @param actualCallSiteType  actual call site type that we use (not Object[] varargs)
-     * @param isRestOf            is this a restof method
      */
-    public ApplySpecialization(final RecompilableScriptFunctionData data, final FunctionNode functionNode, final MethodType actualCallSiteType, final boolean isRestOf) {
+    public ApplySpecialization(final RecompilableScriptFunctionData data, final FunctionNode functionNode, final MethodType actualCallSiteType) {
         this.data = data;
         this.functionNode = functionNode;
         this.actualCallSiteType = actualCallSiteType;
-        this.isRestOf = isRestOf;
     }
 
     /**
@@ -194,6 +193,10 @@
      * @return true if successful, false otherwise
      */
     public boolean transform() {
+        if (!USE_APPLY2CALL) {
+            return false;
+        }
+
         if (finished) {
             throw new AssertionError("Can't apply transform twice");
         }
@@ -262,7 +265,8 @@
                     setParameters(null, newParams);
         }
 
-        LOG.info("Successfully specialized apply to call in '" + functionNode.getName() + "' id=" + functionNode.getId() + " signature=" + actualCallSiteType + " isRestOf=" + isRestOf);
+        LOG.info("Successfully specialized apply to call in '" + functionNode.getName() + "' id=" + functionNode.getId() + " signature=" + actualCallSiteType);
+
         return finish();
     }
 
--- a/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java	Fri Apr 11 16:52:56 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java	Thu Apr 17 20:01:19 2014 +0200
@@ -1533,7 +1533,7 @@
             if (isRestOf) {
                 final ContinuationInfo ci = new ContinuationInfo();
                 fnIdToContinuationInfo.put(fnId, ci);
-                method._goto(ci.handlerLabel);
+                method._goto(ci.getHandlerLabel());
             }
         }
 
@@ -1939,7 +1939,7 @@
         //in the properties above, we need to reset the map to oc.getMap() in the continuation
         //handler
         if (restOfProperty) {
-            getContinuationInfo().objectLiteralMap = oc.getMap();
+            getContinuationInfo().setObjectLiteralMap(oc.getMap());
         }
 
         method.dup();
@@ -3995,15 +3995,13 @@
                 }
                 if(currentContinuationEntryPoint) {
                     final ContinuationInfo ci = getContinuationInfo();
-                    assert ci.targetLabel == null; // No duplicate program points
-                    ci.targetLabel = afterConsumeStack;
-                    ci.localVariableTypes = localTypes;
-
-                    ci.stackStoreSpec = localLoads;
-
-                    ci.stackTypes = Arrays.copyOf(method.getTypesFromStack(method.getStackSize()), stackSizeOnEntry);
-                    assert ci.stackStoreSpec.length == ci.stackTypes.length;
-                    ci.returnValueType = method.peekType();
+                    assert !ci.hasTargetLabel(); // No duplicate program points
+                    ci.setTargetLabel(afterConsumeStack);
+                    ci.setLocalVariableTypes(localTypes);
+                    ci.setStackStoreSpec(localLoads);
+                    ci.setStackTypes(Arrays.copyOf(method.getTypesFromStack(method.getStackSize()), stackSizeOnEntry));
+                    assert ci.getStackStoreSpec().length == ci.getStackTypes().length;
+                    ci.setReturnValueType(method.peekType());
                 }
             }
             return method;
@@ -4436,28 +4434,83 @@
     }
 
     private static class ContinuationInfo {
-        final Label handlerLabel;
-        Label targetLabel; // Label for the target instruction.
+        private final Label handlerLabel;
+        private Label targetLabel; // Label for the target instruction.
         // Types the local variable slots have to have when this node completes
-        Type[] localVariableTypes;
+        private Type[] localVariableTypes;
         // Indices of local variables that need to be loaded on the stack when this node completes
-        int[] stackStoreSpec;
+        private int[] stackStoreSpec;
         // Types of values loaded on the stack
-        Type[] stackTypes;
+        private Type[] stackTypes;
         // If non-null, this node should perform the requisite type conversion
-        Type returnValueType;
-        // If we are in the middle of an object literal initialization, we need to update
-        // the map
-        PropertyMap objectLiteralMap;
+        private Type returnValueType;
+        // If we are in the middle of an object literal initialization, we need to update the map
+        private PropertyMap objectLiteralMap;
 
         ContinuationInfo() {
             this.handlerLabel = new Label("continuation_handler");
         }
 
+        Label getHandlerLabel() {
+            return handlerLabel;
+        }
+
+        boolean hasTargetLabel() {
+            return targetLabel != null;
+        }
+
+        Label getTargetLabel() {
+            return targetLabel;
+        }
+
+        void setTargetLabel(final Label targetLabel) {
+            this.targetLabel = targetLabel;
+        }
+
+        Type[] getLocalVariableTypes() {
+            return localVariableTypes.clone();
+        }
+
+        void setLocalVariableTypes(final Type[] localVariableTypes) {
+            this.localVariableTypes = localVariableTypes;
+        }
+
+        int[] getStackStoreSpec() {
+            return stackStoreSpec.clone();
+        }
+
+        void setStackStoreSpec(final int[] stackStoreSpec) {
+            this.stackStoreSpec = stackStoreSpec;
+        }
+
+        Type[] getStackTypes() {
+            return stackTypes.clone();
+        }
+
+        void setStackTypes(final Type[] stackTypes) {
+            this.stackTypes = stackTypes;
+        }
+
+        Type getReturnValueType() {
+            return returnValueType;
+        }
+
+        void setReturnValueType(final Type returnValueType) {
+            this.returnValueType = returnValueType;
+        }
+
+        PropertyMap getObjectLiteralMap() {
+            return objectLiteralMap;
+        }
+
+        void setObjectLiteralMap(final PropertyMap objectLiteralMap) {
+            this.objectLiteralMap = objectLiteralMap;
+        }
+
         @Override
         public String toString() {
-            return "[localVariableTypes=" + Arrays.toString(localVariableTypes) + ", stackStoreSpec=" +
-                    Arrays.toString(stackStoreSpec) + ", returnValueType=" + returnValueType + "]";
+             return "[localVariableTypes=" + Arrays.toString(localVariableTypes) + ", stackStoreSpec=" +
+                     Arrays.toString(stackStoreSpec) + ", returnValueType=" + returnValueType + "]";
         }
     }
 
@@ -4471,13 +4524,13 @@
         }
 
         final ContinuationInfo ci = getContinuationInfo();
-        method.label(ci.handlerLabel);
+        method.label(ci.getHandlerLabel());
 
         // There should never be an exception thrown from the continuation handler, but in case there is (meaning,
         // Nashorn has a bug), then line number 0 will be an indication of where it came from (line numbers are Uint16).
         method.lineNumber(0);
 
-        final Type[] lvarTypes = ci.localVariableTypes;
+        final Type[] lvarTypes = ci.getLocalVariableTypes();
         final int    lvarCount = lvarTypes.length;
 
         final Type rewriteExceptionType = Type.typeFor(RewriteException.class);
@@ -4499,9 +4552,9 @@
             lvarIndex = nextLvarIndex;
         }
 
-        final int[] stackStoreSpec = ci.stackStoreSpec;
-        final Type[] stackTypes = ci.stackTypes;
-        final boolean isStackEmpty = stackStoreSpec.length == 0;
+        final int[]   stackStoreSpec = ci.getStackStoreSpec();
+        final Type[]  stackTypes     = ci.getStackTypes();
+        final boolean isStackEmpty   = stackStoreSpec.length == 0;
         if(!isStackEmpty) {
             // Store the RewriteException into an unused local variable slot.
             method.store(rewriteExceptionType, lvarCount);
@@ -4515,10 +4568,10 @@
             // stack: s0=object literal being initialized
             // change map of s0 so that the property we are initilizing when we failed
             // is now ci.returnValueType
-            if (ci.objectLiteralMap != null) {
+            if (ci.getObjectLiteralMap() != null) {
                 method.dup(); //dup script object
                 assert ScriptObject.class.isAssignableFrom(method.peekType().getTypeClass()) : method.peekType().getTypeClass() + " is not a script object";
-                loadConstant(ci.objectLiteralMap);
+                loadConstant(ci.getObjectLiteralMap());
                 method.invoke(ScriptObject.SET_MAP);
             }
 
@@ -4530,9 +4583,9 @@
 
         // Load return value on the stack
         method.invoke(RewriteException.GET_RETURN_VALUE);
-        method.convert(ci.returnValueType);
+        method.convert(ci.getReturnValueType());
 
         // Jump to continuation point
-        method._goto(ci.targetLabel);
+        method._goto(ci.getTargetLabel());
     }
 }
--- a/nashorn/src/jdk/nashorn/internal/codegen/ParamTypeMap.java	Fri Apr 11 16:52:56 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/ParamTypeMap.java	Thu Apr 17 20:01:19 2014 +0200
@@ -26,6 +26,7 @@
 package jdk.nashorn.internal.codegen;
 
 import java.lang.invoke.MethodType;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -64,7 +65,7 @@
     }
 
     ParamTypeMap(final Map<FunctionNode, Type[]> typeMap) {
-        for (Map.Entry<FunctionNode, Type[]> entry : typeMap.entrySet()) {
+        for (final Map.Entry<FunctionNode, Type[]> entry : typeMap.entrySet()) {
             map.put(entry.getKey().getId(), entry.getValue());
         }
     }
@@ -77,10 +78,24 @@
      */
     Type get(final FunctionNode functionNode, final int pos) {
         final Type[] types = map.get(functionNode.getId());
-        assert types == null || pos < types.length;
+        assert types == null || pos < types.length : "fn = " + functionNode.getId() + " " + "types=" + Arrays.toString(types) + " || pos=" + pos + " >= length=" + types.length + " in " + this;
         if (types != null && pos < types.length) {
             return types[pos];
         }
         return null;
     }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("\n[ParamTypeMap]\n");
+        if (map.isEmpty()) {
+            sb.append("\t{}");
+        } else {
+            for (final Map.Entry<Integer, Type[]> entry : map.entrySet()) {
+                sb.append('\t').append(entry.getKey() + "=>" + ((entry.getValue() == null) ? "[]" : Arrays.toString(entry.getValue()))).append('\n');
+            }
+        }
+        return sb.toString();
+    }
 }
--- a/nashorn/src/jdk/nashorn/internal/codegen/ProgramPoints.java	Fri Apr 11 16:52:56 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/ProgramPoints.java	Thu Apr 17 20:01:19 2014 +0200
@@ -29,6 +29,9 @@
 
 import java.util.ArrayDeque;
 import java.util.Deque;
+import java.util.HashSet;
+import java.util.Set;
+
 import jdk.nashorn.internal.ir.AccessNode;
 import jdk.nashorn.internal.ir.BinaryNode;
 import jdk.nashorn.internal.ir.CallNode;
@@ -40,6 +43,7 @@
 import jdk.nashorn.internal.ir.Node;
 import jdk.nashorn.internal.ir.Optimistic;
 import jdk.nashorn.internal.ir.UnaryNode;
+import jdk.nashorn.internal.ir.VarNode;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 
 /**
@@ -48,6 +52,7 @@
 class ProgramPoints extends NodeVisitor<LexicalContext> {
 
     private final Deque<int[]> nextProgramPoint = new ArrayDeque<>();
+    private final Set<Node> noProgramPoint = new HashSet<>();
 
     ProgramPoints() {
         super(new LexicalContext());
@@ -73,9 +78,25 @@
         return functionNode;
     }
 
-    private static Optimistic setProgramPoint(final Optimistic optimistic, final int programPoint) {
-        final Expression node = (Expression)optimistic.setProgramPoint(programPoint);
-        return (Optimistic)node;
+    private Optimistic setProgramPoint(final Optimistic optimistic, final int programPoint) {
+        if (noProgramPoint.contains(optimistic)) {
+            return optimistic;
+        }
+        return (Optimistic)(Expression)optimistic.setProgramPoint(programPoint);
+    }
+
+    @Override
+    public boolean enterVarNode(final VarNode varNode) {
+        noProgramPoint.add(varNode.getAssignmentDest());
+        return true;
+    }
+
+    @Override
+    public boolean enterIdentNode(final IdentNode identNode) {
+        if (identNode.isInternal()) {
+            noProgramPoint.add(identNode);
+        }
+        return true;
     }
 
     @Override
--- a/nashorn/src/jdk/nashorn/internal/ir/CallNode.java	Fri Apr 11 16:52:56 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/CallNode.java	Thu Apr 17 20:01:19 2014 +0200
@@ -233,6 +233,8 @@
         function.toString(fsb);
         if (isApplyToCall()) {
             sb.append(fsb.toString().replace("apply", "[apply => call]"));
+        } else {
+            sb.append(fsb);
         }
 
         sb.append('(');
--- a/nashorn/src/jdk/nashorn/internal/ir/FunctionNode.java	Fri Apr 11 16:52:56 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/FunctionNode.java	Thu Apr 17 20:01:19 2014 +0200
@@ -554,10 +554,13 @@
      * (since it exposes {@code arguments.callee} property) will need to have a callee parameter. We also return true
      * for split functions to make sure symbols slots are the same in the main and split methods.
      *
+     * A function that has had an apply(this,arguments) turned into a call doesn't need arguments anymore, but still
+     * has to fit the old callsite, thus, we require a dummy callee parameter for those functions as well
+     *
      * @return true if the function's generated Java method needs a {@code callee} parameter.
      */
     public boolean needsCallee() {
-        return needsParentScope() || usesSelfSymbol() || isSplit() || (needsArguments() && !isStrict());
+        return needsParentScope() || usesSelfSymbol() || isSplit() || (needsArguments() && !isStrict()) || hasOptimisticApplyToCall();
     }
 
     /**
--- a/nashorn/src/jdk/nashorn/internal/ir/IdentNode.java	Fri Apr 11 16:52:56 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/IdentNode.java	Thu Apr 17 20:01:19 2014 +0200
@@ -269,6 +269,16 @@
         return (flags & OPTIMISTIC) == OPTIMISTIC;
     }
 
+    /**
+     * Is this an internal symbol, i.e. one that starts with ':'. Those can
+     * never be optimistic.
+     * @return true if internal symbol
+     */
+    public boolean isInternal() {
+        assert name != null;
+        return name.charAt(0) == ':';
+    }
+
     @Override
     public Optimistic setIsOptimistic(final boolean isOptimistic) {
         if (isOptimistic() == isOptimistic) {
--- a/nashorn/src/jdk/nashorn/internal/runtime/CompiledFunction.java	Fri Apr 11 16:52:56 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/runtime/CompiledFunction.java	Thu Apr 17 20:01:19 2014 +0200
@@ -27,6 +27,7 @@
 import static jdk.nashorn.internal.lookup.Lookup.MH;
 import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
 import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.isValid;
+
 import java.lang.invoke.CallSite;
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
@@ -73,6 +74,7 @@
     private MethodHandle constructor;
     private OptimismInfo optimismInfo;
     private int flags; // from FunctionNode
+    private boolean applyToCall;
 
     CompiledFunction(final MethodHandle invoker) {
         this.invoker = invoker;
@@ -102,13 +104,21 @@
         return flags;
     }
 
+    void setIsApplyToCall() {
+        applyToCall = true;
+    }
+
+    boolean isApplyToCall() {
+        return applyToCall;
+    }
+
     boolean isVarArg() {
         return isVarArgsType(invoker.type());
     }
 
     @Override
     public String toString() {
-        return "<callSiteType=" + invoker.type() + " invoker=" + invoker + " ctor=" + constructor + " weight=" + weight() + ">";
+        return "[invokerType=" + invoker.type() + " ctor=" + constructor + " weight=" + weight() + " isApplyToCall=" + isApplyToCall() + "]";
     }
 
     boolean needsCallee() {
@@ -637,7 +647,7 @@
                 return null;
             }
             SwitchPoint.invalidateAll(new SwitchPoint[] { optimisticAssumptions });
-            return data.compile(callSiteType, invalidatedProgramPoints, e.getRuntimeScope(), "Deoptimizing recompilation");
+            return data.compile(callSiteType, invalidatedProgramPoints, e.getRuntimeScope(), "Deoptimizing recompilation", data.getDefaultTransform(callSiteType));
         }
 
         MethodHandle compileRestOfMethod(final MethodType callSiteType, final RewriteException e) {
@@ -651,7 +661,7 @@
                 System.arraycopy(prevEntryPoints, 0, entryPoints, 1, l);
             }
             entryPoints[0] = e.getProgramPoint();
-            return data.compileRestOfMethod(callSiteType, invalidatedProgramPoints, entryPoints, e.getRuntimeScope());
+            return data.compileRestOfMethod(callSiteType, invalidatedProgramPoints, entryPoints, e.getRuntimeScope(), data.getDefaultTransform(callSiteType));
         }
     }
 
--- a/nashorn/src/jdk/nashorn/internal/runtime/CompiledFunctions.java	Fri Apr 11 16:52:56 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/runtime/CompiledFunctions.java	Thu Apr 17 20:01:19 2014 +0200
@@ -24,6 +24,8 @@
  */
 package jdk.nashorn.internal.runtime;
 
+import static jdk.nashorn.internal.lookup.Lookup.MH;
+
 import java.lang.invoke.MethodType;
 import java.util.LinkedList;
 
@@ -61,6 +63,38 @@
         return '\'' + name + "' code=" + functions;
     }
 
+    /**
+     * Used to find an apply to call version that fits this callsite.
+     * We cannot just, as in the normal matcher case, return e.g. (Object, Object, int)
+     * for (Object, Object, int, int, int) or we will destroy the semantics and get
+     * a function that, when padded with undefineds, behaves differently
+     * @param type actual call site type
+     * @return apply to call that perfectly fits this callsite or null if none found
+     */
+    CompiledFunction lookupExactApplyToCall(final MethodType type) {
+        for (final CompiledFunction cf : functions) {
+            if (!cf.isApplyToCall()) {
+                continue;
+            }
+
+            final MethodType cftype = cf.type();
+            if (cftype.parameterCount() != type.parameterCount()) {
+                continue;
+            }
+
+            final Class<?>[] paramTypes = new Class<?>[cftype.parameterCount()];
+            for (int i = 0; i < cftype.parameterCount(); i++) {
+                paramTypes[i] = cftype.parameterType(i).isPrimitive() ? cftype.parameterType(i) : Object.class;
+            }
+
+            if (MH.type(cftype.returnType(), paramTypes).equals(type)) {
+                return cf;
+            }
+        }
+
+        return null;
+    }
+
     private CompiledFunction pick(final MethodType callSiteType, final boolean canPickVarArg) {
         for (final CompiledFunction candidate : functions) {
             if (candidate.matchesCallSite(callSiteType, false)) {
--- a/nashorn/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java	Fri Apr 11 16:52:56 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java	Thu Apr 17 20:01:19 2014 +0200
@@ -38,9 +38,9 @@
 import java.util.concurrent.atomic.AtomicInteger;
 
 import jdk.internal.dynalink.support.NameCodec;
+import jdk.nashorn.internal.codegen.ApplySpecialization;
 import jdk.nashorn.internal.codegen.CompilationEnvironment;
 import jdk.nashorn.internal.codegen.CompilationEnvironment.CompilationPhases;
-import jdk.nashorn.internal.codegen.ApplySpecialization;
 import jdk.nashorn.internal.codegen.CompileUnit;
 import jdk.nashorn.internal.codegen.Compiler;
 import jdk.nashorn.internal.codegen.CompilerConstants;
@@ -339,7 +339,7 @@
     }
 
     private static String getShortDescriptor(final Object value) {
-        if (value.getClass() == Object.class) {
+        if (value == null || !value.getClass().isPrimitive() || value.getClass() != Boolean.class) {
             return "O";
         }
         return value.getClass().getSimpleName();
@@ -365,17 +365,49 @@
         return sb.toString();
     }
 
-    MethodHandle compileRestOfMethod(final MethodType fnCallSiteType, final Map<Integer, Type> invalidatedProgramPoints, final int[] continuationEntryPoints, final ScriptObject runtimeScope) {
+    FunctionNodeTransform getNopTransform() {
+        return new FunctionNodeTransform() {
+            @Override
+            FunctionNode apply(final FunctionNode functionNode) {
+                return functionNode;
+            }
+
+            @Override
+            int getArity() {
+                return RecompilableScriptFunctionData.this.getArity();
+            }
+
+            @Override
+            public String toString() {
+                return "[NopTransform]";
+            }
+        };
+    }
+
+    FunctionNodeTransform getDefaultTransform(final MethodType callSiteType) {
+        return new ApplyToCallTransform(this, callSiteType);
+    }
+
+    private FunctionNode compileTypeSpecialization(final MethodType actualCallSiteType, final ScriptObject runtimeScope, final FunctionNodeTransform tr) {
+        return compile(actualCallSiteType, null, runtimeScope, "Type specialized compilation", tr);
+    }
+
+    private ParamTypeMap typeMap(final MethodType fnCallSiteType, final FunctionNodeTransform tr) {
+        if (isVariableArity() && !tr.wasTransformed()) {
+            return null;
+        }
+        return new ParamTypeMap(functionNodeId, explicitParams(fnCallSiteType, tr.getArity()));
+    }
+
+    MethodHandle compileRestOfMethod(final MethodType fnCallSiteType, final Map<Integer, Type> invalidatedProgramPoints, final int[] continuationEntryPoints, final ScriptObject runtimeScope, final FunctionNodeTransform tr) {
         if (LOG.isEnabled()) {
             LOG.info("Rest-of compilation of '", functionName, "' signature: ", fnCallSiteType, " ", stringifyInvalidations(invalidatedProgramPoints));
         }
 
         final String scriptName = RECOMPILATION_PREFIX + RECOMPILE_ID.incrementAndGet() + "$restOf";
-        FunctionNode fn = reparse(scriptName);
 
-        final ApplyToCallTransform tr = new ApplyToCallTransform(fn, fnCallSiteType, true);
-        fn = tr.transform();
-        final int newArity = tr.arity;
+        FunctionNode fn = tr.apply(reparse(scriptName));
+        final ParamTypeMap ptm = typeMap(fnCallSiteType, tr);
 
         final Compiler compiler = new Compiler(
                 new CompilationEnvironment(
@@ -383,9 +415,7 @@
                     isStrict(),
                     this,
                     runtimeScope,
-                    isVariableArity() && !tr.transformed ?
-                            null :
-                            new ParamTypeMap(functionNodeId, explicitParams(fnCallSiteType, newArity)),
+                    ptm,
                     invalidatedProgramPoints,
                     continuationEntryPoints,
                     true
@@ -400,11 +430,7 @@
         return mh;
     }
 
-    private FunctionNode compileTypeSpecialization(final MethodType actualCallSiteType, final ScriptObject runtimeScope) {
-        return compile(actualCallSiteType, null, runtimeScope, "Type specialized compilation");
-    }
-
-    FunctionNode compile(final MethodType actualCallSiteType, final Map<Integer, Type> invalidatedProgramPoints, final ScriptObject runtimeScope, final String reason) {
+    FunctionNode compile(final MethodType actualCallSiteType, final Map<Integer, Type> invalidatedProgramPoints, final ScriptObject runtimeScope, final String reason, final FunctionNodeTransform tr) {
         final String scriptName = RECOMPILATION_PREFIX + RECOMPILE_ID.incrementAndGet();
         final MethodType fnCallSiteType = actualCallSiteType == null ? null : actualCallSiteType.changeParameterType(0, ScriptFunction.class);
 
@@ -412,11 +438,9 @@
             LOG.info(reason, " of '", functionName, "' signature: ", fnCallSiteType, " ", stringifyInvalidations(invalidatedProgramPoints));
         }
 
-        FunctionNode fn = reparse(scriptName);
+        FunctionNode fn = tr.apply(reparse(scriptName));
 
-        final ApplyToCallTransform tr = new ApplyToCallTransform(fn, actualCallSiteType, false);
-        fn = tr.transform();
-        final int newArity = tr.arity;
+        final ParamTypeMap ptm = fnCallSiteType == null ? null : typeMap(fnCallSiteType, tr);
 
         final CompilationPhases phases = CompilationPhases.EAGER;
         final Compiler compiler = new Compiler(
@@ -425,17 +449,12 @@
                 isStrict(),
                 this,
                 runtimeScope,
-                fnCallSiteType == null || isVariableArity() && !tr.transformed ?
-                    null :
-                    new ParamTypeMap(
-                        functionNodeId,
-                        explicitParams(fnCallSiteType, newArity)),
+                ptm,
                 invalidatedProgramPoints,
                 true),
             installer);
 
         fn = compiler.compile(scriptName, fn);
-
         compiler.install(fn);
 
         return fn;
@@ -552,42 +571,51 @@
             // If method has an Object parameter, but call site had String, preserve it as Object. No need to narrow it
             // artificially. Note that this is related to how CompiledFunction.matchesCallSite() works, specifically
             // the fact that various reference types compare to equal (see "fnType.isEquivalentTo(csType)" there).
-            if(fromParam != toParam && !fromParam.isPrimitive() && !toParam.isPrimitive()) {
+            if (fromParam != toParam && !fromParam.isPrimitive() && !toParam.isPrimitive()) {
                 assert fromParam.isAssignableFrom(toParam);
                 toType = toType.changeParameterType(i, fromParam);
             }
         }
-        if(fromCount > toCount) {
+        if (fromCount > toCount) {
             toType = toType.appendParameterTypes(fromType.parameterList().subList(toCount, fromCount));
-        } else if(fromCount < toCount) {
+        } else if (fromCount < toCount) {
             toType = toType.dropParameterTypes(fromCount, toCount);
         }
 
         return addCode(lookup(fn).asType(toType), fn.getFlags());
     }
 
+
     @Override
     CompiledFunction getBest(final MethodType callSiteType, final ScriptObject runtimeScope) {
         synchronized (code) {
-            final CompiledFunction existingBest = super.getBest(callSiteType, runtimeScope);
-            if (existingBest != null) {
-                /*
-                 * If callsite type isn't vararg and our best is vararg, generate a specialization
-                 * we DO have a generic version, which means that we know which ones of the applies
-                 * were actual applies
-                 */
-                if (existingBest.isVarArg() && !CompiledFunction.isVarArgsType(callSiteType)) {
-                    //System.err.println("Looking in code for best " + callSiteType + " " + existingBest + " code=" + code);
-                    final FunctionNode fn = compileTypeSpecialization(callSiteType, runtimeScope);
-                    if (fn.hasOptimisticApplyToCall()) { //did the specialization work
-                        final CompiledFunction cf = addCode(fn, callSiteType);
-                        assert !cf.isVarArg();
-                        return cf;
-                    }
+            CompiledFunction existingBest = super.getBest(callSiteType, runtimeScope);
+            if (existingBest == null) {
+                existingBest = addCode(compileTypeSpecialization(callSiteType, runtimeScope, getNopTransform()), callSiteType);
+            }
+
+            assert existingBest != null;
+            boolean applyToCall = existingBest.isVarArg() && !CompiledFunction.isVarArgsType(callSiteType);
+
+            //if the best one is an apply to call, it has to match the callsite exactly
+            //or we need to regenerate
+            if (existingBest.isApplyToCall()) {
+                final CompiledFunction best = code.lookupExactApplyToCall(callSiteType);
+                if (best != null) {
+                    return best;
                 }
-                return existingBest;
+                applyToCall = true;
             }
-            return addCode(compileTypeSpecialization(callSiteType, runtimeScope), callSiteType);
+
+            if (applyToCall) {
+                final FunctionNode fn = compileTypeSpecialization(callSiteType, runtimeScope, new ApplyToCallTransform(this, callSiteType));
+                if (fn.hasOptimisticApplyToCall()) { //did the specialization work
+                    existingBest = addCode(fn, callSiteType);
+                    existingBest.setIsApplyToCall();
+                }
+            }
+
+            return existingBest;
         }
     }
 
@@ -676,33 +704,65 @@
         return true;
     }
 
+    private static abstract class FunctionNodeTransform {
+
+        abstract int getArity();
+
+        boolean wasTransformed() {
+            return false;
+        }
+
+        abstract FunctionNode apply(final FunctionNode functionNode);
+    }
+
     /**
      * Helper class for transforming apply calls to calls
      */
-    private class ApplyToCallTransform {
+    private static class ApplyToCallTransform extends FunctionNodeTransform {
+        private final RecompilableScriptFunctionData data;
         private final MethodType actualCallSiteType;
-        private final boolean isRestOf;
         private int arity;
-        private FunctionNode functionNode;
-        private boolean transformed;
+        private FunctionNode initialFunctionNode;
+        private FunctionNode transformedFunctionNode;
 
-        ApplyToCallTransform(final FunctionNode functionNode, final MethodType actualCallSiteType, final boolean isRestOf) {
-            this.functionNode = functionNode;
+        ApplyToCallTransform(final RecompilableScriptFunctionData data, final MethodType actualCallSiteType) {
+            this.data = data;
             this.actualCallSiteType = actualCallSiteType;
-            this.arity = getArity();
-            this.isRestOf = isRestOf;
+            this.arity = data.getArity();
         }
 
-        FunctionNode transform() {
-            if (isVariableArity()) {
-                final ApplySpecialization spec = new ApplySpecialization(RecompilableScriptFunctionData.this, functionNode, actualCallSiteType, isRestOf);
+        @Override
+        public FunctionNode apply(final FunctionNode functionNode) {
+            this.initialFunctionNode = functionNode;
+            if (data.isVariableArity()) {
+                final ApplySpecialization spec = new ApplySpecialization(data, functionNode, actualCallSiteType);
                 if (spec.transform()) {
-                    functionNode = spec.getFunctionNode();
-                    arity = functionNode.getParameters().size();
-                    transformed = true;
+                    setTransformedFunctionNode(spec.getFunctionNode());
+                    return transformedFunctionNode;
                 }
             }
             return functionNode;
         }
+
+        private void setTransformedFunctionNode(final FunctionNode transformedFunctionNode) {
+            this.transformedFunctionNode = transformedFunctionNode;
+            assert !transformedFunctionNode.isVarArg();
+            this.arity = transformedFunctionNode.getParameters().size();
+        }
+
+        @Override
+        public int getArity() {
+            return arity;
+        }
+
+        @Override
+        public boolean wasTransformed() {
+            return initialFunctionNode != transformedFunctionNode;
+        }
+
+        @Override
+        public String toString() {
+            return "[ApplyToCallTransform]";
+        }
     }
 }
--- a/nashorn/test/script/basic/apply_to_call/apply_to_call1.js	Fri Apr 11 16:52:56 2014 +0200
+++ b/nashorn/test/script/basic/apply_to_call/apply_to_call1.js	Thu Apr 17 20:01:19 2014 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+* 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
--- a/nashorn/test/script/basic/apply_to_call/apply_to_call2.js	Fri Apr 11 16:52:56 2014 +0200
+++ b/nashorn/test/script/basic/apply_to_call/apply_to_call2.js	Thu Apr 17 20:01:19 2014 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * 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
--- a/nashorn/test/script/basic/apply_to_call/apply_to_call3.js	Fri Apr 11 16:52:56 2014 +0200
+++ b/nashorn/test/script/basic/apply_to_call/apply_to_call3.js	Thu Apr 17 20:01:19 2014 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * 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
--- a/nashorn/test/script/basic/apply_to_call/apply_to_call4.js	Fri Apr 11 16:52:56 2014 +0200
+++ b/nashorn/test/script/basic/apply_to_call/apply_to_call4.js	Thu Apr 17 20:01:19 2014 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * 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
--- a/nashorn/test/script/basic/apply_to_call/apply_to_call_bench.js	Fri Apr 11 16:52:56 2014 +0200
+++ b/nashorn/test/script/basic/apply_to_call/apply_to_call_bench.js	Thu Apr 17 20:01:19 2014 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/apply_to_call/apply_to_call_recompile.js	Thu Apr 17 20:01:19 2014 +0200
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ * 
+ * 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.
+ */
+
+/**
+ * apply_to_recompile.js - make sure that recompilations of methods are
+ * transform equivalent when it comes to apply2call, or we will have
+ * erroneous contiunation info generation
+ *
+ * @test
+ * @run
+ */
+
+function K() {
+    K.b2BoundValues.apply(this, arguments);
+    this.constructor === K && K.b2BoundValues.apply(this, arguments)
+}
+
+K.b2BoundValues = function(a,b,c) {
+    print(a);
+    print(b);
+    print(c);
+};
+
+new K(11,12,13,14,15,16);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/apply_to_call/apply_to_call_recompile.js.EXPECTED	Thu Apr 17 20:01:19 2014 +0200
@@ -0,0 +1,6 @@
+11
+12
+13
+11
+12
+13
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/apply_to_call/apply_to_call_varargs.js	Thu Apr 17 20:01:19 2014 +0200
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ * 
+ * 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.
+ */
+
+/**
+ * apply_to_call_varars - make sure that apply to call transform works
+ * even when supplying too few arguments
+ *
+ * @test
+ * @run
+ */
+
+var Class = {
+  create: function() {
+    return function() { //vararg
+        this.initialize.apply(this, arguments);
+    }
+  }
+};
+
+Color = Class.create();
+Color.prototype = {
+    red: 0, green: 0, blue: 0,
+    initialize: function(r) {
+	this.red = r;
+	this.green = 255;
+	this.blue = 255;
+    },
+    toString: function() {
+	print("[red=" + this.red + ", green=" + this.green + ", blue=" + this.blue + "]");
+    }
+};
+
+var colors = new Array(16);
+function run() {
+    for (var i = 0; i < colors.length; i++) {
+	colors[i&0xf] = (new Color(i));
+    }
+}
+
+run();
+for (var i = 0; i < colors.length; i++) {
+    print(colors[i]);
+}
+
+print("Swapping out call");
+Function.prototype.call = function() {
+    throw "This should not happen, apply should be called instead";
+};
+
+run();
+for (var i = 0; i < colors.length; i++) {
+    print(colors[i]);
+}
+
+print("All done!");
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/apply_to_call/apply_to_call_varargs.js.EXPECTED	Thu Apr 17 20:01:19 2014 +0200
@@ -0,0 +1,66 @@
+[red=0, green=255, blue=255]
+undefined
+[red=1, green=255, blue=255]
+undefined
+[red=2, green=255, blue=255]
+undefined
+[red=3, green=255, blue=255]
+undefined
+[red=4, green=255, blue=255]
+undefined
+[red=5, green=255, blue=255]
+undefined
+[red=6, green=255, blue=255]
+undefined
+[red=7, green=255, blue=255]
+undefined
+[red=8, green=255, blue=255]
+undefined
+[red=9, green=255, blue=255]
+undefined
+[red=10, green=255, blue=255]
+undefined
+[red=11, green=255, blue=255]
+undefined
+[red=12, green=255, blue=255]
+undefined
+[red=13, green=255, blue=255]
+undefined
+[red=14, green=255, blue=255]
+undefined
+[red=15, green=255, blue=255]
+undefined
+Swapping out call
+[red=0, green=255, blue=255]
+undefined
+[red=1, green=255, blue=255]
+undefined
+[red=2, green=255, blue=255]
+undefined
+[red=3, green=255, blue=255]
+undefined
+[red=4, green=255, blue=255]
+undefined
+[red=5, green=255, blue=255]
+undefined
+[red=6, green=255, blue=255]
+undefined
+[red=7, green=255, blue=255]
+undefined
+[red=8, green=255, blue=255]
+undefined
+[red=9, green=255, blue=255]
+undefined
+[red=10, green=255, blue=255]
+undefined
+[red=11, green=255, blue=255]
+undefined
+[red=12, green=255, blue=255]
+undefined
+[red=13, green=255, blue=255]
+undefined
+[red=14, green=255, blue=255]
+undefined
+[red=15, green=255, blue=255]
+undefined
+All done!
--- a/nashorn/test/script/basic/run-octane.js	Fri Apr 11 16:52:56 2014 +0200
+++ b/nashorn/test/script/basic/run-octane.js	Thu Apr 17 20:01:19 2014 +0200
@@ -156,7 +156,9 @@
 
     } catch (e) {
 	print_always("*** Aborted and setting score to zero. Reason: " + e);
-	e.printStackTrace();
+	if (e instanceof java.lang.Throwable) {
+	    e.printStackTrace();
+	}
 	mean_score = min_score = max_score = 0;
 	scores = [0];
     }
@@ -218,13 +220,19 @@
 for (var i = 0; i < args.length; i++) { 
     arg = args[i];
     if (arg == "--iterations") {
-	iters = +args[++i];
+	iters = +args[++i];	
+	if (isNaN(iters)) {
+	    throw "'--iterations' must be followed by integer";
+	}
     } else if (arg == "--runtime") {
 	runtime = args[++i];
     } else if (arg == "--verbose") {
 	verbose = true;
     } else if (arg == "--min-time") {
 	min_time = +args[++i];
+	if (isNaN(iters)) {
+	    throw "'--min-time' must be followed by integer";
+	}
     } else if (arg == "") {
 	continue; //skip
     } else {