All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codehaus.groovy.vmplugin.v8.IndyInterface Maven / Gradle / Ivy

There is a newer version: 3.0.8-01
Show newest version
/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */
package org.codehaus.groovy.vmplugin.v8;

import groovy.lang.GroovySystem;
import org.apache.groovy.util.SystemUtil;
import org.codehaus.groovy.GroovyBugError;
import org.codehaus.groovy.runtime.NullObject;

import java.lang.invoke.CallSite;
import java.lang.invoke.ConstantCallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.lang.invoke.MutableCallSite;
import java.lang.invoke.SwitchPoint;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Bytecode level interface for bootstrap methods used by invokedynamic.
 * This class provides a logging ability by using the boolean system property
 * groovy.indy.logging. Other than that this class contains the
 * interfacing methods with bytecode for invokedynamic as well as some helper
 * methods and classes.
 */
public class IndyInterface {
    private static final long INDY_OPTIMIZE_THRESHOLD = SystemUtil.getLongSafe("groovy.indy.optimize.threshold", 100_000L);
    private static final long INDY_FALLBACK_THRESHOLD = SystemUtil.getLongSafe("groovy.indy.fallback.threshold", 100_000L);

    /**
     * flags for method and property calls
     */
    public static final int
            SAFE_NAVIGATION = 1, THIS_CALL = 2,
            GROOVY_OBJECT = 4, IMPLICIT_THIS = 8,
            SPREAD_CALL = 16, UNCACHED_CALL = 32;
    private static final MethodHandleWrapper NULL_METHOD_HANDLE_WRAPPER = MethodHandleWrapper.getNullMethodHandleWrapper();

    /**
     * Enum for easy differentiation between call types
     */
    public enum CallType {
        /**
         * Method invocation type
         */
        METHOD("invoke"),
        /**
         * Constructor invocation type
         */
        INIT("init"),
        /**
         * Get property invocation type
         */
        GET("getProperty"),
        /**
         * Set property invocation type
         */
        SET("setProperty"),
        /**
         * Cast invocation type
         */
        CAST("cast");

        private static final Map NAME_CALLTYPE_MAP =
                Stream.of(CallType.values()).collect(Collectors.toMap(CallType::getCallSiteName, Function.identity()));

        /**
         * The name of the call site type
         */
        private final String name;

        CallType(String callSiteName) {
            this.name = callSiteName;
        }

        /**
         * Returns the name of the call site type
         */
        public String getCallSiteName() {
            return name;
        }

        public static CallType fromCallSiteName(String callSiteName) {
            return NAME_CALLTYPE_MAP.get(callSiteName);
        }
    }

    /**
     * Logger
     */
    protected static final Logger LOG;
    /**
     * boolean to indicate if logging for indy is enabled
     */
    protected static final boolean LOG_ENABLED;

    static {
        boolean enableLogger = false;

        LOG = Logger.getLogger(IndyInterface.class.getName());

        try {
            if (System.getProperty("groovy.indy.logging") != null) {
                LOG.setLevel(Level.ALL);
                enableLogger = true;
            }
        } catch (SecurityException e) {
            // Allow security managers to prevent system property access
        }

        LOG_ENABLED = enableLogger;
    }

    /**
     * LOOKUP constant used for for example unreflect calls
     */
    public static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();

    /**
     * handle for the fromCache method
     */
    private static final MethodHandle FROM_CACHE_METHOD;

    /**
     * handle for the selectMethod method
     */
    private static final MethodHandle SELECT_METHOD;

    static {

        try {
            MethodType mt = MethodType.methodType(Object.class, MutableCallSite.class, Class.class, String.class, int.class, Boolean.class, Boolean.class, Boolean.class, Object.class, Object[].class);
            FROM_CACHE_METHOD = LOOKUP.findStatic(IndyInterface.class, "fromCache", mt);
        } catch (Exception e) {
            throw new GroovyBugError(e);
        }

        try {
            MethodType mt = MethodType.methodType(Object.class, MutableCallSite.class, Class.class, String.class, int.class, Boolean.class, Boolean.class, Boolean.class, Object.class, Object[].class);
            SELECT_METHOD = LOOKUP.findStatic(IndyInterface.class, "selectMethod", mt);
        } catch (Exception e) {
            throw new GroovyBugError(e);
        }
    }

    protected static SwitchPoint switchPoint = new SwitchPoint();

    static {
        GroovySystem.getMetaClassRegistry().addMetaClassRegistryChangeEventListener(cmcu -> invalidateSwitchPoints());
    }

    /**
     * Callback for constant meta class update change
     */
    protected static void invalidateSwitchPoints() {
        if (LOG_ENABLED) {
            LOG.info("invalidating switch point");
        }

        synchronized (IndyInterface.class) {
            SwitchPoint old = switchPoint;
            switchPoint = new SwitchPoint();
            SwitchPoint.invalidateAll(new SwitchPoint[]{old});
        }
    }

    /**
     * bootstrap method for method calls from Groovy compiled code with indy
     * enabled. This method gets a flags parameter which uses the following
     * encoding:
    *
  • {@value #SAFE_NAVIGATION} is the flag value for safe navigation see {@link #SAFE_NAVIGATION}
  • *
  • {@value #THIS_CALL} is the flag value for a call on this see {@link #THIS_CALL}
  • *
* * @param caller - the caller * @param callType - the type of the call * @param type - the call site type * @param name - the real method name * @param flags - call flags * @return the produced CallSite * @since Groovy 2.1.0 */ public static CallSite bootstrap(Lookup caller, String callType, MethodType type, String name, int flags) { CallType ct = CallType.fromCallSiteName(callType); if (null == ct) throw new GroovyBugError("Unknown call type: " + callType); int callID = ct.ordinal(); boolean safe = (flags & SAFE_NAVIGATION) != 0; boolean thisCall = (flags & THIS_CALL) != 0; boolean spreadCall = (flags & SPREAD_CALL) != 0; return realBootstrap(caller, name, callID, type, safe, thisCall, spreadCall); } /** * backing bootstrap method with all parameters */ private static CallSite realBootstrap(Lookup caller, String name, int callID, MethodType type, boolean safe, boolean thisCall, boolean spreadCall) { // since indy does not give us the runtime types // we produce first a dummy call site, which then changes the target to one when INDY_OPTIMIZE_THRESHOLD is reached, // that does the method selection including the direct call to the // real method. CacheableCallSite mc = new CacheableCallSite(type); final Class sender = caller.lookupClass(); MethodHandle mh = makeAdapter(mc, sender, name, callID, type, safe, thisCall, spreadCall); mc.setTarget(mh); mc.setDefaultTarget(mh); mc.setFallbackTarget(makeFallBack(mc, sender, name, callID, type, safe, thisCall, spreadCall)); return mc; } /** * Makes a fallback method for an invalidated method selection */ protected static MethodHandle makeFallBack(MutableCallSite mc, Class sender, String name, int callID, MethodType type, boolean safeNavigation, boolean thisCall, boolean spreadCall) { return make(mc, sender, name, callID, type, safeNavigation, thisCall, spreadCall, SELECT_METHOD); } /** * Makes an adapter method for method selection, i.e. get the cached methodhandle(fast path) or fallback */ private static MethodHandle makeAdapter(MutableCallSite mc, Class sender, String name, int callID, MethodType type, boolean safeNavigation, boolean thisCall, boolean spreadCall) { return make(mc, sender, name, callID, type, safeNavigation, thisCall, spreadCall, FROM_CACHE_METHOD); } private static MethodHandle make(MutableCallSite mc, Class sender, String name, int callID, MethodType type, boolean safeNavigation, boolean thisCall, boolean spreadCall, MethodHandle originalMH) { MethodHandle mh = MethodHandles.insertArguments(originalMH, 0, mc, sender, name, callID, safeNavigation, thisCall, spreadCall, /*dummy receiver:*/ 1); return mh.asCollector(Object[].class, type.parameterCount()).asType(type); } private static class FallbackSupplier { private final MutableCallSite callSite; private final Class sender; private final String methodName; private final int callID; private final Boolean safeNavigation; private final Boolean thisCall; private final Boolean spreadCall; private final Object dummyReceiver; private final Object[] arguments; private MethodHandleWrapper result; FallbackSupplier(MutableCallSite callSite, Class sender, String methodName, int callID, Boolean safeNavigation, Boolean thisCall, Boolean spreadCall, Object dummyReceiver, Object[] arguments) { this.callSite = callSite; this.sender = sender; this.methodName = methodName; this.callID = callID; this.safeNavigation = safeNavigation; this.thisCall = thisCall; this.spreadCall = spreadCall; this.dummyReceiver = dummyReceiver; this.arguments = arguments; } MethodHandleWrapper get() { if (null == result) { result = fallback(callSite, sender, methodName, callID, safeNavigation, thisCall, spreadCall, dummyReceiver, arguments); } return result; } } /** * Get the cached methodhandle. if the related methodhandle is not found in the inline cache, cache and return it. */ public static Object fromCache(MutableCallSite callSite, Class sender, String methodName, int callID, Boolean safeNavigation, Boolean thisCall, Boolean spreadCall, Object dummyReceiver, Object[] arguments) throws Throwable { FallbackSupplier fallbackSupplier = new FallbackSupplier(callSite, sender, methodName, callID, safeNavigation, thisCall, spreadCall, dummyReceiver, arguments); MethodHandleWrapper mhw = doWithCallSite( callSite, arguments, (cs, receiver) -> cs.getAndPut( receiver.getClass().getName(), c -> { MethodHandleWrapper fbMhw = fallbackSupplier.get(); return fbMhw.isCanSetTarget() ? fbMhw : NULL_METHOD_HANDLE_WRAPPER; } ) ); if (NULL_METHOD_HANDLE_WRAPPER == mhw) { mhw = fallbackSupplier.get(); } if (mhw.isCanSetTarget() && (callSite.getTarget() != mhw.getTargetMethodHandle()) && (mhw.getLatestHitCount() > INDY_OPTIMIZE_THRESHOLD)) { callSite.setTarget(mhw.getTargetMethodHandle()); if (LOG_ENABLED) LOG.info("call site target set, preparing outside invocation"); mhw.resetLatestHitCount(); } return mhw.getCachedMethodHandle().invokeExact(arguments); } /** * Core method for indy method selection using runtime types. */ public static Object selectMethod(MutableCallSite callSite, Class sender, String methodName, int callID, Boolean safeNavigation, Boolean thisCall, Boolean spreadCall, Object dummyReceiver, Object[] arguments) throws Throwable { final MethodHandleWrapper mhw = fallback(callSite, sender, methodName, callID, safeNavigation, thisCall, spreadCall, dummyReceiver, arguments); if (callSite instanceof CacheableCallSite) { CacheableCallSite cacheableCallSite = (CacheableCallSite) callSite; final MethodHandle defaultTarget = cacheableCallSite.getDefaultTarget(); final long fallbackCount = cacheableCallSite.incrementFallbackCount(); if ((fallbackCount > INDY_FALLBACK_THRESHOLD) && (cacheableCallSite.getTarget() != defaultTarget)) { cacheableCallSite.setTarget(defaultTarget); if (LOG_ENABLED) LOG.info("call site target reset to default, preparing outside invocation"); cacheableCallSite.resetFallbackCount(); } if (defaultTarget == cacheableCallSite.getTarget()) { // correct the stale methodhandle in the inline cache of callsite // it is important but impacts the performance somehow when cache misses frequently doWithCallSite(callSite, arguments, (cs, receiver) -> cs.put(receiver.getClass().getName(), mhw)); } } return mhw.getCachedMethodHandle().invokeExact(arguments); } private static MethodHandleWrapper fallback(MutableCallSite callSite, Class sender, String methodName, int callID, Boolean safeNavigation, Boolean thisCall, Boolean spreadCall, Object dummyReceiver, Object[] arguments) { Selector selector = Selector.getSelector(callSite, sender, methodName, callID, safeNavigation, thisCall, spreadCall, arguments); selector.setCallSiteTarget(); return new MethodHandleWrapper( selector.handle.asSpreader(Object[].class, arguments.length).asType(MethodType.methodType(Object.class, Object[].class)), selector.handle, selector.cache ); } private static T doWithCallSite(MutableCallSite callSite, Object[] arguments, BiFunction f) { if (callSite instanceof CacheableCallSite) { CacheableCallSite cacheableCallSite = (CacheableCallSite) callSite; Object receiver = arguments[0]; if (null == receiver) receiver = NullObject.getNullObject(); return f.apply(cacheableCallSite, receiver); } throw new GroovyBugError("CacheableCallSite is expected, but the actual callsite is: " + callSite); } /** * @since 2.5.0 */ public static CallSite staticArrayAccess(MethodHandles.Lookup lookup, String name, MethodType type) { if (type.parameterCount() == 2) { return new ConstantCallSite(IndyArrayAccess.arrayGet(type)); } else { return new ConstantCallSite(IndyArrayAccess.arraySet(type)); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy