src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java

Fri, 16 Aug 2013 18:51:53 +0200

author
lagergren
date
Fri, 16 Aug 2013 18:51:53 +0200
changeset 505
36fb36217e1d
parent 489
dd79c04ef7df
child 582
2016a6b9e1f3
permissions
-rw-r--r--

8023017: SUB missing for widest op == number for BinaryNode
Reviewed-by: sundar, jlaskey

lagergren@137 1 /*
lagergren@137 2 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
lagergren@137 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
lagergren@137 4 *
lagergren@137 5 * This code is free software; you can redistribute it and/or modify it
lagergren@137 6 * under the terms of the GNU General Public License version 2 only, as
lagergren@137 7 * published by the Free Software Foundation. Oracle designates this
lagergren@137 8 * particular file as subject to the "Classpath" exception as provided
lagergren@137 9 * by Oracle in the LICENSE file that accompanied this code.
lagergren@137 10 *
lagergren@137 11 * This code is distributed in the hope that it will be useful, but WITHOUT
lagergren@137 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
lagergren@137 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
lagergren@137 14 * version 2 for more details (a copy is included in the LICENSE file that
lagergren@137 15 * accompanied this code).
lagergren@137 16 *
lagergren@137 17 * You should have received a copy of the GNU General Public License version
lagergren@137 18 * 2 along with this work; if not, write to the Free Software Foundation,
lagergren@137 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
lagergren@137 20 *
lagergren@137 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
lagergren@137 22 * or visit www.oracle.com if you need additional information or have any
lagergren@137 23 * questions.
lagergren@137 24 */
lagergren@137 25
lagergren@137 26 package jdk.nashorn.internal.runtime;
lagergren@137 27
lagergren@211 28 import static jdk.nashorn.internal.lookup.Lookup.MH;
lagergren@211 29
lagergren@137 30 import java.lang.invoke.MethodHandle;
lagergren@137 31 import java.lang.invoke.MethodHandles;
lagergren@137 32 import java.lang.invoke.MethodType;
lagergren@277 33 import java.util.ArrayList;
lagergren@277 34 import java.util.Arrays;
lagergren@247 35 import java.util.LinkedList;
lagergren@247 36
lagergren@137 37 import jdk.nashorn.internal.codegen.Compiler;
lagergren@137 38 import jdk.nashorn.internal.codegen.CompilerConstants;
lagergren@137 39 import jdk.nashorn.internal.codegen.FunctionSignature;
lagergren@247 40 import jdk.nashorn.internal.codegen.types.Type;
lagergren@137 41 import jdk.nashorn.internal.ir.FunctionNode;
lagergren@137 42 import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
lagergren@137 43 import jdk.nashorn.internal.parser.Token;
lagergren@137 44 import jdk.nashorn.internal.parser.TokenType;
lagergren@137 45
lagergren@137 46 /**
lagergren@137 47 * This is a subclass that represents a script function that may be regenerated,
lagergren@137 48 * for example with specialization based on call site types, or lazily generated.
lagergren@137 49 * The common denominator is that it can get new invokers during its lifespan,
lagergren@505 50 * unlike {@code FinalScriptFunctionData}
lagergren@137 51 */
lagergren@137 52 public final class RecompilableScriptFunctionData extends ScriptFunctionData {
lagergren@137 53
lagergren@277 54 /** FunctionNode with the code for this ScriptFunction */
sundar@489 55 private volatile FunctionNode functionNode;
sundar@489 56
sundar@489 57 /** Source from which FunctionNode was parsed. */
sundar@489 58 private final Source source;
sundar@489 59
sundar@489 60 /** Token of this function within the source. */
sundar@489 61 private final long token;
lagergren@277 62
lagergren@277 63 /** Allocator map from makeMap() */
lagergren@277 64 private final PropertyMap allocatorMap;
lagergren@277 65
lagergren@277 66 /** Code installer used for all further recompilation/specialization of this ScriptFunction */
sundar@489 67 private volatile CodeInstaller<ScriptEnvironment> installer;
lagergren@277 68
lagergren@277 69 /** Name of class where allocator function resides */
lagergren@137 70 private final String allocatorClassName;
lagergren@137 71
lagergren@137 72 /** lazily generated allocator */
lagergren@137 73 private MethodHandle allocator;
lagergren@137 74
lagergren@137 75 private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
lagergren@137 76
lagergren@137 77 /**
lagergren@277 78 * Used for specialization based on runtime arguments. Whenever we specialize on
lagergren@277 79 * callsite parameter types at runtime, we need to use a parameter type guard to
lagergren@277 80 * ensure that the specialized version of the script function continues to be
lagergren@277 81 * applicable for a particular callsite *
lagergren@277 82 */
lagergren@277 83 private static final MethodHandle PARAM_TYPE_GUARD = findOwnMH("paramTypeGuard", boolean.class, Type[].class, Object[].class);
lagergren@277 84
lagergren@277 85 /**
lagergren@277 86 * It is usually a good gamble whever we detect a runtime callsite with a double
lagergren@277 87 * (or java.lang.Number instance) to specialize the parameter to an integer, if the
lagergren@277 88 * parameter in question can be represented as one. The double typically only exists
lagergren@277 89 * because the compiler doesn't know any better than "a number type" and conservatively
lagergren@277 90 * picks doubles when it can't prove that an integer addition wouldn't overflow
lagergren@277 91 */
lagergren@277 92 private static final MethodHandle ENSURE_INT = findOwnMH("ensureInt", int.class, Object.class);
lagergren@277 93
lagergren@277 94 /**
lagergren@137 95 * Constructor - public as scripts use it
lagergren@137 96 *
lagergren@137 97 * @param functionNode functionNode that represents this function code
lagergren@137 98 * @param installer installer for code regeneration versions of this function
lagergren@137 99 * @param allocatorClassName name of our allocator class, will be looked up dynamically if used as a constructor
lagergren@137 100 * @param allocatorMap allocator map to seed instances with, when constructing
lagergren@137 101 */
lagergren@137 102 public RecompilableScriptFunctionData(final FunctionNode functionNode, final CodeInstaller<ScriptEnvironment> installer, final String allocatorClassName, final PropertyMap allocatorMap) {
lagergren@137 103 super(functionNode.isAnonymous() ?
lagergren@137 104 "" :
lagergren@137 105 functionNode.getIdent().getName(),
lagergren@137 106 functionNode.getParameters().size(),
lagergren@211 107 functionNode.isStrict(),
lagergren@137 108 false,
lagergren@137 109 true);
lagergren@137 110
lagergren@137 111 this.functionNode = functionNode;
sundar@489 112 this.source = functionNode.getSource();
sundar@489 113 this.token = tokenFor(functionNode);
lagergren@137 114 this.installer = installer;
lagergren@137 115 this.allocatorClassName = allocatorClassName;
lagergren@137 116 this.allocatorMap = allocatorMap;
lagergren@137 117 }
lagergren@137 118
lagergren@137 119 @Override
lagergren@137 120 String toSource() {
lagergren@137 121 if (source != null && token != 0) {
lagergren@137 122 return source.getString(Token.descPosition(token), Token.descLength(token));
lagergren@137 123 }
lagergren@137 124
lagergren@137 125 return "function " + (name == null ? "" : name) + "() { [native code] }";
lagergren@137 126 }
lagergren@137 127
lagergren@137 128 @Override
lagergren@137 129 public String toString() {
lagergren@137 130 final StringBuilder sb = new StringBuilder();
lagergren@137 131
lagergren@137 132 if (source != null) {
lagergren@137 133 sb.append(source.getName())
lagergren@137 134 .append(':')
lagergren@137 135 .append(source.getLine(Token.descPosition(token)))
lagergren@137 136 .append(' ');
lagergren@137 137 }
lagergren@137 138
lagergren@137 139 return sb.toString() + super.toString();
lagergren@137 140 }
lagergren@137 141
lagergren@137 142 private static long tokenFor(final FunctionNode fn) {
lagergren@137 143 final int position = Token.descPosition(fn.getFirstToken());
lagergren@137 144 final int length = Token.descPosition(fn.getLastToken()) - position + Token.descLength(fn.getLastToken());
lagergren@137 145
lagergren@137 146 return Token.toDesc(TokenType.FUNCTION, position, length);
lagergren@137 147 }
lagergren@137 148
lagergren@137 149 @Override
lagergren@137 150 ScriptObject allocate() {
lagergren@137 151 try {
lagergren@137 152 ensureHasAllocator(); //if allocatorClass name is set to null (e.g. for bound functions) we don't even try
lagergren@137 153 return allocator == null ? null : (ScriptObject)allocator.invokeExact(allocatorMap);
lagergren@137 154 } catch (final RuntimeException | Error e) {
lagergren@137 155 throw e;
lagergren@137 156 } catch (final Throwable t) {
lagergren@137 157 throw new RuntimeException(t);
lagergren@137 158 }
lagergren@137 159 }
lagergren@137 160
lagergren@137 161 private void ensureHasAllocator() throws ClassNotFoundException {
lagergren@137 162 if (allocator == null && allocatorClassName != null) {
lagergren@211 163 this.allocator = MH.findStatic(LOOKUP, Context.forStructureClass(allocatorClassName), CompilerConstants.ALLOCATE.symbolName(), MH.type(ScriptObject.class, PropertyMap.class));
lagergren@137 164 }
lagergren@137 165 }
lagergren@137 166
lagergren@137 167 @Override
lagergren@137 168 protected void ensureCodeGenerated() {
lagergren@137 169 if (!code.isEmpty()) {
lagergren@137 170 return; // nothing to do, we have code, at least some.
lagergren@137 171 }
lagergren@137 172
lagergren@137 173 if (functionNode.isLazy()) {
lagergren@211 174 Compiler.LOG.info("Trampoline hit: need to do lazy compilation of '", functionNode.getName(), "'");
lagergren@247 175 final Compiler compiler = new Compiler(installer);
lagergren@247 176 functionNode = compiler.compile(functionNode);
lagergren@211 177 assert !functionNode.isLazy();
lagergren@247 178 compiler.install(functionNode);
lagergren@137 179
lagergren@277 180 /*
lagergren@277 181 * We don't need to update any flags - varArgs and needsCallee are instrincic
lagergren@277 182 * in the function world we need to get a destination node from the compile instead
lagergren@277 183 * and replace it with our function node. TODO
lagergren@277 184 */
lagergren@137 185 }
lagergren@137 186
lagergren@277 187 /*
lagergren@277 188 * We can't get to this program point unless we have bytecode, either from
lagergren@277 189 * eager compilation or from running a lazy compile on the lines above
lagergren@277 190 */
lagergren@137 191
lagergren@211 192 assert functionNode.hasState(CompilationState.EMITTED) : functionNode.getName() + " " + functionNode.getState() + " " + Debug.id(functionNode);
lagergren@137 193
lagergren@137 194 // code exists - look it up and add it into the automatically sorted invoker list
lagergren@277 195 addCode(functionNode);
sundar@489 196
sundar@489 197 if (! functionNode.canSpecialize()) {
sundar@489 198 // allow GC to claim IR stuff that is not needed anymore
sundar@489 199 functionNode = null;
sundar@489 200 installer = null;
sundar@489 201 }
lagergren@247 202 }
lagergren@247 203
lagergren@277 204 private MethodHandle addCode(final FunctionNode fn) {
lagergren@277 205 return addCode(fn, null, null, null);
lagergren@277 206 }
lagergren@277 207
lagergren@277 208 private MethodHandle addCode(final FunctionNode fn, final MethodType runtimeType, final MethodHandle guard, final MethodHandle fallback) {
lagergren@277 209 final MethodType targetType = new FunctionSignature(fn).getMethodType();
lagergren@277 210 MethodHandle target =
lagergren@247 211 MH.findStatic(
lagergren@137 212 LOOKUP,
lagergren@247 213 fn.getCompileUnit().getCode(),
lagergren@247 214 fn.getName(),
lagergren@277 215 targetType);
lagergren@277 216
lagergren@277 217 /*
lagergren@277 218 * For any integer argument. a double that is representable as an integer is OK.
lagergren@277 219 * otherwise the guard would have failed. in that case introduce a filter that
lagergren@277 220 * casts the double to an integer, which we know will preserve all precision.
lagergren@277 221 */
lagergren@277 222 for (int i = 0; i < targetType.parameterCount(); i++) {
lagergren@277 223 if (targetType.parameterType(i) == int.class) {
lagergren@277 224 //representable as int
lagergren@277 225 target = MH.filterArguments(target, i, ENSURE_INT);
lagergren@247 226 }
lagergren@247 227 }
lagergren@247 228
lagergren@277 229 MethodHandle mh = target;
lagergren@277 230 if (guard != null) {
lagergren@277 231 mh = MH.guardWithTest(MH.asCollector(guard, Object[].class, target.type().parameterCount()), MH.asType(target, fallback.type()), fallback);
lagergren@277 232 }
lagergren@277 233
lagergren@277 234 final CompiledFunction cf = new CompiledFunction(runtimeType == null ? targetType : runtimeType, mh);
lagergren@247 235 code.add(cf);
lagergren@247 236
lagergren@247 237 return cf.getInvoker();
lagergren@137 238 }
lagergren@137 239
lagergren@247 240 private static Type runtimeType(final Object arg) {
lagergren@247 241 if (arg == null) {
lagergren@247 242 return Type.OBJECT;
lagergren@247 243 }
lagergren@247 244
lagergren@247 245 final Class<?> clazz = arg.getClass();
lagergren@247 246 assert !clazz.isPrimitive() : "always boxed";
lagergren@247 247 if (clazz == Double.class) {
lagergren@247 248 return JSType.isRepresentableAsInt((double)arg) ? Type.INT : Type.NUMBER;
lagergren@247 249 } else if (clazz == Integer.class) {
lagergren@247 250 return Type.INT;
lagergren@247 251 } else if (clazz == Long.class) {
lagergren@247 252 return Type.LONG;
lagergren@247 253 } else if (clazz == String.class) {
lagergren@247 254 return Type.STRING;
lagergren@247 255 }
lagergren@247 256 return Type.OBJECT;
lagergren@247 257 }
lagergren@247 258
lagergren@277 259 private static boolean canCoerce(final Object arg, final Type type) {
lagergren@277 260 Type argType = runtimeType(arg);
lagergren@277 261 if (Type.widest(argType, type) == type || arg == ScriptRuntime.UNDEFINED) {
lagergren@277 262 return true;
lagergren@277 263 }
lagergren@277 264 System.err.println(arg + " does not fit in "+ argType + " " + type + " " + arg.getClass());
lagergren@277 265 new Throwable().printStackTrace();
lagergren@247 266 return false;
lagergren@247 267 }
lagergren@247 268
lagergren@277 269 @SuppressWarnings("unused")
lagergren@277 270 private static boolean paramTypeGuard(final Type[] paramTypes, final Object... args) {
lagergren@277 271 final int length = args.length;
lagergren@277 272 assert args.length >= paramTypes.length;
lagergren@277 273
lagergren@277 274 //i==start, skip the this, callee params etc
lagergren@277 275 int start = args.length - paramTypes.length;
lagergren@277 276 for (int i = start; i < args.length; i++) {
lagergren@277 277 final Object arg = args[i];
lagergren@277 278 if (!canCoerce(arg, paramTypes[i - start])) {
lagergren@277 279 return false;
lagergren@277 280 }
lagergren@277 281 }
lagergren@277 282 return true;
lagergren@277 283 }
lagergren@277 284
lagergren@277 285 @SuppressWarnings("unused")
lagergren@277 286 private static int ensureInt(final Object arg) {
lagergren@277 287 if (arg instanceof Number) {
lagergren@277 288 return ((Number)arg).intValue();
lagergren@277 289 } else if (arg instanceof Undefined) {
lagergren@277 290 return 0;
lagergren@277 291 }
lagergren@277 292 throw new AssertionError(arg);
lagergren@277 293 }
lagergren@277 294
lagergren@277 295 /**
lagergren@277 296 * Given the runtime callsite args, compute a method type that is equivalent to what
lagergren@277 297 * was passed - this is typically a lot more specific that what the compiler has been
lagergren@277 298 * able to deduce
lagergren@277 299 * @param callSiteType callsite type for the compiled callsite target
lagergren@277 300 * @param args runtime arguments to the compiled callsite target
lagergren@277 301 * @return adjusted method type, narrowed as to conform to runtime callsite type instead
lagergren@277 302 */
lagergren@277 303 private static MethodType runtimeType(final MethodType callSiteType, final Object[] args) {
lagergren@277 304 if (args == null) {
lagergren@277 305 //for example bound, or otherwise runtime arguments to callsite unavailable, then
lagergren@277 306 //do not change the type
lagergren@277 307 return callSiteType;
lagergren@277 308 }
lagergren@277 309 final Class<?>[] paramTypes = new Class<?>[callSiteType.parameterCount()];
lagergren@277 310 final int start = args.length - callSiteType.parameterCount();
lagergren@277 311 for (int i = start; i < args.length; i++) {
lagergren@277 312 paramTypes[i - start] = runtimeType(args[i]).getTypeClass();
lagergren@277 313 }
lagergren@277 314 return MH.type(callSiteType.returnType(), paramTypes);
lagergren@277 315 }
lagergren@277 316
lagergren@277 317 private static ArrayList<Type> runtimeType(final MethodType mt) {
lagergren@277 318 final ArrayList<Type> type = new ArrayList<>();
lagergren@277 319 for (int i = 0; i < mt.parameterCount(); i++) {
lagergren@277 320 type.add(Type.typeFor(mt.parameterType(i)));
lagergren@277 321 }
lagergren@277 322 return type;
lagergren@277 323 }
lagergren@247 324
lagergren@137 325 @Override
lagergren@137 326 MethodHandle getBestInvoker(final MethodType callSiteType, final Object[] args) {
lagergren@277 327 final MethodType runtimeType = runtimeType(callSiteType, args);
lagergren@277 328 assert runtimeType.parameterCount() == callSiteType.parameterCount();
lagergren@247 329
lagergren@277 330 final MethodHandle mh = super.getBestInvoker(runtimeType, args);
lagergren@277 331
lagergren@277 332 /*
lagergren@277 333 * Not all functions can be specialized, for example, if we deemed memory
lagergren@277 334 * footprint too large to store a parse snapshot, or if it is meaningless
lagergren@277 335 * to do so, such as e.g. for runScript
lagergren@277 336 */
sundar@489 337 if (functionNode == null || !functionNode.canSpecialize()) {
lagergren@247 338 return mh;
lagergren@137 339 }
lagergren@247 340
lagergren@277 341 /*
lagergren@277 342 * Check if best invoker is equally specific or more specific than runtime
lagergren@277 343 * type. In that case, we don't need further specialization, but can use
lagergren@277 344 * whatever we have already. We know that it will match callSiteType, or it
lagergren@277 345 * would not have been returned from getBestInvoker
lagergren@277 346 */
lagergren@277 347 if (!code.isLessSpecificThan(runtimeType)) {
lagergren@252 348 return mh;
lagergren@252 349 }
lagergren@252 350
lagergren@247 351 int i;
lagergren@277 352 final FunctionNode snapshot = functionNode.getSnapshot();
lagergren@277 353 assert snapshot != null;
lagergren@247 354
lagergren@277 355 /*
lagergren@277 356 * Create a list of the arg types that the compiler knows about
lagergren@277 357 * typically, the runtime args are a lot more specific, and we should aggressively
lagergren@277 358 * try to use those whenever possible
lagergren@277 359 * We WILL try to make an aggressive guess as possible, and add guards if needed.
lagergren@277 360 * For example, if the compiler can deduce that we have a number type, but the runtime
lagergren@277 361 * passes and int, we might still want to keep it an int, and the gamble to
lagergren@277 362 * check that whatever is passed is int representable usually pays off
lagergren@277 363 * If the compiler only knows that a parameter is an "Object", it is still worth
lagergren@277 364 * it to try to specialize it by looking at the runtime arg.
lagergren@277 365 */
lagergren@277 366 final LinkedList<Type> compileTimeArgs = new LinkedList<>();
lagergren@277 367 for (i = callSiteType.parameterCount() - 1; i >= 0 && compileTimeArgs.size() < snapshot.getParameters().size(); i--) {
lagergren@277 368 compileTimeArgs.addFirst(Type.typeFor(callSiteType.parameterType(i)));
lagergren@247 369 }
lagergren@247 370
lagergren@277 371 /*
lagergren@277 372 * The classes known at compile time are a safe to generate as primitives without parameter guards
lagergren@277 373 * But the classes known at runtime (if more specific than compile time types) are safe to generate as primitives
lagergren@277 374 * IFF there are parameter guards
lagergren@277 375 */
lagergren@277 376 MethodHandle guard = null;
lagergren@277 377 final ArrayList<Type> runtimeParamTypes = runtimeType(runtimeType);
lagergren@277 378 while (runtimeParamTypes.size() > functionNode.getParameters().size()) {
lagergren@277 379 runtimeParamTypes.remove(0);
lagergren@247 380 }
lagergren@277 381 for (i = 0; i < compileTimeArgs.size(); i++) {
lagergren@277 382 final Type rparam = Type.typeFor(runtimeType.parameterType(i));
lagergren@277 383 final Type cparam = compileTimeArgs.get(i);
lagergren@247 384
lagergren@277 385 if (cparam.isObject() && !rparam.isObject()) {
lagergren@277 386 //check that the runtime object is still coercible to the runtime type, because compiler can't prove it's always primitive
lagergren@247 387 if (guard == null) {
lagergren@277 388 guard = MH.insertArguments(PARAM_TYPE_GUARD, 0, (Object)runtimeParamTypes.toArray(new Type[runtimeParamTypes.size()]));
lagergren@247 389 }
lagergren@247 390 }
lagergren@247 391 }
lagergren@247 392
lagergren@277 393 Compiler.LOG.info("Callsite specialized ", name, " runtimeType=", runtimeType, " parameters=", snapshot.getParameters(), " args=", Arrays.asList(args));
lagergren@247 394
lagergren@247 395 assert snapshot != null;
lagergren@247 396 assert snapshot != functionNode;
lagergren@247 397
lagergren@247 398 final Compiler compiler = new Compiler(installer);
lagergren@247 399
lagergren@277 400 final FunctionNode compiledSnapshot = compiler.compile(
lagergren@277 401 snapshot.setHints(
lagergren@277 402 null,
lagergren@277 403 new Compiler.Hints(runtimeParamTypes.toArray(new Type[runtimeParamTypes.size()]))));
lagergren@277 404
lagergren@277 405 /*
lagergren@277 406 * No matter how narrow your types were, they can never be narrower than Attr during recompile made them. I.e. you
lagergren@277 407 * can put an int into the function here, if you see it as a runtime type, but if the function uses a multiplication
lagergren@277 408 * on it, it will still need to be a double. At least until we have overflow checks. Similarly, if an int is
lagergren@277 409 * passed but it is used as a string, it makes no sense to make the parameter narrower than Object. At least until
lagergren@277 410 * the "different types for one symbol in difference places" work is done
lagergren@277 411 */
lagergren@247 412 compiler.install(compiledSnapshot);
lagergren@247 413
lagergren@277 414 return addCode(compiledSnapshot, runtimeType, guard, mh);
lagergren@247 415 }
lagergren@247 416
lagergren@247 417 private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
lagergren@247 418 return MH.findStatic(MethodHandles.lookup(), RecompilableScriptFunctionData.class, name, MH.type(rtype, types));
lagergren@137 419 }
lagergren@137 420
lagergren@137 421 }
lagergren@137 422

mercurial