<commit_before>package org.elasticsearch.painless;

import org.elasticsearch.common.SuppressForbidden;

/*
 * Licensed to Elasticsearch under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch 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.
 */

import java.lang.invoke.CallSite;
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;

/**
 * Painless invokedynamic bootstrap for the call site.
 * <p>
 * Has 7 flavors (passed as static bootstrap parameters): dynamic method call,
 * dynamic field load (getter), and dynamic field store (setter), dynamic array load,
 * dynamic array store, iterator, and method reference.
 * <p>
 * When a new type is encountered at the call site, we lookup from the appropriate
 * whitelist, and cache with a guard. If we encounter too many types, we stop caching.
 * <p>
 * Based on the cascaded inlining cache from the JSR 292 cookbook
 * (https://code.google.com/archive/p/jsr292-cookbook/, BSD license)
 */
// NOTE: this class must be public, because generated painless classes are in a different classloader,
// and it needs to be accessible by that code.
public final class DefBootstrap {

    private DefBootstrap() {} // no instance!

    // NOTE: these must be primitive types, see https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokedynamic
    /** static bootstrap parameter indicating a dynamic method call, e.g. foo.bar(...) */
    public static final int METHOD_CALL = 0;
    /** static bootstrap parameter indicating a dynamic load (getter), e.g. baz = foo.bar */
    public static final int LOAD = 1;
    /** static bootstrap parameter indicating a dynamic store (setter), e.g. foo.bar = baz */
    public static final int STORE = 2;
    /** static bootstrap parameter indicating a dynamic array load, e.g. baz = foo[bar] */
    public static final int ARRAY_LOAD = 3;
    /** static bootstrap parameter indicating a dynamic array store, e.g. foo[bar] = baz */
    public static final int ARRAY_STORE = 4;
    /** static bootstrap parameter indicating a dynamic iteration, e.g. for (x : y) */
    public static final int ITERATOR = 5;
    /** static bootstrap parameter indicating a dynamic method reference, e.g. foo::bar */
    public static final int REFERENCE = 6;
    /** static bootstrap parameter indicating a unary math operator, e.g. ~foo */
    public static final int UNARY_OPERATOR = 7;
    /** static bootstrap parameter indicating a binary math operator, e.g. foo / bar */
    public static final int BINARY_OPERATOR = 8;
    /** static bootstrap parameter indicating a shift operator, e.g. foo &gt;&gt; bar */
    public static final int SHIFT_OPERATOR = 9;
    
    // constants for the flags parameter of operators
    /** 
     * static bootstrap parameter indicating the binary operator allows nulls (e.g. == and +) 
     * <p>
     * requires additional {@link MethodHandles#catchException} guard, which will invoke
     * the fallback if a null is encountered.
     */
    public static final int OPERATOR_ALLOWS_NULL = 1 << 0;
    
    /**
     * static bootstrap parameter indicating the binary operator is part of compound assignment (e.g. +=).
     * 
     */
    public static final int OPERATOR_COMPOUND_ASSIGNMENT = 1 << 1;

    /**
     * CallSite that implements the polymorphic inlining cache (PIC).
     */
    static final class PIC extends MutableCallSite {
        /** maximum number of types before we go megamorphic */
        static final int MAX_DEPTH = 5;

        private final Lookup lookup;
        private final String name;
        private final int flavor;
        private final Object[] args;
        int depth; // pkg-protected for testing

        PIC(Lookup lookup, String name, MethodType type, int flavor, Object[] args) {
            super(type);
            if (type.parameterType(0) != Object.class) {
                throw new BootstrapMethodError("The receiver type (1st arg) of invokedynamic descriptor must be Object.");
            }
            this.lookup = lookup;
            this.name = name;
            this.flavor = flavor;
            this.args = args;

            MethodHandle fallback = FALLBACK.bindTo(this)
              .asCollector(Object[].class, type.parameterCount())
              .asType(type);

            setTarget(fallback);
        }
        
        /**
         * guard method for inline caching: checks the receiver's class is the same
         * as the cached class
         */
        static boolean checkClass(Class<?> clazz, Object receiver) {
            return receiver.getClass() == clazz;
        }

        /**
         * Does a slow lookup against the whitelist.
         */
        private MethodHandle lookup(int flavor, String name, Class<?> receiver, Object[] callArgs) throws Throwable {
            switch(flavor) {
                case METHOD_CALL:
                    return Def.lookupMethod(lookup, type(), receiver, name, callArgs, (Long) this.args[0]);
                case LOAD:
                    return Def.lookupGetter(receiver, name);
                case STORE:
                    return Def.lookupSetter(receiver, name);
                case ARRAY_LOAD:
                    return Def.lookupArrayLoad(receiver);
                case ARRAY_STORE:
                    return Def.lookupArrayStore(receiver);
                case ITERATOR:
                    return Def.lookupIterator(receiver);
                case REFERENCE:
                    return Def.lookupReference(lookup, (String) this.args[0], receiver, name);
                default: throw new AssertionError();
            }
        }

        /**
         * Called when a new type is encountered (or, when we have encountered more than {@code MAX_DEPTH}
         * types at this call site and given up on caching using this fallback and we switch to a
         * megamorphic cache using {@link ClassValue}).
         */
        @SuppressForbidden(reason = "slow path")
        Object fallback(final Object[] callArgs) throws Throwable {
            final Class<?> receiver = callArgs[0].getClass();
            final MethodType type = type();
            
            if (depth >= MAX_DEPTH) {
                // we revert the whole cache and build a new megamorphic one
                // using ClassValue and MethodHandles.exactInvoker():
                final ClassValue<MethodHandle> megamorphicCache = new ClassValue<MethodHandle>() {
                    @Override
                    protected MethodHandle computeValue(Class<?> receiverType) {
                        // it's too stupid that we cannot throw checked exceptions... (use rethrow puzzler):
                        try {
                            return lookup(flavor, name, receiverType, callArgs).asType(type);
                        } catch (Throwable t) {
                            Def.rethrow(t);
                            throw new AssertionError();
                        }
                    }
                };
                MethodHandle cacheLookup = MEGAMORPHIC_LOOKUP.bindTo(megamorphicCache);
                cacheLookup = MethodHandles.dropArguments(cacheLookup,
                        1, type.parameterList().subList(1, type.parameterCount()));
                MethodHandle target = MethodHandles.foldArguments(MethodHandles.exactInvoker(type), cacheLookup);
                setTarget(target);
                return target.invokeWithArguments(callArgs);                    
            }
            
            final MethodHandle target = lookup(flavor, name, receiver, callArgs).asType(type);

            MethodHandle test = CHECK_CLASS.bindTo(receiver);
            MethodHandle guard = MethodHandles.guardWithTest(test, target, getTarget());
            
            depth++;

            setTarget(guard);
            return target.invokeWithArguments(callArgs);
        }

        private static final MethodHandle CHECK_CLASS;
        private static final MethodHandle FALLBACK;
        private static final MethodHandle MEGAMORPHIC_LOOKUP;
        static {
            final Lookup lookup = MethodHandles.lookup();
            final Lookup publicLookup = MethodHandles.publicLookup();
            try {
                CHECK_CLASS = lookup.findStatic(lookup.lookupClass(), "checkClass",
                                              MethodType.methodType(boolean.class, Class.class, Object.class));
                FALLBACK = lookup.findVirtual(lookup.lookupClass(), "fallback",
                        MethodType.methodType(Object.class, Object[].class));
                MethodHandle mh = publicLookup.findVirtual(ClassValue.class, "get",
                        MethodType.methodType(Object.class, Class.class));
                mh = MethodHandles.filterArguments(mh, 1, 
                        publicLookup.findVirtual(Object.class, "getClass", MethodType.methodType(Class.class)));
                MEGAMORPHIC_LOOKUP = mh.asType(mh.type().changeReturnType(MethodHandle.class));
            } catch (ReflectiveOperationException e) {<commit_msg>Small refactoring and cleanup to separate the polymorphic and megamorphic case<commit_after>package org.elasticsearch.painless;

import org.elasticsearch.common.SuppressForbidden;

/*
 * Licensed to Elasticsearch under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch 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.
 */

import java.lang.invoke.CallSite;
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;

/**
 * Painless invokedynamic bootstrap for the call site.
 * <p>
 * Has 7 flavors (passed as static bootstrap parameters): dynamic method call,
 * dynamic field load (getter), and dynamic field store (setter), dynamic array load,
 * dynamic array store, iterator, and method reference.
 * <p>
 * When a new type is encountered at the call site, we lookup from the appropriate
 * whitelist, and cache with a guard. If we encounter too many types, we stop caching.
 * <p>
 * Based on the cascaded inlining cache from the JSR 292 cookbook
 * (https://code.google.com/archive/p/jsr292-cookbook/, BSD license)
 */
// NOTE: this class must be public, because generated painless classes are in a different classloader,
// and it needs to be accessible by that code.
public final class DefBootstrap {

    private DefBootstrap() {} // no instance!

    // NOTE: these must be primitive types, see https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokedynamic
    /** static bootstrap parameter indicating a dynamic method call, e.g. foo.bar(...) */
    public static final int METHOD_CALL = 0;
    /** static bootstrap parameter indicating a dynamic load (getter), e.g. baz = foo.bar */
    public static final int LOAD = 1;
    /** static bootstrap parameter indicating a dynamic store (setter), e.g. foo.bar = baz */
    public static final int STORE = 2;
    /** static bootstrap parameter indicating a dynamic array load, e.g. baz = foo[bar] */
    public static final int ARRAY_LOAD = 3;
    /** static bootstrap parameter indicating a dynamic array store, e.g. foo[bar] = baz */
    public static final int ARRAY_STORE = 4;
    /** static bootstrap parameter indicating a dynamic iteration, e.g. for (x : y) */
    public static final int ITERATOR = 5;
    /** static bootstrap parameter indicating a dynamic method reference, e.g. foo::bar */
    public static final int REFERENCE = 6;
    /** static bootstrap parameter indicating a unary math operator, e.g. ~foo */
    public static final int UNARY_OPERATOR = 7;
    /** static bootstrap parameter indicating a binary math operator, e.g. foo / bar */
    public static final int BINARY_OPERATOR = 8;
    /** static bootstrap parameter indicating a shift operator, e.g. foo &gt;&gt; bar */
    public static final int SHIFT_OPERATOR = 9;
    
    // constants for the flags parameter of operators
    /** 
     * static bootstrap parameter indicating the binary operator allows nulls (e.g. == and +) 
     * <p>
     * requires additional {@link MethodHandles#catchException} guard, which will invoke
     * the fallback if a null is encountered.
     */
    public static final int OPERATOR_ALLOWS_NULL = 1 << 0;
    
    /**
     * static bootstrap parameter indicating the binary operator is part of compound assignment (e.g. +=).
     * 
     */
    public static final int OPERATOR_COMPOUND_ASSIGNMENT = 1 << 1;

    /**
     * CallSite that implements the polymorphic inlining cache (PIC).
     */
    static final class PIC extends MutableCallSite {
        /** maximum number of types before we go megamorphic */
        static final int MAX_DEPTH = 5;

        private final Lookup lookup;
        private final String name;
        private final int flavor;
        private final Object[] args;
        int depth; // pkg-protected for testing

        PIC(Lookup lookup, String name, MethodType type, int flavor, Object[] args) {
            super(type);
            if (type.parameterType(0) != Object.class) {
                throw new BootstrapMethodError("The receiver type (1st arg) of invokedynamic descriptor must be Object.");
            }
            this.lookup = lookup;
            this.name = name;
            this.flavor = flavor;
            this.args = args;

            MethodHandle fallback = FALLBACK.bindTo(this)
              .asCollector(Object[].class, type.parameterCount())
              .asType(type);

            setTarget(fallback);
        }
        
        /**
         * guard method for inline caching: checks the receiver's class is the same
         * as the cached class
         */
        static boolean checkClass(Class<?> clazz, Object receiver) {
            return receiver.getClass() == clazz;
        }

        /**
         * Does a slow lookup against the whitelist.
         */
        private MethodHandle lookup(int flavor, String name, Class<?> receiver, Object[] callArgs) throws Throwable {
            switch(flavor) {
                case METHOD_CALL:
                    return Def.lookupMethod(lookup, type(), receiver, name, callArgs, (Long) this.args[0]);
                case LOAD:
                    return Def.lookupGetter(receiver, name);
                case STORE:
                    return Def.lookupSetter(receiver, name);
                case ARRAY_LOAD:
                    return Def.lookupArrayLoad(receiver);
                case ARRAY_STORE:
                    return Def.lookupArrayStore(receiver);
                case ITERATOR:
                    return Def.lookupIterator(receiver);
                case REFERENCE:
                    return Def.lookupReference(lookup, (String) this.args[0], receiver, name);
                default: throw new AssertionError();
            }
        }
        
        /**
         * Creates the {@link MethodHandle} for the megamorphic call site
         * using {@link ClassValue} and {@link MethodHandles#exactInvoker(MethodType)}:
         * <p>
         * TODO: Remove the variable args and just use {@code type()}!
         */
        private MethodHandle createMegamorphicHandle(final Object[] callArgs) throws Throwable {
            final MethodType type = type();
            final ClassValue<MethodHandle> megamorphicCache = new ClassValue<MethodHandle>() {
                @Override
                protected MethodHandle computeValue(Class<?> receiverType) {
                    // it's too stupid that we cannot throw checked exceptions... (use rethrow puzzler):
                    try {
                        return lookup(flavor, name, receiverType, callArgs).asType(type);
                    } catch (Throwable t) {
                        Def.rethrow(t);
                        throw new AssertionError();
                    }
                }
            };
            MethodHandle cacheLookup = MEGAMORPHIC_LOOKUP.bindTo(megamorphicCache);
            cacheLookup = MethodHandles.dropArguments(cacheLookup,
                    1, type.parameterList().subList(1, type.parameterCount()));
            return MethodHandles.foldArguments(MethodHandles.exactInvoker(type), cacheLookup);            
        }

        /**
         * Called when a new type is encountered (or, when we have encountered more than {@code MAX_DEPTH}
         * types at this call site and given up on caching using this fallback and we switch to a
         * megamorphic cache using {@link ClassValue}).
         */
        @SuppressForbidden(reason = "slow path")
        Object fallback(final Object[] callArgs) throws Throwable {
            if (depth >= MAX_DEPTH) {
                // we revert the whole cache and build a new megamorphic one
                final MethodHandle target = this.createMegamorphicHandle(callArgs);
                
                setTarget(target);
                return target.invokeWithArguments(callArgs);                    
            } else {
                final Class<?> receiver = callArgs[0].getClass();
                final MethodHandle target = lookup(flavor, name, receiver, callArgs).asType(type());
    
                MethodHandle test = CHECK_CLASS.bindTo(receiver);
                MethodHandle guard = MethodHandles.guardWithTest(test, target, getTarget());
                
                depth++;
    
                setTarget(guard);
                return target.invokeWithArguments(callArgs);
            }
        }

        private static final MethodHandle CHECK_CLASS;
        private static final MethodHandle FALLBACK;
        private static final MethodHandle MEGAMORPHIC_LOOKUP;
        static {
            final Lookup lookup = MethodHandles.lookup();
            final Lookup publicLookup = MethodHandles.publicLookup();
            try {
                CHECK_CLASS = lookup.findStatic(lookup.lookupClass(), "checkClass",
                                              MethodType.methodType(boolean.class, Class.class, Object.class));
                FALLBACK = lookup.findVirtual(lookup.lookupClass(), "fallback",
                        MethodType.methodType(Object.class, Object[].class));
                MethodHandle mh = publicLookup.findVirtual(ClassValue.class, "get",
                        MethodType.methodType(Object.class, Class.class));
                mh = MethodHandles.filterArguments(mh, 1, 
                        publicLookup.findVirtual(Object.class, "getClass", MethodType.methodType(Class.class)));
                MEGAMORPHIC_LOOKUP = mh.asType(mh.type().changeReturnType(MethodHandle.class));
            } catch (ReflectiveOperationException e) {
{'fraction_non_alphanumeric': 0.08513500049815682, 'fraction_numerical': 0.003785991830228156, 'mean_word_length': 2.24313408723748, 'pattern_counts': {'":': 0, '<': 32, '<?xml version=': 0, '>': 26, 'https://': 4, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7253107', 'n_tokens_mistral': 5146, 'n_tokens_neox': 4884, 'n_words': 1745}