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