8046921: Deoptimization type information peristence
authorattila
Fri, 20 Jun 2014 12:25:00 +0200
changeset 25236 fac419f1e889
parent 25235 5e800a7fd352
child 25237 bf5efe0a93ba
8046921: Deoptimization type information peristence Reviewed-by: hannesw, lagergren
nashorn/make/project.properties
nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java
nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java
nashorn/src/jdk/nashorn/internal/codegen/Compiler.java
nashorn/src/jdk/nashorn/internal/codegen/OptimisticTypesPersistence.java
nashorn/src/jdk/nashorn/internal/codegen/TypeMap.java
nashorn/src/jdk/nashorn/internal/runtime/CompiledFunction.java
nashorn/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java
nashorn/test/script/basic/JDK-8030182_2.js.EXPECTED
--- a/nashorn/make/project.properties	Thu Jun 19 20:36:03 2014 +0530
+++ b/nashorn/make/project.properties	Fri Jun 20 12:25:00 2014 +0200
@@ -279,6 +279,7 @@
   -Dfile.encoding=UTF-8 \
   -Duser.language=${run.test.user.language} \
   -Duser.country=${run.test.user.country} \
+  -Dnashorn.typeInfo.cacheDir=${build.dir}${file.separator}test${file.separator}type_info_cache \
   ${jfr.args} \
   -XX:+HeapDumpOnOutOfMemoryError
 
--- a/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java	Thu Jun 19 20:36:03 2014 +0530
+++ b/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java	Fri Jun 20 12:25:00 2014 +0200
@@ -145,6 +145,7 @@
 import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData;
 import jdk.nashorn.internal.runtime.RewriteException;
 import jdk.nashorn.internal.runtime.Scope;
+import jdk.nashorn.internal.runtime.ScriptEnvironment;
 import jdk.nashorn.internal.runtime.ScriptFunction;
 import jdk.nashorn.internal.runtime.ScriptObject;
 import jdk.nashorn.internal.runtime.ScriptRuntime;
@@ -1821,19 +1822,40 @@
         method.storeCompilerConstant(ARGUMENTS);
     }
 
-    /**
-     * Should this code generator skip generating code for inner functions? If lazy compilation is on, or we're
-     * doing an on-demand ("just-in-time") compilation, then we aren't generating code for inner functions.
-     */
-    private boolean compileOutermostOnly() {
-        return compiler.isOnDemandCompilation() || compiler.getScriptEnvironment()._lazy_compilation;
+    private boolean skipFunction(final FunctionNode functionNode) {
+        final ScriptEnvironment env = compiler.getScriptEnvironment();
+        final boolean lazy = env._lazy_compilation;
+        final boolean onDemand = compiler.isOnDemandCompilation();
+
+        // If this is on-demand or lazy compilation, don't compile a nested (not topmost) function.
+        if((onDemand || lazy) && lc.getOutermostFunction() != functionNode) {
+            return true;
+        }
+
+        // If lazy compiling with optimistic types, don't compile the program eagerly either. It will soon be
+        // invalidated anyway. In presence of a class cache, this further means that an obsoleted program version
+        // lingers around. Also, currently loading previously persisted optimistic types information only works if
+        // we're on-demand compiling a function, so with this strategy the :program method can also have the warmup
+        // benefit of using previously persisted types.
+        // NOTE that this means the first compiled class will effectively just have a :createProgramFunction method, and
+        // the RecompilableScriptFunctionData (RSFD) object in its constants array. It won't even have the :program
+        // method. This is by design. It does mean that we're wasting one compiler execution (and we could minimize this
+        // by just running it up to scope depth calculation, which creates the RSFDs and then this limited codegen).
+        // We could emit an initial separate compile unit with the initial version of :program in it to better utilize
+        // the compilation pipeline, but that would need more invasive changes, as currently the assumption that
+        // :program is emitted into the first compilation unit of the function lives in many places.
+        if(!onDemand && lazy && env._optimistic_types && functionNode.isProgram()) {
+            return true;
+        }
+
+        return false;
     }
 
     @Override
     public boolean enterFunctionNode(final FunctionNode functionNode) {
         final int fnId = functionNode.getId();
 
-        if (compileOutermostOnly() && lc.getOutermostFunction() != functionNode) {
+        if (skipFunction(functionNode)) {
             // In case we are not generating code for the function, we must create or retrieve the function object and
             // load it on the stack here.
             newFunctionObject(functionNode, false);
--- a/nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java	Thu Jun 19 20:36:03 2014 +0530
+++ b/nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java	Fri Jun 20 12:25:00 2014 +0200
@@ -432,7 +432,9 @@
             compiler.getLogger().fine("Starting bytecode generation for ", quote(fn.getName()), " - restOf=", phases.isRestOfCompilation());
             final CodeGenerator codegen = new CodeGenerator(compiler, phases.isRestOfCompilation() ? compiler.getContinuationEntryPoints() : null);
             try {
-                newFunctionNode = (FunctionNode)newFunctionNode.accept(codegen);
+                // 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);
                 codegen.generateScopeCalls();
             } catch (final VerifyError e) {
                 if (senv._verify_code || senv._print_code) {
--- a/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java	Thu Jun 19 20:36:03 2014 +0530
+++ b/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java	Fri Jun 20 12:25:00 2014 +0200
@@ -45,11 +45,12 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
+import java.util.TreeMap;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
 import java.util.logging.Level;
-
 import jdk.internal.dynalink.support.NameCodec;
 import jdk.nashorn.internal.codegen.ClassEmitter.Flag;
 import jdk.nashorn.internal.codegen.types.Type;
@@ -121,6 +122,15 @@
      * that using whatever was at program point 17 as an int failed.
      */
     private final Map<Integer, Type> invalidatedProgramPoints;
+    /**
+     * The snapshot of invalidatedProgramPoints before the compilation. Used to compare it to invalidatedProgramPoints,
+     * and if the two are equal, not write the type information to a file.
+     */
+    private final Map<Integer, Type> invalidatedProgramPointsOnEntry;
+    /**
+     * Descriptor of the location where we write the type information after compilation.
+     */
+    private final Object typeInformationFile;
 
     /**
      * Compile unit name of first compile unit - this prefix will be used for all
@@ -317,7 +327,7 @@
             final Source source,
             final String sourceURL,
             final boolean isStrict) {
-        this(context, env, installer, source, sourceURL, isStrict, false, null, null, null, null, null);
+        this(context, env, installer, source, sourceURL, isStrict, false, null, null, null, null, null, null);
     }
 
     /**
@@ -333,6 +343,7 @@
      * @param compiledFunction         compiled function, if any
      * @param types                    parameter and return value type information, if any is known
      * @param invalidatedProgramPoints invalidated program points for recompilation
+     * @param typeInformationFile      descriptor of the location where type information is persisted
      * @param continuationEntryPoints  continuation entry points for restof method
      * @param runtimeScope             runtime scope for recompilation type lookup in {@code TypeEvaluator}
      */
@@ -347,6 +358,7 @@
             final RecompilableScriptFunctionData compiledFunction,
             final TypeMap types,
             final Map<Integer, Type> invalidatedProgramPoints,
+            final Object typeInformationFile,
             final int[] continuationEntryPoints,
             final ScriptObject runtimeScope) {
         this.context                  = context;
@@ -363,11 +375,13 @@
         this.compiledFunction         = compiledFunction;
         this.types                    = types;
         this.invalidatedProgramPoints = invalidatedProgramPoints == null ? new HashMap<Integer, Type>() : invalidatedProgramPoints;
+        this.typeInformationFile      = typeInformationFile;
         this.continuationEntryPoints  = continuationEntryPoints == null ? null: continuationEntryPoints.clone();
         this.typeEvaluator            = new TypeEvaluator(this, runtimeScope);
         this.firstCompileUnitName     = firstCompileUnitName();
         this.strict                   = isStrict;
 
+        this.invalidatedProgramPointsOnEntry = typeInformationFile == null ? null : new HashMap<>(this.invalidatedProgramPoints);
         this.optimistic = env._optimistic_types;
     }
 
@@ -457,6 +471,16 @@
         invalidatedProgramPoints.put(programPoint, type);
     }
 
+
+    /**
+     * Returns a copy of this compiler's current mapping of invalidated optimistic program points to their types. The
+     * copy is not live with regard to changes in state in this compiler instance, and is mutable.
+     * @return a copy of this compiler's current mapping of invalidated optimistic program points to their types.
+     */
+    public Map<Integer, Type> getInvalidatedProgramPoints() {
+        return invalidatedProgramPoints == null ? null : new TreeMap<>(invalidatedProgramPoints);
+    }
+
     TypeMap getTypeMap() {
         return types;
     }
@@ -513,6 +537,10 @@
             time += (env.isTimingEnabled() ? phase.getEndTime() - phase.getStartTime() : 0L);
         }
 
+        if(typeInformationFile != null && !phases.isRestOfCompilation() && !Objects.equals(invalidatedProgramPoints, invalidatedProgramPointsOnEntry)) {
+            OptimisticTypesPersistence.store(typeInformationFile, invalidatedProgramPoints);
+        }
+
         log.unindent();
 
         if (info) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/src/jdk/nashorn/internal/codegen/OptimisticTypesPersistence.java	Fri Jun 20 12:25:00 2014 +0200
@@ -0,0 +1,345 @@
+/*
+ * 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.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.MessageDigest;
+import java.security.PrivilegedAction;
+import java.sql.Date;
+import java.text.SimpleDateFormat;
+import java.util.Base64;
+import java.util.Map;
+import java.util.TreeMap;
+import jdk.nashorn.internal.codegen.types.Type;
+import jdk.nashorn.internal.runtime.Context;
+import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData;
+import jdk.nashorn.internal.runtime.Source;
+import jdk.nashorn.internal.runtime.logging.DebugLogger;
+import jdk.nashorn.internal.runtime.options.Options;
+
+/**
+ * Static utility that encapsulates persistence of decompilation information for functions. Normally, the type info
+ * persistence feature is enabled and operates in an operating-system specific per-user cache directory. You can
+ * override the directory by specifying it in the {@code nashorn.typeInfo.cacheDir} directory. Also, you can disable the
+ * type info persistence altogether by specifying the {@code nashorn.typeInfo.disabled} system property.
+ */
+public final class OptimisticTypesPersistence {
+    private static final File cacheDir = createCacheDir();
+    // In-process locks to make sure we don't have a cross-thread race condition manipulating any file.
+    private static final Object[] locks = cacheDir == null ? null : createLockArray();
+
+    // Only report one read/write error every minute
+    private static final long ERROR_REPORT_THRESHOLD = 60000L;
+
+    private static volatile long lastReportedError;
+
+    /**
+     * Retrieves an opaque descriptor for the persistence location for a given function. It should be passed to
+     * {@link #load(Object)} and {@link #store(Object, Map)} methods.
+     * @param source the source where the function comes from
+     * @param functionId the unique ID number of the function within the source
+     * @param paramTypes the types of the function parameters (as persistence is per parameter type specialization).
+     * @return an opaque descriptor for the persistence location. Can be null if persistence is disabled.
+     */
+    public static Object getLocationDescriptor(final Source source, final int functionId, final Type[] paramTypes) {
+        if(cacheDir == null) {
+            return null;
+        }
+        final StringBuilder b = new StringBuilder(48);
+        // Base64-encode the digest of the source, and append the function id.
+        b.append(Base64.getUrlEncoder().encodeToString(source.getDigest())).append('-').append(functionId);
+        // Finally, if this is a parameter-type specialized version of the function, add the parameter types to the file
+        // name.
+        if(paramTypes != null && paramTypes.length > 0) {
+            b.append('-');
+            for(final Type t: paramTypes) {
+                b.append(t.getBytecodeStackType());
+            }
+        }
+        return new LocationDescriptor(new File(cacheDir, b.toString()));
+    }
+
+    private static final class LocationDescriptor {
+        private final File file;
+
+        LocationDescriptor(final File file) {
+            this.file = file;
+        }
+    }
+
+
+    /**
+     * Stores the map of optimistic types for a given function.
+     * @param locationDescriptor the opaque persistence location descriptor, retrieved by calling
+     * {@link #getLocationDescriptor(Source, int, Type[])}.
+     * @param optimisticTypes the map of optimistic types.
+     */
+    @SuppressWarnings("resource")
+    public static void store(final Object locationDescriptor, final Map<Integer, Type> optimisticTypes) {
+        if(locationDescriptor == null) {
+            return;
+        }
+        final File file = ((LocationDescriptor)locationDescriptor).file;
+        AccessController.doPrivileged(new PrivilegedAction<Void>() {
+            @Override
+            public Void run() {
+                synchronized(getFileLock(file)) {
+                    try (final FileOutputStream out = new FileOutputStream(file);) {
+                        out.getChannel().lock(); // lock exclusive
+                        final DataOutputStream dout = new DataOutputStream(new BufferedOutputStream(out));
+                        dout.writeInt(optimisticTypes.size());
+                        for(Map.Entry<Integer, Type> e: optimisticTypes.entrySet()) {
+                            dout.writeInt(e.getKey());
+                            final byte typeChar;
+                            final Type type = e.getValue();
+                            if(type == Type.OBJECT) {
+                                typeChar = 'L';
+                            } else if(type == Type.NUMBER) {
+                                typeChar = 'D';
+                            } else if(type == Type.LONG) {
+                                typeChar = 'J';
+                            } else {
+                                throw new AssertionError();
+                            }
+                            dout.write(typeChar);
+                        }
+                        dout.flush();
+                    } catch(final Exception e) {
+                        final long now = System.currentTimeMillis();
+                        if(now - lastReportedError > ERROR_REPORT_THRESHOLD) {
+                            getLogger().warning("Failed to write " + file, e);
+                            lastReportedError = now;
+                        }
+                    }
+                }
+                return null;
+            }
+        });
+    }
+
+    /**
+     * Loads the map of optimistic types for a given function.
+     * @param locationDescriptor the opaque persistence location descriptor, retrieved by calling
+     * {@link #getLocationDescriptor(Source, int, Type[])}.
+     * @return the map of optimistic types, or null if persisted type information could not be retrieved.
+     */
+    @SuppressWarnings("resource")
+    public static Map<Integer, Type> load(final Object locationDescriptor) {
+        if (locationDescriptor == null) {
+            return null;
+        }
+        final File file = ((LocationDescriptor)locationDescriptor).file;
+
+        return AccessController.doPrivileged(new PrivilegedAction<Map<Integer, Type>>() {
+            @Override
+            public Map<Integer, Type> run() {
+                try {
+                    if(!file.isFile()) {
+                        return null;
+                    }
+                    synchronized(getFileLock(file)) {
+                        try (final FileInputStream in = new FileInputStream(file);) {
+                            in.getChannel().lock(0, Long.MAX_VALUE, true); // lock shared
+                            final DataInputStream din = new DataInputStream(new BufferedInputStream(in));
+                            final Map<Integer, Type> map = new TreeMap<>();
+                            final int size = din.readInt();
+                            for(int i = 0; i < size; ++i) {
+                                final int pp = din.readInt();
+                                final int typeChar = din.read();
+                                final Type type;
+                                switch(typeChar) {
+                                case 'L': type = Type.OBJECT; break;
+                                case 'D': type = Type.NUMBER; break;
+                                case 'J': type = Type.LONG; break;
+                                default: throw new AssertionError();
+                                }
+                                map.put(pp, type);
+                            }
+                            return map;
+                        }
+                    }
+                } catch (final Exception e) {
+                    final long now = System.currentTimeMillis();
+                    if(now - lastReportedError > ERROR_REPORT_THRESHOLD) {
+                        getLogger().warning("Failed to read " + file, e);
+                        lastReportedError = now;
+                    }
+                    return null;
+                }
+            }
+        });
+    }
+
+    private static File createCacheDir() {
+        if(Options.getBooleanProperty("nashorn.typeInfo.disabled")) {
+            return null;
+        }
+        try {
+            return createCacheDirPrivileged();
+        } catch(final Exception e) {
+            getLogger().warning("Failed to create cache dir", e);
+            return null;
+        }
+    }
+
+    private static File createCacheDirPrivileged() {
+        return AccessController.doPrivileged(new PrivilegedAction<File>() {
+            @Override
+            public File run() {
+                final String explicitDir = System.getProperty("nashorn.typeInfo.cacheDir");
+                final File dir;
+                if(explicitDir != null) {
+                    dir = new File(explicitDir);
+                } else {
+                    // When no directory is explicitly specified, get an operating system specific cache directory,
+                    // and create "com.oracle.java.NashornTypeInfo" in it.
+                    dir = new File(getCacheDirBase(), "com.oracle.java.NashornTypeInfo");
+                }
+                final String versionDirName;
+                try {
+                    versionDirName = getVersionDirName();
+                } catch(Exception e) {
+                    getLogger().warning("Failed to calculate version dir name", e);
+                    return null;
+                }
+                final File versionDir = new File(dir, versionDirName);
+                versionDir.mkdirs();
+                if(versionDir.isDirectory()) {
+                    getLogger().info("Optimistic type persistence directory is " + versionDir);
+                    return versionDir;
+                }
+                getLogger().warning("Could not create optimistic type persistence directory " + versionDir);
+                return null;
+            }
+        });
+    }
+
+    /**
+     * Returns an operating system specific root directory for cache files.
+     * @return an operating system specific root directory for cache files.
+     */
+    private static File getCacheDirBase() {
+        final String os = System.getProperty("os.name", "generic");
+        if("Mac OS X".equals(os)) {
+            // Mac OS X stores caches in ~/Library/Caches
+            return new File(new File(System.getProperty("user.home"), "Library"), "Caches");
+        } else if(os.startsWith("Windows")) {
+            // On Windows, temp directory is the best approximation of a cache directory, as its contents persist across
+            // reboots and various cleanup utilities know about it. java.io.tmpdir normally points to a user-specific
+            // temp directory, %HOME%\LocalSettings\Temp.
+            return new File(System.getProperty("java.io.tmpdir"));
+        } else {
+            // In all other cases we're presumably dealing with a UNIX flavor (Linux, Solaris, etc.); "~/.cache"
+            return new File(System.getProperty("user.home"), ".cache");
+        }
+    }
+
+    /**
+     * In order to ensure that changes in Nashorn code don't cause corruption in the data, we'll create a
+     * per-code-version directory. Normally, this will create the SHA-1 digest of the nashorn.jar. In case the classpath
+     * for nashorn is local directory (e.g. during development), this will create the string "dev-" followed by the
+     * timestamp of the most recent .class file.
+     * @return
+     */
+    private static String getVersionDirName() throws Exception {
+        final URL url = OptimisticTypesPersistence.class.getResource("");
+        final String protocol = url.getProtocol();
+        if(protocol.equals("jar")) {
+            // Normal deployment: nashorn.jar
+            final String jarUrlFile = url.getFile();
+            final String filePath = jarUrlFile.substring(0, jarUrlFile.indexOf('!'));
+            final URL file = new URL(filePath);
+            try (final InputStream in = file.openStream()) {
+                final byte[] buf = new byte[128*1024];
+                final MessageDigest digest = MessageDigest.getInstance("SHA-1");
+                for(;;) {
+                    final int l = in.read(buf);
+                    if(l == -1) {
+                        return Base64.getUrlEncoder().encodeToString(digest.digest());
+                    }
+                    digest.update(buf, 0, l);
+                }
+            }
+        } else if(protocol.equals("file")) {
+            // Development
+            final String fileStr = url.getFile();
+            final String className = OptimisticTypesPersistence.class.getName();
+            final int packageNameLen = className.lastIndexOf('.');
+            final String dirStr = fileStr.substring(0, fileStr.length() - packageNameLen - 1);
+            final File dir = new File(dirStr);
+            return "dev-" + new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date(getLastModifiedClassFile(dir, 0L)));
+        } else {
+            throw new AssertionError();
+        }
+    }
+
+    private static long getLastModifiedClassFile(final File dir, final long max) {
+        long currentMax = max;
+        for(File f: dir.listFiles()) {
+            if(f.getName().endsWith(".class")) {
+                final long lastModified = f.lastModified();
+                if(lastModified > currentMax) {
+                    currentMax = lastModified;
+                }
+            } else if(f.isDirectory()) {
+                final long lastModified = getLastModifiedClassFile(f, currentMax);
+                if(lastModified > currentMax) {
+                    currentMax = lastModified;
+                }
+            }
+        }
+        return currentMax;
+    }
+
+    private static Object[] createLockArray() {
+        final Object[] lockArray = new Object[Runtime.getRuntime().availableProcessors() * 2];
+        for(int i = 0; i < lockArray.length; ++i) {
+            lockArray[i] = new Object();
+        }
+        return lockArray;
+    }
+
+    private static Object getFileLock(final File file) {
+        return locks[(file.hashCode() & Integer.MAX_VALUE) % locks.length];
+    }
+
+    private static DebugLogger getLogger() {
+        try {
+            return Context.getContext().getLogger(RecompilableScriptFunctionData.class);
+        } catch (final Exception e) {
+            e.printStackTrace();
+            return DebugLogger.DISABLED_LOGGER;
+        }
+    }
+}
--- a/nashorn/src/jdk/nashorn/internal/codegen/TypeMap.java	Thu Jun 19 20:36:03 2014 +0530
+++ b/nashorn/src/jdk/nashorn/internal/codegen/TypeMap.java	Fri Jun 20 12:25:00 2014 +0200
@@ -29,6 +29,7 @@
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.NoSuchElementException;
 import jdk.nashorn.internal.codegen.types.Type;
 import jdk.nashorn.internal.ir.FunctionNode;
 import jdk.nashorn.internal.runtime.ScriptFunction;
@@ -61,6 +62,20 @@
         this.needsCallee = needsCallee;
     }
 
+    /**
+     * Returns the array of parameter types for a particular function node
+     * @param functionNodeId the ID of the function node
+     * @return an array of parameter types
+     * @throws NoSuchElementException if the type map has no mapping for the requested function
+     */
+    public Type[] getParameterTypes(final int functionNodeId) {
+        final Type[] paramTypes = paramTypeMap.get(functionNodeId);
+        if (paramTypes == null) {
+            throw new NoSuchElementException(Integer.toString(functionNodeId));
+        }
+        return paramTypes.clone();
+    }
+
     MethodType getCallSiteType(final FunctionNode functionNode) {
         final Type[] types = paramTypeMap.get(functionNode.getId());
         if (types == null) {
--- a/nashorn/src/jdk/nashorn/internal/runtime/CompiledFunction.java	Thu Jun 19 20:36:03 2014 +0530
+++ b/nashorn/src/jdk/nashorn/internal/runtime/CompiledFunction.java	Fri Jun 20 12:25:00 2014 +0200
@@ -89,11 +89,12 @@
         this.log = log;
     }
 
-    CompiledFunction(final MethodHandle invoker, final RecompilableScriptFunctionData functionData, final int flags) {
+    CompiledFunction(final MethodHandle invoker, final RecompilableScriptFunctionData functionData,
+            final Map<Integer, Type> invalidatedProgramPoints, final int flags) {
         this(invoker, null, functionData.getLogger());
         this.flags = flags;
         if ((flags & FunctionNode.IS_DEOPTIMIZABLE) != 0) {
-            optimismInfo = new OptimismInfo(functionData);
+            optimismInfo = new OptimismInfo(functionData, invalidatedProgramPoints);
         } else {
             optimismInfo = null;
         }
@@ -691,13 +692,14 @@
     private static class OptimismInfo {
         // TODO: this is pointing to its owning ScriptFunctionData. Re-evaluate if that's okay.
         private final RecompilableScriptFunctionData data;
-        private final Map<Integer, Type> invalidatedProgramPoints = new TreeMap<>();
+        private final Map<Integer, Type> invalidatedProgramPoints;
         private SwitchPoint optimisticAssumptions;
         private final DebugLogger log;
 
-        OptimismInfo(final RecompilableScriptFunctionData data) {
+        OptimismInfo(final RecompilableScriptFunctionData data, final Map<Integer, Type> invalidatedProgramPoints) {
             this.data = data;
             this.log  = data.getLogger();
+            this.invalidatedProgramPoints = invalidatedProgramPoints == null ? new TreeMap<Integer, Type>() : invalidatedProgramPoints;
             newOptimisticAssumptions();
         }
 
--- a/nashorn/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java	Thu Jun 19 20:36:03 2014 +0530
+++ b/nashorn/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java	Fri Jun 20 12:25:00 2014 +0200
@@ -36,12 +36,14 @@
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeMap;
 import jdk.internal.dynalink.support.NameCodec;
 import jdk.nashorn.internal.codegen.CompileUnit;
 import jdk.nashorn.internal.codegen.Compiler;
 import jdk.nashorn.internal.codegen.Compiler.CompilationPhases;
 import jdk.nashorn.internal.codegen.CompilerConstants;
 import jdk.nashorn.internal.codegen.FunctionSignature;
+import jdk.nashorn.internal.codegen.OptimisticTypesPersistence;
 import jdk.nashorn.internal.codegen.TypeMap;
 import jdk.nashorn.internal.codegen.types.Type;
 import jdk.nashorn.internal.ir.FunctionNode;
@@ -390,7 +392,11 @@
         return getCompiler(fn, actualCallSiteType, newLocals(runtimeScope), null, null);
     }
 
-    Compiler getCompiler(final FunctionNode functionNode, final MethodType actualCallSiteType, final ScriptObject runtimeScope, final Map<Integer, Type> ipp, final int[] cep) {
+    Compiler getCompiler(final FunctionNode functionNode, final MethodType actualCallSiteType,
+            final ScriptObject runtimeScope, final Map<Integer, Type> invalidatedProgramPoints,
+            final int[] continuationEntryPoints) {
+        final TypeMap typeMap = typeMap(actualCallSiteType);
+        final Object typeInformationFile = OptimisticTypesPersistence.getLocationDescriptor(source, functionNodeId, typeMap == null ? null : typeMap.getParameterTypes(functionNodeId));
         final Context context = Context.getContextTrusted();
         return new Compiler(
                 context,
@@ -402,12 +408,31 @@
                 true,       // is on demand
                 this,       // compiledFunction, i.e. this RecompilableScriptFunctionData
                 typeMap(actualCallSiteType), // type map
-                ipp, // invalidated program points
-                cep, // continuation entry points
+                getEffectiveInvalidatedProgramPoints(invalidatedProgramPoints, typeInformationFile), // invalidated program points
+                typeInformationFile,
+                continuationEntryPoints, // continuation entry points
                 runtimeScope); // runtime scope
     }
 
-    private FunctionNode compileTypeSpecialization(final MethodType actualCallSiteType, final ScriptObject runtimeScope) {
+    /**
+     * If the function being compiled already has its own invalidated program points map, use it. Otherwise, attempt to
+     * load invalidated program points map from the persistent type info cache.
+     * @param invalidatedProgramPoints the function's current invalidated program points map. Null if the function
+     * doesn't have it.
+     * @param typeInformationFile the object describing the location of the persisted type information.
+     * @return either the existing map, or a loaded map from the persistent type info cache, or a new empty map if
+     * neither an existing map or a persistent cached type info is available.
+     */
+    private static Map<Integer, Type> getEffectiveInvalidatedProgramPoints(
+            final Map<Integer, Type> invalidatedProgramPoints, final Object typeInformationFile) {
+        if(invalidatedProgramPoints != null) {
+            return invalidatedProgramPoints;
+        }
+        final Map<Integer, Type> loadedProgramPoints = OptimisticTypesPersistence.load(typeInformationFile);
+        return loadedProgramPoints != null ? loadedProgramPoints : new TreeMap<Integer, Type>();
+    }
+
+    private TypeSpecializedFunction compileTypeSpecialization(final MethodType actualCallSiteType, final ScriptObject runtimeScope) {
         // We're creating an empty script object for holding local variables. AssignSymbols will populate it with
         // explicit Undefined values for undefined local variables (see AssignSymbols#defineSymbol() and
         // CompilationEnvironment#declareLocalSymbol()).
@@ -417,7 +442,20 @@
         }
 
         final FunctionNode fn = reparse();
-        return getCompiler(fn, actualCallSiteType, runtimeScope).compile(fn, CompilationPhases.COMPILE_ALL);
+        final Compiler compiler = getCompiler(fn, actualCallSiteType, runtimeScope);
+
+        final FunctionNode compiledFn = compiler.compile(fn, CompilationPhases.COMPILE_ALL);
+        return new TypeSpecializedFunction(compiledFn, compiler.getInvalidatedProgramPoints());
+    }
+
+    private static class TypeSpecializedFunction {
+        private final FunctionNode fn;
+        private final Map<Integer, Type> invalidatedProgramPoints;
+
+        TypeSpecializedFunction(final FunctionNode fn, final Map<Integer, Type> invalidatedProgramPoints) {
+            this.fn = fn;
+            this.invalidatedProgramPoints = invalidatedProgramPoints;
+        }
     }
 
     private MethodType explicitParams(final MethodType callSiteType) {
@@ -494,14 +532,14 @@
         methodLocator = new MethodLocator(functionNode);
     }
 
-    private CompiledFunction addCode(final MethodHandle target, final int fnFlags) {
-        final CompiledFunction cfn = new CompiledFunction(target, this, fnFlags);
+    private CompiledFunction addCode(final MethodHandle target, final Map<Integer, Type> invalidatedProgramPoints, final int fnFlags) {
+        final CompiledFunction cfn = new CompiledFunction(target, this, invalidatedProgramPoints, fnFlags);
         code.add(cfn);
         return cfn;
     }
 
     private CompiledFunction addCode(final FunctionNode fn) {
-        return addCode(lookup(fn), fn.getFlags());
+        return addCode(lookup(fn), null, fn.getFlags());
     }
 
     /**
@@ -510,11 +548,12 @@
      * up getting one that takes a "double" etc. because of internal function logic causes widening (e.g. assignment of
      * a wider value to the parameter variable). However, we use the method handle type for matching subsequent lookups
      * for the same specialization, so we must adapt the handle to the expected type.
-     * @param fn the function
+     * @param tfn the function
      * @param callSiteType the call site type
      * @return the compiled function object, with its type matching that of the call site type.
      */
-    private CompiledFunction addCode(final FunctionNode fn, final MethodType callSiteType) {
+    private CompiledFunction addCode(final TypeSpecializedFunction tfn, final MethodType callSiteType) {
+        final FunctionNode fn = tfn.fn;
         if (fn.isVarArg()) {
             return addCode(fn);
         }
@@ -544,7 +583,7 @@
             toType = toType.dropParameterTypes(fromCount, toCount);
         }
 
-        return addCode(lookup(fn).asType(toType), fn.getFlags());
+        return addCode(lookup(fn).asType(toType), tfn.invalidatedProgramPoints, fn.getFlags());
     }
 
 
@@ -555,7 +594,7 @@
             if (existingBest == null) {
                 if(code.isEmpty() && methodLocator != null) {
                     // This is a deserialized object, reconnect from method handle
-                    existingBest = addCode(methodLocator.getMethodHandle(), methodLocator.getFunctionFlags());
+                    existingBest = addCode(methodLocator.getMethodHandle(), null, methodLocator.getFunctionFlags());
                 } else {
                     existingBest = addCode(compileTypeSpecialization(callSiteType, runtimeScope), callSiteType);
                 }
@@ -576,9 +615,9 @@
             }
 
             if (applyToCall) {
-                final FunctionNode fn = compileTypeSpecialization(callSiteType, runtimeScope);
-                if (fn.hasOptimisticApplyToCall()) { //did the specialization work
-                    existingBest = addCode(fn, callSiteType);
+                final TypeSpecializedFunction tfn = compileTypeSpecialization(callSiteType, runtimeScope);
+                if (tfn.fn.hasOptimisticApplyToCall()) { //did the specialization work
+                    existingBest = addCode(tfn, callSiteType);
                 }
             }
 
--- a/nashorn/test/script/basic/JDK-8030182_2.js.EXPECTED	Thu Jun 19 20:36:03 2014 +0530
+++ b/nashorn/test/script/basic/JDK-8030182_2.js.EXPECTED	Fri Jun 20 12:25:00 2014 +0200
@@ -1,3 +1,3 @@
 ReferenceError: "g" is not defined
-	at <program> (test/script/basic/JDK-8030182_2.js#42:4<eval>@0:-1)
+	at <program> (test/script/basic/JDK-8030182_2.js#42:4<eval>@1:-1)
 	at <program> (test/script/basic/JDK-8030182_2.js:42)