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. This is used when adding properties to the Global scope, which
145 * is necessary for outermost levels in a script (the ScriptObject is represented by
146 * a JO-prefixed ScriptObject class, but the properties need to be in the Global scope
147 * and are thus rebound with that as receiver
148 *
149 * @param property accessor property to rebind
150 * @param delegate delegate object to rebind receiver to
151 */
152 AccessorProperty(final AccessorProperty property, final Object delegate) {
153 super(property);
154
155 this.primitiveGetter = bindTo(property.primitiveGetter, delegate);
156 this.primitiveSetter = bindTo(property.primitiveSetter, delegate);
157 this.objectGetter = bindTo(property.ensureObjectGetter(), delegate);
158 this.objectSetter = bindTo(property.ensureObjectSetter(), delegate);
159
160 this.flags |= IS_BOUND;
161 setCurrentType(property.getCurrentType());
162 }
163
164 /**
165 * Constructor for spill properties. Array getters and setters will be created on demand.
166 *
167 * @param key the property key
168 * @param flags the property flags
169 * @param slot spill slot
170 */
171 public AccessorProperty(final String key, final int flags, final int slot) {
172 super(key, flags, slot);
173 assert (flags & IS_SPILL) == IS_SPILL;
174
175 setCurrentType(Object.class);
176 }
177
178 /**
179 * Constructor. Similar to the constructor with both primitive getters and setters, the difference
180 * here being that only one getter and setter (setter is optional for non writable fields) is given
181 * to the constructor, and the rest are created from those. Used e.g. by Nasgen classes
182 *
183 * @param key the property key
184 * @param flags the property flags
185 * @param slot the property field number or spill slot
186 * @param getter the property getter
187 * @param setter the property setter or null if non writable, non configurable
188 */
189 AccessorProperty(final String key, final int flags, final int slot, final MethodHandle getter, final MethodHandle setter) {
190 super(key, flags, slot);
191
192 // we don't need to prep the setters these will never be invalidated as this is a nasgen
193 // or known type getter/setter. No invalidations will take place
194
195 final Class<?> getterType = getter.type().returnType();
196 final Class<?> setterType = setter == null ? null : setter.type().parameterType(1);
197
198 assert setterType == null || setterType == getterType;
199
200 if (getterType.isPrimitive()) {
201 for (int i = 0; i < NOOF_TYPES; i++) {
202 getters[i] = MH.asType(
203 Lookup.filterReturnType(
204 getter,
205 getAccessorType(i).getTypeClass()),
206 ACCESSOR_GETTER_TYPES[i]);
207 }
208 } else {
209 objectGetter = getter.type() != Lookup.GET_OBJECT_TYPE ? MH.asType(getter, Lookup.GET_OBJECT_TYPE) : getter;
210 objectSetter = setter != null && setter.type() != Lookup.SET_OBJECT_TYPE ? MH.asType(setter, Lookup.SET_OBJECT_TYPE) : setter;
211 }
212
213 setCurrentType(getterType);
214 }
215
216 private static class GettersSetters {
217 final MethodHandle[] getters;
218 final MethodHandle[] setters;
219
220 public GettersSetters(Class<?> structure) {
221 final int fieldCount = ObjectClassGenerator.getFieldCount(structure);
222 getters = new MethodHandle[fieldCount];
223 setters = new MethodHandle[fieldCount];
224 for(int i = 0; i < fieldCount; ++i) {
225 final String fieldName = ObjectClassGenerator.getFieldName(i, Type.OBJECT);
226 getters[i] = MH.asType(MH.getter(lookup, structure, fieldName, Type.OBJECT.getTypeClass()), Lookup.GET_OBJECT_TYPE);
227 setters[i] = MH.asType(MH.setter(lookup, structure, fieldName, Type.OBJECT.getTypeClass()), Lookup.SET_OBJECT_TYPE);
228 }
229 }
230 }
231
232 /**
233 * Constructor for dual field AccessorPropertys.
234 *
235 * @param key property key
236 * @param flags property flags
237 * @param structure structure for objects associated with this property
238 * @param slot property field number or spill slot
239 */
240 public AccessorProperty(final String key, final int flags, final Class<?> structure, final int slot) {
241 super(key, flags, slot);
242
243 /*
244 * primitiveGetter and primitiveSetter are only used in dual fields mode. Setting them to null also
245 * works in dual field mode, it only means that the property never has a primitive
246 * representation.
247 */
248 primitiveGetter = null;
249 primitiveSetter = null;
250
251 if (isParameter() && hasArguments()) {
252 final MethodHandle arguments = MH.getter(lookup, structure, "arguments", ScriptObject.class);
253
254 objectGetter = MH.asType(MH.insertArguments(MH.filterArguments(ScriptObject.GET_ARGUMENT.methodHandle(), 0, arguments), 1, slot), Lookup.GET_OBJECT_TYPE);
255 objectSetter = MH.asType(MH.insertArguments(MH.filterArguments(ScriptObject.SET_ARGUMENT.methodHandle(), 0, arguments), 1, slot), Lookup.SET_OBJECT_TYPE);
256 } else {
257 final GettersSetters gs = GETTERS_SETTERS.get(structure);
258 objectGetter = gs.getters[slot];
259 objectSetter = gs.setters[slot];
260
261 if (!OBJECT_FIELDS_ONLY) {
262 final String fieldNamePrimitive = ObjectClassGenerator.getFieldName(slot, PRIMITIVE_TYPE);
263 final Class<?> typeClass = PRIMITIVE_TYPE.getTypeClass();
264 primitiveGetter = MH.asType(MH.getter(lookup, structure, fieldNamePrimitive, typeClass), ACCESSOR_GETTER_PRIMITIVE_TYPE);
265 primitiveSetter = MH.asType(MH.setter(lookup, structure, fieldNamePrimitive, typeClass), ACCESSOR_SETTER_PRIMITIVE_TYPE);
266 }
267 }
268
269 Class<?> initialType = null;
270
271 if (OBJECT_FIELDS_ONLY || isAlwaysObject()) {
272 initialType = Object.class;
273 } else if (!canBePrimitive()) {
274 info(key + " cannot be primitive");
275 initialType = Object.class;
276 } else {
277 info(key + " CAN be primitive");
278 if (!canBeUndefined()) {
279 info(key + " is always defined");
280 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
281 }
282 }
283
284 // is always object means "is never initialized to undefined, and always of object type
285 setCurrentType(initialType);
286 }
287
288 /**
289 * Copy constructor
290 *
291 * @param property source property
292 */
293 protected AccessorProperty(final AccessorProperty property) {
294 super(property);
295
296 this.getters = property.getters;
297 this.primitiveGetter = property.primitiveGetter;
298 this.primitiveSetter = property.primitiveSetter;
299 this.objectGetter = property.objectGetter;
300 this.objectSetter = property.objectSetter;
301
302 setCurrentType(property.getCurrentType());
303 }
304
305 private static MethodHandle bindTo(final MethodHandle mh, final Object receiver) {
306 if (mh == null) {
307 return null;
308 }
309
310 return MH.dropArguments(MH.bindTo(mh, receiver), 0, Object.class);
311 }
312
313 @Override
314 protected Property copy() {
315 return new AccessorProperty(this);
316 }
317
318 @Override
319 public void setObjectValue(final ScriptObject self, final ScriptObject owner, final Object value, final boolean strict) {
320 if (isSpill()) {
321 self.spill[getSlot()] = value;
322 } else {
323 try {
324 getSetter(Object.class, self.getMap()).invokeExact((Object)self, value);
325 } catch (final Error|RuntimeException e) {
326 throw e;
327 } catch (final Throwable e) {
328 throw new RuntimeException(e);
329 }
330 }
331 }
332
333 @Override
334 public Object getObjectValue(final ScriptObject self, final ScriptObject owner) {
335 if (isSpill()) {
336 return self.spill[getSlot()];
337 }
338
339 try {
340 return getGetter(Object.class).invokeExact((Object)self);
341 } catch (final Error|RuntimeException e) {
342 throw e;
343 } catch (final Throwable e) {
344 throw new RuntimeException(e);
345 }
346 }
347
348 // Spill getters and setters are lazily initialized, see JDK-8011630
349 private MethodHandle ensureObjectGetter() {
350 if (isSpill() && objectGetter == null) {
351 objectGetter = getSpillGetter();
352 }
353 return objectGetter;
354 }
355
356 private MethodHandle ensureObjectSetter() {
357 if (isSpill() && objectSetter == null) {
358 objectSetter = getSpillSetter();
359 }
360 return objectSetter;
361 }
362
363 @Override
364 public MethodHandle getGetter(final Class<?> type) {
365 final int i = getAccessorTypeIndex(type);
366 ensureObjectGetter();
367
368 if (getters[i] == null) {
369 getters[i] = debug(
370 createGetter(currentType, type, primitiveGetter, objectGetter),
371 currentType, type, "get");
372 }
373
374 return getters[i];
375 }
376
377 private Property getWiderProperty(final Class<?> type) {
378 final AccessorProperty newProperty = new AccessorProperty(this);
379 newProperty.invalidate(type);
380 return newProperty;
381 }
382
383 private PropertyMap getWiderMap(final PropertyMap oldMap, final Property newProperty) {
384 final PropertyMap newMap = oldMap.replaceProperty(this, newProperty);
385 assert oldMap.size() > 0;
386 assert newMap.size() == oldMap.size();
387 return newMap;
388 }
389
390 // the final three arguments are for debug printout purposes only
391 @SuppressWarnings("unused")
392 private static Object replaceMap(final Object sobj, final PropertyMap newMap, final String key, final Class<?> oldType, final Class<?> newType) {
393 if (DEBUG_FIELDS) {
394 final PropertyMap oldMap = ((ScriptObject)sobj).getMap();
395 info("Type change for '" + key + "' " + oldType + "=>" + newType);
396 finest("setting map " + sobj + " from " + Debug.id(oldMap) + " to " + Debug.id(newMap) + " " + oldMap + " => " + newMap);
397 }
398 ((ScriptObject)sobj).setMap(newMap);
399 return sobj;
400 }
401
402 private MethodHandle generateSetter(final Class<?> forType, final Class<?> type) {
403 ensureObjectSetter();
404 MethodHandle mh = createSetter(forType, type, primitiveSetter, objectSetter);
405 mh = debug(mh, currentType, type, "set");
406 return mh;
407 }
408
409 @Override
410 public MethodHandle getSetter(final Class<?> type, final PropertyMap currentMap) {
411 final int i = getAccessorTypeIndex(type);
412 final int ci = currentType == null ? -1 : getAccessorTypeIndex(currentType);
413 final Class<?> forType = currentType == null ? type : currentType;
414
415 //if we are asking for an object setter, but are still a primitive type, we might try to box it
416 MethodHandle mh;
417
418 if (needsInvalidator(i, ci)) {
419 final Property newProperty = getWiderProperty(type);
420 final PropertyMap newMap = getWiderMap(currentMap, newProperty);
421 final MethodHandle widerSetter = newProperty.getSetter(type, newMap);
422 final MethodHandle explodeTypeSetter = MH.filterArguments(widerSetter, 0, MH.insertArguments(REPLACE_MAP, 1, newMap, getKey(), currentType, type));
423 if (currentType != null && currentType.isPrimitive() && type == Object.class) {
424 //might try a box check on this to avoid widening field to object storage
425 mh = createGuardBoxedPrimitiveSetter(currentType, generateSetter(currentType, currentType), explodeTypeSetter);
426 } else {
427 mh = explodeTypeSetter;
428 }
429 } else {
430 mh = generateSetter(forType, type);
431 }
432
433 return mh;
434 }
435
436 @Override
437 public boolean canChangeType() {
438 if (OBJECT_FIELDS_ONLY) {
439 return false;
440 }
441 return currentType != Object.class && (isConfigurable() || isWritable());
442 }
443
444 private boolean needsInvalidator(final int ti, final int fti) {
445 return canChangeType() && ti > fti;
446 }
447
448 private void invalidate(final Class<?> newType) {
449 getters = new MethodHandle[NOOF_TYPES];
450 setCurrentType(newType);
451 }
452
453 private MethodHandle getSpillGetter() {
454 final int slot = getSlot();
455 MethodHandle getter = slot < SPILL_CACHE_SIZE ? SPILL_ACCESSORS[slot * 2] : null;
456 if (getter == null) {
457 getter = MH.insertArguments(SPILL_ELEMENT_GETTER, 1, slot);
458 if (slot < SPILL_CACHE_SIZE) {
459 SPILL_ACCESSORS[slot * 2 + 0] = getter;
460 }
461 }
462 return getter;
463 }
464
465 private MethodHandle getSpillSetter() {
466 final int slot = getSlot();
467 MethodHandle setter = slot < SPILL_CACHE_SIZE ? SPILL_ACCESSORS[slot * 2 + 1] : null;
468 if (setter == null) {
469 setter = MH.insertArguments(SPILL_ELEMENT_SETTER, 1, slot);
470 if (slot < SPILL_CACHE_SIZE) {
471 SPILL_ACCESSORS[slot * 2 + 1] = setter;
472 }
473 }
474 return setter;
475 }
476
477 private static void finest(final String str) {
478 if (DEBUG_FIELDS) {
479 LOG.finest(str);
480 }
481 }
482
483 private static void info(final String str) {
484 if (DEBUG_FIELDS) {
485 LOG.info(str);
486 }
487 }
488
489 private MethodHandle debug(final MethodHandle mh, final Class<?> forType, final Class<?> type, final String tag) {
490 if (DEBUG_FIELDS) {
491 return MethodHandleFactory.addDebugPrintout(
492 LOG,
493 mh,
494 tag + " '" + getKey() + "' (property="+ Debug.id(this) + ", forType=" + stripName(forType) + ", type=" + stripName(type) + ')');
495 }
496 return mh;
497 }
498
499 private void setCurrentType(final Class<?> currentType) {
500 this.currentType = currentType;
501 }
502
503 @Override
504 public Class<?> getCurrentType() {
505 return currentType;
506 }
507
508 private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
509 return MH.findStatic(lookup, AccessorProperty.class, name, MH.type(rtype, types));
510 }
511
512 }
--- EOF ---