1 /*
   2  * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.nashorn.internal.codegen;
  27 
  28 import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCallNoLookup;
  29 import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_OPTIMISTIC;
  30 
  31 import java.util.Arrays;
  32 import java.util.EnumSet;
  33 import jdk.nashorn.internal.codegen.types.Type;
  34 import jdk.nashorn.internal.ir.Symbol;
  35 import jdk.nashorn.internal.runtime.ScriptObject;
  36 import jdk.nashorn.internal.runtime.UnwarrantedOptimismException;
  37 import jdk.nashorn.internal.runtime.options.Options;
  38 
  39 /**
  40  * A scope call or get operation that can be shared by several call sites. This generates a static
  41  * method that wraps the invokedynamic instructions to get or call scope variables.
  42  * The reason for this is to reduce memory footprint and initial linking overhead of huge scripts.
  43  *
  44  * <p>Static methods generated by this class expect three parameters in addition to the parameters of the
  45  * function call: The current scope object, the depth of the target scope relative to the scope argument,
  46  * and the program point in case the target operation is optimistic.</p>
  47  *
  48  * <p>Optimistic operations are called with program point <code>0</code>. If an <code>UnwarrentedOptimismException</code>
  49  * is thrown, it is caught by the shared call method and rethrown with the program point of the invoking call site.</p>
  50  *
  51  * <p>Shared scope calls are not used if the scope contains a <code>with</code> statement or a call to
  52  * <code>eval</code>.</p>
  53  */
  54 class SharedScopeCall {
  55 
  56     /**
  57      * Threshold for using shared scope function calls.
  58      */
  59     public static final int SHARED_CALL_THRESHOLD =
  60             Options.getIntProperty("nashorn.shared.scope.call.threshold", 5);
  61     /**
  62      * Threshold for using shared scope variable getter. This is higher than for calls as lower values
  63      * degrade performance on many scripts.
  64      */
  65     public static final int SHARED_GET_THRESHOLD  =
  66             Options.getIntProperty("nashorn.shared.scope.get.threshold", 100);
  67 
  68     private static final CompilerConstants.Call REPLACE_PROGRAM_POINT = virtualCallNoLookup(
  69                     UnwarrantedOptimismException.class, "replaceProgramPoint",
  70                     UnwarrantedOptimismException.class, int.class);
  71 
  72     /** Number of fixed parameters */
  73     private static final int FIXED_PARAM_COUNT = 3;
  74 
  75     private final Type valueType;
  76     private final Symbol symbol;
  77     private final Type returnType;
  78     private final Type[] paramTypes;
  79     private final int flags;
  80     private final boolean isCall;
  81     private final boolean isOptimistic;
  82     private CompileUnit compileUnit;
  83     private String methodName;
  84     private String staticSignature;
  85 
  86     /**
  87      * Constructor.
  88      *
  89      * @param symbol the symbol
  90      * @param valueType the type of the value
  91      * @param returnType the return type
  92      * @param paramTypes the function parameter types
  93      * @param flags the callsite flags
  94      * @param isOptimistic whether target call is optimistic and we need to handle UnwarrentedOptimismException
  95      */
  96     SharedScopeCall(final Symbol symbol, final Type valueType, final Type returnType, final Type[] paramTypes,
  97                     final int flags, final boolean isOptimistic) {
  98         this.symbol = symbol;
  99         this.valueType = valueType;
 100         this.returnType = returnType;
 101         this.paramTypes = paramTypes;
 102         this.flags = flags;
 103         this.isCall = paramTypes != null; // If paramTypes is not null this is a call, otherwise it's just a get.
 104         this.isOptimistic = isOptimistic;
 105     }
 106 
 107     @Override
 108     public int hashCode() {
 109         return symbol.hashCode() ^ returnType.hashCode() ^ Arrays.hashCode(paramTypes) ^ flags ^ Boolean.hashCode(isOptimistic);
 110     }
 111 
 112     @Override
 113     public boolean equals(final Object obj) {
 114         if (obj instanceof SharedScopeCall) {
 115             final SharedScopeCall c = (SharedScopeCall) obj;
 116             return symbol.equals(c.symbol)
 117                     && flags == c.flags
 118                     && returnType.equals(c.returnType)
 119                     && Arrays.equals(paramTypes, c.paramTypes)
 120                     && isOptimistic == c.isOptimistic;
 121         }
 122         return false;
 123     }
 124 
 125     /**
 126      * Set the compile unit and method name.
 127      * @param compileUnit the compile unit
 128      * @param methodName the method name
 129      */
 130     protected void setClassAndName(final CompileUnit compileUnit, final String methodName) {
 131         this.compileUnit = compileUnit;
 132         this.methodName  = methodName;
 133     }
 134 
 135     /**
 136      * Generate the invoke instruction for this shared scope call.
 137      * @param method the method emitter
 138      */
 139     public void generateInvoke(final MethodEmitter method) {
 140         method.invokestatic(compileUnit.getUnitClassName(), methodName, getStaticSignature());
 141     }
 142 
 143     /**
 144      * Generate the method that implements the scope get or call.
 145      */
 146     protected void generateScopeCall() {
 147         final ClassEmitter classEmitter = compileUnit.getClassEmitter();
 148         final EnumSet<ClassEmitter.Flag> methodFlags = EnumSet.of(ClassEmitter.Flag.STATIC);
 149 
 150         // This method expects two fixed parameters in addition to any parameters that may be
 151         // passed on to the function: A ScriptObject representing the caller's current scope object,
 152         // and an int specifying the distance to the target scope containing the symbol we want to
 153         // access, or -1 if this is not known at compile time (e.g. because of a "with" or "eval").
 154 
 155         final MethodEmitter method = classEmitter.method(methodFlags, methodName, getStaticSignature());
 156         method.begin();
 157 
 158         // Load correct scope by calling getProto(int) on the scope argument with the supplied depth argument
 159         method.load(Type.OBJECT, 0);
 160         method.load(Type.INT, 1);
 161         method.invoke(ScriptObject.GET_PROTO_DEPTH);
 162 
 163         assert !isCall || valueType.isObject(); // Callables are always loaded as object
 164 
 165         // Labels for catch of UnsupportedOptimismException
 166         final Label beginTry;
 167         final Label endTry;
 168         final Label catchLabel;
 169 
 170         if(isOptimistic) {
 171             beginTry = new Label("begin_try");
 172             endTry = new Label("end_try");
 173             catchLabel = new Label("catch_label");
 174             method.label(beginTry);
 175             method._try(beginTry, endTry, catchLabel, UnwarrantedOptimismException.class, false);
 176         } else {
 177             beginTry = endTry = catchLabel = null;
 178         }
 179 
 180         // If this is an optimistic get we set the optimistic flag but don't set the program point,
 181         // which implies a program point of 0. If optimism fails we'll replace it with the actual
 182         // program point which caller supplied as third argument.
 183         final int getFlags = isOptimistic && !isCall ? flags | CALLSITE_OPTIMISTIC : flags;
 184         method.dynamicGet(valueType, symbol.getName(), getFlags, isCall, false);
 185 
 186         // If this is a get we're done, otherwise call the value as function.
 187         if (isCall) {
 188             method.convert(Type.OBJECT);
 189             // ScriptFunction will see CALLSITE_SCOPE and will bind scope accordingly.
 190             method.loadUndefined(Type.OBJECT);
 191             int slot = FIXED_PARAM_COUNT;
 192             for (final Type type : paramTypes) {
 193                 method.load(type, slot);
 194                 slot += type.getSlots();
 195             }
 196 
 197             // Same as above, set optimistic flag but leave program point as 0.
 198             final int callFlags = isOptimistic ? flags | CALLSITE_OPTIMISTIC : flags;
 199 
 200             method.dynamicCall(returnType, 2 + paramTypes.length, callFlags, symbol.getName());
 201 
 202         }
 203 
 204         if (isOptimistic) {
 205             method.label(endTry);
 206         }
 207 
 208         method._return(returnType);
 209 
 210         if (isOptimistic) {
 211             // We caught a UnwarrantedOptimismException, replace 0 program point with actual program point
 212             method._catch(catchLabel);
 213             method.load(Type.INT, 2);
 214             method.invoke(REPLACE_PROGRAM_POINT);
 215             method.athrow();
 216         }
 217 
 218         method.end();
 219     }
 220 
 221     private String getStaticSignature() {
 222         if (staticSignature == null) {
 223             if (paramTypes == null) {
 224                 staticSignature = Type.getMethodDescriptor(returnType, Type.typeFor(ScriptObject.class), Type.INT, Type.INT);
 225             } else {
 226                 final Type[] params = new Type[paramTypes.length + FIXED_PARAM_COUNT];
 227                 params[0] = Type.typeFor(ScriptObject.class);
 228                 params[1] = Type.INT;
 229                 params[2] = Type.INT;
 230                 System.arraycopy(paramTypes, 0, params, FIXED_PARAM_COUNT, paramTypes.length);
 231                 staticSignature = Type.getMethodDescriptor(returnType, params);
 232             }
 233         }
 234         return staticSignature;
 235     }
 236 
 237     @Override
 238     public String toString() {
 239         return methodName + " " + staticSignature;
 240     }
 241 
 242 }