Tue, 22 Jan 2013 14:14:37 +0100
8006570: This-value for non-strict functions should be converted to object
Reviewed-by: jlaskey, lagergren, attila
jlaskey@3 | 1 | /* |
jlaskey@7 | 2 | * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. |
jlaskey@3 | 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
jlaskey@3 | 4 | * |
jlaskey@3 | 5 | * This code is free software; you can redistribute it and/or modify it |
jlaskey@3 | 6 | * under the terms of the GNU General Public License version 2 only, as |
jlaskey@3 | 7 | * published by the Free Software Foundation. Oracle designates this |
jlaskey@3 | 8 | * particular file as subject to the "Classpath" exception as provided |
jlaskey@3 | 9 | * by Oracle in the LICENSE file that accompanied this code. |
jlaskey@3 | 10 | * |
jlaskey@3 | 11 | * This code is distributed in the hope that it will be useful, but WITHOUT |
jlaskey@3 | 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
jlaskey@3 | 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
jlaskey@3 | 14 | * version 2 for more details (a copy is included in the LICENSE file that |
jlaskey@3 | 15 | * accompanied this code). |
jlaskey@3 | 16 | * |
jlaskey@3 | 17 | * You should have received a copy of the GNU General Public License version |
jlaskey@3 | 18 | * 2 along with this work; if not, write to the Free Software Foundation, |
jlaskey@3 | 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
jlaskey@3 | 20 | * |
jlaskey@3 | 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
jlaskey@3 | 22 | * or visit www.oracle.com if you need additional information or have any |
jlaskey@3 | 23 | * questions. |
jlaskey@3 | 24 | */ |
jlaskey@3 | 25 | |
jlaskey@3 | 26 | package jdk.nashorn.internal.runtime; |
jlaskey@3 | 27 | |
jlaskey@3 | 28 | import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCallNoLookup; |
jlaskey@3 | 29 | import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; |
jlaskey@3 | 30 | import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; |
jlaskey@3 | 31 | import static jdk.nashorn.internal.runtime.linker.Lookup.MH; |
jlaskey@3 | 32 | |
jlaskey@3 | 33 | import java.lang.invoke.MethodHandle; |
jlaskey@3 | 34 | import java.lang.invoke.MethodHandles; |
jlaskey@3 | 35 | import java.lang.invoke.MethodType; |
jlaskey@3 | 36 | import jdk.nashorn.internal.codegen.CompilerConstants.Call; |
jlaskey@3 | 37 | import jdk.nashorn.internal.codegen.types.Type; |
jlaskey@3 | 38 | import jdk.nashorn.internal.objects.annotations.SpecializedConstructor; |
jlaskey@3 | 39 | import jdk.nashorn.internal.objects.annotations.SpecializedFunction; |
jlaskey@3 | 40 | import jdk.nashorn.internal.parser.Token; |
jlaskey@3 | 41 | import jdk.nashorn.internal.runtime.linker.MethodHandleFactory; |
jlaskey@3 | 42 | import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor; |
hannesw@42 | 43 | import jdk.nashorn.internal.runtime.linker.NashornGuardedInvocation; |
jlaskey@3 | 44 | import jdk.nashorn.internal.runtime.linker.NashornGuards; |
jlaskey@3 | 45 | import jdk.nashorn.internal.runtime.options.Options; |
jlaskey@3 | 46 | import org.dynalang.dynalink.CallSiteDescriptor; |
jlaskey@3 | 47 | import org.dynalang.dynalink.linker.GuardedInvocation; |
jlaskey@3 | 48 | |
jlaskey@3 | 49 | /** |
jlaskey@3 | 50 | * Runtime representation of a JavaScript function. |
jlaskey@3 | 51 | */ |
jlaskey@3 | 52 | public abstract class ScriptFunction extends ScriptObject { |
jlaskey@3 | 53 | |
jlaskey@3 | 54 | /** Method handle for prototype getter for this ScriptFunction */ |
jlaskey@3 | 55 | public static final MethodHandle G$PROTOTYPE = findOwnMH("G$prototype", Object.class, Object.class); |
jlaskey@3 | 56 | |
jlaskey@3 | 57 | /** Method handle for prototype setter for this ScriptFunction */ |
jlaskey@3 | 58 | public static final MethodHandle S$PROTOTYPE = findOwnMH("S$prototype", void.class, Object.class, Object.class); |
jlaskey@3 | 59 | |
jlaskey@3 | 60 | /** Method handle for length getter for this ScriptFunction */ |
jlaskey@3 | 61 | public static final MethodHandle G$LENGTH = findOwnMH("G$length", int.class, Object.class); |
jlaskey@3 | 62 | |
jlaskey@3 | 63 | /** Method handle for name getter for this ScriptFunction */ |
jlaskey@3 | 64 | public static final MethodHandle G$NAME = findOwnMH("G$name", Object.class, Object.class); |
jlaskey@3 | 65 | |
jlaskey@3 | 66 | /** Method handle for allocate function for this ScriptFunction */ |
jlaskey@3 | 67 | public static final MethodHandle ALLOCATE = findOwnMH("allocate", Object.class); |
jlaskey@3 | 68 | |
jlaskey@3 | 69 | private static final MethodHandle NEWFILTER = findOwnMH("newFilter", Object.class, Object.class, Object.class); |
jlaskey@3 | 70 | |
jlaskey@3 | 71 | /** method handle to arity setter for this ScriptFunction */ |
jlaskey@3 | 72 | public static final Call SET_ARITY = virtualCallNoLookup(ScriptFunction.class, "setArity", void.class, int.class); |
jlaskey@3 | 73 | /** method handle to scope getter for this ScriptFunction */ |
jlaskey@3 | 74 | public static final Call GET_SCOPE = virtualCallNoLookup(ScriptFunction.class, "getScope", ScriptObject.class); |
jlaskey@3 | 75 | |
jlaskey@3 | 76 | /** Should specialized function and specialized constructors for the builtin be used if available? */ |
jlaskey@3 | 77 | private static final boolean DISABLE_SPECIALIZATION = Options.getBooleanProperty("nashorn.scriptfunction.specialization.disable"); |
jlaskey@3 | 78 | |
jlaskey@3 | 79 | /** Name of function or null. */ |
jlaskey@3 | 80 | private final String name; |
jlaskey@3 | 81 | |
jlaskey@3 | 82 | /** Source of function. */ |
jlaskey@3 | 83 | private final Source source; |
jlaskey@3 | 84 | |
jlaskey@3 | 85 | /** Start position and length in source. */ |
jlaskey@3 | 86 | private final long token; |
jlaskey@3 | 87 | |
jlaskey@3 | 88 | /** Reference to code for this method. */ |
jlaskey@3 | 89 | private final MethodHandle invokeHandle; |
jlaskey@3 | 90 | |
jlaskey@3 | 91 | /** Reference to code for this method when called to create "new" object */ |
jlaskey@3 | 92 | protected MethodHandle constructHandle; |
jlaskey@3 | 93 | |
jlaskey@3 | 94 | /** Reference to constructor prototype. */ |
jlaskey@3 | 95 | protected Object prototype; |
jlaskey@3 | 96 | |
jlaskey@3 | 97 | /** Constructor to create a new instance. */ |
jlaskey@3 | 98 | private MethodHandle allocator; |
jlaskey@3 | 99 | |
jlaskey@3 | 100 | /** Map for new instance constructor. */ |
jlaskey@3 | 101 | private PropertyMap allocatorMap; |
jlaskey@3 | 102 | |
jlaskey@3 | 103 | /** The parent scope. */ |
jlaskey@3 | 104 | private final ScriptObject scope; |
jlaskey@3 | 105 | |
jlaskey@3 | 106 | /** Specializations - see @SpecializedFunction */ |
jlaskey@3 | 107 | private MethodHandle[] invokeSpecializations; |
jlaskey@3 | 108 | |
jlaskey@3 | 109 | /** Specializations - see @SpecializedFunction */ |
jlaskey@3 | 110 | private MethodHandle[] constructSpecializations; |
jlaskey@3 | 111 | |
jlaskey@3 | 112 | /** This field is either computed in constructor or set explicitly by calling setArity method. */ |
jlaskey@3 | 113 | private int arity; |
jlaskey@3 | 114 | |
jlaskey@3 | 115 | /** |
jlaskey@3 | 116 | * Constructor |
jlaskey@3 | 117 | * |
jlaskey@3 | 118 | * @param name function name |
jlaskey@3 | 119 | * @param methodHandle method handle to function (if specializations are present, assumed to be most generic) |
jlaskey@3 | 120 | * @param map property map |
jlaskey@3 | 121 | * @param scope scope |
jlaskey@3 | 122 | * @param specs specialized version of this function - other method handles |
jlaskey@3 | 123 | */ |
jlaskey@3 | 124 | protected ScriptFunction( |
jlaskey@3 | 125 | final String name, |
jlaskey@3 | 126 | final MethodHandle methodHandle, |
jlaskey@3 | 127 | final PropertyMap map, |
jlaskey@3 | 128 | final ScriptObject scope, |
jlaskey@3 | 129 | final MethodHandle[] specs) { |
jlaskey@3 | 130 | this(name, methodHandle, map, scope, null, 0, false, specs); |
jlaskey@3 | 131 | } |
jlaskey@3 | 132 | |
jlaskey@3 | 133 | /** |
jlaskey@3 | 134 | * Constructor |
jlaskey@3 | 135 | * |
jlaskey@3 | 136 | * @param name function name |
jlaskey@3 | 137 | * @param methodHandle method handle to function (if specializations are present, assumed to be most generic) |
jlaskey@3 | 138 | * @param map property map |
jlaskey@3 | 139 | * @param scope scope |
jlaskey@3 | 140 | * @param source the source |
jlaskey@3 | 141 | * @param token token |
jlaskey@3 | 142 | * @param allocator method handle to this function's allocator - see JO$ classes |
jlaskey@3 | 143 | * @param allocatorMap property map to be used for all constructors |
jlaskey@3 | 144 | * @param needsCallee does this method use the {@code callee} variable |
jlaskey@3 | 145 | * @param specs specialized version of this function - other method handles |
jlaskey@3 | 146 | */ |
jlaskey@3 | 147 | protected ScriptFunction( |
jlaskey@3 | 148 | final String name, |
jlaskey@3 | 149 | final MethodHandle methodHandle, |
jlaskey@3 | 150 | final PropertyMap map, |
jlaskey@3 | 151 | final ScriptObject scope, |
jlaskey@3 | 152 | final Source source, |
jlaskey@3 | 153 | final long token, |
jlaskey@3 | 154 | final MethodHandle allocator, |
jlaskey@3 | 155 | final PropertyMap allocatorMap, |
jlaskey@3 | 156 | final boolean needsCallee, |
jlaskey@3 | 157 | final MethodHandle[] specs) { |
jlaskey@3 | 158 | |
jlaskey@3 | 159 | this(name, methodHandle, map, scope, source, token, needsCallee, specs); |
jlaskey@3 | 160 | |
jlaskey@3 | 161 | //this is the internal constructor |
jlaskey@3 | 162 | |
jlaskey@3 | 163 | this.allocator = allocator; |
jlaskey@3 | 164 | this.allocatorMap = allocatorMap; |
jlaskey@3 | 165 | } |
jlaskey@3 | 166 | |
jlaskey@3 | 167 | /** |
jlaskey@3 | 168 | * Constructor |
jlaskey@3 | 169 | * |
jlaskey@3 | 170 | * @param name function name |
jlaskey@3 | 171 | * @param methodHandle method handle to function (if specializations are present, assumed to be most generic) |
jlaskey@3 | 172 | * @param map property map |
jlaskey@3 | 173 | * @param scope scope |
jlaskey@3 | 174 | * @param source the source |
jlaskey@3 | 175 | * @param token token |
jlaskey@3 | 176 | * @param needsCallee does this method use the {@code callee} variable |
jlaskey@3 | 177 | * @param specs specialized version of this function - other method handles |
jlaskey@3 | 178 | */ |
jlaskey@3 | 179 | protected ScriptFunction( |
jlaskey@3 | 180 | final String name, |
jlaskey@3 | 181 | final MethodHandle methodHandle, |
jlaskey@3 | 182 | final PropertyMap map, |
jlaskey@3 | 183 | final ScriptObject scope, |
jlaskey@3 | 184 | final Source source, |
jlaskey@3 | 185 | final long token, |
jlaskey@3 | 186 | final boolean needsCallee, |
jlaskey@3 | 187 | final MethodHandle[] specs) { |
jlaskey@3 | 188 | |
jlaskey@3 | 189 | super(map); |
jlaskey@3 | 190 | |
jlaskey@3 | 191 | if (Context.DEBUG) { |
jlaskey@3 | 192 | constructorCount++; |
jlaskey@3 | 193 | } |
jlaskey@3 | 194 | |
jlaskey@3 | 195 | // needCallee => scope != null |
jlaskey@3 | 196 | assert !needsCallee || scope != null; |
jlaskey@3 | 197 | this.name = name; |
jlaskey@3 | 198 | this.source = source; |
jlaskey@3 | 199 | this.token = token; |
jlaskey@3 | 200 | this.scope = scope; |
jlaskey@3 | 201 | |
jlaskey@3 | 202 | final MethodType type = methodHandle.type(); |
jlaskey@3 | 203 | final int paramCount = type.parameterCount(); |
jlaskey@3 | 204 | final boolean isVarArg = type.parameterType(paramCount - 1).isArray(); |
jlaskey@3 | 205 | |
jlaskey@3 | 206 | final MethodHandle mh = MH.asType(methodHandle, adaptType(type, scope != null, isVarArg)); |
jlaskey@3 | 207 | |
jlaskey@3 | 208 | this.arity = isVarArg ? -1 : paramCount - 1; //drop the self param for arity |
jlaskey@3 | 209 | |
jlaskey@3 | 210 | if (scope != null) { |
jlaskey@3 | 211 | if (needsCallee) { |
jlaskey@3 | 212 | if (!isVarArg) { |
jlaskey@3 | 213 | this.arity--; |
jlaskey@3 | 214 | } |
jlaskey@3 | 215 | } |
jlaskey@3 | 216 | this.invokeHandle = mh; |
jlaskey@3 | 217 | this.constructHandle = mh; |
jlaskey@3 | 218 | } else if (isConstructor(mh)) { |
jlaskey@3 | 219 | if (!isVarArg) { |
jlaskey@3 | 220 | this.arity--; // drop the boolean flag for arity |
jlaskey@3 | 221 | } |
jlaskey@3 | 222 | /* |
jlaskey@3 | 223 | * We insert a boolean argument to tell if the method was invoked as |
jlaskey@3 | 224 | * constructor or not if the method handle's first argument is boolean. |
jlaskey@3 | 225 | */ |
jlaskey@3 | 226 | this.invokeHandle = MH.insertArguments(mh, 0, false); |
jlaskey@3 | 227 | this.constructHandle = MH.insertArguments(mh, 0, true); |
jlaskey@3 | 228 | |
jlaskey@3 | 229 | if (specs != null) { |
jlaskey@3 | 230 | this.invokeSpecializations = new MethodHandle[specs.length]; |
jlaskey@3 | 231 | this.constructSpecializations = new MethodHandle[specs.length]; |
jlaskey@3 | 232 | for (int i = 0; i < specs.length; i++) { |
jlaskey@3 | 233 | this.invokeSpecializations[i] = MH.insertArguments(specs[i], 0, false); |
jlaskey@3 | 234 | this.constructSpecializations[i] = MH.insertArguments(specs[i], 0, true); |
jlaskey@3 | 235 | } |
jlaskey@3 | 236 | } |
jlaskey@3 | 237 | } else { |
jlaskey@3 | 238 | this.invokeHandle = mh; |
jlaskey@3 | 239 | this.constructHandle = mh; |
jlaskey@3 | 240 | this.invokeSpecializations = specs; |
jlaskey@3 | 241 | this.constructSpecializations = specs; |
jlaskey@3 | 242 | } |
jlaskey@3 | 243 | } |
jlaskey@3 | 244 | |
jlaskey@3 | 245 | /** |
jlaskey@3 | 246 | * Takes a method type, and returns a (potentially different method type) that the method handles used by |
jlaskey@3 | 247 | * ScriptFunction must conform to in order to be usable in {@link #invoke(Object, Object...)} and |
jlaskey@3 | 248 | * {@link #construct(Object, Object...)}. The returned method type will be sure to return {@code Object}, and will |
jlaskey@3 | 249 | * have all its parameters turned into {@code Object} as well, except for the following ones: |
jlaskey@3 | 250 | * <ul> |
jlaskey@3 | 251 | * <li>an optional first {@code boolean} parameter, used for some functions to distinguish method and constructor |
jlaskey@3 | 252 | * invocation,</li> |
jlaskey@3 | 253 | * <li>a last parameter of type {@code Object[]} which is used for vararg functions,</li> |
jlaskey@3 | 254 | * <li>the second (or, in presence of boolean parameter, third) argument, which is forced to be |
jlaskey@3 | 255 | * {@link ScriptFunction}, in case the function receives itself (callee) as an argument</li> |
jlaskey@3 | 256 | * @param type the original type |
jlaskey@3 | 257 | * @param hasCallee true if the function uses the callee argument |
jlaskey@3 | 258 | * @param isVarArg if the function is a vararg |
jlaskey@3 | 259 | * @return the new type, conforming to the rules above. |
jlaskey@3 | 260 | */ |
jlaskey@3 | 261 | private static MethodType adaptType(final MethodType type, final boolean hasCallee, final boolean isVarArg) { |
jlaskey@3 | 262 | // Generify broadly |
jlaskey@3 | 263 | MethodType newType = type.generic().changeReturnType(Object.class); |
jlaskey@3 | 264 | if(isVarArg) { |
jlaskey@3 | 265 | // Change back to vararg if we over-generified it |
jlaskey@3 | 266 | newType = newType.changeParameterType(type.parameterCount() - 1, Object[].class); |
jlaskey@3 | 267 | } |
jlaskey@3 | 268 | final boolean hasBoolean = type.parameterType(0) == boolean.class; |
jlaskey@3 | 269 | if(hasBoolean) { |
jlaskey@3 | 270 | // Restore the initial boolean argument |
jlaskey@3 | 271 | newType = newType.changeParameterType(0, boolean.class); |
jlaskey@3 | 272 | } |
jlaskey@3 | 273 | if(hasCallee) { |
jlaskey@3 | 274 | // Restore the ScriptFunction argument |
jlaskey@3 | 275 | newType = newType.changeParameterType(hasBoolean ? 2 : 1, ScriptFunction.class); |
jlaskey@3 | 276 | } |
jlaskey@3 | 277 | return newType; |
jlaskey@3 | 278 | } |
jlaskey@3 | 279 | |
jlaskey@3 | 280 | @Override |
jlaskey@3 | 281 | public String getClassName() { |
jlaskey@3 | 282 | return "Function"; |
jlaskey@3 | 283 | } |
jlaskey@3 | 284 | |
jlaskey@3 | 285 | /** |
jlaskey@3 | 286 | * ECMA 15.3.5.3 [[HasInstance]] (V) |
jlaskey@3 | 287 | * Step 3 if "prototype" value is not an Object, throw TypeError |
jlaskey@3 | 288 | */ |
jlaskey@3 | 289 | @Override |
jlaskey@3 | 290 | public boolean isInstance(final ScriptObject instance) { |
jlaskey@3 | 291 | if (!(prototype instanceof ScriptObject)) { |
jlaskey@3 | 292 | typeError(Context.getGlobal(), "prototype.not.an.object", ScriptRuntime.safeToString(this), ScriptRuntime.safeToString(prototype)); |
jlaskey@3 | 293 | } |
jlaskey@3 | 294 | |
jlaskey@3 | 295 | for (ScriptObject proto = instance.getProto(); proto != null; proto = proto.getProto()) { |
jlaskey@3 | 296 | if (proto == prototype) { |
jlaskey@3 | 297 | return true; |
jlaskey@3 | 298 | } |
jlaskey@3 | 299 | } |
jlaskey@3 | 300 | |
jlaskey@3 | 301 | return false; |
jlaskey@3 | 302 | } |
jlaskey@3 | 303 | |
jlaskey@3 | 304 | /** |
jlaskey@3 | 305 | * Get the arity of this ScriptFunction |
jlaskey@3 | 306 | * @return arity |
jlaskey@3 | 307 | */ |
jlaskey@3 | 308 | public final int getArity() { |
jlaskey@3 | 309 | return arity; |
jlaskey@3 | 310 | } |
jlaskey@3 | 311 | |
jlaskey@3 | 312 | /** |
jlaskey@3 | 313 | * Set the arity of this ScriptFunction |
jlaskey@3 | 314 | * @param arity arity |
jlaskey@3 | 315 | */ |
jlaskey@3 | 316 | public final void setArity(final int arity) { |
jlaskey@3 | 317 | this.arity = arity; |
jlaskey@3 | 318 | } |
jlaskey@3 | 319 | |
jlaskey@3 | 320 | /** |
jlaskey@3 | 321 | * Is this a ECMAScript 'use strict' function? |
jlaskey@3 | 322 | * @return true if function is in strict mode |
jlaskey@3 | 323 | */ |
jlaskey@3 | 324 | public abstract boolean isStrict(); |
jlaskey@3 | 325 | |
jlaskey@3 | 326 | /** |
jlaskey@3 | 327 | * Is this a ECMAScript built-in function (like parseInt, Array.isArray) ? |
jlaskey@3 | 328 | * @return true if built-in |
jlaskey@3 | 329 | */ |
jlaskey@3 | 330 | public abstract boolean isBuiltin(); |
jlaskey@3 | 331 | |
jlaskey@3 | 332 | /** |
hannesw@42 | 333 | * Is this a non-strict (not built-in) script function? |
hannesw@42 | 334 | * @return true if neither strict nor built-in |
hannesw@42 | 335 | */ |
hannesw@42 | 336 | public boolean isNonStrictFunction() { |
hannesw@42 | 337 | return !isStrict() && !isBuiltin(); |
hannesw@42 | 338 | } |
hannesw@42 | 339 | |
hannesw@42 | 340 | /** |
jlaskey@3 | 341 | * Execute this script function. |
jlaskey@3 | 342 | * @param self Target object. |
jlaskey@3 | 343 | * @param arguments Call arguments. |
jlaskey@3 | 344 | * @return ScriptFunction result. |
jlaskey@3 | 345 | * @throws Throwable if there is an exception/error with the invocation or thrown from it |
jlaskey@3 | 346 | */ |
jlaskey@3 | 347 | public Object invoke(final Object self, final Object... arguments) throws Throwable { |
jlaskey@3 | 348 | if (Context.DEBUG) { |
jlaskey@3 | 349 | invokes++; |
jlaskey@3 | 350 | } |
jlaskey@3 | 351 | |
jlaskey@3 | 352 | final Object[] args = arguments == null ? ScriptRuntime.EMPTY_ARRAY : arguments; |
jlaskey@3 | 353 | |
jlaskey@3 | 354 | if (isVarArg(invokeHandle)) { |
jlaskey@3 | 355 | if (hasCalleeParameter()) { |
jlaskey@3 | 356 | return invokeHandle.invokeExact(self, this, args); |
jlaskey@3 | 357 | } |
jlaskey@3 | 358 | return invokeHandle.invokeExact(self, args); |
jlaskey@3 | 359 | } |
jlaskey@3 | 360 | |
jlaskey@3 | 361 | final int paramCount = invokeHandle.type().parameterCount(); |
jlaskey@3 | 362 | if (hasCalleeParameter()) { |
jlaskey@3 | 363 | switch (paramCount) { |
jlaskey@3 | 364 | case 2: |
jlaskey@3 | 365 | return invokeHandle.invokeExact(self, this); |
jlaskey@3 | 366 | case 3: |
jlaskey@3 | 367 | return invokeHandle.invokeExact(self, this, getArg(args, 0)); |
jlaskey@3 | 368 | case 4: |
jlaskey@3 | 369 | return invokeHandle.invokeExact(self, this, getArg(args, 0), getArg(args, 1)); |
jlaskey@3 | 370 | case 5: |
jlaskey@3 | 371 | return invokeHandle.invokeExact(self, this, getArg(args, 0), getArg(args, 1), getArg(args, 2)); |
jlaskey@3 | 372 | default: |
jlaskey@3 | 373 | return invokeHandle.invokeWithArguments(withArguments(self, this, paramCount, args)); |
jlaskey@3 | 374 | } |
jlaskey@3 | 375 | } |
jlaskey@3 | 376 | |
jlaskey@3 | 377 | switch (paramCount) { |
jlaskey@3 | 378 | case 1: |
jlaskey@3 | 379 | return invokeHandle.invokeExact(self); |
jlaskey@3 | 380 | case 2: |
jlaskey@3 | 381 | return invokeHandle.invokeExact(self, getArg(args, 0)); |
jlaskey@3 | 382 | case 3: |
jlaskey@3 | 383 | return invokeHandle.invokeExact(self, getArg(args, 0), getArg(args, 1)); |
jlaskey@3 | 384 | case 4: |
jlaskey@3 | 385 | return invokeHandle.invokeExact(self, getArg(args, 0), getArg(args, 1), getArg(args, 2)); |
jlaskey@3 | 386 | default: |
jlaskey@3 | 387 | return invokeHandle.invokeWithArguments(withArguments(self, null, paramCount, args)); |
jlaskey@3 | 388 | } |
jlaskey@3 | 389 | } |
jlaskey@3 | 390 | |
jlaskey@3 | 391 | private static Object getArg(final Object[] args, final int i) { |
jlaskey@3 | 392 | return i < args.length ? args[i] : UNDEFINED; |
jlaskey@3 | 393 | } |
jlaskey@3 | 394 | |
jlaskey@3 | 395 | /** |
jlaskey@3 | 396 | * Construct new object using this constructor. |
jlaskey@3 | 397 | * @param self Target object. |
jlaskey@3 | 398 | * @param args Call arguments. |
jlaskey@3 | 399 | * @return ScriptFunction result. |
jlaskey@3 | 400 | * @throws Throwable if there is an exception/error with the constructor invocation or thrown from it |
jlaskey@3 | 401 | */ |
jlaskey@3 | 402 | public Object construct(final Object self, final Object... args) throws Throwable { |
jlaskey@3 | 403 | if (constructHandle == null) { |
jlaskey@3 | 404 | typeError(Context.getGlobal(), "not.a.constructor", ScriptRuntime.safeToString(this)); |
jlaskey@3 | 405 | } |
jlaskey@3 | 406 | |
jlaskey@3 | 407 | if (isVarArg(constructHandle)) { |
jlaskey@3 | 408 | if (hasCalleeParameter()) { |
jlaskey@3 | 409 | return constructHandle.invokeExact(self, this, args); |
jlaskey@3 | 410 | } |
jlaskey@3 | 411 | return constructHandle.invokeExact(self, args); |
jlaskey@3 | 412 | } |
jlaskey@3 | 413 | |
jlaskey@3 | 414 | final int paramCount = constructHandle.type().parameterCount(); |
jlaskey@3 | 415 | if (hasCalleeParameter()) { |
jlaskey@3 | 416 | switch (paramCount) { |
jlaskey@3 | 417 | case 2: |
jlaskey@3 | 418 | return constructHandle.invokeExact(self, this); |
jlaskey@3 | 419 | case 3: |
jlaskey@3 | 420 | return constructHandle.invokeExact(self, this, getArg(args, 0)); |
jlaskey@3 | 421 | case 4: |
jlaskey@3 | 422 | return constructHandle.invokeExact(self, this, getArg(args, 0), getArg(args, 1)); |
jlaskey@3 | 423 | case 5: |
jlaskey@3 | 424 | return constructHandle.invokeExact(self, this, getArg(args, 0), getArg(args, 1), getArg(args, 2)); |
jlaskey@3 | 425 | default: |
jlaskey@3 | 426 | return constructHandle.invokeWithArguments(withArguments(self, this, args)); |
jlaskey@3 | 427 | } |
jlaskey@3 | 428 | } |
jlaskey@3 | 429 | |
jlaskey@3 | 430 | switch(paramCount) { |
jlaskey@3 | 431 | case 1: |
jlaskey@3 | 432 | return constructHandle.invokeExact(self); |
jlaskey@3 | 433 | case 2: |
jlaskey@3 | 434 | return constructHandle.invokeExact(self, getArg(args, 0)); |
jlaskey@3 | 435 | case 3: |
jlaskey@3 | 436 | return constructHandle.invokeExact(self, getArg(args, 0), getArg(args, 1)); |
jlaskey@3 | 437 | case 4: |
jlaskey@3 | 438 | return constructHandle.invokeExact(self, getArg(args, 0), getArg(args, 1), getArg(args, 2)); |
jlaskey@3 | 439 | default: |
jlaskey@3 | 440 | return constructHandle.invokeWithArguments(withArguments(self, null, args)); |
jlaskey@3 | 441 | } |
jlaskey@3 | 442 | } |
jlaskey@3 | 443 | |
jlaskey@3 | 444 | private static Object[] withArguments(final Object self, final ScriptFunction function, final Object... args) { |
jlaskey@3 | 445 | return withArguments(self, function, args.length + (function == null ? 1 : 2), args); // + 2 to include self and function |
jlaskey@3 | 446 | } |
jlaskey@3 | 447 | |
jlaskey@3 | 448 | private static Object[] withArguments(final Object self, final ScriptFunction function, final int paramCount, final Object... args) { |
jlaskey@3 | 449 | final Object[] finalArgs = new Object[paramCount]; |
jlaskey@3 | 450 | |
jlaskey@3 | 451 | finalArgs[0] = self; |
jlaskey@3 | 452 | int nextArg = 1; |
jlaskey@3 | 453 | if (function != null) { |
jlaskey@3 | 454 | finalArgs[nextArg++] = function; |
jlaskey@3 | 455 | } |
jlaskey@3 | 456 | |
jlaskey@3 | 457 | //don't add more args that there is paramcount in the handle (including self) |
jlaskey@3 | 458 | final int maxArgs = Math.min(args.length, paramCount - (function == null ? 1 : 2)); |
jlaskey@3 | 459 | for (int i = 0; i < maxArgs;) { |
jlaskey@3 | 460 | finalArgs[nextArg++] = args[i++]; |
jlaskey@3 | 461 | } |
jlaskey@3 | 462 | |
jlaskey@3 | 463 | //if we have fewer params than paramcount, pad undefined |
jlaskey@3 | 464 | while (nextArg < paramCount) { |
jlaskey@3 | 465 | finalArgs[nextArg++] = UNDEFINED; |
jlaskey@3 | 466 | } |
jlaskey@3 | 467 | |
jlaskey@3 | 468 | return finalArgs; |
jlaskey@3 | 469 | } |
jlaskey@3 | 470 | |
jlaskey@3 | 471 | /** |
jlaskey@3 | 472 | * Allocate function. Called from generated {@link ScriptObject} code |
jlaskey@3 | 473 | * for allocation as a factory method |
jlaskey@3 | 474 | * |
jlaskey@3 | 475 | * @return a new instance of the {@link ScriptObject} whose allocator this is |
jlaskey@3 | 476 | */ |
jlaskey@3 | 477 | public Object allocate() { |
jlaskey@3 | 478 | if (Context.DEBUG) { |
jlaskey@3 | 479 | allocations++; |
jlaskey@3 | 480 | } |
jlaskey@3 | 481 | |
jlaskey@3 | 482 | if (getConstructHandle() == null) { |
jlaskey@3 | 483 | typeError(Context.getGlobal(), "not.a.constructor", ScriptRuntime.safeToString(this)); |
jlaskey@3 | 484 | } |
jlaskey@3 | 485 | |
jlaskey@3 | 486 | ScriptObject object = null; |
jlaskey@3 | 487 | |
jlaskey@3 | 488 | if (allocator != null) { |
jlaskey@3 | 489 | try { |
jlaskey@3 | 490 | object = (ScriptObject)allocator.invokeExact(allocatorMap); |
jlaskey@3 | 491 | } catch (final RuntimeException | Error e) { |
jlaskey@3 | 492 | throw e; |
jlaskey@3 | 493 | } catch (final Throwable t) { |
jlaskey@3 | 494 | throw new RuntimeException(t); |
jlaskey@3 | 495 | } |
jlaskey@3 | 496 | } |
jlaskey@3 | 497 | |
jlaskey@3 | 498 | if (object != null) { |
jlaskey@3 | 499 | if (prototype instanceof ScriptObject) { |
jlaskey@3 | 500 | object.setProto((ScriptObject)prototype); |
jlaskey@3 | 501 | } |
jlaskey@3 | 502 | |
jlaskey@3 | 503 | if (object.getProto() == null) { |
jlaskey@3 | 504 | object.setProto(getObjectPrototype()); |
jlaskey@3 | 505 | } |
jlaskey@3 | 506 | } |
jlaskey@3 | 507 | |
jlaskey@3 | 508 | return object; |
jlaskey@3 | 509 | } |
jlaskey@3 | 510 | |
jlaskey@3 | 511 | /** |
jlaskey@3 | 512 | * Return Object.prototype - used by "allocate" |
jlaskey@3 | 513 | * @return Object.prototype |
jlaskey@3 | 514 | */ |
jlaskey@3 | 515 | protected abstract ScriptObject getObjectPrototype(); |
jlaskey@3 | 516 | |
jlaskey@3 | 517 | /** |
jlaskey@3 | 518 | * Creates a version of this function bound to a specific "self" and other argumentss |
jlaskey@3 | 519 | * @param self the self to bind the function to |
jlaskey@3 | 520 | * @param args other arguments (beside self) to bind the function to |
jlaskey@3 | 521 | * @return the bound function |
jlaskey@3 | 522 | */ |
jlaskey@3 | 523 | public abstract ScriptFunction makeBoundFunction(Object self, Object[] args); |
jlaskey@3 | 524 | |
jlaskey@3 | 525 | /** |
jlaskey@3 | 526 | * Test if a methodHandle refers to a constructor. |
jlaskey@3 | 527 | * @param methodHandle MethodHandle to test. |
jlaskey@3 | 528 | * @return True if method is a constructor. |
jlaskey@3 | 529 | */ |
jlaskey@3 | 530 | private static boolean isConstructor(final MethodHandle methodHandle) { |
jlaskey@3 | 531 | return methodHandle.type().parameterCount() >= 1 && methodHandle.type().parameterType(0) == boolean.class; |
jlaskey@3 | 532 | } |
jlaskey@3 | 533 | |
jlaskey@3 | 534 | /** |
jlaskey@3 | 535 | * Test if a methodHandle refers to a variable argument method. |
jlaskey@3 | 536 | * @param methodHandle MethodHandle to test. |
jlaskey@3 | 537 | * @return True if variable arguments. |
jlaskey@3 | 538 | */ |
jlaskey@3 | 539 | public boolean isVarArg(final MethodHandle methodHandle) { |
jlaskey@3 | 540 | return hasCalleeParameter() |
jlaskey@3 | 541 | ? methodHandle.type().parameterCount() == 3 && methodHandle.type().parameterType(2).isArray() |
jlaskey@3 | 542 | : methodHandle.type().parameterCount() == 2 && methodHandle.type().parameterType(1).isArray(); |
jlaskey@3 | 543 | } |
jlaskey@3 | 544 | |
jlaskey@3 | 545 | @Override |
jlaskey@3 | 546 | public final String safeToString() { |
jlaskey@3 | 547 | return toSource(); |
jlaskey@3 | 548 | } |
jlaskey@3 | 549 | |
jlaskey@3 | 550 | @Override |
jlaskey@3 | 551 | public String toString() { |
jlaskey@3 | 552 | final StringBuilder sb = new StringBuilder(); |
jlaskey@3 | 553 | |
jlaskey@3 | 554 | sb.append(super.toString()) |
jlaskey@3 | 555 | .append(" [ ") |
jlaskey@3 | 556 | .append(invokeHandle) |
jlaskey@3 | 557 | .append(", ") |
jlaskey@3 | 558 | .append((name == null || name.isEmpty()) ? "<anonymous>" : name); |
jlaskey@3 | 559 | |
jlaskey@3 | 560 | if (source != null) { |
jlaskey@3 | 561 | sb.append(" @ ") |
jlaskey@3 | 562 | .append(source.getName()) |
jlaskey@3 | 563 | .append(':') |
jlaskey@3 | 564 | .append(source.getLine(Token.descPosition(token))); |
jlaskey@3 | 565 | } |
jlaskey@3 | 566 | sb.append(" ]"); |
jlaskey@3 | 567 | |
jlaskey@3 | 568 | return sb.toString(); |
jlaskey@3 | 569 | } |
jlaskey@3 | 570 | |
jlaskey@3 | 571 | /** |
jlaskey@3 | 572 | * Get this function as a String containing its source code. If no source code |
jlaskey@3 | 573 | * exists in this ScriptFunction, its contents will be displayed as {@code [native code]} |
jlaskey@3 | 574 | * @return string representation of this function's source |
jlaskey@3 | 575 | */ |
jlaskey@3 | 576 | public final String toSource() { |
jlaskey@3 | 577 | if (source != null && token != 0) { |
jlaskey@3 | 578 | return source.getString(Token.descPosition(token), Token.descLength(token)); |
jlaskey@3 | 579 | } |
jlaskey@3 | 580 | |
jlaskey@3 | 581 | return "function " + (name == null ? "" : name) + "() { [native code] }"; |
jlaskey@3 | 582 | } |
jlaskey@3 | 583 | |
jlaskey@3 | 584 | /** |
jlaskey@3 | 585 | * Get the prototype object for this function |
jlaskey@3 | 586 | * @return prototype |
jlaskey@3 | 587 | */ |
jlaskey@3 | 588 | public final Object getPrototype() { |
jlaskey@3 | 589 | return prototype; |
jlaskey@3 | 590 | } |
jlaskey@3 | 591 | |
jlaskey@3 | 592 | /** |
jlaskey@3 | 593 | * Set the prototype object for this function |
jlaskey@3 | 594 | * @param prototype new prototype object |
jlaskey@3 | 595 | * @return the prototype parameter |
jlaskey@3 | 596 | */ |
jlaskey@3 | 597 | public final Object setPrototype(final Object prototype) { |
jlaskey@3 | 598 | this.prototype = prototype; |
jlaskey@3 | 599 | return prototype; |
jlaskey@3 | 600 | } |
jlaskey@3 | 601 | |
jlaskey@3 | 602 | private static int weigh(final MethodType t) { |
jlaskey@3 | 603 | int weight = Type.typeFor(t.returnType()).getWeight(); |
jlaskey@3 | 604 | for (final Class<?> paramType : t.parameterArray()) { |
jlaskey@3 | 605 | final int pweight = Type.typeFor(paramType).getWeight(); |
jlaskey@3 | 606 | weight += pweight; |
jlaskey@3 | 607 | } |
jlaskey@3 | 608 | return weight; |
jlaskey@3 | 609 | } |
jlaskey@3 | 610 | |
jlaskey@3 | 611 | private static boolean typeCompatible(final MethodType desc, final MethodType spec) { |
jlaskey@3 | 612 | //spec must fit in desc |
jlaskey@3 | 613 | final Class<?>[] dparray = desc.parameterArray(); |
jlaskey@3 | 614 | final Class<?>[] sparray = spec.parameterArray(); |
jlaskey@3 | 615 | |
jlaskey@3 | 616 | if (dparray.length != sparray.length) { |
jlaskey@3 | 617 | return false; |
jlaskey@3 | 618 | } |
jlaskey@3 | 619 | |
jlaskey@3 | 620 | for (int i = 0; i < dparray.length; i++) { |
jlaskey@3 | 621 | final Type dp = Type.typeFor(dparray[i]); |
jlaskey@3 | 622 | final Type sp = Type.typeFor(sparray[i]); |
jlaskey@3 | 623 | |
jlaskey@3 | 624 | if (dp.isBoolean()) { |
jlaskey@3 | 625 | return false; //don't specialize on booleans, we have the "true" vs int 1 ambiguity in resolution |
jlaskey@3 | 626 | } |
jlaskey@3 | 627 | |
jlaskey@3 | 628 | //specialization arguments must be at least as wide as dp, if not wider |
jlaskey@3 | 629 | if (Type.widest(dp, sp) != sp) { |
jlaskey@3 | 630 | //e.g. specialization takes double and callsite says "object". reject. |
jlaskey@3 | 631 | //but if specialization says double and callsite says "int" or "long" or "double", that's fine |
jlaskey@3 | 632 | return false; |
jlaskey@3 | 633 | } |
jlaskey@3 | 634 | } |
jlaskey@3 | 635 | |
jlaskey@3 | 636 | return true; // anything goes for return type, take the convenient one and it will be upcasted thru dynalink magic. |
jlaskey@3 | 637 | } |
jlaskey@3 | 638 | |
jlaskey@3 | 639 | private MethodHandle candidateWithLowestWeight(final MethodType descType, final MethodHandle initialCandidate, final MethodHandle[] specs) { |
jlaskey@3 | 640 | if (DISABLE_SPECIALIZATION || specs == null) { |
jlaskey@3 | 641 | return initialCandidate; |
jlaskey@3 | 642 | } |
jlaskey@3 | 643 | |
jlaskey@3 | 644 | int minimumWeight = Integer.MAX_VALUE; |
jlaskey@3 | 645 | MethodHandle candidate = initialCandidate; |
jlaskey@3 | 646 | |
jlaskey@3 | 647 | for (final MethodHandle spec : specs) { |
jlaskey@3 | 648 | final MethodType specType = spec.type(); |
jlaskey@3 | 649 | |
jlaskey@3 | 650 | if (!typeCompatible(descType, specType)) { |
jlaskey@3 | 651 | continue; |
jlaskey@3 | 652 | } |
jlaskey@3 | 653 | |
jlaskey@3 | 654 | //return type is ok. we want a wider or equal one for our callsite. |
jlaskey@3 | 655 | final int specWeight = weigh(specType); |
jlaskey@3 | 656 | if (specWeight < minimumWeight) { |
jlaskey@3 | 657 | candidate = spec; |
jlaskey@3 | 658 | minimumWeight = specWeight; |
jlaskey@3 | 659 | } |
jlaskey@3 | 660 | } |
jlaskey@3 | 661 | |
jlaskey@3 | 662 | if (DISABLE_SPECIALIZATION && candidate != initialCandidate) { |
jlaskey@3 | 663 | Context.err("### Specializing builtin " + getName() + " -> " + candidate + "?"); |
jlaskey@3 | 664 | } |
jlaskey@3 | 665 | |
jlaskey@3 | 666 | return candidate; |
jlaskey@3 | 667 | } |
jlaskey@3 | 668 | |
jlaskey@3 | 669 | /** |
jlaskey@3 | 670 | * Return the most appropriate invoke handle if there are specializations |
jlaskey@3 | 671 | * @param type most specific method type to look for invocation with |
jlaskey@3 | 672 | * @return invoke method handle |
jlaskey@3 | 673 | */ |
jlaskey@3 | 674 | public final MethodHandle getBestSpecializedInvokeHandle(final MethodType type) { |
jlaskey@3 | 675 | return candidateWithLowestWeight(type, getInvokeHandle(), invokeSpecializations); |
jlaskey@3 | 676 | } |
jlaskey@3 | 677 | |
jlaskey@3 | 678 | /** |
jlaskey@3 | 679 | * Get the invoke handle - the most generic (and if no specializations are in place, only) invocation |
jlaskey@3 | 680 | * method handle for this ScriptFunction |
jlaskey@3 | 681 | * @see SpecializedFunction |
jlaskey@3 | 682 | * @return invokeHandle |
jlaskey@3 | 683 | */ |
jlaskey@3 | 684 | public final MethodHandle getInvokeHandle() { |
jlaskey@3 | 685 | return invokeHandle; |
jlaskey@3 | 686 | } |
jlaskey@3 | 687 | |
jlaskey@3 | 688 | /** |
jlaskey@3 | 689 | * Return the invoke handle bound to a given ScriptObject self reference. |
jlaskey@3 | 690 | * If callee parameter is required result is rebound to this. |
jlaskey@3 | 691 | * |
jlaskey@3 | 692 | * @param self self reference |
jlaskey@3 | 693 | * @return bound invoke handle |
jlaskey@3 | 694 | */ |
jlaskey@3 | 695 | public final MethodHandle getBoundInvokeHandle(final ScriptObject self) { |
jlaskey@3 | 696 | final MethodHandle bound = MH.bindTo(getInvokeHandle(), self); |
jlaskey@3 | 697 | return hasCalleeParameter() ? MH.bindTo(bound, this) : bound; |
jlaskey@3 | 698 | } |
jlaskey@3 | 699 | |
jlaskey@3 | 700 | private boolean hasCalleeParameter() { |
jlaskey@3 | 701 | return scope != null; |
jlaskey@3 | 702 | } |
jlaskey@3 | 703 | |
jlaskey@3 | 704 | /** |
jlaskey@3 | 705 | * Get the construct handle - the most generic (and if no specializations are in place, only) constructor |
jlaskey@3 | 706 | * method handle for this ScriptFunction |
jlaskey@3 | 707 | * @see SpecializedConstructor |
jlaskey@3 | 708 | * @param type type for wanted constructor |
jlaskey@3 | 709 | * @return construct handle |
jlaskey@3 | 710 | */ |
jlaskey@3 | 711 | public final MethodHandle getConstructHandle(final MethodType type) { |
jlaskey@3 | 712 | return candidateWithLowestWeight(type, getConstructHandle(), constructSpecializations); |
jlaskey@3 | 713 | } |
jlaskey@3 | 714 | |
jlaskey@3 | 715 | /** |
jlaskey@3 | 716 | * Get a method handle to the constructor for this function |
jlaskey@3 | 717 | * @return constructor handle |
jlaskey@3 | 718 | */ |
jlaskey@3 | 719 | public final MethodHandle getConstructHandle() { |
jlaskey@3 | 720 | return constructHandle; |
jlaskey@3 | 721 | } |
jlaskey@3 | 722 | |
jlaskey@3 | 723 | /** |
jlaskey@3 | 724 | * Set a method handle to the constructor for this function |
jlaskey@3 | 725 | * @param constructHandle constructor handle |
jlaskey@3 | 726 | */ |
jlaskey@3 | 727 | public final void setConstructHandle(final MethodHandle constructHandle) { |
jlaskey@3 | 728 | this.constructHandle = constructHandle; |
jlaskey@3 | 729 | this.constructSpecializations = null; |
jlaskey@3 | 730 | } |
jlaskey@3 | 731 | |
jlaskey@3 | 732 | /** |
jlaskey@3 | 733 | * Get the name for this function |
jlaskey@3 | 734 | * @return the name |
jlaskey@3 | 735 | */ |
jlaskey@3 | 736 | public final String getName() { |
jlaskey@3 | 737 | return name; |
jlaskey@3 | 738 | } |
jlaskey@3 | 739 | |
jlaskey@3 | 740 | /** |
jlaskey@3 | 741 | * Does this script function need to be compiled. This determined by |
jlaskey@3 | 742 | * null checking invokeHandle |
jlaskey@3 | 743 | * |
jlaskey@3 | 744 | * @return true if this needs compilation |
jlaskey@3 | 745 | */ |
jlaskey@3 | 746 | public final boolean needsCompilation() { |
jlaskey@3 | 747 | return invokeHandle == null; |
jlaskey@3 | 748 | } |
jlaskey@3 | 749 | |
jlaskey@3 | 750 | /** |
jlaskey@3 | 751 | * Get token for this function |
jlaskey@3 | 752 | * @return token |
jlaskey@3 | 753 | */ |
jlaskey@3 | 754 | public final long getToken() { |
jlaskey@3 | 755 | return token; |
jlaskey@3 | 756 | } |
jlaskey@3 | 757 | |
jlaskey@3 | 758 | /** |
jlaskey@3 | 759 | * Get the scope for this function |
jlaskey@3 | 760 | * @return the scope |
jlaskey@3 | 761 | */ |
jlaskey@3 | 762 | public final ScriptObject getScope() { |
jlaskey@3 | 763 | return scope; |
jlaskey@3 | 764 | } |
jlaskey@3 | 765 | |
jlaskey@3 | 766 | /** |
jlaskey@3 | 767 | * Prototype getter for this ScriptFunction - follows the naming convention |
jlaskey@3 | 768 | * used by Nasgen and the code generator |
jlaskey@3 | 769 | * |
jlaskey@3 | 770 | * @param self self reference |
jlaskey@3 | 771 | * @return self's prototype |
jlaskey@3 | 772 | */ |
jlaskey@3 | 773 | public static Object G$prototype(final Object self) { |
jlaskey@3 | 774 | return (self instanceof ScriptFunction) ? |
jlaskey@3 | 775 | ((ScriptFunction)self).getPrototype() : |
jlaskey@3 | 776 | UNDEFINED; |
jlaskey@3 | 777 | } |
jlaskey@3 | 778 | |
jlaskey@3 | 779 | /** |
jlaskey@3 | 780 | * Prototype setter for this ScriptFunction - follows the naming convention |
jlaskey@3 | 781 | * used by Nasgen and the code generator |
jlaskey@3 | 782 | * |
jlaskey@3 | 783 | * @param self self reference |
jlaskey@3 | 784 | * @param prototype prototype to set |
jlaskey@3 | 785 | */ |
jlaskey@3 | 786 | public static void S$prototype(final Object self, final Object prototype) { |
jlaskey@3 | 787 | if (self instanceof ScriptFunction) { |
jlaskey@3 | 788 | ((ScriptFunction)self).setPrototype(prototype); |
jlaskey@3 | 789 | } |
jlaskey@3 | 790 | } |
jlaskey@3 | 791 | |
jlaskey@3 | 792 | /** |
jlaskey@3 | 793 | * Length getter - ECMA 15.3.3.2: Function.length |
jlaskey@3 | 794 | * @param self self reference |
jlaskey@3 | 795 | * @return length |
jlaskey@3 | 796 | */ |
jlaskey@3 | 797 | public static int G$length(final Object self) { |
jlaskey@3 | 798 | if (self instanceof ScriptFunction) { |
jlaskey@3 | 799 | return ((ScriptFunction)self).getArity(); |
jlaskey@3 | 800 | } |
jlaskey@3 | 801 | |
jlaskey@3 | 802 | return 0; |
jlaskey@3 | 803 | } |
jlaskey@3 | 804 | |
jlaskey@3 | 805 | /** |
jlaskey@3 | 806 | * Name getter - ECMA Function.name |
jlaskey@3 | 807 | * @param self self refence |
jlaskey@3 | 808 | * @return the name, or undefined if none |
jlaskey@3 | 809 | */ |
jlaskey@3 | 810 | public static Object G$name(final Object self) { |
jlaskey@3 | 811 | if (self instanceof ScriptFunction) { |
jlaskey@3 | 812 | return ((ScriptFunction)self).getName(); |
jlaskey@3 | 813 | } |
jlaskey@3 | 814 | |
jlaskey@3 | 815 | return UNDEFINED; |
jlaskey@3 | 816 | } |
jlaskey@3 | 817 | |
jlaskey@3 | 818 | /** |
jlaskey@3 | 819 | * Get the prototype for this ScriptFunction |
jlaskey@3 | 820 | * @param constructor constructor |
jlaskey@3 | 821 | * @return prototype, or null if given constructor is not a ScriptFunction |
jlaskey@3 | 822 | */ |
jlaskey@3 | 823 | public static ScriptObject getPrototype(final Object constructor) { |
jlaskey@3 | 824 | if (constructor instanceof ScriptFunction) { |
jlaskey@3 | 825 | final Object proto = ((ScriptFunction)constructor).getPrototype(); |
jlaskey@3 | 826 | if (proto instanceof ScriptObject) { |
jlaskey@3 | 827 | return (ScriptObject)proto; |
jlaskey@3 | 828 | } |
jlaskey@3 | 829 | } |
jlaskey@3 | 830 | |
jlaskey@3 | 831 | return null; |
jlaskey@3 | 832 | } |
jlaskey@3 | 833 | |
jlaskey@3 | 834 | // These counters are updated only in debug mode. |
jlaskey@3 | 835 | private static int constructorCount; |
jlaskey@3 | 836 | private static int invokes; |
jlaskey@3 | 837 | private static int allocations; |
jlaskey@3 | 838 | |
jlaskey@3 | 839 | /** |
jlaskey@3 | 840 | * @return the constructorCount |
jlaskey@3 | 841 | */ |
jlaskey@3 | 842 | public static int getConstructorCount() { |
jlaskey@3 | 843 | return constructorCount; |
jlaskey@3 | 844 | } |
jlaskey@3 | 845 | |
jlaskey@3 | 846 | /** |
jlaskey@3 | 847 | * @return the invokes |
jlaskey@3 | 848 | */ |
jlaskey@3 | 849 | public static int getInvokes() { |
jlaskey@3 | 850 | return invokes; |
jlaskey@3 | 851 | } |
jlaskey@3 | 852 | |
jlaskey@3 | 853 | /** |
jlaskey@3 | 854 | * @return the allocations |
jlaskey@3 | 855 | */ |
jlaskey@3 | 856 | public static int getAllocations() { |
jlaskey@3 | 857 | return allocations; |
jlaskey@3 | 858 | } |
jlaskey@3 | 859 | |
jlaskey@3 | 860 | @Override |
jlaskey@3 | 861 | protected GuardedInvocation findNewMethod(final CallSiteDescriptor desc) { |
jlaskey@3 | 862 | final MethodType type = desc.getMethodType(); |
jlaskey@3 | 863 | MethodHandle constructor = getConstructHandle(type); |
jlaskey@3 | 864 | |
jlaskey@3 | 865 | if (constructor == null) { |
jlaskey@3 | 866 | typeError(Context.getGlobal(), "not.a.constructor", ScriptRuntime.safeToString(this)); |
jlaskey@3 | 867 | return null; |
jlaskey@3 | 868 | } |
jlaskey@3 | 869 | |
jlaskey@3 | 870 | final MethodType ctorType = constructor.type(); |
jlaskey@3 | 871 | |
jlaskey@3 | 872 | // guard against primitive constructor return types |
jlaskey@3 | 873 | constructor = MH.asType(constructor, constructor.type().changeReturnType(Object.class)); |
jlaskey@3 | 874 | |
jlaskey@3 | 875 | // apply new filter |
jlaskey@3 | 876 | final Class<?>[] ctorArgs = ctorType.dropParameterTypes(0, 1).parameterArray(); // drop self |
jlaskey@3 | 877 | MethodHandle handle = MH.foldArguments(MH.dropArguments(NEWFILTER, 2, ctorArgs), constructor); |
jlaskey@3 | 878 | |
jlaskey@3 | 879 | if (hasCalleeParameter()) { |
jlaskey@3 | 880 | final MethodHandle allocate = MH.bindTo(MethodHandles.exactInvoker(ALLOCATE.type()), ScriptFunction.ALLOCATE); |
jlaskey@3 | 881 | handle = MH.foldArguments(handle, allocate); |
jlaskey@3 | 882 | handle = MH.asType(handle, handle.type().changeParameterType(0, Object.class)); // ScriptFunction => Object |
jlaskey@3 | 883 | } else { |
jlaskey@3 | 884 | final MethodHandle allocate = MH.dropArguments(MH.bindTo(ScriptFunction.ALLOCATE, this), 0, Object.class); |
jlaskey@3 | 885 | handle = MH.filterArguments(handle, 0, allocate); |
jlaskey@3 | 886 | } |
jlaskey@3 | 887 | |
jlaskey@3 | 888 | final MethodHandle filterIn = MH.asType(pairArguments(handle, type), type); |
jlaskey@3 | 889 | return new GuardedInvocation(filterIn, null, NashornGuards.getFunctionGuard(this)); |
jlaskey@3 | 890 | } |
jlaskey@3 | 891 | |
jlaskey@3 | 892 | @SuppressWarnings("unused") |
jlaskey@3 | 893 | private static Object newFilter(final Object result, final Object allocation) { |
jlaskey@3 | 894 | return result instanceof ScriptObject ? result : allocation; |
jlaskey@3 | 895 | } |
jlaskey@3 | 896 | |
jlaskey@3 | 897 | /** |
jlaskey@3 | 898 | * dyn:call call site signature: (callee, thiz, [args...]) |
jlaskey@3 | 899 | * generated method signature: (thiz, callee, [args...]) |
jlaskey@3 | 900 | * |
jlaskey@3 | 901 | * cases: |
jlaskey@3 | 902 | * (a) method has callee parameter |
jlaskey@3 | 903 | * (1) for local/scope calls, we just bind thiz and drop the second argument. |
jlaskey@3 | 904 | * (2) for normal this-calls, we have to swap thiz and callee to get matching signatures. |
jlaskey@3 | 905 | * (b) method doesn't have callee parameter (builtin functions) |
jlaskey@3 | 906 | * (3) for local/scope calls, bind thiz and drop both callee and thiz. |
jlaskey@3 | 907 | * (4) for normal this-calls, drop callee. |
jlaskey@3 | 908 | */ |
jlaskey@3 | 909 | @Override |
jlaskey@3 | 910 | protected GuardedInvocation findCallMethod(final CallSiteDescriptor desc, final boolean megaMorphic) { |
jlaskey@3 | 911 | final MethodType type = desc.getMethodType(); |
jlaskey@3 | 912 | |
jlaskey@3 | 913 | if(megaMorphic) { |
jlaskey@3 | 914 | // (this, callee, args...) => (this, callee, args[]) |
jlaskey@3 | 915 | final MethodHandle collector = MH.asCollector(ScriptRuntime.APPLY.methodHandle(), Object[].class, |
jlaskey@3 | 916 | type.parameterCount() - 2); |
jlaskey@3 | 917 | |
jlaskey@3 | 918 | return new GuardedInvocation(collector, |
jlaskey@3 | 919 | desc.getMethodType().parameterType(0) == ScriptFunction.class ? null : NashornGuards.getScriptFunctionGuard()); |
jlaskey@3 | 920 | } |
jlaskey@3 | 921 | |
jlaskey@3 | 922 | MethodHandle boundHandle; |
jlaskey@3 | 923 | if (hasCalleeParameter()) { |
jlaskey@3 | 924 | final MethodHandle callHandle = getBestSpecializedInvokeHandle(type); |
jlaskey@3 | 925 | |
jlaskey@3 | 926 | if(NashornCallSiteDescriptor.isScope(desc)) { |
jlaskey@3 | 927 | // (this, callee, args...) => (callee, args...) => (callee, [this], args...) |
hannesw@42 | 928 | boundHandle = MH.bindTo(callHandle, isNonStrictFunction() ? Context.getGlobal(): ScriptRuntime.UNDEFINED); |
jlaskey@3 | 929 | boundHandle = MH.dropArguments(boundHandle, 1, Object.class); |
jlaskey@3 | 930 | } else { |
jlaskey@3 | 931 | // (this, callee, args...) permute => (callee, this, args...) which is what we get in |
jlaskey@3 | 932 | final MethodType oldType = callHandle.type(); |
jlaskey@3 | 933 | final int[] reorder = new int[oldType.parameterCount()]; |
jlaskey@3 | 934 | for (int i = 2; i < reorder.length; i++) { |
jlaskey@3 | 935 | reorder[i] = i; |
jlaskey@3 | 936 | } |
jlaskey@3 | 937 | reorder[0] = 1; |
jlaskey@3 | 938 | assert reorder[1] == 0; |
jlaskey@3 | 939 | final MethodType newType = oldType.changeParameterType(0, oldType.parameterType(1)).changeParameterType(1, oldType.parameterType(0)); |
jlaskey@3 | 940 | boundHandle = MethodHandles.permuteArguments(callHandle, newType, reorder); |
jlaskey@3 | 941 | } |
jlaskey@3 | 942 | } else { |
jlaskey@3 | 943 | final MethodHandle callHandle = getBestSpecializedInvokeHandle(type.dropParameterTypes(0, 1)); |
jlaskey@3 | 944 | |
jlaskey@3 | 945 | if(NashornCallSiteDescriptor.isScope(desc)) { |
hannesw@42 | 946 | boundHandle = MH.bindTo(callHandle, isNonStrictFunction()? Context.getGlobal() : ScriptRuntime.UNDEFINED); |
jlaskey@3 | 947 | boundHandle = MH.dropArguments(boundHandle, 0, Object.class, Object.class); |
jlaskey@3 | 948 | } else { |
jlaskey@3 | 949 | boundHandle = MH.dropArguments(callHandle, 0, Object.class); |
jlaskey@3 | 950 | } |
jlaskey@3 | 951 | } |
jlaskey@3 | 952 | |
jlaskey@3 | 953 | boundHandle = pairArguments(boundHandle, type); |
hannesw@42 | 954 | return new NashornGuardedInvocation(boundHandle, null, NashornGuards.getFunctionGuard(this), isNonStrictFunction()); |
jlaskey@3 | 955 | } |
jlaskey@3 | 956 | |
jlaskey@3 | 957 | /** |
jlaskey@3 | 958 | * Used for noSuchMethod/noSuchProperty and JSAdapter hooks. |
jlaskey@3 | 959 | * |
jlaskey@3 | 960 | * These don't want a callee parameter, so bind that. Name binding is optional. |
jlaskey@3 | 961 | */ |
jlaskey@3 | 962 | MethodHandle getCallMethodHandle(final MethodType type, final String bindName) { |
jlaskey@3 | 963 | MethodHandle methodHandle = getBestSpecializedInvokeHandle(type); |
jlaskey@3 | 964 | |
jlaskey@3 | 965 | if (bindName != null) { |
jlaskey@3 | 966 | if (hasCalleeParameter()) { |
jlaskey@3 | 967 | methodHandle = MH.insertArguments(methodHandle, 1, this, bindName); |
jlaskey@3 | 968 | } else { |
jlaskey@3 | 969 | methodHandle = MH.insertArguments(methodHandle, 1, bindName); |
jlaskey@3 | 970 | } |
jlaskey@3 | 971 | } else { |
jlaskey@3 | 972 | if (hasCalleeParameter()) { |
jlaskey@3 | 973 | methodHandle = MH.insertArguments(methodHandle, 1, this); |
jlaskey@3 | 974 | } |
jlaskey@3 | 975 | } |
jlaskey@3 | 976 | |
jlaskey@3 | 977 | return pairArguments(methodHandle, type); |
jlaskey@3 | 978 | } |
jlaskey@3 | 979 | |
jlaskey@3 | 980 | private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) { |
jlaskey@3 | 981 | final Class<?> own = ScriptFunction.class; |
jlaskey@3 | 982 | final MethodType mt = MH.type(rtype, types); |
jlaskey@3 | 983 | try { |
jlaskey@3 | 984 | return MH.findStatic(MethodHandles.lookup(), own, name, mt); |
jlaskey@3 | 985 | } catch (final MethodHandleFactory.LookupException e) { |
jlaskey@3 | 986 | return MH.findVirtual(MethodHandles.lookup(), own, name, mt); |
jlaskey@3 | 987 | } |
jlaskey@3 | 988 | } |
jlaskey@3 | 989 | } |
jlaskey@3 | 990 |