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