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