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.runtime; 27 28 import jdk.nashorn.internal.ir.FunctionNode; 29 import jdk.nashorn.internal.parser.Token; 30 import jdk.nashorn.internal.parser.TokenType; 31 32 import java.lang.invoke.MethodHandle; 33 import java.lang.invoke.MethodType; 34 35 import static jdk.nashorn.internal.runtime.linker.Lookup.MH; 36 37 /** 38 * A container for data needed to instantiate a specific {@link ScriptFunction} at runtime. 39 * Instances of this class are created during codegen and stored in script classes' 40 * constants array to reduce function instantiation overhead during runtime. 41 */ 42 public class ScriptFunctionData { 43 44 // per-function object flags 45 private static final int IS_STRICT = 0b0000_0001; 46 private static final int IS_BUILTIN = 0b0000_0010; 47 private static final int HAS_CALLEE = 0b0000_0100; 48 private static final int IS_VARARGS = 0b0000_1000; 49 50 /** Name of the function or "" */ 51 private final String name; 52 /** Source of this function, or null */ 53 private final Source source; 54 /** Map for new instance constructor */ 55 private PropertyMap allocatorMap; 56 /** Start position and length in source */ 57 private final long token; 58 /** Number of expected arguments, either taken from FunctionNode or calculated from method handle signature*/ 59 private int arity; 60 /** Does this function need a callee argument? */ 61 private final int flags; 62 63 /** Reference to code for this method. */ 64 private MethodHandle invoker; 65 /** Reference to code for this method when called to create "new" object */ 66 private MethodHandle constructor; 67 /** Constructor to create a new instance. */ 68 private MethodHandle allocator; 69 /** Generic invoker to used in {@link ScriptFunction#invoke(Object, Object...)}. */ 70 private MethodHandle genericInvoker; 71 /** Generic constructor used in {@link ScriptFunction#construct(Object, Object...)}. */ 72 private MethodHandle genericConstructor; 73 /** Specializations - see @SpecializedFunction */ 74 private MethodHandle[] invokeSpecializations; 75 /** Specializations - see @SpecializedFunction */ 76 private MethodHandle[] constructSpecializations; 77 78 /** 79 * Constructor 80 * @param fn the function node 81 * @param allocatorMap the allocator property map 82 */ 83 public ScriptFunctionData(final FunctionNode fn, final PropertyMap allocatorMap) { 84 85 final long firstToken = fn.getFirstToken(); 86 final long lastToken = fn.getLastToken(); 87 final int position = Token.descPosition(firstToken); 88 final int length = Token.descPosition(lastToken) - position + Token.descLength(lastToken); 89 90 this.name = fn.isAnonymous() ? "" : fn.getIdent().getName(); 91 this.source = fn.getSource(); 92 this.allocatorMap = allocatorMap; 93 this.token = Token.toDesc(TokenType.FUNCTION, position, length); 94 this.arity = fn.getParameters().size(); 95 this.flags = makeFlags(fn.needsCallee(), fn.isVarArg(), fn.isStrictMode(), false); 96 } 97 98 /** 99 * Constructor 100 * @param name the function name 101 * @param methodHandle the method handle 102 * @param specs array of specialized method handles 103 * @param strict strict flag 104 * @param builtin builtin flag 105 */ 106 public ScriptFunctionData(final String name, final MethodHandle methodHandle, final MethodHandle[] specs, final boolean strict, final boolean builtin) { 107 this.name = name; 108 this.source = null; 109 this.token = 0; 110 111 final MethodType type = methodHandle.type(); 112 final int paramCount = type.parameterCount(); 113 final boolean isVarArg = type.parameterType(paramCount - 1).isArray(); 114 final boolean needsCallee = needsCallee(methodHandle); 115 116 this.flags = makeFlags(needsCallee, isVarArg, strict, builtin); 117 this.arity = isVarArg ? -1 : paramCount - 1; //drop the self param for arity 118 119 if (needsCallee && !isVarArg) { 120 this.arity--; 121 } 122 123 if (isConstructor(methodHandle)) { 124 if (!isVarArg) { 125 this.arity--; // drop the boolean flag for arity 126 } 127 /* 128 * We insert a boolean argument to tell if the method was invoked as 129 * constructor or not if the method handle's first argument is boolean. 130 */ 131 this.invoker = MH.insertArguments(methodHandle, 0, false); 132 this.constructor = MH.insertArguments(methodHandle, 0, true); 133 134 if (specs != null) { 135 this.invokeSpecializations = new MethodHandle[specs.length]; 136 this.constructSpecializations = new MethodHandle[specs.length]; 137 for (int i = 0; i < specs.length; i++) { 138 this.invokeSpecializations[i] = MH.insertArguments(specs[i], 0, false); 139 this.constructSpecializations[i] = MH.insertArguments(specs[i], 0, true); 140 } 141 } 142 } else { 143 this.invoker = methodHandle; 144 this.constructor = methodHandle; 145 this.invokeSpecializations = specs; 146 this.constructSpecializations = specs; 147 } 148 } 149 150 /** 151 * Get the arity of the function. 152 * @return the arity 153 */ 154 public int getArity() { 155 return arity; 156 } 157 158 /** 159 * Set the arity of the function. 160 * @param arity the arity 161 */ 162 public void setArity(int arity) { 163 this.arity = arity; 164 } 165 166 /** 167 * Get the function name. 168 * @return function name 169 */ 170 public String getName() { 171 return name; 172 } 173 174 /** 175 * Get the source of the function. 176 * @return the source 177 */ 178 public Source getSource() { 179 return source; 180 } 181 182 /** 183 * Get this function as a String containing its source code. If no source code 184 * exists in this ScriptFunction, its contents will be displayed as {@code [native code]} 185 * @return string representation of this function's source 186 */ 187 public String toSource() { 188 if (source != null && token != 0) { 189 return source.getString(Token.descPosition(token), Token.descLength(token)); 190 } 191 192 return "function " + (name == null ? "" : name) + "() { [native code] }"; 193 } 194 195 @Override 196 public String toString() { 197 final StringBuilder sb = new StringBuilder(); 198 199 sb.append(super.toString()) 200 .append(" [ ") 201 .append(invoker) 202 .append(", ") 203 .append((name == null || name.isEmpty()) ? "<anonymous>" : name); 204 205 if (source != null) { 206 sb.append(" @ ") 207 .append(source.getName()) 208 .append(':') 209 .append(source.getLine(Token.descPosition(token))); 210 } 211 sb.append(" ]"); 212 213 return sb.toString(); 214 } 215 216 /** 217 * Get the allocator property map. 218 * @return the allocator map 219 */ 220 public PropertyMap getAllocatorMap() { 221 return allocatorMap; 222 } 223 224 /** 225 * Get the function's parse token. 226 * @return the token 227 */ 228 public long getToken() { 229 return token; 230 } 231 232 /** 233 * Returns true if the function needs a callee argument. 234 * @return the needsCallee flag 235 */ 236 public boolean needsCallee() { 237 return (flags & HAS_CALLEE) != 0; 238 } 239 240 /** 241 * Returns true if this is a strict-mode function. 242 * @return the strict flag 243 */ 244 public boolean isStrict() { 245 return (flags & IS_STRICT) != 0; 246 } 247 248 /** 249 * Returns true if this is a built-in function. 250 * @return the built-in flag 251 */ 252 public boolean isBuiltin() { 253 return (flags & IS_BUILTIN) != 0; 254 } 255 256 /** 257 * Returns true if this is a var-arg function. 258 * @return the var-arg flag 259 */ 260 public boolean isVarArg() { 261 return (flags & IS_VARARGS) != 0; 262 } 263 264 /** 265 * Returns true if this is a non-strict, non-built-in function that requires non-primitive this argument 266 * according to ECMA 10.4.3. 267 * @return true if this argument must be an object 268 */ 269 public boolean needsWrappedThis() { 270 return (flags & (IS_STRICT | IS_BUILTIN)) == 0; 271 } 272 273 /** 274 * Get the method handle used to invoke this function. 275 * @return the invoke handle 276 */ 277 public MethodHandle getInvoker() { 278 return invoker; 279 } 280 281 /** 282 * Get the method handle used to invoke this function as a constructor. 283 * @return the constructor handle 284 */ 285 public MethodHandle getConstructor() { 286 return constructor; 287 } 288 289 /** 290 * Set the constructor method handle. 291 * @param constructor the constructor handle 292 */ 293 public void setConstructor(MethodHandle constructor) { 294 this.constructor = constructor; 295 this.constructSpecializations = null; 296 } 297 298 /** 299 * Get the method handle used to allocate a new object for this constructor. 300 * @return the allocator handle 301 */ 302 public MethodHandle getAllocator() { 303 return allocator; 304 } 305 306 /** 307 * Get an adapted version of the invoker handle that only uses {@code Object} as parameter and return types. 308 * @return the generic invoke handle 309 */ 310 public MethodHandle getGenericInvoker() { 311 if (genericInvoker == null) { 312 assert invoker != null : "invoker is null"; 313 genericInvoker = adaptMethodType(invoker); 314 } 315 return genericInvoker; 316 } 317 318 /** 319 * Get an adapted version of the constructor handle that only uses {@code Object} as parameter and return types. 320 * @return the generic constructor handle 321 */ 322 public MethodHandle getGenericConstructor() { 323 if (genericConstructor == null) { 324 assert constructor != null : "constructor is null"; 325 genericConstructor = adaptMethodType(constructor); 326 } 327 return genericConstructor; 328 } 329 330 /** 331 * Get the specialized invoke handles for this function. 332 * @return array of specialized invoke handles 333 */ 334 public MethodHandle[] getInvokeSpecializations() { 335 return invokeSpecializations; 336 } 337 338 /** 339 * Get the specialized construct handles for this function. 340 * @return array of specialized construct handles 341 */ 342 public MethodHandle[] getConstructSpecializations() { 343 return constructSpecializations; 344 } 345 346 /** 347 * Set the method handles for this function. 348 * @param invoker the invoker handle 349 * @param allocator the allocator handle 350 */ 351 public void setMethodHandles(MethodHandle invoker, MethodHandle allocator) { 352 // We can't make method handle fields final because they're not available during codegen 353 // and they're set when first called, so we enforce set-once here. 354 if (this.invoker == null) { 355 this.invoker = invoker; 356 this.constructor = invoker; 357 this.allocator = allocator; 358 } 359 } 360 361 /** 362 * Convert boolean flags to int. 363 * @param needsCallee needs-callee flag 364 * @param isVarArg var-arg flag 365 * @param isStrict strict flag 366 * @param isBuiltin builtin flag 367 * @return int flags 368 */ 369 private static int makeFlags(final boolean needsCallee, final boolean isVarArg, final boolean isStrict, final boolean isBuiltin) { 370 int flags = 0; 371 if (needsCallee) { 372 flags |= HAS_CALLEE; 373 } 374 if (isVarArg) { 375 flags |= IS_VARARGS; 376 } 377 if (isStrict) { 378 flags |= IS_STRICT; 379 } 380 if (isBuiltin) { 381 flags |= IS_BUILTIN; 382 } 383 return flags; 384 } 385 386 /** 387 * Test if a methodHandle refers to a constructor. 388 * @param methodHandle MethodHandle to test. 389 * @return True if method is a constructor. 390 */ 391 private static boolean isConstructor(final MethodHandle methodHandle) { 392 return methodHandle.type().parameterCount() >= 1 && methodHandle.type().parameterType(0) == boolean.class; 393 } 394 395 /** 396 * Heuristic to figure out if the method handle has a callee argument. If it's type is either 397 * {@code (boolean, Object, ScriptFunction, ...)} or {@code (Object, ScriptFunction, ...)}, then we'll assume it has 398 * a callee argument. We need this as the constructor above is not passed this information, and can't just blindly 399 * assume it's false (notably, it's being invoked for creation of new scripts, and scripts have scopes, therefore 400 * they also always receive a callee. 401 * @param methodHandle the examined method handle 402 * @return true if the method handle expects a callee, false otherwise 403 */ 404 private static boolean needsCallee(MethodHandle methodHandle) { 405 final MethodType type = methodHandle.type(); 406 final int len = type.parameterCount(); 407 if(len == 0) { 408 return false; 409 } 410 if(type.parameterType(0) == boolean.class) { 411 return len > 2 && type.parameterType(2) == ScriptFunction.class; 412 } 413 return len > 1 && type.parameterType(1) == ScriptFunction.class; 414 } 415 416 /** 417 * Takes a method handle, and returns a potentially different method handle that can be used in 418 * {@link ScriptFunction#invoke(Object, Object...)} or {@link ScriptFunction#construct(Object, Object...)}. 419 * The returned method handle will be sure to return {@code Object}, and will have all its parameters turned into 420 * {@code Object} as well, except for the following ones: 421 * <ul> 422 * <li>a last parameter of type {@code Object[]} which is used for vararg functions,</li> 423 * <li>the second argument, which is forced to be {@link ScriptFunction}, in case the function receives itself 424 * (callee) as an argument</li> 425 * </ul> 426 * 427 * @param handle the original method handle 428 * @return the new handle, conforming to the rules above. 429 */ 430 private MethodHandle adaptMethodType(final MethodHandle handle) { 431 final MethodType type = handle.type(); 432 MethodType newType = type.generic(); 433 if (isVarArg()) { 434 newType = newType.changeParameterType(type.parameterCount() - 1, Object[].class); 435 } 436 if (needsCallee()) { 437 newType = newType.changeParameterType(1, ScriptFunction.class); 438 } 439 return type.equals(newType) ? handle : handle.asType(newType); 440 } 441 442 }