view src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java @ 636:aa452eb4a5d0

8026367: Add a sync keyword to mozilla_compat Reviewed-by: sundar, attila, lagergren
author hannesw
date Tue, 15 Oct 2013 17:37:47 +0200
parents a781ea074521
children
line wrap: on
line source

/*
 * 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.runtime;

import static jdk.nashorn.internal.lookup.Lookup.MH;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import jdk.internal.dynalink.support.NameCodec;

import jdk.nashorn.internal.codegen.Compiler;
import jdk.nashorn.internal.codegen.CompilerConstants;
import jdk.nashorn.internal.codegen.FunctionSignature;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.ir.FunctionNode;
import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
import jdk.nashorn.internal.parser.Token;
import jdk.nashorn.internal.parser.TokenType;

/**
 * This is a subclass that represents a script function that may be regenerated,
 * for example with specialization based on call site types, or lazily generated.
 * The common denominator is that it can get new invokers during its lifespan,
 * unlike {@code FinalScriptFunctionData}
 */
public final class RecompilableScriptFunctionData extends ScriptFunctionData {

    /** FunctionNode with the code for this ScriptFunction */
    private FunctionNode functionNode;

    /** Source from which FunctionNode was parsed. */
    private final Source source;

    /** Token of this function within the source. */
    private final long token;

    /** Allocator map from makeMap() */
    private final PropertyMap allocatorMap;

    /** Code installer used for all further recompilation/specialization of this ScriptFunction */
    private CodeInstaller<ScriptEnvironment> installer;

    /** Name of class where allocator function resides */
    private final String allocatorClassName;

    /** lazily generated allocator */
    private MethodHandle allocator;

    private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();

    /**
     * Used for specialization based on runtime arguments. Whenever we specialize on
     * callsite parameter types at runtime, we need to use a parameter type guard to
     * ensure that the specialized version of the script function continues to be
     * applicable for a particular callsite *
     */
    private static final MethodHandle PARAM_TYPE_GUARD = findOwnMH("paramTypeGuard", boolean.class, Type[].class,  Object[].class);

    /**
     * It is usually a good gamble whever we detect a runtime callsite with a double
     * (or java.lang.Number instance) to specialize the parameter to an integer, if the
     * parameter in question can be represented as one. The double typically only exists
     * because the compiler doesn't know any better than "a number type" and conservatively
     * picks doubles when it can't prove that an integer addition wouldn't overflow
     */
    private static final MethodHandle ENSURE_INT = findOwnMH("ensureInt", int.class, Object.class);

    /**
     * Constructor - public as scripts use it
     *
     * @param functionNode       functionNode that represents this function code
     * @param installer          installer for code regeneration versions of this function
     * @param allocatorClassName name of our allocator class, will be looked up dynamically if used as a constructor
     * @param allocatorMap       allocator map to seed instances with, when constructing
     */
    public RecompilableScriptFunctionData(final FunctionNode functionNode, final CodeInstaller<ScriptEnvironment> installer, final String allocatorClassName, final PropertyMap allocatorMap) {
        super(functionName(functionNode),
              functionNode.getParameters().size(),
              functionNode.isStrict(),
              false,
              true);

        this.functionNode       = functionNode;
        this.source             = functionNode.getSource();
        this.token              = tokenFor(functionNode);
        this.installer          = installer;
        this.allocatorClassName = allocatorClassName;
        this.allocatorMap       = allocatorMap;
    }

    @Override
    String toSource() {
        if (source != null && token != 0) {
            return source.getString(Token.descPosition(token), Token.descLength(token));
        }

        return "function " + (name == null ? "" : name) + "() { [native code] }";
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder();

        if (source != null) {
            sb.append(source.getName())
                .append(':')
                .append(functionNode.getLineNumber())
                .append(' ');
        }

        return sb.toString() + super.toString();
    }

    private static String functionName(final FunctionNode fn) {
        if (fn.isAnonymous()) {
            return "";
        } else {
            final FunctionNode.Kind kind = fn.getKind();
            if (kind == FunctionNode.Kind.GETTER || kind == FunctionNode.Kind.SETTER) {
                final String name = NameCodec.decode(fn.getIdent().getName());
                return name.substring(4); // 4 is "get " or "set "
            } else {
                return fn.getIdent().getName();
            }
        }
    }

    private static long tokenFor(final FunctionNode fn) {
        final int  position   = Token.descPosition(fn.getFirstToken());
        final int  length     = Token.descPosition(fn.getLastToken()) - position + Token.descLength(fn.getLastToken());

        return Token.toDesc(TokenType.FUNCTION, position, length);
    }

    @Override
    ScriptObject allocate() {
        try {
            ensureHasAllocator(); //if allocatorClass name is set to null (e.g. for bound functions) we don't even try
            return allocator == null ? null : (ScriptObject)allocator.invokeExact(allocatorMap);
        } catch (final RuntimeException | Error e) {
            throw e;
        } catch (final Throwable t) {
            throw new RuntimeException(t);
        }
    }

    private void ensureHasAllocator() throws ClassNotFoundException {
        if (allocator == null && allocatorClassName != null) {
            this.allocator = MH.findStatic(LOOKUP, Context.forStructureClass(allocatorClassName), CompilerConstants.ALLOCATE.symbolName(), MH.type(ScriptObject.class, PropertyMap.class));
        }
    }

    @Override
    protected synchronized void ensureCodeGenerated() {
         if (!code.isEmpty()) {
             return; // nothing to do, we have code, at least some.
         }

         if (functionNode.isLazy()) {
             Compiler.LOG.info("Trampoline hit: need to do lazy compilation of '", functionNode.getName(), "'");
             final Compiler compiler = new Compiler(installer);
             functionNode = compiler.compile(functionNode);
             assert !functionNode.isLazy();
             compiler.install(functionNode);

             /*
              * We don't need to update any flags - varArgs and needsCallee are instrincic
              * in the function world we need to get a destination node from the compile instead
              * and replace it with our function node. TODO
              */
         }

         /*
          * We can't get to this program point unless we have bytecode, either from
          * eager compilation or from running a lazy compile on the lines above
          */

         assert functionNode.hasState(CompilationState.EMITTED) : functionNode.getName() + " " + functionNode.getState() + " " + Debug.id(functionNode);

         // code exists - look it up and add it into the automatically sorted invoker list
         addCode(functionNode);

         if (! functionNode.canSpecialize()) {
             // allow GC to claim IR stuff that is not needed anymore
             functionNode = null;
             installer = null;
         }
    }

    private MethodHandle addCode(final FunctionNode fn) {
        return addCode(fn, null, null, null);
    }

    private MethodHandle addCode(final FunctionNode fn, final MethodType runtimeType, final MethodHandle guard, final MethodHandle fallback) {
        final MethodType targetType = new FunctionSignature(fn).getMethodType();
        MethodHandle target =
            MH.findStatic(
                    LOOKUP,
                    fn.getCompileUnit().getCode(),
                    fn.getName(),
                    targetType);

        /*
         * For any integer argument. a double that is representable as an integer is OK.
         * otherwise the guard would have failed. in that case introduce a filter that
         * casts the double to an integer, which we know will preserve all precision.
         */
        for (int i = 0; i < targetType.parameterCount(); i++) {
            if (targetType.parameterType(i) == int.class) {
                //representable as int
                target = MH.filterArguments(target, i, ENSURE_INT);
            }
        }

        MethodHandle mh = target;
        if (guard != null) {
            mh = MH.guardWithTest(MH.asCollector(guard, Object[].class, target.type().parameterCount()), MH.asType(target, fallback.type()), fallback);
        }

        final CompiledFunction cf = new CompiledFunction(runtimeType == null ? targetType : runtimeType, mh);
        code.add(cf);

        return cf.getInvoker();
    }

    private static Type runtimeType(final Object arg) {
        if (arg == null) {
            return Type.OBJECT;
        }

        final Class<?> clazz = arg.getClass();
        assert !clazz.isPrimitive() : "always boxed";
        if (clazz == Double.class) {
            return JSType.isRepresentableAsInt((double)arg) ? Type.INT : Type.NUMBER;
        } else if (clazz == Integer.class) {
            return Type.INT;
        } else if (clazz == Long.class) {
            return Type.LONG;
        } else if (clazz == String.class) {
            return Type.STRING;
        }
        return Type.OBJECT;
    }

    private static boolean canCoerce(final Object arg, final Type type) {
        Type argType = runtimeType(arg);
        if (Type.widest(argType, type) == type || arg == ScriptRuntime.UNDEFINED) {
            return true;
        }
        System.err.println(arg + " does not fit in "+ argType + " " + type + " " + arg.getClass());
        new Throwable().printStackTrace();
        return false;
    }

    @SuppressWarnings("unused")
    private static boolean paramTypeGuard(final Type[] paramTypes, final Object... args) {
        final int length = args.length;
        assert args.length >= paramTypes.length;

        //i==start, skip the this, callee params etc
        int start = args.length - paramTypes.length;
        for (int i = start; i < args.length; i++) {
            final Object arg = args[i];
            if (!canCoerce(arg, paramTypes[i - start])) {
                return false;
            }
        }
        return true;
    }

    @SuppressWarnings("unused")
    private static int ensureInt(final Object arg) {
        if (arg instanceof Number) {
            return ((Number)arg).intValue();
        } else if (arg instanceof Undefined) {
            return 0;
        }
        throw new AssertionError(arg);
    }

    /**
     * Given the runtime callsite args, compute a method type that is equivalent to what
     * was passed - this is typically a lot more specific that what the compiler has been
     * able to deduce
     * @param callSiteType callsite type for the compiled callsite target
     * @param args runtime arguments to the compiled callsite target
     * @return adjusted method type, narrowed as to conform to runtime callsite type instead
     */
    private static MethodType runtimeType(final MethodType callSiteType, final Object[] args) {
        if (args == null) {
            //for example bound, or otherwise runtime arguments to callsite unavailable, then
            //do not change the type
            return callSiteType;
        }
        final Class<?>[] paramTypes = new Class<?>[callSiteType.parameterCount()];
        final int        start      = args.length - callSiteType.parameterCount();
        for (int i = start; i < args.length; i++) {
            paramTypes[i - start] = runtimeType(args[i]).getTypeClass();
        }
        return MH.type(callSiteType.returnType(), paramTypes);
    }

    private static ArrayList<Type> runtimeType(final MethodType mt) {
        final ArrayList<Type> type = new ArrayList<>();
        for (int i = 0; i < mt.parameterCount(); i++) {
            type.add(Type.typeFor(mt.parameterType(i)));
        }
        return type;
    }

    @Override
    synchronized MethodHandle getBestInvoker(final MethodType callSiteType, final Object[] args) {
        final MethodType runtimeType = runtimeType(callSiteType, args);
        assert runtimeType.parameterCount() == callSiteType.parameterCount();

        final MethodHandle mh = super.getBestInvoker(runtimeType, args);

        /*
         * Not all functions can be specialized, for example, if we deemed memory
         * footprint too large to store a parse snapshot, or if it is meaningless
         * to do so, such as e.g. for runScript
         */
        if (functionNode == null || !functionNode.canSpecialize()) {
            return mh;
        }

        /*
         * Check if best invoker is equally specific or more specific than runtime
         * type. In that case, we don't need further specialization, but can use
         * whatever we have already. We know that it will match callSiteType, or it
         * would not have been returned from getBestInvoker
         */
        if (!code.isLessSpecificThan(runtimeType)) {
            return mh;
        }

        int i;
        final FunctionNode snapshot = functionNode.getSnapshot();
        assert snapshot != null;

        /*
         * Create a list of the arg types that the compiler knows about
         * typically, the runtime args are a lot more specific, and we should aggressively
         * try to use those whenever possible
         * We WILL try to make an aggressive guess as possible, and add guards if needed.
         * For example, if the compiler can deduce that we have a number type, but the runtime
         * passes and int, we might still want to keep it an int, and the gamble to
         * check that whatever is passed is int representable usually pays off
         * If the compiler only knows that a parameter is an "Object", it is still worth
         * it to try to specialize it by looking at the runtime arg.
         */
        final LinkedList<Type> compileTimeArgs = new LinkedList<>();
        for (i = callSiteType.parameterCount() - 1; i >= 0 && compileTimeArgs.size() < snapshot.getParameters().size(); i--) {
            compileTimeArgs.addFirst(Type.typeFor(callSiteType.parameterType(i)));
        }

        /*
         * The classes known at compile time are a safe to generate as primitives without parameter guards
         * But the classes known at runtime (if more specific than compile time types) are safe to generate as primitives
         * IFF there are parameter guards
         */
        MethodHandle guard = null;
        final ArrayList<Type> runtimeParamTypes = runtimeType(runtimeType);
        while (runtimeParamTypes.size() > functionNode.getParameters().size()) {
            runtimeParamTypes.remove(0);
        }
        for (i = 0; i < compileTimeArgs.size(); i++) {
            final Type rparam = Type.typeFor(runtimeType.parameterType(i));
            final Type cparam = compileTimeArgs.get(i);

            if (cparam.isObject() && !rparam.isObject()) {
                //check that the runtime object is still coercible to the runtime type, because compiler can't prove it's always primitive
                if (guard == null) {
                    guard = MH.insertArguments(PARAM_TYPE_GUARD, 0, (Object)runtimeParamTypes.toArray(new Type[runtimeParamTypes.size()]));
                }
            }
        }

        Compiler.LOG.info("Callsite specialized ", name, " runtimeType=", runtimeType, " parameters=", snapshot.getParameters(), " args=", Arrays.asList(args));

        assert snapshot != null;
        assert snapshot != functionNode;

        final Compiler compiler = new Compiler(installer);

        final FunctionNode compiledSnapshot = compiler.compile(
            snapshot.setHints(
                null,
                new Compiler.Hints(runtimeParamTypes.toArray(new Type[runtimeParamTypes.size()]))));

        /*
         * No matter how narrow your types were, they can never be narrower than Attr during recompile made them. I.e. you
         * can put an int into the function here, if you see it as a runtime type, but if the function uses a multiplication
         * on it, it will still need to be a double. At least until we have overflow checks. Similarly, if an int is
         * passed but it is used as a string, it makes no sense to make the parameter narrower than Object. At least until
         * the "different types for one symbol in difference places" work is done
         */
        compiler.install(compiledSnapshot);

        return addCode(compiledSnapshot, runtimeType, guard, mh);
    }

    private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
        return MH.findStatic(MethodHandles.lookup(), RecompilableScriptFunctionData.class, name, MH.type(rtype, types));
    }

}