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 static jdk.nashorn.internal.codegen.ObjectClassGenerator.ACCESSOR_TYPES; 29 import static jdk.nashorn.internal.codegen.ObjectClassGenerator.DEBUG_FIELDS; 30 import static jdk.nashorn.internal.codegen.ObjectClassGenerator.LOG; 31 import static jdk.nashorn.internal.codegen.ObjectClassGenerator.OBJECT_FIELDS_ONLY; 32 import static jdk.nashorn.internal.codegen.ObjectClassGenerator.PRIMITIVE_TYPE; 33 import static jdk.nashorn.internal.codegen.ObjectClassGenerator.createGetter; 34 import static jdk.nashorn.internal.codegen.ObjectClassGenerator.createGuardBoxedPrimitiveSetter; 35 import static jdk.nashorn.internal.codegen.ObjectClassGenerator.createSetter; 36 import static jdk.nashorn.internal.codegen.ObjectClassGenerator.getAccessorType; 37 import static jdk.nashorn.internal.codegen.ObjectClassGenerator.getAccessorTypeIndex; 38 import static jdk.nashorn.internal.codegen.ObjectClassGenerator.getNumberOfAccessorTypes; 39 import static jdk.nashorn.internal.lookup.Lookup.MH; 40 import static jdk.nashorn.internal.lookup.MethodHandleFactory.stripName; 41 42 import java.lang.invoke.MethodHandle; 43 import java.lang.invoke.MethodHandles; 44 import java.lang.invoke.MethodType; 45 import jdk.nashorn.internal.codegen.ObjectClassGenerator; 46 import jdk.nashorn.internal.codegen.types.Type; 47 import jdk.nashorn.internal.lookup.Lookup; 48 import jdk.nashorn.internal.lookup.MethodHandleFactory; 49 50 /** 51 * An AccessorProperty is the most generic property type. An AccessorProperty is 52 * represented as fields in a ScriptObject class. 53 */ 54 public final class AccessorProperty extends Property { 55 private static final MethodHandles.Lookup lookup = MethodHandles.lookup(); 56 private static final MethodHandle REPLACE_MAP = findOwnMH("replaceMap", Object.class, Object.class, PropertyMap.class, String.class, Class.class, Class.class); 57 58 private static final int NOOF_TYPES = getNumberOfAccessorTypes(); 59 60 /** 61 * Properties in different maps for the same structure class will share their field getters and setters. This could 62 * be further extended to other method handles that are looked up in the AccessorProperty constructor, but right now 63 * these are the most frequently retrieved ones, and lookup of method handle natives only registers in the profiler 64 * for them. 65 */ 66 private static ClassValue<GettersSetters> GETTERS_SETTERS = new ClassValue<GettersSetters>() { 67 @Override 68 protected GettersSetters computeValue(Class<?> structure) { 69 return new GettersSetters(structure); 70 } 71 }; 72 73 /** Property getter cache */ 74 private MethodHandle[] getters = new MethodHandle[NOOF_TYPES]; 75 76 private static final MethodType[] ACCESSOR_GETTER_TYPES = new MethodType[NOOF_TYPES]; 77 private static final MethodType[] ACCESSOR_SETTER_TYPES = new MethodType[NOOF_TYPES]; 78 private static final MethodType ACCESSOR_GETTER_PRIMITIVE_TYPE; 79 private static final MethodType ACCESSOR_SETTER_PRIMITIVE_TYPE; 80 private static final MethodHandle SPILL_ELEMENT_GETTER; 81 private static final MethodHandle SPILL_ELEMENT_SETTER; 82 83 private static final int SPILL_CACHE_SIZE = 8; 84 private static final MethodHandle[] SPILL_ACCESSORS = new MethodHandle[SPILL_CACHE_SIZE * 2]; 85 86 static { 87 MethodType getterPrimitiveType = null; 88 MethodType setterPrimitiveType = null; 89 90 for (int i = 0; i < NOOF_TYPES; i++) { 91 final Type type = ACCESSOR_TYPES.get(i); 92 ACCESSOR_GETTER_TYPES[i] = MH.type(type.getTypeClass(), Object.class); 93 ACCESSOR_SETTER_TYPES[i] = MH.type(void.class, Object.class, type.getTypeClass()); 94 95 if (type == PRIMITIVE_TYPE) { 96 getterPrimitiveType = ACCESSOR_GETTER_TYPES[i]; 97 setterPrimitiveType = ACCESSOR_SETTER_TYPES[i]; 98 } 99 } 100 101 ACCESSOR_GETTER_PRIMITIVE_TYPE = getterPrimitiveType; 102 ACCESSOR_SETTER_PRIMITIVE_TYPE = setterPrimitiveType; 103 104 final MethodType spillGetterType = MethodType.methodType(Object[].class, Object.class); 105 final MethodHandle spillGetter = MH.asType(MH.getter(MethodHandles.lookup(), ScriptObject.class, "spill", Object[].class), spillGetterType); 106 SPILL_ELEMENT_GETTER = MH.filterArguments(MH.arrayElementGetter(Object[].class), 0, spillGetter); 107 SPILL_ELEMENT_SETTER = MH.filterArguments(MH.arrayElementSetter(Object[].class), 0, spillGetter); 108 } 109 110 /** 111 * Create a new accessor property. Factory method used by nasgen generated code. 112 * 113 * @param key {@link Property} key. 114 * @param propertyFlags {@link Property} flags. 115 * @param getter {@link Property} get accessor method. 116 * @param setter {@link Property} set accessor method. 117 * 118 * @return New {@link AccessorProperty} created. 119 */ 120 public static AccessorProperty create(final String key, final int propertyFlags, final MethodHandle getter, final MethodHandle setter) { 121 return new AccessorProperty(key, propertyFlags, -1, getter, setter); 122 } 123 124 /** Seed getter for the primitive version of this field (in -Dnashorn.fields.dual=true mode) */ 125 private MethodHandle primitiveGetter; 126 127 /** Seed setter for the primitive version of this field (in -Dnashorn.fields.dual=true mode) */ 128 private MethodHandle primitiveSetter; 129 130 /** Seed getter for the Object version of this field */ 131 private MethodHandle objectGetter; 132 133 /** Seed setter for the Object version of this field */ 134 private MethodHandle objectSetter; 135 136 /** 137 * Current type of this object, in object only mode, this is an Object.class. In dual-fields mode 138 * null means undefined, and primitive types are allowed. The reason a special type is used for 139 * undefined, is that are no bits left to represent it in primitive types 140 */ 141 private Class<?> currentType; 142 143 /** 144 * Delegate constructor for bound properties. This is used for properties created by 145 * {@link ScriptRuntime#mergeScope} and the Nashorn {@code Object.bindProperties} method. 146 * The former is used to add a script's defined globals to the current global scope while 147 * still storing them in a JO-prefixed ScriptObject class. 148 * 149 * <p>All properties created by this constructor have the {@link #IS_BOUND} flag set.</p> 150 * 151 * @param property accessor property to rebind 152 * @param delegate delegate object to rebind receiver to 153 */ 154 AccessorProperty(final AccessorProperty property, final Object delegate) { 155 super(property); 156 157 this.primitiveGetter = bindTo(property.primitiveGetter, delegate); 158 this.primitiveSetter = bindTo(property.primitiveSetter, delegate); 159 this.objectGetter = bindTo(property.ensureObjectGetter(), delegate); 160 this.objectSetter = bindTo(property.ensureObjectSetter(), delegate); 161 162 // Properties created this way are bound to a delegate 163 this.flags |= IS_BOUND; 164 setCurrentType(property.getCurrentType()); 165 } 166 167 /** 168 * Constructor for spill properties. Array getters and setters will be created on demand. 169 * 170 * @param key the property key 171 * @param flags the property flags 172 * @param slot spill slot 173 */ 174 public AccessorProperty(final String key, final int flags, final int slot) { 175 super(key, flags, slot); 176 assert (flags & IS_SPILL) == IS_SPILL; 177 178 setCurrentType(Object.class); 179 } 180 181 /** 182 * Constructor. Similar to the constructor with both primitive getters and setters, the difference 183 * here being that only one getter and setter (setter is optional for non writable fields) is given 184 * to the constructor, and the rest are created from those. Used e.g. by Nasgen classes 185 * 186 * @param key the property key 187 * @param flags the property flags 188 * @param slot the property field number or spill slot 189 * @param getter the property getter 190 * @param setter the property setter or null if non writable, non configurable 191 */ 192 AccessorProperty(final String key, final int flags, final int slot, final MethodHandle getter, final MethodHandle setter) { 193 super(key, flags, slot); 194 195 // we don't need to prep the setters these will never be invalidated as this is a nasgen 196 // or known type getter/setter. No invalidations will take place 197 198 final Class<?> getterType = getter.type().returnType(); 199 final Class<?> setterType = setter == null ? null : setter.type().parameterType(1); 200 201 assert setterType == null || setterType == getterType; 202 203 if (getterType.isPrimitive()) { 204 for (int i = 0; i < NOOF_TYPES; i++) { 205 getters[i] = MH.asType( 206 Lookup.filterReturnType( 207 getter, 208 getAccessorType(i).getTypeClass()), 209 ACCESSOR_GETTER_TYPES[i]); 210 } 211 } else { 212 objectGetter = getter.type() != Lookup.GET_OBJECT_TYPE ? MH.asType(getter, Lookup.GET_OBJECT_TYPE) : getter; 213 objectSetter = setter != null && setter.type() != Lookup.SET_OBJECT_TYPE ? MH.asType(setter, Lookup.SET_OBJECT_TYPE) : setter; 214 } 215 216 setCurrentType(getterType); 217 } 218 219 private static class GettersSetters { 220 final MethodHandle[] getters; 221 final MethodHandle[] setters; 222 223 public GettersSetters(Class<?> structure) { 224 final int fieldCount = ObjectClassGenerator.getFieldCount(structure); 225 getters = new MethodHandle[fieldCount]; 226 setters = new MethodHandle[fieldCount]; 227 for(int i = 0; i < fieldCount; ++i) { 228 final String fieldName = ObjectClassGenerator.getFieldName(i, Type.OBJECT); 229 getters[i] = MH.asType(MH.getter(lookup, structure, fieldName, Type.OBJECT.getTypeClass()), Lookup.GET_OBJECT_TYPE); 230 setters[i] = MH.asType(MH.setter(lookup, structure, fieldName, Type.OBJECT.getTypeClass()), Lookup.SET_OBJECT_TYPE); 231 } 232 } 233 } 234 235 /** 236 * Constructor for dual field AccessorPropertys. 237 * 238 * @param key property key 239 * @param flags property flags 240 * @param structure structure for objects associated with this property 241 * @param slot property field number or spill slot 242 */ 243 public AccessorProperty(final String key, final int flags, final Class<?> structure, final int slot) { 244 super(key, flags, slot); 245 246 /* 247 * primitiveGetter and primitiveSetter are only used in dual fields mode. Setting them to null also 248 * works in dual field mode, it only means that the property never has a primitive 249 * representation. 250 */ 251 primitiveGetter = null; 252 primitiveSetter = null; 253 254 if (isParameter() && hasArguments()) { 255 final MethodHandle arguments = MH.getter(lookup, structure, "arguments", ScriptObject.class); 256 257 objectGetter = MH.asType(MH.insertArguments(MH.filterArguments(ScriptObject.GET_ARGUMENT.methodHandle(), 0, arguments), 1, slot), Lookup.GET_OBJECT_TYPE); 258 objectSetter = MH.asType(MH.insertArguments(MH.filterArguments(ScriptObject.SET_ARGUMENT.methodHandle(), 0, arguments), 1, slot), Lookup.SET_OBJECT_TYPE); 259 } else { 260 final GettersSetters gs = GETTERS_SETTERS.get(structure); 261 objectGetter = gs.getters[slot]; 262 objectSetter = gs.setters[slot]; 263 264 if (!OBJECT_FIELDS_ONLY) { 265 final String fieldNamePrimitive = ObjectClassGenerator.getFieldName(slot, PRIMITIVE_TYPE); 266 final Class<?> typeClass = PRIMITIVE_TYPE.getTypeClass(); 267 primitiveGetter = MH.asType(MH.getter(lookup, structure, fieldNamePrimitive, typeClass), ACCESSOR_GETTER_PRIMITIVE_TYPE); 268 primitiveSetter = MH.asType(MH.setter(lookup, structure, fieldNamePrimitive, typeClass), ACCESSOR_SETTER_PRIMITIVE_TYPE); 269 } 270 } 271 272 Class<?> initialType = null; 273 274 if (OBJECT_FIELDS_ONLY || isAlwaysObject()) { 275 initialType = Object.class; 276 } else if (!canBePrimitive()) { 277 info(key + " cannot be primitive"); 278 initialType = Object.class; 279 } else { 280 info(key + " CAN be primitive"); 281 if (!canBeUndefined()) { 282 info(key + " is always defined"); 283 initialType = int.class; //double works too for less type invalidation, but this requires experimentation, e.g. var x = 17; x += 2 will turn it into double now because of lack of range analysis 284 } 285 } 286 287 // is always object means "is never initialized to undefined, and always of object type 288 setCurrentType(initialType); 289 } 290 291 /** 292 * Copy constructor 293 * 294 * @param property source property 295 */ 296 protected AccessorProperty(final AccessorProperty property) { 297 super(property); 298 299 this.getters = property.getters; 300 this.primitiveGetter = property.primitiveGetter; 301 this.primitiveSetter = property.primitiveSetter; 302 this.objectGetter = property.objectGetter; 303 this.objectSetter = property.objectSetter; 304 305 setCurrentType(property.getCurrentType()); 306 } 307 308 private static MethodHandle bindTo(final MethodHandle mh, final Object receiver) { 309 if (mh == null) { 310 return null; 311 } 312 313 return MH.dropArguments(MH.bindTo(mh, receiver), 0, Object.class); 314 } 315 316 @Override 317 protected Property copy() { 318 return new AccessorProperty(this); 319 } 320 321 @Override 322 public void setObjectValue(final ScriptObject self, final ScriptObject owner, final Object value, final boolean strict) { 323 if (isSpill()) { 324 self.spill[getSlot()] = value; 325 } else { 326 try { 327 getSetter(Object.class, self.getMap()).invokeExact((Object)self, value); 328 } catch (final Error|RuntimeException e) { 329 throw e; 330 } catch (final Throwable e) { 331 throw new RuntimeException(e); 332 } 333 } 334 } 335 336 @Override 337 public Object getObjectValue(final ScriptObject self, final ScriptObject owner) { 338 if (isSpill()) { 339 return self.spill[getSlot()]; 340 } 341 342 try { 343 return getGetter(Object.class).invokeExact((Object)self); 344 } catch (final Error|RuntimeException e) { 345 throw e; 346 } catch (final Throwable e) { 347 throw new RuntimeException(e); 348 } 349 } 350 351 // Spill getters and setters are lazily initialized, see JDK-8011630 352 private MethodHandle ensureObjectGetter() { 353 if (isSpill() && objectGetter == null) { 354 objectGetter = getSpillGetter(); 355 } 356 return objectGetter; 357 } 358 359 private MethodHandle ensureObjectSetter() { 360 if (isSpill() && objectSetter == null) { 361 objectSetter = getSpillSetter(); 362 } 363 return objectSetter; 364 } 365 366 @Override 367 public MethodHandle getGetter(final Class<?> type) { 368 final int i = getAccessorTypeIndex(type); 369 ensureObjectGetter(); 370 371 if (getters[i] == null) { 372 getters[i] = debug( 373 createGetter(currentType, type, primitiveGetter, objectGetter), 374 currentType, type, "get"); 375 } 376 377 return getters[i]; 378 } 379 380 private Property getWiderProperty(final Class<?> type) { 381 final AccessorProperty newProperty = new AccessorProperty(this); 382 newProperty.invalidate(type); 383 return newProperty; 384 } 385 386 private PropertyMap getWiderMap(final PropertyMap oldMap, final Property newProperty) { 387 final PropertyMap newMap = oldMap.replaceProperty(this, newProperty); 388 assert oldMap.size() > 0; 389 assert newMap.size() == oldMap.size(); 390 return newMap; 391 } 392 393 // the final three arguments are for debug printout purposes only 394 @SuppressWarnings("unused") 395 private static Object replaceMap(final Object sobj, final PropertyMap newMap, final String key, final Class<?> oldType, final Class<?> newType) { 396 if (DEBUG_FIELDS) { 397 final PropertyMap oldMap = ((ScriptObject)sobj).getMap(); 398 info("Type change for '" + key + "' " + oldType + "=>" + newType); 399 finest("setting map " + sobj + " from " + Debug.id(oldMap) + " to " + Debug.id(newMap) + " " + oldMap + " => " + newMap); 400 } 401 ((ScriptObject)sobj).setMap(newMap); 402 return sobj; 403 } 404 405 private MethodHandle generateSetter(final Class<?> forType, final Class<?> type) { 406 ensureObjectSetter(); 407 MethodHandle mh = createSetter(forType, type, primitiveSetter, objectSetter); 408 mh = debug(mh, currentType, type, "set"); 409 return mh; 410 } 411 412 @Override 413 public MethodHandle getSetter(final Class<?> type, final PropertyMap currentMap) { 414 final int i = getAccessorTypeIndex(type); 415 final int ci = currentType == null ? -1 : getAccessorTypeIndex(currentType); 416 final Class<?> forType = currentType == null ? type : currentType; 417 418 //if we are asking for an object setter, but are still a primitive type, we might try to box it 419 MethodHandle mh; 420 421 if (needsInvalidator(i, ci)) { 422 final Property newProperty = getWiderProperty(type); 423 final PropertyMap newMap = getWiderMap(currentMap, newProperty); 424 final MethodHandle widerSetter = newProperty.getSetter(type, newMap); 425 final MethodHandle explodeTypeSetter = MH.filterArguments(widerSetter, 0, MH.insertArguments(REPLACE_MAP, 1, newMap, getKey(), currentType, type)); 426 if (currentType != null && currentType.isPrimitive() && type == Object.class) { 427 //might try a box check on this to avoid widening field to object storage 428 mh = createGuardBoxedPrimitiveSetter(currentType, generateSetter(currentType, currentType), explodeTypeSetter); 429 } else { 430 mh = explodeTypeSetter; 431 } 432 } else { 433 mh = generateSetter(forType, type); 434 } 435 436 return mh; 437 } 438 439 @Override 440 public boolean canChangeType() { 441 if (OBJECT_FIELDS_ONLY) { 442 return false; 443 } 444 return currentType != Object.class && (isConfigurable() || isWritable()); 445 } 446 447 private boolean needsInvalidator(final int ti, final int fti) { 448 return canChangeType() && ti > fti; 449 } 450 451 private void invalidate(final Class<?> newType) { 452 getters = new MethodHandle[NOOF_TYPES]; 453 setCurrentType(newType); 454 } 455 456 private MethodHandle getSpillGetter() { 457 final int slot = getSlot(); 458 MethodHandle getter = slot < SPILL_CACHE_SIZE ? SPILL_ACCESSORS[slot * 2] : null; 459 if (getter == null) { 460 getter = MH.insertArguments(SPILL_ELEMENT_GETTER, 1, slot); 461 if (slot < SPILL_CACHE_SIZE) { 462 SPILL_ACCESSORS[slot * 2 + 0] = getter; 463 } 464 } 465 return getter; 466 } 467 468 private MethodHandle getSpillSetter() { 469 final int slot = getSlot(); 470 MethodHandle setter = slot < SPILL_CACHE_SIZE ? SPILL_ACCESSORS[slot * 2 + 1] : null; 471 if (setter == null) { 472 setter = MH.insertArguments(SPILL_ELEMENT_SETTER, 1, slot); 473 if (slot < SPILL_CACHE_SIZE) { 474 SPILL_ACCESSORS[slot * 2 + 1] = setter; 475 } 476 } 477 return setter; 478 } 479 480 private static void finest(final String str) { 481 if (DEBUG_FIELDS) { 482 LOG.finest(str); 483 } 484 } 485 486 private static void info(final String str) { 487 if (DEBUG_FIELDS) { 488 LOG.info(str); 489 } 490 } 491 492 private MethodHandle debug(final MethodHandle mh, final Class<?> forType, final Class<?> type, final String tag) { 493 if (DEBUG_FIELDS) { 494 return MethodHandleFactory.addDebugPrintout( 495 LOG, 496 mh, 497 tag + " '" + getKey() + "' (property="+ Debug.id(this) + ", forType=" + stripName(forType) + ", type=" + stripName(type) + ')'); 498 } 499 return mh; 500 } 501 502 private void setCurrentType(final Class<?> currentType) { 503 this.currentType = currentType; 504 } 505 506 @Override 507 public Class<?> getCurrentType() { 508 return currentType; 509 } 510 511 private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) { 512 return MH.findStatic(lookup, AccessorProperty.class, name, MH.type(rtype, types)); 513 } 514 515 }