GlobalConstants.java revision 1036:f0b5e3900a10
1/*
2 * Copyright (c) 2010, 2014, 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
26package jdk.nashorn.internal.runtime;
27
28import static jdk.nashorn.internal.codegen.CompilerConstants.staticCall;
29import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCall;
30import static jdk.nashorn.internal.lookup.Lookup.MH;
31import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
32import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.getProgramPoint;
33import static jdk.nashorn.internal.runtime.logging.DebugLogger.quote;
34
35import java.lang.invoke.MethodHandle;
36import java.lang.invoke.MethodHandles;
37import java.lang.invoke.SwitchPoint;
38import java.util.Arrays;
39import java.util.HashMap;
40import java.util.Map;
41import java.util.logging.Level;
42import jdk.internal.dynalink.CallSiteDescriptor;
43import jdk.internal.dynalink.DynamicLinker;
44import jdk.internal.dynalink.linker.GuardedInvocation;
45import jdk.internal.dynalink.linker.LinkRequest;
46import jdk.nashorn.internal.lookup.Lookup;
47import jdk.nashorn.internal.lookup.MethodHandleFactory;
48import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor;
49import jdk.nashorn.internal.runtime.logging.DebugLogger;
50import jdk.nashorn.internal.runtime.logging.Loggable;
51import jdk.nashorn.internal.runtime.logging.Logger;
52
53/**
54 * Each global owns one of these. This is basically table of accessors
55 * for global properties. A global constant is evaluated to a MethodHandle.constant
56 * for faster access and to avoid walking to proto chain looking for it.
57 *
58 * We put a switchpoint on the global setter, which invalidates the
59 * method handle constant getters, and reverts to the standard access strategy
60 *
61 * However, there is a twist - while certain globals like "undefined" and "Math"
62 * are usually never reassigned, a global value can be reset once, and never again.
63 * This is a rather common pattern, like:
64 *
65 * x = function(something) { ...
66 *
67 * Thus everything registered as a global constant gets an extra chance. Set once,
68 * reregister the switchpoint. Set twice or more - don't try again forever, or we'd
69 * just end up relinking our way into megamorphisism.
70 *
71 * We can extend this to ScriptObjects in general (GLOBAL_ONLY=false), which requires
72 * a receiver guard on the constant getter, but it currently leaks memory and its benefits
73 * have not yet been investigated property.
74 *
75 * As long as all Globals share the same constant instance, we need synchronization
76 * whenever we access the instance.
77 */
78@Logger(name="const")
79public final class GlobalConstants implements Loggable {
80
81    /**
82     * Should we only try to link globals as constants, and not generic script objects.
83     * Script objects require a receiver guard, which is memory intensive, so this is currently
84     * disabled. We might implement a weak reference based approach to this later.
85     */
86    private static final boolean GLOBAL_ONLY = true;
87
88    private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
89
90    private static final MethodHandle INVALIDATE_SP  = virtualCall(LOOKUP, GlobalConstants.class, "invalidateSwitchPoint", Object.class, Object.class, Access.class).methodHandle();
91    private static final MethodHandle RECEIVER_GUARD = staticCall(LOOKUP, GlobalConstants.class, "receiverGuard", boolean.class, Access.class, Object.class, Object.class).methodHandle();
92
93    /** Logger for constant getters */
94    private final DebugLogger log;
95
96    /**
97     * Access map for this global - associates a symbol name with an Access object, with getter
98     * and invalidation information
99     */
100    private final Map<String, Access> map = new HashMap<>();
101
102    /**
103     * Constructor - used only by global
104     * @param log logger, or null if none
105     */
106    public GlobalConstants(final DebugLogger log) {
107        this.log = log == null ? DebugLogger.DISABLED_LOGGER : log;
108    }
109
110    @Override
111    public DebugLogger getLogger() {
112        return log;
113    }
114
115    @Override
116    public DebugLogger initLogger(final Context context) {
117        return DebugLogger.DISABLED_LOGGER;
118    }
119
120    /**
121     * Information about a constant access and its potential invalidations
122     */
123    private static class Access {
124        /** name of symbol */
125        private final String name;
126
127        /** switchpoint that invalidates the getters and setters for this access */
128        private SwitchPoint sp;
129
130        /** invalidation count for this access, i.e. how many times has this property been reset */
131        private int invalidations;
132
133        /** has a guard guarding this property getter failed? */
134        private boolean guardFailed;
135
136        private static final int MAX_RETRIES = 2;
137
138        private Access(final String name, final SwitchPoint sp) {
139            this.name      = name;
140            this.sp        = sp;
141        }
142
143        private boolean hasBeenInvalidated() {
144            return sp.hasBeenInvalidated();
145        }
146
147        private boolean guardFailed() {
148            return guardFailed;
149        }
150
151        private void failGuard() {
152            invalidateOnce();
153            guardFailed = true;
154        }
155
156        private void newSwitchPoint() {
157            assert hasBeenInvalidated();
158            sp = new SwitchPoint();
159        }
160
161        private void invalidate(final int count) {
162            if (!sp.hasBeenInvalidated()) {
163                SwitchPoint.invalidateAll(new SwitchPoint[] { sp });
164                invalidations += count;
165            }
166        }
167
168        /**
169         * Invalidate the access, but do not contribute to the invalidation count
170         */
171        private void invalidateUncounted() {
172            invalidate(0);
173        }
174
175        /**
176         * Invalidate the access, and contribute 1 to the invalidation count
177         */
178        private void invalidateOnce() {
179            invalidate(1);
180        }
181
182        /**
183         * Invalidate the access and make sure that we never try to turn this into
184         * a MethodHandle.constant getter again
185         */
186        private void invalidateForever() {
187            invalidate(MAX_RETRIES);
188        }
189
190        /**
191         * Are we allowed to relink this as constant getter, even though it
192         * it has been reset
193         * @return true if we can relink as constant, one retry is allowed
194         */
195        private boolean mayRetry() {
196            return invalidations < MAX_RETRIES;
197        }
198
199        @Override
200        public String toString() {
201            return "[" + quote(name) + " <id=" + Debug.id(this) + "> inv#=" + invalidations + '/' + MAX_RETRIES + " sp_inv=" + sp.hasBeenInvalidated() + ']';
202        }
203
204        String getName() {
205            return name;
206        }
207
208        SwitchPoint getSwitchPoint() {
209            return sp;
210        }
211    }
212
213    /**
214     * To avoid an expensive global guard "is this the same global", similar to the
215     * receiver guard on the ScriptObject level, we invalidate all getters once
216     * when we switch globals. This is used from the class cache. We _can_ reuse
217     * the same class for a new global, but the builtins and global scoped variables
218     * will have changed.
219     */
220    public synchronized void invalidateAll() {
221        log.info("New global created - invalidating all constant callsites without increasing invocation count.");
222        for (final Access acc : map.values()) {
223            acc.invalidateUncounted();
224        }
225    }
226
227    /**
228     * Invalidate the switchpoint of an access - we have written to
229     * the property
230     *
231     * @param obj receiver
232     * @param acc access
233     *
234     * @return receiver, so this can be used as param filter
235     */
236    @SuppressWarnings("unused")
237    private synchronized Object invalidateSwitchPoint(final Object obj, final Access acc) {
238        if (log.isEnabled()) {
239            log.info("*** Invalidating switchpoint " + acc.getSwitchPoint() + " for receiver=" + obj + " access=" + acc);
240        }
241        acc.invalidateOnce();
242        if (acc.mayRetry()) {
243            if (log.isEnabled()) {
244                log.info("Retry is allowed for " + acc + "... Creating a new switchpoint.");
245            }
246            acc.newSwitchPoint();
247        } else {
248            if (log.isEnabled()) {
249                log.info("This was the last time I allowed " + quote(acc.getName()) + " to relink as constant.");
250            }
251        }
252        return obj;
253    }
254
255    private synchronized Access getOrCreateSwitchPoint(final String name) {
256        Access acc = map.get(name);
257        if (acc != null) {
258            return acc;
259        }
260        final SwitchPoint sp = new SwitchPoint();
261        map.put(name, acc = new Access(name, sp));
262        return acc;
263    }
264
265    /**
266     * Called from script object on property deletion to erase a property
267     * that might be linked as MethodHandle.constant and force relink
268     * @param name name of property
269     */
270    void delete(final String name) {
271        final Access acc = map.get(name);
272        if (acc != null) {
273            acc.invalidateForever();
274        }
275    }
276
277    /**
278     * Receiver guard is used if we extend the global constants to script objects in general.
279     * As the property can have different values in different script objects, while Global is
280     * by definition a singleton, we need this for ScriptObject constants (currently disabled)
281     *
282     * TODO: Note - this seems to cause memory leaks. Use weak references? But what is leaking seems
283     * to be the Access objects, which isn't the case for Globals. Weird.
284     *
285     * @param acc            access
286     * @param boundReceiver  the receiver bound to the callsite
287     * @param receiver       the receiver to check against
288     *
289     * @return true if this receiver is still the one we bound to the callsite
290     */
291    @SuppressWarnings("unused")
292    private static boolean receiverGuard(final Access acc, final Object boundReceiver, final Object receiver) {
293        final boolean id = receiver == boundReceiver;
294        if (!id) {
295            acc.failGuard();
296        }
297        return id;
298    }
299
300    private static boolean isGlobalSetter(final ScriptObject receiver, final FindProperty find) {
301        if (find == null) {
302            return receiver.isScope();
303        }
304        return find.getOwner().isGlobal();
305    }
306
307    /**
308     * Augment a setter with switchpoint for invalidating its getters, should the setter be called
309     *
310     * @param find    property lookup
311     * @param inv     normal guarded invocation for this setter, as computed by the ScriptObject linker
312     * @param desc    callsite descriptor
313     * @param request link request
314     *
315     * @return null if failed to set up constant linkage
316     */
317    synchronized GuardedInvocation findSetMethod(final FindProperty find, final ScriptObject receiver, final GuardedInvocation inv, final CallSiteDescriptor desc, final LinkRequest request) {
318        if (GLOBAL_ONLY && !isGlobalSetter(receiver, find)) {
319            return null;
320        }
321
322        final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND);
323
324        final Access acc  = getOrCreateSwitchPoint(name);
325
326        if (log.isEnabled()) {
327            log.fine("Trying to link constant SETTER ", acc);
328        }
329
330        if (!acc.mayRetry()) {
331            log.info("*** SET: Giving up on " + quote(name) + " - retry count has exceeded " + DynamicLinker.getLinkedCallSiteLocation());
332            return null;
333        }
334
335        assert acc.mayRetry();
336
337        if (acc.hasBeenInvalidated()) {
338            log.info("New chance for " + acc);
339            acc.newSwitchPoint();
340        }
341
342        assert !acc.hasBeenInvalidated();
343
344        // if we haven't given up on this symbol, add a switchpoint invalidation filter to the receiver parameter
345        final MethodHandle target           = inv.getInvocation();
346        final Class<?>     receiverType     = target.type().parameterType(0);
347        final MethodHandle boundInvalidator = MH.bindTo(INVALIDATE_SP,  this);
348        final MethodHandle invalidator      = MH.asType(boundInvalidator, boundInvalidator.type().changeParameterType(0, receiverType).changeReturnType(receiverType));
349        final MethodHandle mh               = MH.filterArguments(inv.getInvocation(), 0, MH.insertArguments(invalidator, 1, acc));
350
351        assert inv.getSwitchPoints() == null : Arrays.asList(inv.getSwitchPoints());
352        log.info("Linked setter " + quote(name) + " " + acc.getSwitchPoint());
353        return new GuardedInvocation(mh, inv.getGuard(), acc.getSwitchPoint(), inv.getException());
354    }
355
356    /**
357     * Try to reuse constant method handles for getters
358     * @param c constant value
359     * @return method handle (with dummy receiver) that returns this constant
360     */
361    public static MethodHandle staticConstantGetter(final Object c) {
362        return MH.dropArguments(JSType.unboxConstant(c), 0, Object.class);
363    }
364
365    private MethodHandle constantGetter(final Object c) {
366        final MethodHandle mh = staticConstantGetter(c);
367        if (log.isEnabled()) {
368            return MethodHandleFactory.addDebugPrintout(log, Level.FINEST, mh, "getting as constant");
369        }
370        return mh;
371    }
372
373    /**
374     * Try to turn a getter into a MethodHandle.constant, if possible
375     *
376     * @param find      property lookup
377     * @param receiver  receiver
378     * @param desc      callsite descriptor
379     *
380     * @return resulting getter, or null if failed to create constant
381     */
382    synchronized GuardedInvocation findGetMethod(final FindProperty find, final ScriptObject receiver, final CallSiteDescriptor desc) {
383        // Only use constant getter for fast scope access, because the receiver may change between invocations
384        // for slow-scope and non-scope callsites.
385        // Also return null for user accessor properties as they may have side effects.
386        if (!NashornCallSiteDescriptor.isFastScope(desc)
387                || (GLOBAL_ONLY && !find.getOwner().isGlobal())
388                || find.getProperty() instanceof UserAccessorProperty) {
389            return null;
390        }
391
392        final boolean  isOptimistic = NashornCallSiteDescriptor.isOptimistic(desc);
393        final int      programPoint = isOptimistic ? getProgramPoint(desc) : INVALID_PROGRAM_POINT;
394        final Class<?> retType      = desc.getMethodType().returnType();
395        final String   name         = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND);
396
397        final Access acc = getOrCreateSwitchPoint(name);
398
399        log.fine("Starting to look up object value " + name);
400        final Object c = find.getObjectValue();
401
402        if (log.isEnabled()) {
403            log.fine("Trying to link constant GETTER " + acc + " value = " + c);
404        }
405
406        if (acc.hasBeenInvalidated() || acc.guardFailed()) {
407            log.fine("*** GET: Giving up on " + quote(name) + " - retry count has exceeded");
408            return null;
409        }
410
411        final MethodHandle cmh = constantGetter(c);
412
413        MethodHandle mh;
414        MethodHandle guard;
415
416        if (isOptimistic) {
417            if (JSType.getAccessorTypeIndex(cmh.type().returnType()) <= JSType.getAccessorTypeIndex(retType)) {
418                //widen return type - this is pessimistic, so it will always work
419                mh = MH.asType(cmh, cmh.type().changeReturnType(retType));
420            } else {
421                //immediately invalidate - we asked for a too wide constant as a narrower one
422                mh = MH.dropArguments(MH.insertArguments(JSType.THROW_UNWARRANTED.methodHandle(), 0, c, programPoint), 0, Object.class);
423            }
424        } else {
425            //pessimistic return type filter
426            mh = Lookup.filterReturnType(cmh, retType);
427        }
428
429        if (find.getOwner().isGlobal()) {
430            guard = null;
431        } else {
432            guard = MH.insertArguments(RECEIVER_GUARD, 0, acc, receiver);
433        }
434
435        if (log.isEnabled()) {
436            log.info("Linked getter " + quote(name) + " as MethodHandle.constant() -> " + c + " " + acc.getSwitchPoint());
437            mh = MethodHandleFactory.addDebugPrintout(log, Level.FINE, mh, "get const " + acc);
438        }
439
440        return new GuardedInvocation(mh, guard, acc.getSwitchPoint(), null);
441    }
442}
443