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.Compiler.SCRIPTS_PACKAGE; 29 import static jdk.nashorn.internal.codegen.CompilerConstants.ALLOCATE; 30 import static jdk.nashorn.internal.codegen.CompilerConstants.INIT_ARGUMENTS; 31 import static jdk.nashorn.internal.codegen.CompilerConstants.INIT_MAP; 32 import static jdk.nashorn.internal.codegen.CompilerConstants.INIT_SCOPE; 33 import static jdk.nashorn.internal.codegen.CompilerConstants.JAVA_THIS; 34 import static jdk.nashorn.internal.codegen.CompilerConstants.JS_OBJECT_DUAL_FIELD_PREFIX; 35 import static jdk.nashorn.internal.codegen.CompilerConstants.JS_OBJECT_SINGLE_FIELD_PREFIX; 36 import static jdk.nashorn.internal.codegen.CompilerConstants.className; 37 import static jdk.nashorn.internal.codegen.CompilerConstants.constructorNoLookup; 38 import static jdk.nashorn.internal.lookup.Lookup.MH; 39 import static jdk.nashorn.internal.runtime.JSType.CONVERT_OBJECT; 40 import static jdk.nashorn.internal.runtime.JSType.CONVERT_OBJECT_OPTIMISTIC; 41 import static jdk.nashorn.internal.runtime.JSType.GET_UNDEFINED; 42 import static jdk.nashorn.internal.runtime.JSType.TYPE_DOUBLE_INDEX; 43 import static jdk.nashorn.internal.runtime.JSType.TYPE_INT_INDEX; 44 import static jdk.nashorn.internal.runtime.JSType.TYPE_LONG_INDEX; 45 import static jdk.nashorn.internal.runtime.JSType.TYPE_OBJECT_INDEX; 46 import static jdk.nashorn.internal.runtime.JSType.TYPE_UNDEFINED_INDEX; 47 import static jdk.nashorn.internal.runtime.JSType.getAccessorTypeIndex; 48 import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.isValid; 49 50 import java.lang.invoke.MethodHandle; 51 import java.lang.invoke.MethodHandles; 52 import java.lang.invoke.MethodType; 53 import java.util.EnumSet; 54 import java.util.Iterator; 55 import java.util.LinkedList; 56 import java.util.List; 57 import jdk.nashorn.internal.codegen.ClassEmitter.Flag; 58 import jdk.nashorn.internal.codegen.types.Type; 59 import jdk.nashorn.internal.runtime.AccessorProperty; 60 import jdk.nashorn.internal.runtime.AllocationStrategy; 61 import jdk.nashorn.internal.runtime.Context; 62 import jdk.nashorn.internal.runtime.FunctionScope; 63 import jdk.nashorn.internal.runtime.JSType; 64 import jdk.nashorn.internal.runtime.PropertyMap; 65 import jdk.nashorn.internal.runtime.ScriptEnvironment; 66 import jdk.nashorn.internal.runtime.ScriptObject; 67 import jdk.nashorn.internal.runtime.Undefined; 68 import jdk.nashorn.internal.runtime.UnwarrantedOptimismException; 69 import jdk.nashorn.internal.runtime.logging.DebugLogger; 70 import jdk.nashorn.internal.runtime.logging.Loggable; 71 import jdk.nashorn.internal.runtime.logging.Logger; 72 import jdk.nashorn.internal.runtime.options.Options; 73 74 /** 75 * Generates the ScriptObject subclass structure with fields for a user objects. 76 */ 77 @Logger(name="fields") 78 public final class ObjectClassGenerator implements Loggable { 79 80 /** 81 * Type guard to make sure we don't unnecessarily explode field storages. Rather unbox e.g. 82 * a java.lang.Number than blow up the field. Gradually, optimistic types should create almost 83 * no boxed types 84 */ 85 private static final MethodHandle IS_TYPE_GUARD = findOwnMH("isType", boolean.class, Class.class, Object.class); 86 87 /** 88 * Marker for scope parameters 89 */ 90 private static final String SCOPE_MARKER = "P"; 91 92 /** 93 * Minimum number of extra fields in an object. 94 */ 95 static final int FIELD_PADDING = 4; 96 97 /** 98 * Debug field logger 99 * Should we print debugging information for fields when they are generated and getters/setters are called? 100 */ 101 private final DebugLogger log; 102 103 /** Field types for object-only fields */ 104 private static final Type[] FIELD_TYPES_OBJECT = new Type[] { Type.OBJECT }; 105 /** Field types for dual primitive/object fields */ 106 private static final Type[] FIELD_TYPES_DUAL = new Type[] { Type.LONG, Type.OBJECT }; 107 108 /** What type is the primitive type in dual representation */ 109 public static final Type PRIMITIVE_FIELD_TYPE = Type.LONG; 110 111 private static final MethodHandle GET_DIFFERENT = findOwnMH("getDifferent", Object.class, Object.class, Class.class, MethodHandle.class, MethodHandle.class, int.class); 112 private static final MethodHandle GET_DIFFERENT_UNDEFINED = findOwnMH("getDifferentUndefined", Object.class, int.class); 113 114 private static boolean initialized = false; 115 116 /** The context */ 117 private final Context context; 118 119 private final boolean dualFields; 120 121 /** 122 * Constructor 123 * 124 * @param context a context 125 * @param dualFields whether to use dual fields representation 126 */ 127 public ObjectClassGenerator(final Context context, final boolean dualFields) { 128 this.context = context; 129 this.dualFields = dualFields; 130 assert context != null; 131 this.log = initLogger(context); 132 if (!initialized) { 133 initialized = true; 134 if (!dualFields) { 135 log.warning("Running with object fields only - this is a deprecated configuration."); 136 } 137 } 138 } 139 140 @Override 141 public DebugLogger getLogger() { 142 return log; 143 } 144 145 @Override 146 public DebugLogger initLogger(final Context ctxt) { 147 return ctxt.getLogger(this.getClass()); 148 } 149 150 /** 151 * Pack a number into a primitive long field 152 * @param n number object 153 * @return primitive long value with all the bits in the number 154 */ 155 public static long pack(final Number n) { 156 if (n instanceof Integer) { 157 return n.intValue(); 158 } else if (n instanceof Long) { 159 return n.longValue(); 160 } else if (n instanceof Double) { 161 return Double.doubleToRawLongBits(n.doubleValue()); 162 } 163 throw new AssertionError("cannot pack" + n); 164 } 165 166 private static String getPrefixName(final boolean dualFields) { 167 return dualFields ? JS_OBJECT_DUAL_FIELD_PREFIX.symbolName() : JS_OBJECT_SINGLE_FIELD_PREFIX.symbolName(); 168 } 169 170 private static String getPrefixName(final String className) { 171 if (className.startsWith(JS_OBJECT_DUAL_FIELD_PREFIX.symbolName())) { 172 return getPrefixName(true); 173 } else if (className.startsWith(JS_OBJECT_SINGLE_FIELD_PREFIX.symbolName())) { 174 return getPrefixName(false); 175 } 176 throw new AssertionError("Not a structure class: " + className); 177 } 178 179 /** 180 * Returns the class name for JavaScript objects with fieldCount fields. 181 * 182 * @param fieldCount Number of fields to allocate. 183 * @param dualFields whether to use dual fields representation 184 * @return The class name. 185 */ 186 public static String getClassName(final int fieldCount, final boolean dualFields) { 187 final String prefix = getPrefixName(dualFields); 188 return fieldCount != 0 ? SCRIPTS_PACKAGE + '/' + prefix + fieldCount : 189 SCRIPTS_PACKAGE + '/' + prefix; 190 } 191 192 /** 193 * Returns the class name for JavaScript scope with fieldCount fields and 194 * paramCount parameters. 195 * 196 * @param fieldCount Number of fields to allocate. 197 * @param paramCount Number of parameters to allocate 198 * @param dualFields whether to use dual fields representation 199 * @return The class name. 200 */ 201 public static String getClassName(final int fieldCount, final int paramCount, final boolean dualFields) { 202 return SCRIPTS_PACKAGE + '/' + getPrefixName(dualFields) + fieldCount + SCOPE_MARKER + paramCount; 203 } 204 205 /** 206 * Returns the number of fields in the JavaScript scope class. Its name had to be generated using either 207 * {@link #getClassName(int, boolean)} or {@link #getClassName(int, int, boolean)}. 208 * @param clazz the JavaScript scope class. 209 * @return the number of fields in the scope class. 210 */ 211 public static int getFieldCount(final Class<?> clazz) { 212 final String name = clazz.getSimpleName(); 213 final String prefix = getPrefixName(name); 214 215 if (prefix.equals(name)) { 216 return 0; 217 } 218 final int scopeMarker = name.indexOf(SCOPE_MARKER); 219 return Integer.parseInt(scopeMarker == -1 ? name.substring(prefix.length()) : name.substring(prefix.length(), scopeMarker)); 220 } 221 222 /** 223 * Returns the name of a field based on number and type. 224 * 225 * @param fieldIndex Ordinal of field. 226 * @param type Type of field. 227 * 228 * @return The field name. 229 */ 230 public static String getFieldName(final int fieldIndex, final Type type) { 231 return type.getDescriptor().substring(0, 1) + fieldIndex; 232 } 233 234 /** 235 * In the world of Object fields, we also have no undefined SwitchPoint, to reduce as much potential 236 * MethodHandle overhead as possible. In that case, we explicitly need to assign undefined to fields 237 * when we initialize them. 238 * 239 * @param init constructor to generate code in 240 * @param className name of class 241 * @param fieldNames fields to initialize to undefined, where applicable 242 */ 243 private void initializeToUndefined(final MethodEmitter init, final String className, final List<String> fieldNames) { 244 if (dualFields) { 245 // no need to initialize anything to undefined in the dual field world 246 // - then we have a constant getter for undefined for any unknown type 247 return; 248 } 249 250 if (fieldNames.isEmpty()) { 251 return; 252 } 253 254 init.load(Type.OBJECT, JAVA_THIS.slot()); 255 init.loadUndefined(Type.OBJECT); 256 257 final Iterator<String> iter = fieldNames.iterator(); 258 while (iter.hasNext()) { 259 final String fieldName = iter.next(); 260 if (iter.hasNext()) { 261 init.dup2(); 262 } 263 init.putField(className, fieldName, Type.OBJECT.getDescriptor()); 264 } 265 } 266 267 /** 268 * Generate the byte codes for a JavaScript object class or scope. 269 * Class name is a function of number of fields and number of param 270 * fields 271 * 272 * @param descriptor Descriptor pulled from class name. 273 * 274 * @return Byte codes for generated class. 275 */ 276 public byte[] generate(final String descriptor) { 277 final String[] counts = descriptor.split(SCOPE_MARKER); 278 final int fieldCount = Integer.valueOf(counts[0]); 279 280 if (counts.length == 1) { 281 return generate(fieldCount); 282 } 283 284 final int paramCount = Integer.valueOf(counts[1]); 285 286 return generate(fieldCount, paramCount); 287 } 288 289 /** 290 * Generate the byte codes for a JavaScript object class with fieldCount fields. 291 * 292 * @param fieldCount Number of fields in the JavaScript object. 293 * 294 * @return Byte codes for generated class. 295 */ 296 public byte[] generate(final int fieldCount) { 297 final String className = getClassName(fieldCount, dualFields); 298 final String superName = className(ScriptObject.class); 299 final ClassEmitter classEmitter = newClassEmitter(className, superName); 300 301 addFields(classEmitter, fieldCount); 302 303 final MethodEmitter init = newInitMethod(classEmitter); 304 init.returnVoid(); 305 init.end(); 306 307 final MethodEmitter initWithSpillArrays = newInitWithSpillArraysMethod(classEmitter, ScriptObject.class); 308 initWithSpillArrays.returnVoid(); 309 initWithSpillArrays.end(); 310 311 newEmptyInit(className, classEmitter); 312 newAllocate(className, classEmitter); 313 314 return toByteArray(className, classEmitter); 315 } 316 317 /** 318 * Generate the byte codes for a JavaScript scope class with fieldCount fields 319 * and paramCount parameters. 320 * 321 * @param fieldCount Number of fields in the JavaScript scope. 322 * @param paramCount Number of parameters in the JavaScript scope 323 * . 324 * @return Byte codes for generated class. 325 */ 326 public byte[] generate(final int fieldCount, final int paramCount) { 327 final String className = getClassName(fieldCount, paramCount, dualFields); 328 final String superName = className(FunctionScope.class); 329 final ClassEmitter classEmitter = newClassEmitter(className, superName); 330 final List<String> initFields = addFields(classEmitter, fieldCount); 331 332 final MethodEmitter init = newInitScopeMethod(classEmitter); 333 initializeToUndefined(init, className, initFields); 334 init.returnVoid(); 335 init.end(); 336 337 final MethodEmitter initWithSpillArrays = newInitWithSpillArraysMethod(classEmitter, FunctionScope.class); 338 initializeToUndefined(initWithSpillArrays, className, initFields); 339 initWithSpillArrays.returnVoid(); 340 initWithSpillArrays.end(); 341 342 final MethodEmitter initWithArguments = newInitScopeWithArgumentsMethod(classEmitter); 343 initializeToUndefined(initWithArguments, className, initFields); 344 initWithArguments.returnVoid(); 345 initWithArguments.end(); 346 347 return toByteArray(className, classEmitter); 348 } 349 350 /** 351 * Generates the needed fields. 352 * 353 * @param classEmitter Open class emitter. 354 * @param fieldCount Number of fields. 355 * 356 * @return List fields that need to be initialized. 357 */ 358 private List<String> addFields(final ClassEmitter classEmitter, final int fieldCount) { 359 final List<String> initFields = new LinkedList<>(); 360 final Type[] fieldTypes = dualFields ? FIELD_TYPES_DUAL : FIELD_TYPES_OBJECT; 361 for (int i = 0; i < fieldCount; i++) { 362 for (final Type type : fieldTypes) { 363 final String fieldName = getFieldName(i, type); 364 classEmitter.field(fieldName, type.getTypeClass()); 365 366 if (type == Type.OBJECT) { 367 initFields.add(fieldName); 368 } 369 } 370 } 371 372 return initFields; 373 } 374 375 /** 376 * Allocate and initialize a new class emitter. 377 * 378 * @param className Name of JavaScript class. 379 * 380 * @return Open class emitter. 381 */ 382 private ClassEmitter newClassEmitter(final String className, final String superName) { 383 final ClassEmitter classEmitter = new ClassEmitter(context, className, superName); 384 classEmitter.begin(); 385 386 return classEmitter; 387 } 388 389 /** 390 * Allocate and initialize a new <init> method. 391 * 392 * @param classEmitter Open class emitter. 393 * 394 * @return Open method emitter. 395 */ 396 private static MethodEmitter newInitMethod(final ClassEmitter classEmitter) { 397 final MethodEmitter init = classEmitter.init(PropertyMap.class); 398 init.begin(); 399 init.load(Type.OBJECT, JAVA_THIS.slot()); 400 init.load(Type.OBJECT, INIT_MAP.slot()); 401 init.invoke(constructorNoLookup(ScriptObject.class, PropertyMap.class)); 402 403 return init; 404 } 405 406 private static MethodEmitter newInitWithSpillArraysMethod(final ClassEmitter classEmitter, final Class<?> superClass) { 407 final MethodEmitter init = classEmitter.init(PropertyMap.class, long[].class, Object[].class); 408 init.begin(); 409 init.load(Type.OBJECT, JAVA_THIS.slot()); 410 init.load(Type.OBJECT, INIT_MAP.slot()); 411 init.load(Type.LONG_ARRAY, 2); 412 init.load(Type.OBJECT_ARRAY, 3); 413 init.invoke(constructorNoLookup(superClass, PropertyMap.class, long[].class, Object[].class)); 414 415 return init; 416 } 417 418 /** 419 * Allocate and initialize a new <init> method for scopes. 420 * @param classEmitter Open class emitter. 421 * @return Open method emitter. 422 */ 423 private static MethodEmitter newInitScopeMethod(final ClassEmitter classEmitter) { 424 final MethodEmitter init = classEmitter.init(PropertyMap.class, ScriptObject.class); 425 init.begin(); 426 init.load(Type.OBJECT, JAVA_THIS.slot()); 427 init.load(Type.OBJECT, INIT_MAP.slot()); 428 init.load(Type.OBJECT, INIT_SCOPE.slot()); 429 init.invoke(constructorNoLookup(FunctionScope.class, PropertyMap.class, ScriptObject.class)); 430 431 return init; 432 } 433 434 /** 435 * Allocate and initialize a new <init> method for scopes with arguments. 436 * @param classEmitter Open class emitter. 437 * @return Open method emitter. 438 */ 439 private static MethodEmitter newInitScopeWithArgumentsMethod(final ClassEmitter classEmitter) { 440 final MethodEmitter init = classEmitter.init(PropertyMap.class, ScriptObject.class, ScriptObject.class); 441 init.begin(); 442 init.load(Type.OBJECT, JAVA_THIS.slot()); 443 init.load(Type.OBJECT, INIT_MAP.slot()); 444 init.load(Type.OBJECT, INIT_SCOPE.slot()); 445 init.load(Type.OBJECT, INIT_ARGUMENTS.slot()); 446 init.invoke(constructorNoLookup(FunctionScope.class, PropertyMap.class, ScriptObject.class, ScriptObject.class)); 447 448 return init; 449 } 450 451 /** 452 * Add an empty <init> method to the JavaScript class. 453 * 454 * @param classEmitter Open class emitter. 455 * @param className Name of JavaScript class. 456 */ 457 private static void newEmptyInit(final String className, final ClassEmitter classEmitter) { 458 final MethodEmitter emptyInit = classEmitter.init(); 459 emptyInit.begin(); 460 emptyInit.load(Type.OBJECT, JAVA_THIS.slot()); 461 emptyInit.loadNull(); 462 emptyInit.invoke(constructorNoLookup(className, PropertyMap.class)); 463 emptyInit.returnVoid(); 464 emptyInit.end(); 465 } 466 467 /** 468 * Add an empty <init> method to the JavaScript class. 469 * 470 * @param classEmitter Open class emitter. 471 * @param className Name of JavaScript class. 472 */ 473 private static void newAllocate(final String className, final ClassEmitter classEmitter) { 474 final MethodEmitter allocate = classEmitter.method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), ALLOCATE.symbolName(), ScriptObject.class, PropertyMap.class); 475 allocate.begin(); 476 allocate._new(className, Type.typeFor(ScriptObject.class)); 477 allocate.dup(); 478 allocate.load(Type.typeFor(PropertyMap.class), 0); 479 allocate.invoke(constructorNoLookup(className, PropertyMap.class)); 480 allocate._return(); 481 allocate.end(); 482 } 483 484 /** 485 * Collects the byte codes for a generated JavaScript class. 486 * 487 * @param classEmitter Open class emitter. 488 * @return Byte codes for the class. 489 */ 490 private byte[] toByteArray(final String className, final ClassEmitter classEmitter) { 491 classEmitter.end(); 492 493 final byte[] code = classEmitter.toByteArray(); 494 final ScriptEnvironment env = context.getEnv(); 495 496 DumpBytecode.dumpBytecode(env, log, code, className); 497 498 if (env._verify_code) { 499 context.verify(code); 500 } 501 502 return code; 503 } 504 505 /** Double to long bits, used with --dual-fields for primitive double values */ 506 public static final MethodHandle PACK_DOUBLE = 507 MH.explicitCastArguments(MH.findStatic(MethodHandles.publicLookup(), Double.class, "doubleToRawLongBits", MH.type(long.class, double.class)), MH.type(long.class, double.class)); 508 509 /** double bits to long, used with --dual-fields for primitive double values */ 510 public static final MethodHandle UNPACK_DOUBLE = 511 MH.findStatic(MethodHandles.publicLookup(), Double.class, "longBitsToDouble", MH.type(double.class, long.class)); 512 513 //type != forType, so use the correct getter for forType, box it and throw 514 @SuppressWarnings("unused") 515 private static Object getDifferent(final Object receiver, final Class<?> forType, final MethodHandle primitiveGetter, final MethodHandle objectGetter, final int programPoint) { 516 //create the sametype getter, and upcast to value. no matter what the store format is, 517 // 518 final MethodHandle sameTypeGetter = getterForType(forType, primitiveGetter, objectGetter); 519 final MethodHandle mh = MH.asType(sameTypeGetter, sameTypeGetter.type().changeReturnType(Object.class)); 520 try { 521 final Object value = mh.invokeExact(receiver); 522 throw new UnwarrantedOptimismException(value, programPoint); 523 } catch (final Error | RuntimeException e) { 524 throw e; 525 } catch (final Throwable e) { 526 throw new RuntimeException(e); 527 } 528 } 529 530 @SuppressWarnings("unused") 531 private static Object getDifferentUndefined(final int programPoint) { 532 throw new UnwarrantedOptimismException(Undefined.getUndefined(), programPoint); 533 } 534 535 private static MethodHandle getterForType(final Class<?> forType, final MethodHandle primitiveGetter, final MethodHandle objectGetter) { 536 switch (getAccessorTypeIndex(forType)) { 537 case TYPE_INT_INDEX: 538 return MH.explicitCastArguments(primitiveGetter, primitiveGetter.type().changeReturnType(int.class)); 539 case TYPE_LONG_INDEX: 540 return primitiveGetter; 541 case TYPE_DOUBLE_INDEX: 542 return MH.filterReturnValue(primitiveGetter, UNPACK_DOUBLE); 543 case TYPE_OBJECT_INDEX: 544 return objectGetter; 545 default: 546 throw new AssertionError(forType); 547 } 548 } 549 550 //no optimism here. we do unconditional conversion to types 551 private static MethodHandle createGetterInner(final Class<?> forType, final Class<?> type, final MethodHandle primitiveGetter, final MethodHandle objectGetter, final List<MethodHandle> converters, final int programPoint) { 552 final int fti = forType == null ? TYPE_UNDEFINED_INDEX : getAccessorTypeIndex(forType); 553 final int ti = getAccessorTypeIndex(type); 554 //this means fail if forType != type 555 final boolean isOptimistic = converters == CONVERT_OBJECT_OPTIMISTIC; 556 final boolean isPrimitiveStorage = forType != null && forType.isPrimitive(); 557 558 //which is the primordial getter 559 final MethodHandle getter = primitiveGetter == null ? objectGetter : isPrimitiveStorage ? primitiveGetter : objectGetter; 560 561 if (forType == null) { 562 if (isOptimistic) { 563 //return undefined if asking for object. otherwise throw UnwarrantedOptimismException 564 if (ti == TYPE_OBJECT_INDEX) { 565 return MH.dropArguments(GET_UNDEFINED.get(TYPE_OBJECT_INDEX), 0, Object.class); 566 } 567 //throw exception 568 return MH.asType( 569 MH.dropArguments( 570 MH.insertArguments( 571 GET_DIFFERENT_UNDEFINED, 572 0, 573 programPoint), 574 0, 575 Object.class), 576 getter.type().changeReturnType(type)); 577 } 578 //return an undefined and coerce it to the appropriate type 579 return MH.dropArguments(GET_UNDEFINED.get(ti), 0, Object.class); 580 } 581 582 assert primitiveGetter != null || forType == Object.class : forType; 583 584 if (isOptimistic) { 585 if (fti < ti) { 586 //asking for a wider type than currently stored. then it's OK to coerce. 587 //e.g. stored as int, ask for long or double 588 //e.g. stored as long, ask for double 589 assert fti != TYPE_UNDEFINED_INDEX; 590 final MethodHandle tgetter = getterForType(forType, primitiveGetter, objectGetter); 591 return MH.asType(tgetter, tgetter.type().changeReturnType(type)); 592 } else if (fti == ti) { 593 //Fast path, never throw exception - exact getter, just unpack if needed 594 return getterForType(forType, primitiveGetter, objectGetter); 595 } else { 596 assert fti > ti; 597 //if asking for a narrower type than the storage - throw exception 598 //unless FTI is object, in that case we have to go through the converters 599 //there is no 600 if (fti == TYPE_OBJECT_INDEX) { 601 return MH.filterReturnValue( 602 objectGetter, 603 MH.insertArguments( 604 converters.get(ti), 605 1, 606 programPoint)); 607 } 608 609 //asking for narrower primitive than we have stored, that is an 610 //UnwarrantedOptimismException 611 return MH.asType( 612 MH.filterArguments( 613 objectGetter, 614 0, 615 MH.insertArguments( 616 GET_DIFFERENT, 617 1, 618 forType, 619 primitiveGetter, 620 objectGetter, 621 programPoint)), 622 objectGetter.type().changeReturnType(type)); 623 } 624 } 625 626 assert !isOptimistic; 627 //freely coerce the result to whatever you asked for, this is e.g. Object->int for a & b 628 final MethodHandle tgetter = getterForType(forType, primitiveGetter, objectGetter); 629 if (fti == TYPE_OBJECT_INDEX) { 630 if (fti != ti) { 631 return MH.filterReturnValue(tgetter, CONVERT_OBJECT.get(ti)); 632 } 633 return tgetter; 634 } 635 636 assert primitiveGetter != null; 637 final MethodType tgetterType = tgetter.type(); 638 switch (fti) { 639 case TYPE_INT_INDEX: { 640 return MH.asType(tgetter, tgetterType.changeReturnType(type)); 641 } 642 case TYPE_LONG_INDEX: 643 switch (ti) { 644 case TYPE_INT_INDEX: 645 //get int while an int, truncating cast of long value 646 return MH.filterReturnValue(tgetter, JSType.TO_INT32_L.methodHandle); 647 case TYPE_LONG_INDEX: 648 return primitiveGetter; 649 default: 650 return MH.asType(tgetter, tgetterType.changeReturnType(type)); 651 } 652 case TYPE_DOUBLE_INDEX: 653 switch (ti) { 654 case TYPE_INT_INDEX: 655 return MH.filterReturnValue(tgetter, JSType.TO_INT32_D.methodHandle); 656 case TYPE_LONG_INDEX: 657 return MH.explicitCastArguments(tgetter, tgetterType.changeReturnType(type)); 658 case TYPE_DOUBLE_INDEX: 659 assert tgetterType.returnType() == double.class; 660 return tgetter; 661 default: 662 return MH.asType(tgetter, tgetterType.changeReturnType(Object.class)); 663 } 664 default: 665 throw new UnsupportedOperationException(forType + "=>" + type); 666 } 667 } 668 669 /** 670 * Given a primitiveGetter (optional for non dual fields) and an objectSetter that retrieve 671 * the primitive and object version of a field respectively, return one with the correct 672 * method type and the correct filters. For example, if the value is stored as a double 673 * and we want an Object getter, in the dual fields world we'd pick the primitiveGetter, 674 * which reads a long, use longBitsToDouble on the result to unpack it, and then change the 675 * return type to Object, boxing it. In the objects only world there are only object fields, 676 * primitives are boxed when asked for them and we don't need to bother with primitive encoding 677 * (or even undefined, which if forType==null) representation, so we just return whatever is 678 * in the object field. The object field is always initiated to Undefined, so here, where we have 679 * the representation for Undefined in all our bits, this is not a problem. 680 * <p> 681 * Representing undefined in a primitive is hard, for an int there aren't enough bits, for a long 682 * we could limit the width of a representation, and for a double (as long as it is stored as long, 683 * as all NaNs will turn into QNaN on ia32, which is one bit pattern, we should use a special NaN). 684 * Naturally we could have special undefined values for all types which mean "go look in a wider field", 685 * but the guards needed on every getter took too much time. 686 * <p> 687 * To see how this is used, look for example in {@link AccessorProperty#getGetter} 688 * <p> 689 * @param forType representation of the underlying type in the field, null if undefined 690 * @param type type to retrieve it as 691 * @param primitiveGetter getter to read the primitive version of this field (null if Objects Only) 692 * @param objectGetter getter to read the object version of this field 693 * @param programPoint program point for getter, if program point is INVALID_PROGRAM_POINT, then this is not an optimistic getter 694 * 695 * @return getter for the given representation that returns the given type 696 */ 697 public static MethodHandle createGetter(final Class<?> forType, final Class<?> type, final MethodHandle primitiveGetter, final MethodHandle objectGetter, final int programPoint) { 698 return createGetterInner( 699 forType, 700 type, 701 primitiveGetter, 702 objectGetter, 703 isValid(programPoint) ? CONVERT_OBJECT_OPTIMISTIC : CONVERT_OBJECT, 704 programPoint); 705 } 706 707 /** 708 * This is similar to the {@link ObjectClassGenerator#createGetter} function. Performs 709 * the necessary operations to massage a setter operand of type {@code type} to 710 * fit into the primitive field (if primitive and dual fields is enabled) or into 711 * the object field (box if primitive and dual fields is disabled) 712 * 713 * @param forType representation of the underlying object 714 * @param type representation of field to write, and setter signature 715 * @param primitiveSetter setter that writes to the primitive field (null if Objects Only) 716 * @param objectSetter setter that writes to the object field 717 * 718 * @return the setter for the given representation that takes a {@code type} 719 */ 720 public static MethodHandle createSetter(final Class<?> forType, final Class<?> type, final MethodHandle primitiveSetter, final MethodHandle objectSetter) { 721 assert forType != null; 722 723 final int fti = getAccessorTypeIndex(forType); 724 final int ti = getAccessorTypeIndex(type); 725 726 if (fti == TYPE_OBJECT_INDEX || primitiveSetter == null) { 727 if (ti == TYPE_OBJECT_INDEX) { 728 return objectSetter; 729 } 730 731 return MH.asType(objectSetter, objectSetter.type().changeParameterType(1, type)); 732 } 733 734 final MethodType pmt = primitiveSetter.type(); 735 736 switch (fti) { 737 case TYPE_INT_INDEX: 738 case TYPE_LONG_INDEX: 739 switch (ti) { 740 case TYPE_INT_INDEX: 741 return MH.asType(primitiveSetter, pmt.changeParameterType(1, int.class)); 742 case TYPE_LONG_INDEX: 743 return primitiveSetter; 744 case TYPE_DOUBLE_INDEX: 745 return MH.filterArguments(primitiveSetter, 1, PACK_DOUBLE); 746 default: 747 return objectSetter; 748 } 749 case TYPE_DOUBLE_INDEX: 750 if (ti == TYPE_OBJECT_INDEX) { 751 return objectSetter; 752 } 753 return MH.asType(MH.filterArguments(primitiveSetter, 1, PACK_DOUBLE), pmt.changeParameterType(1, type)); 754 default: 755 throw new UnsupportedOperationException(forType + "=>" + type); 756 } 757 } 758 759 @SuppressWarnings("unused") 760 private static boolean isType(final Class<?> boxedForType, final Object x) { 761 return x != null && x.getClass() == boxedForType; 762 } 763 764 private static Class<? extends Number> getBoxedType(final Class<?> forType) { 765 if (forType == int.class) { 766 return Integer.class; 767 } 768 769 if (forType == long.class) { 770 return Long.class; 771 } 772 773 if (forType == double.class) { 774 return Double.class; 775 } 776 777 assert false; 778 return null; 779 } 780 781 /** 782 * If we are setting boxed types (because the compiler couldn't determine which they were) to 783 * a primitive field, we can reuse the primitive field getter, as long as we are setting an element 784 * of the same boxed type as the primitive type representation 785 * 786 * @param forType the current type 787 * @param primitiveSetter primitive setter for the current type with an element of the current type 788 * @param objectSetter the object setter 789 * 790 * @return method handle that checks if the element to be set is of the currenttype, even though it's boxed 791 * and instead of using the generic object setter, that would blow up the type and invalidate the map, 792 * unbox it and call the primitive setter instead 793 */ 794 public static MethodHandle createGuardBoxedPrimitiveSetter(final Class<?> forType, final MethodHandle primitiveSetter, final MethodHandle objectSetter) { 795 final Class<? extends Number> boxedForType = getBoxedType(forType); 796 //object setter that checks for primitive if current type is primitive 797 return MH.guardWithTest( 798 MH.insertArguments( 799 MH.dropArguments( 800 IS_TYPE_GUARD, 801 1, 802 Object.class), 803 0, 804 boxedForType), 805 MH.asType( 806 primitiveSetter, 807 objectSetter.type()), 808 objectSetter); 809 } 810 /** 811 * Add padding to field count to avoid creating too many classes and have some spare fields 812 * @param count the field count 813 * @return the padded field count 814 */ 815 static int getPaddedFieldCount(final int count) { 816 return count / FIELD_PADDING * FIELD_PADDING + FIELD_PADDING; 817 } 818 819 private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) { 820 return MH.findStatic(MethodHandles.lookup(), ObjectClassGenerator.class, name, MH.type(rtype, types)); 821 } 822 823 /** 824 * Creates the allocator class name and property map for a constructor function with the specified 825 * number of "this" properties that it initializes. 826 * @param thisProperties number of properties assigned to "this" 827 * @return the allocation strategy 828 */ 829 static AllocationStrategy createAllocationStrategy(final int thisProperties, final boolean dualFields) { 830 final int paddedFieldCount = getPaddedFieldCount(thisProperties); 831 return new AllocationStrategy(paddedFieldCount, dualFields); 832 } 833 }