src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.replacements/src/org/graalvm/compiler/replacements/SnippetTemplate.java
author chegar
Thu, 17 Oct 2019 20:54:25 +0100
branchdatagramsocketimpl-branch
changeset 58679 9c3209ff7550
parent 58678 9cf78a70fa4f
parent 58299 6df94ce3ab2f
permissions -rw-r--r--
datagramsocketimpl-branch: merge with default

/*
 * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */


package org.graalvm.compiler.replacements;

import static java.util.FormattableFlags.ALTERNATE;
import static jdk.vm.ci.services.Services.IS_IN_NATIVE_IMAGE;
import static org.graalvm.compiler.debug.DebugContext.DEFAULT_LOG_STREAM;
import static org.graalvm.compiler.debug.DebugContext.applyFormattingFlagsAndWidth;
import static org.graalvm.compiler.debug.DebugOptions.DebugStubsAndSnippets;
import static org.graalvm.compiler.graph.iterators.NodePredicates.isNotA;
import static org.graalvm.compiler.nodeinfo.NodeCycles.CYCLES_IGNORED;
import static org.graalvm.compiler.nodeinfo.NodeSize.SIZE_IGNORED;
import static org.graalvm.compiler.phases.common.DeadCodeEliminationPhase.Optionality.Required;
import static jdk.internal.vm.compiler.word.LocationIdentity.any;

import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Formattable;
import java.util.Formatter;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;

import jdk.internal.vm.compiler.collections.EconomicMap;
import jdk.internal.vm.compiler.collections.EconomicSet;
import jdk.internal.vm.compiler.collections.Equivalence;
import jdk.internal.vm.compiler.collections.UnmodifiableEconomicMap;
import org.graalvm.compiler.api.replacements.Fold;
import org.graalvm.compiler.api.replacements.Snippet;
import org.graalvm.compiler.api.replacements.Snippet.ConstantParameter;
import org.graalvm.compiler.api.replacements.Snippet.NonNullParameter;
import org.graalvm.compiler.api.replacements.Snippet.VarargsParameter;
import org.graalvm.compiler.api.replacements.SnippetReflectionProvider;
import org.graalvm.compiler.core.common.GraalOptions;
import org.graalvm.compiler.core.common.type.Stamp;
import org.graalvm.compiler.core.common.type.StampFactory;
import org.graalvm.compiler.core.common.type.StampPair;
import org.graalvm.compiler.core.common.type.TypeReference;
import org.graalvm.compiler.debug.Assertions;
import org.graalvm.compiler.debug.CounterKey;
import org.graalvm.compiler.debug.DebugCloseable;
import org.graalvm.compiler.debug.DebugContext;
import org.graalvm.compiler.debug.DebugContext.Description;
import org.graalvm.compiler.debug.DebugHandlersFactory;
import org.graalvm.compiler.debug.GraalError;
import org.graalvm.compiler.debug.TimerKey;
import org.graalvm.compiler.graph.Graph.Mark;
import org.graalvm.compiler.graph.Node;
import org.graalvm.compiler.graph.Node.NodeIntrinsic;
import org.graalvm.compiler.graph.NodeClass;
import org.graalvm.compiler.graph.NodeSourcePosition;
import org.graalvm.compiler.graph.Position;
import org.graalvm.compiler.loop.LoopEx;
import org.graalvm.compiler.loop.LoopsData;
import org.graalvm.compiler.loop.phases.LoopTransformations;
import org.graalvm.compiler.nodeinfo.InputType;
import org.graalvm.compiler.nodeinfo.NodeInfo;
import org.graalvm.compiler.nodes.AbstractBeginNode;
import org.graalvm.compiler.nodes.AbstractMergeNode;
import org.graalvm.compiler.nodes.ConstantNode;
import org.graalvm.compiler.nodes.ControlSinkNode;
import org.graalvm.compiler.nodes.DeoptimizingNode;
import org.graalvm.compiler.nodes.FixedNode;
import org.graalvm.compiler.nodes.FixedWithNextNode;
import org.graalvm.compiler.nodes.FrameState;
import org.graalvm.compiler.nodes.InliningLog;
import org.graalvm.compiler.nodes.LoopBeginNode;
import org.graalvm.compiler.nodes.MergeNode;
import org.graalvm.compiler.nodes.NodeView;
import org.graalvm.compiler.nodes.ParameterNode;
import org.graalvm.compiler.nodes.PhiNode;
import org.graalvm.compiler.nodes.PiNode.Placeholder;
import org.graalvm.compiler.nodes.PiNode.PlaceholderStamp;
import org.graalvm.compiler.nodes.ReturnNode;
import org.graalvm.compiler.nodes.StartNode;
import org.graalvm.compiler.nodes.StateSplit;
import org.graalvm.compiler.nodes.StructuredGraph;
import org.graalvm.compiler.nodes.StructuredGraph.GuardsStage;
import org.graalvm.compiler.nodes.ValueNode;
import org.graalvm.compiler.nodes.ValueNodeUtil;
import org.graalvm.compiler.nodes.calc.FloatingNode;
import org.graalvm.compiler.nodes.java.LoadIndexedNode;
import org.graalvm.compiler.nodes.java.MethodCallTargetNode;
import org.graalvm.compiler.nodes.java.StoreIndexedNode;
import org.graalvm.compiler.nodes.memory.MemoryAccess;
import org.graalvm.compiler.nodes.memory.MemoryAnchorNode;
import org.graalvm.compiler.nodes.memory.MemoryCheckpoint;
import org.graalvm.compiler.nodes.memory.MemoryMap;
import org.graalvm.compiler.nodes.memory.MemoryMapNode;
import org.graalvm.compiler.nodes.memory.MemoryNode;
import org.graalvm.compiler.nodes.memory.MemoryPhiNode;
import org.graalvm.compiler.nodes.spi.ArrayLengthProvider;
import org.graalvm.compiler.nodes.spi.CoreProviders;
import org.graalvm.compiler.nodes.spi.LoweringTool;
import org.graalvm.compiler.nodes.spi.MemoryProxy;
import org.graalvm.compiler.nodes.util.GraphUtil;
import org.graalvm.compiler.options.Option;
import org.graalvm.compiler.options.OptionKey;
import org.graalvm.compiler.options.OptionValues;
import org.graalvm.compiler.phases.common.CanonicalizerPhase;
import org.graalvm.compiler.phases.common.DeadCodeEliminationPhase;
import org.graalvm.compiler.phases.common.FloatingReadPhase;
import org.graalvm.compiler.phases.common.FloatingReadPhase.MemoryMapImpl;
import org.graalvm.compiler.phases.common.GuardLoweringPhase;
import org.graalvm.compiler.phases.common.LoweringPhase;
import org.graalvm.compiler.phases.common.RemoveValueProxyPhase;
import org.graalvm.compiler.phases.common.inlining.InliningUtil;
import org.graalvm.compiler.phases.util.Providers;
import org.graalvm.compiler.replacements.nodes.ExplodeLoopNode;
import org.graalvm.compiler.replacements.nodes.LoadSnippetVarargParameterNode;
import org.graalvm.util.CollectionsUtil;
import jdk.internal.vm.compiler.word.LocationIdentity;
import jdk.internal.vm.compiler.word.WordBase;

import jdk.vm.ci.code.TargetDescription;
import jdk.vm.ci.meta.Constant;
import jdk.vm.ci.meta.ConstantReflectionProvider;
import jdk.vm.ci.meta.JavaConstant;
import jdk.vm.ci.meta.JavaKind;
import jdk.vm.ci.meta.Local;
import jdk.vm.ci.meta.LocalVariableTable;
import jdk.vm.ci.meta.MetaAccessProvider;
import jdk.vm.ci.meta.ResolvedJavaMethod;
import jdk.vm.ci.meta.ResolvedJavaMethod.Parameter;
import jdk.vm.ci.meta.ResolvedJavaType;
import jdk.vm.ci.meta.Signature;

/**
 * A snippet template is a graph created by parsing a snippet method and then specialized by binding
 * constants to the snippet's {@link ConstantParameter} parameters.
 *
 * Snippet templates can be managed in a cache maintained by {@link AbstractTemplates}.
 */
public class SnippetTemplate {

    private boolean mayRemoveLocation = false;

    /**
     * Holds the {@link ResolvedJavaMethod} of the snippet, together with some information about the
     * method that needs to be computed only once. The {@link SnippetInfo} should be created once
     * per snippet and then cached.
     */
    public abstract static class SnippetInfo {

        protected final ResolvedJavaMethod method;
        protected final ResolvedJavaMethod original;
        protected final LocationIdentity[] privateLocations;
        protected final Object receiver;

        public Object getReceiver() {
            return receiver;
        }

        boolean hasReceiver() {
            assert hasReceiver(method) == (receiver != null) : "Snippet with the receiver must have it set as constant. Snippet: " + this;
            return hasReceiver(method);
        }

        static boolean hasReceiver(ResolvedJavaMethod method) {
            return method.hasReceiver();
        }

        /**
         * Lazily constructed parts of {@link SnippetInfo}.
         */
        static class Lazy {
            Lazy(ResolvedJavaMethod method) {
                int count = method.getSignature().getParameterCount(hasReceiver(method));
                constantParameters = new boolean[count];
                varargsParameters = new boolean[count];
                nonNullParameters = new boolean[count];
                int offset = hasReceiver(method) ? 1 : 0;
                for (int i = offset; i < count; i++) {
                    constantParameters[i] = method.getParameterAnnotation(ConstantParameter.class, i - offset) != null;
                    varargsParameters[i] = method.getParameterAnnotation(VarargsParameter.class, i - offset) != null;
                    nonNullParameters[i] = method.getParameterAnnotation(NonNullParameter.class, i - offset) != null;

                    assert !constantParameters[i - offset] || !varargsParameters[i - offset] : "Parameter cannot be annotated with both @" + ConstantParameter.class.getSimpleName() + " and @" +
                                    VarargsParameter.class.getSimpleName();
                }
                if (method.hasReceiver()) {
                    // Receiver must be constant.
                    assert !constantParameters[0];
                    constantParameters[0] = true;
                }

                // Retrieve the names only when assertions are turned on. Parameter annotations are
                // unsupported in the native image.
                assert IS_IN_NATIVE_IMAGE || initNames(method, count);
            }

            final boolean[] constantParameters;
            final boolean[] varargsParameters;
            final boolean[] nonNullParameters;

            /**
             * The parameter names, taken from the local variables table. Only used for assertion
             * checking, so use only within an assert statement.
             */
            String[] names;

            private boolean initNames(ResolvedJavaMethod method, int parameterCount) {
                names = new String[parameterCount];
                int offset = 0;
                if (method.hasReceiver()) {
                    names[0] = "this";
                    offset = 1;
                }
                Parameter[] params = method.getParameters();
                if (params != null) {
                    for (int i = offset; i < names.length; i++) {
                        if (params[i - offset].isNamePresent()) {
                            names[i] = params[i - offset].getName();
                        }
                    }
                } else {
                    int slotIdx = 0;
                    LocalVariableTable localVariableTable = method.getLocalVariableTable();
                    if (localVariableTable != null) {
                        for (int i = 0; i < names.length; i++) {
                            Local local = localVariableTable.getLocal(slotIdx, 0);
                            if (local != null) {
                                names[i] = local.getName();
                            }
                            JavaKind kind = method.getSignature().getParameterKind(i);
                            slotIdx += kind.getSlotCount();
                        }
                    }
                }
                return true;
            }
        }

        /**
         * Times instantiations of all templates derived from this snippet.
         */
        private final TimerKey instantiationTimer;

        /**
         * Counts instantiations of all templates derived from this snippet.
         */
        private final CounterKey instantiationCounter;

        protected abstract Lazy lazy();

        protected SnippetInfo(ResolvedJavaMethod method, ResolvedJavaMethod original, LocationIdentity[] privateLocations, Object receiver) {
            this.method = method;
            this.original = original;
            this.privateLocations = privateLocations;
            instantiationCounter = DebugContext.counter("SnippetInstantiationCount[%s]", method.getName());
            instantiationTimer = DebugContext.timer("SnippetInstantiationTime[%s]", method.getName());
            this.receiver = receiver;
        }

        public ResolvedJavaMethod getMethod() {
            return method;
        }

        public int getParameterCount() {
            return lazy().constantParameters.length;
        }

        public boolean isConstantParameter(int paramIdx) {
            return lazy().constantParameters[paramIdx];
        }

        public boolean isVarargsParameter(int paramIdx) {
            return lazy().varargsParameters[paramIdx];
        }

        public boolean isNonNullParameter(int paramIdx) {
            return lazy().nonNullParameters[paramIdx];
        }

        public String getParameterName(int paramIdx) {
            String[] names = lazy().names;
            if (names != null) {
                return names[paramIdx];
            }
            return null;
        }

        @Override
        public String toString() {
            return getClass().getSimpleName() + ":" + method.format("%h.%n");
        }
    }

    protected static class LazySnippetInfo extends SnippetInfo {
        protected final AtomicReference<Lazy> lazy = new AtomicReference<>(null);

        protected LazySnippetInfo(ResolvedJavaMethod method, ResolvedJavaMethod original, LocationIdentity[] privateLocations, Object receiver) {
            super(method, original, privateLocations, receiver);
        }

        @Override
        protected Lazy lazy() {
            if (lazy.get() == null) {
                lazy.compareAndSet(null, new Lazy(method));
            }
            return lazy.get();
        }
    }

    protected static class EagerSnippetInfo extends SnippetInfo {
        protected final Lazy lazy;

        protected EagerSnippetInfo(ResolvedJavaMethod method, ResolvedJavaMethod original, LocationIdentity[] privateLocations, Object receiver) {
            super(method, original, privateLocations, receiver);
            lazy = new Lazy(method);
        }

        @Override
        protected Lazy lazy() {
            return lazy;
        }
    }

    /**
     * Values that are bound to the snippet method parameters. The methods {@link #add},
     * {@link #addConst}, and {@link #addVarargs} must be called in the same order as in the
     * signature of the snippet method. The parameter name is passed to the add methods for
     * assertion checking, i.e., to enforce that the order matches. Which method needs to be called
     * depends on the annotation of the snippet method parameter:
     * <ul>
     * <li>Use {@link #add} for a parameter without an annotation. The value is bound when the
     * {@link SnippetTemplate} is {@link SnippetTemplate#instantiate instantiated}.
     * <li>Use {@link #addConst} for a parameter annotated with {@link ConstantParameter}. The value
     * is bound when the {@link SnippetTemplate} is {@link SnippetTemplate#SnippetTemplate created}.
     * <li>Use {@link #addVarargs} for an array parameter annotated with {@link VarargsParameter}. A
     * separate {@link SnippetTemplate} is {@link SnippetTemplate#SnippetTemplate created} for every
     * distinct array length. The actual values are bound when the {@link SnippetTemplate} is
     * {@link SnippetTemplate#instantiate instantiated}
     * </ul>
     */
    public static class Arguments implements Formattable {

        protected final SnippetInfo info;
        protected final CacheKey cacheKey;
        protected final Object[] values;
        protected final Stamp[] constStamps;
        protected boolean cacheable;

        protected int nextParamIdx;

        public Arguments(SnippetInfo info, GuardsStage guardsStage, LoweringTool.LoweringStage loweringStage) {
            this.info = info;
            this.cacheKey = new CacheKey(info, guardsStage, loweringStage);
            this.values = new Object[info.getParameterCount()];
            this.constStamps = new Stamp[info.getParameterCount()];
            this.cacheable = true;
            if (info.hasReceiver()) {
                addConst("this", info.getReceiver());
            }
        }

        public Arguments add(String name, Object value) {
            assert check(name, false, false);
            values[nextParamIdx] = value;
            nextParamIdx++;
            return this;
        }

        public Arguments addConst(String name, Object value) {
            assert value != null;
            return addConst(name, value, null);
        }

        public Arguments addConst(String name, Object value, Stamp stamp) {
            assert check(name, true, false);
            values[nextParamIdx] = value;
            constStamps[nextParamIdx] = stamp;
            cacheKey.setParam(nextParamIdx, value);
            nextParamIdx++;
            return this;
        }

        public Arguments addVarargs(String name, Class<?> componentType, Stamp argStamp, Object value) {
            assert check(name, false, true);
            Varargs varargs = new Varargs(componentType, argStamp, value);
            values[nextParamIdx] = varargs;
            // A separate template is necessary for every distinct array length
            cacheKey.setParam(nextParamIdx, varargs.length);
            nextParamIdx++;
            return this;
        }

        public void setCacheable(boolean cacheable) {
            this.cacheable = cacheable;
        }

        private boolean check(String name, boolean constParam, boolean varargsParam) {
            assert nextParamIdx < info.getParameterCount() : "too many parameters: " + name + "  " + this;
            assert info.getParameterName(nextParamIdx) == null || info.getParameterName(nextParamIdx).equals(name) : "wrong parameter name at " + nextParamIdx + " : " + name + "  " + this;
            assert constParam == info.isConstantParameter(nextParamIdx) : "Parameter " + (constParam ? "not " : "") + "annotated with @" + ConstantParameter.class.getSimpleName() + ": " + name +
                            "  " + this;
            assert varargsParam == info.isVarargsParameter(nextParamIdx) : "Parameter " + (varargsParam ? "not " : "") + "annotated with @" + VarargsParameter.class.getSimpleName() + ": " + name +
                            "  " + this;
            return true;
        }

        @Override
        public String toString() {
            StringBuilder result = new StringBuilder();
            result.append("Parameters<").append(info.method.format("%h.%n")).append(" [");
            String sep = "";
            for (int i = 0; i < info.getParameterCount(); i++) {
                result.append(sep);
                if (info.isConstantParameter(i)) {
                    result.append("const ");
                } else if (info.isVarargsParameter(i)) {
                    result.append("varargs ");
                }
                result.append(info.getParameterName(i)).append(" = ").append(values[i]);
                sep = ", ";
            }
            result.append(">");
            return result.toString();
        }

        @Override
        public void formatTo(Formatter formatter, int flags, int width, int precision) {
            if ((flags & ALTERNATE) == 0) {
                formatter.format(applyFormattingFlagsAndWidth(toString(), flags, width));
            } else {
                StringBuilder sb = new StringBuilder();
                sb.append(info.method.getName()).append('(');
                String sep = "";
                for (int i = 0; i < info.getParameterCount(); i++) {
                    if (info.isConstantParameter(i)) {
                        sb.append(sep);
                        if (info.getParameterName(i) != null) {
                            sb.append(info.getParameterName(i));
                        } else {
                            sb.append(i);
                        }
                        sb.append('=').append(values[i]);
                        sep = ", ";
                    }
                }
                sb.append(")");
                String string = sb.toString();
                if (string.indexOf('%') != -1) {
                    // Quote any % signs
                    string = string.replace("%", "%%");
                }
                formatter.format(applyFormattingFlagsAndWidth(string, flags & ~ALTERNATE, width));
            }
        }
    }

    /**
     * Wrapper for the prototype value of a {@linkplain VarargsParameter varargs} parameter.
     */
    static class Varargs {

        protected final Class<?> componentType;
        protected final Stamp stamp;
        protected final Object value;
        protected final int length;

        protected Varargs(Class<?> componentType, Stamp stamp, Object value) {
            this.componentType = componentType;
            this.stamp = stamp;
            this.value = value;
            if (value instanceof List) {
                this.length = ((List<?>) value).size();
            } else {
                this.length = Array.getLength(value);
            }
        }

        @Override
        public String toString() {
            if (value instanceof boolean[]) {
                return Arrays.toString((boolean[]) value);
            }
            if (value instanceof byte[]) {
                return Arrays.toString((byte[]) value);
            }
            if (value instanceof char[]) {
                return Arrays.toString((char[]) value);
            }
            if (value instanceof short[]) {
                return Arrays.toString((short[]) value);
            }
            if (value instanceof int[]) {
                return Arrays.toString((int[]) value);
            }
            if (value instanceof long[]) {
                return Arrays.toString((long[]) value);
            }
            if (value instanceof float[]) {
                return Arrays.toString((float[]) value);
            }
            if (value instanceof double[]) {
                return Arrays.toString((double[]) value);
            }
            if (value instanceof Object[]) {
                return Arrays.toString((Object[]) value);
            }
            return String.valueOf(value);
        }
    }

    @NodeInfo(cycles = CYCLES_IGNORED, size = SIZE_IGNORED)
    static final class VarargsPlaceholderNode extends FloatingNode implements ArrayLengthProvider {

        public static final NodeClass<VarargsPlaceholderNode> TYPE = NodeClass.create(VarargsPlaceholderNode.class);
        protected final Varargs varargs;

        protected VarargsPlaceholderNode(Varargs varargs, MetaAccessProvider metaAccess) {
            super(TYPE, StampFactory.objectNonNull(TypeReference.createExactTrusted(metaAccess.lookupJavaType(varargs.componentType).getArrayClass())));
            this.varargs = varargs;
        }

        @Override
        public ValueNode findLength(FindLengthMode mode, ConstantReflectionProvider constantReflection) {
            return ConstantNode.forInt(varargs.length);
        }
    }

    static class CacheKey {

        private final ResolvedJavaMethod method;
        private final Object[] values;
        private final GuardsStage guardsStage;
        private final LoweringTool.LoweringStage loweringStage;
        private int hash;

        protected CacheKey(SnippetInfo info, GuardsStage guardsStage, LoweringTool.LoweringStage loweringStage) {
            this.method = info.method;
            this.guardsStage = guardsStage;
            this.loweringStage = loweringStage;
            this.values = new Object[info.getParameterCount()];
            this.hash = info.method.hashCode() + 31 * guardsStage.ordinal();
        }

        protected void setParam(int paramIdx, Object value) {
            values[paramIdx] = value;
            hash = (hash * 31) ^ (value == null ? 0 : value.hashCode());
        }

        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof CacheKey)) {
                return false;
            }
            CacheKey other = (CacheKey) obj;
            if (!method.equals(other.method)) {
                return false;
            }
            if (guardsStage != other.guardsStage || loweringStage != other.loweringStage) {
                return false;
            }
            for (int i = 0; i < values.length; i++) {
                if (values[i] != null && !values[i].equals(other.values[i])) {
                    return false;
                }
            }
            return true;
        }

        @Override
        public int hashCode() {
            return hash;
        }
    }

    private static final TimerKey SnippetTemplateCreationTime = DebugContext.timer("SnippetTemplateCreationTime");
    private static final CounterKey SnippetTemplates = DebugContext.counter("SnippetTemplateCount");

    static class Options {
        @Option(help = "Use a LRU cache for snippet templates.")//
        public static final OptionKey<Boolean> UseSnippetTemplateCache = new OptionKey<>(true);

        @Option(help = "")//
        static final OptionKey<Integer> MaxTemplatesPerSnippet = new OptionKey<>(50);
    }

    /**
     * Base class for snippet classes. It provides a cache for {@link SnippetTemplate}s.
     */
    public abstract static class AbstractTemplates implements org.graalvm.compiler.api.replacements.SnippetTemplateCache {

        protected final OptionValues options;
        protected final Providers providers;
        protected final SnippetReflectionProvider snippetReflection;
        protected final Iterable<DebugHandlersFactory> factories;
        protected final TargetDescription target;
        private final Map<CacheKey, SnippetTemplate> templates;

        protected AbstractTemplates(OptionValues options, Iterable<DebugHandlersFactory> factories, Providers providers, SnippetReflectionProvider snippetReflection, TargetDescription target) {
            this.options = options;
            this.providers = providers;
            this.snippetReflection = snippetReflection;
            this.target = target;
            this.factories = factories;
            if (Options.UseSnippetTemplateCache.getValue(options)) {
                int size = Options.MaxTemplatesPerSnippet.getValue(options);
                this.templates = Collections.synchronizedMap(new LRUCache<>(size, size));
            } else {
                this.templates = null;
            }
        }

        public Providers getProviders() {
            return providers;
        }

        public static Method findMethod(Class<? extends Snippets> declaringClass, String methodName, Method except) {
            for (Method m : declaringClass.getDeclaredMethods()) {
                if (m.getName().equals(methodName) && !m.equals(except)) {
                    return m;
                }
            }
            return null;
        }

        public static ResolvedJavaMethod findMethod(MetaAccessProvider metaAccess, Class<?> declaringClass, String methodName) {
            ResolvedJavaType type = metaAccess.lookupJavaType(declaringClass);
            ResolvedJavaMethod result = null;
            for (ResolvedJavaMethod m : type.getDeclaredMethods()) {
                if (m.getName().equals(methodName)) {
                    if (!Assertions.assertionsEnabled()) {
                        return m;
                    } else {
                        assert result == null : "multiple definitions found";
                        result = m;
                    }
                }
            }
            if (result == null) {
                throw new GraalError("Could not find method in " + declaringClass + " named " + methodName);
            }
            return result;
        }

        protected SnippetInfo snippet(Class<? extends Snippets> declaringClass, String methodName, LocationIdentity... initialPrivateLocations) {
            return snippet(declaringClass, methodName, null, null, initialPrivateLocations);
        }

        /**
         * Finds the unique method in {@code declaringClass} named {@code methodName} annotated by
         * {@link Snippet} and returns a {@link SnippetInfo} value describing it. There must be
         * exactly one snippet method in {@code declaringClass} with a given name.
         */
        protected SnippetInfo snippet(Class<? extends Snippets> declaringClass, String methodName, ResolvedJavaMethod original, Object receiver, LocationIdentity... initialPrivateLocations) {
            assert methodName != null;
            ResolvedJavaMethod javaMethod = findMethod(providers.getMetaAccess(), declaringClass, methodName);
            assert javaMethod != null : "did not find @" + Snippet.class.getSimpleName() + " method in " + declaringClass + " named " + methodName;
            assert javaMethod.getAnnotation(Snippet.class) != null : javaMethod + " must be annotated with @" + Snippet.class.getSimpleName();
            providers.getReplacements().registerSnippet(javaMethod, original, receiver, GraalOptions.TrackNodeSourcePosition.getValue(options), options);
            LocationIdentity[] privateLocations = GraalOptions.SnippetCounters.getValue(options) ? SnippetCounterNode.addSnippetCounters(initialPrivateLocations) : initialPrivateLocations;
            if (GraalOptions.EagerSnippets.getValue(options)) {
                return new EagerSnippetInfo(javaMethod, original, privateLocations, receiver);
            } else {
                return new LazySnippetInfo(javaMethod, original, privateLocations, receiver);
            }
        }

        static final AtomicInteger nextSnippetTemplateId = new AtomicInteger();

        private DebugContext openDebugContext(DebugContext outer, Arguments args) {
            if (DebugStubsAndSnippets.getValue(options)) {
                Description description = new Description(args.cacheKey.method, "SnippetTemplate_" + nextSnippetTemplateId.incrementAndGet());
                return DebugContext.create(options, description, outer.getGlobalMetrics(), DEFAULT_LOG_STREAM, factories);
            }
            return DebugContext.disabled(options);
        }

        /**
         * Gets a template for a given key, creating it first if necessary.
         */
        @SuppressWarnings("try")
        public SnippetTemplate template(ValueNode replacee, final Arguments args) {
            StructuredGraph graph = replacee.graph();
            DebugContext outer = graph.getDebug();
            SnippetTemplate template = Options.UseSnippetTemplateCache.getValue(options) && args.cacheable ? templates.get(args.cacheKey) : null;
            if (template == null || (graph.trackNodeSourcePosition() && !template.snippet.trackNodeSourcePosition())) {
                try (DebugContext debug = openDebugContext(outer, args)) {
                    try (DebugCloseable a = SnippetTemplateCreationTime.start(debug); DebugContext.Scope s = debug.scope("SnippetSpecialization", args.info.method)) {
                        SnippetTemplates.increment(debug);
                        OptionValues snippetOptions = new OptionValues(options, GraalOptions.TraceInlining, GraalOptions.TraceInliningForStubsAndSnippets.getValue(options));
                        template = new SnippetTemplate(snippetOptions, debug, providers, snippetReflection, args, graph.trackNodeSourcePosition(), replacee);
                        if (Options.UseSnippetTemplateCache.getValue(snippetOptions) && args.cacheable) {
                            templates.put(args.cacheKey, template);
                        }
                    } catch (Throwable e) {
                        throw debug.handle(e);
                    }
                }
            }
            return template;
        }
    }

    private static final class LRUCache<K, V> extends LinkedHashMap<K, V> {
        private static final long serialVersionUID = 1L;
        private final int maxCacheSize;

        LRUCache(int initialCapacity, int maxCacheSize) {
            super(initialCapacity, 0.75F, true);
            this.maxCacheSize = maxCacheSize;
        }

        @Override
        protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {
            return size() > maxCacheSize;
        }
    }

    // These values must be compared with equals() not '==' to support replay compilation.
    private static final Object UNUSED_PARAMETER = "UNUSED_PARAMETER";
    private static final Object CONSTANT_PARAMETER = "CONSTANT_PARAMETER";

    /**
     * Determines if any parameter of a given method is annotated with {@link ConstantParameter}.
     */
    public static boolean hasConstantParameter(ResolvedJavaMethod method) {
        for (ConstantParameter p : method.getParameterAnnotations(ConstantParameter.class)) {
            if (p != null) {
                return true;
            }
        }
        return false;
    }

    private final SnippetReflectionProvider snippetReflection;

    /**
     * Creates a snippet template.
     */
    @SuppressWarnings("try")
    protected SnippetTemplate(OptionValues options, DebugContext debug, final Providers providers, SnippetReflectionProvider snippetReflection, Arguments args, boolean trackNodeSourcePosition,
                    Node replacee) {
        this.snippetReflection = snippetReflection;
        this.info = args.info;

        Object[] constantArgs = getConstantArgs(args);
        boolean shouldTrackNodeSourcePosition1 = trackNodeSourcePosition || (providers.getCodeCache() != null && providers.getCodeCache().shouldDebugNonSafepoints());
        StructuredGraph snippetGraph = providers.getReplacements().getSnippet(args.info.method, args.info.original, constantArgs, shouldTrackNodeSourcePosition1, replacee.getNodeSourcePosition(),
                        options);

        ResolvedJavaMethod method = snippetGraph.method();
        Signature signature = method.getSignature();

        // Copy snippet graph, replacing constant parameters with given arguments
        final StructuredGraph snippetCopy = new StructuredGraph.Builder(options, debug).name(snippetGraph.name).method(snippetGraph.method()).trackNodeSourcePosition(
                        snippetGraph.trackNodeSourcePosition()).setIsSubstitution(true).build();
        assert !GraalOptions.TrackNodeSourcePosition.getValue(options) || snippetCopy.trackNodeSourcePosition();
        try (DebugContext.Scope scope = debug.scope("SpecializeSnippet", snippetCopy)) {
            if (!snippetGraph.isUnsafeAccessTrackingEnabled()) {
                snippetCopy.disableUnsafeAccessTracking();
            }

            EconomicMap<Node, Node> nodeReplacements = EconomicMap.create(Equivalence.IDENTITY);
            nodeReplacements.put(snippetGraph.start(), snippetCopy.start());

            MetaAccessProvider metaAccess = providers.getMetaAccess();
            assert checkTemplate(metaAccess, args, method, signature);

            int parameterCount = args.info.getParameterCount();
            VarargsPlaceholderNode[] placeholders = new VarargsPlaceholderNode[parameterCount];

            for (int i = 0; i < parameterCount; i++) {
                ParameterNode parameter = snippetGraph.getParameter(i);
                if (parameter != null) {
                    if (args.info.isConstantParameter(i)) {
                        Object arg = args.values[i];
                        JavaKind kind = signature.getParameterKind(i);
                        ConstantNode constantNode;
                        if (arg instanceof Constant) {
                            Stamp stamp = args.constStamps[i];
                            if (stamp == null) {
                                assert arg instanceof JavaConstant : "could not determine type of constant " + arg;
                                constantNode = ConstantNode.forConstant((JavaConstant) arg, metaAccess, snippetCopy);
                            } else {
                                constantNode = ConstantNode.forConstant(stamp, (Constant) arg, metaAccess, snippetCopy);
                            }
                        } else {
                            constantNode = ConstantNode.forConstant(snippetReflection.forBoxed(kind, arg), metaAccess, snippetCopy);
                        }
                        nodeReplacements.put(parameter, constantNode);
                    } else if (args.info.isVarargsParameter(i)) {
                        Varargs varargs = (Varargs) args.values[i];
                        VarargsPlaceholderNode placeholder = snippetCopy.unique(new VarargsPlaceholderNode(varargs, providers.getMetaAccess()));
                        nodeReplacements.put(parameter, placeholder);
                        placeholders[i] = placeholder;
                    } else if (args.info.isNonNullParameter(i)) {
                        parameter.setStamp(parameter.stamp(NodeView.DEFAULT).join(StampFactory.objectNonNull()));
                    }
                }
            }
            try (InliningLog.UpdateScope updateScope = snippetCopy.getInliningLog().openDefaultUpdateScope()) {
                UnmodifiableEconomicMap<Node, Node> duplicates = snippetCopy.addDuplicates(snippetGraph.getNodes(), snippetGraph, snippetGraph.getNodeCount(), nodeReplacements);
                if (updateScope != null) {
                    snippetCopy.getInliningLog().replaceLog(duplicates, snippetGraph.getInliningLog());
                }
            }

            debug.dump(DebugContext.INFO_LEVEL, snippetCopy, "Before specialization");

            // Gather the template parameters
            parameters = new Object[parameterCount];
            for (int i = 0; i < parameterCount; i++) {
                if (args.info.isConstantParameter(i)) {
                    parameters[i] = CONSTANT_PARAMETER;
                } else if (args.info.isVarargsParameter(i)) {
                    assert snippetCopy.getParameter(i) == null;
                    Varargs varargs = (Varargs) args.values[i];
                    int length = varargs.length;
                    ParameterNode[] params = new ParameterNode[length];
                    Stamp stamp = varargs.stamp;
                    for (int j = 0; j < length; j++) {
                        // Use a decimal friendly numbering make it more obvious how values map
                        assert parameterCount < 10000;
                        int idx = (i + 1) * 10000 + j;
                        assert idx >= parameterCount : "collision in parameter numbering";
                        ParameterNode local = snippetCopy.addOrUnique(new ParameterNode(idx, StampPair.createSingle(stamp)));
                        params[j] = local;
                    }
                    parameters[i] = params;

                    VarargsPlaceholderNode placeholder = placeholders[i];
                    if (placeholder != null) {
                        for (Node usage : placeholder.usages().snapshot()) {
                            if (usage instanceof LoadIndexedNode) {
                                LoadIndexedNode loadIndexed = (LoadIndexedNode) usage;
                                debug.dump(DebugContext.INFO_LEVEL, snippetCopy, "Before replacing %s", loadIndexed);
                                LoadSnippetVarargParameterNode loadSnippetParameter = snippetCopy.add(
                                                new LoadSnippetVarargParameterNode(params, loadIndexed.index(), loadIndexed.stamp(NodeView.DEFAULT)));
                                snippetCopy.replaceFixedWithFixed(loadIndexed, loadSnippetParameter);
                                debug.dump(DebugContext.INFO_LEVEL, snippetCopy, "After replacing %s", loadIndexed);
                            } else if (usage instanceof StoreIndexedNode) {
                                /*
                                 * The template lowering doesn't really treat this as an array so
                                 * you can't store back into the varargs. Allocate your own array if
                                 * you really need this and EA should eliminate it.
                                 */
                                throw new GraalError("Can't store into VarargsParameter array");
                            }
                        }
                    }
                } else {
                    ParameterNode local = snippetCopy.getParameter(i);
                    if (local == null) {
                        // Parameter value was eliminated
                        parameters[i] = UNUSED_PARAMETER;
                    } else {
                        parameters[i] = local;
                    }
                }
            }

            explodeLoops(snippetCopy, providers);

            GuardsStage guardsStage = args.cacheKey.guardsStage;
            // Perform lowering on the snippet
            if (!guardsStage.allowsFloatingGuards()) {
                new GuardLoweringPhase().apply(snippetCopy, null);
            }
            snippetCopy.setGuardsStage(guardsStage);
            try (DebugContext.Scope s = debug.scope("LoweringSnippetTemplate", snippetCopy)) {
                new LoweringPhase(new CanonicalizerPhase(), args.cacheKey.loweringStage).apply(snippetCopy, providers);
            } catch (Throwable e) {
                throw debug.handle(e);
            }

            ArrayList<StateSplit> curSideEffectNodes = new ArrayList<>();
            ArrayList<DeoptimizingNode> curDeoptNodes = new ArrayList<>();
            ArrayList<ValueNode> curPlaceholderStampedNodes = new ArrayList<>();
            for (Node node : snippetCopy.getNodes()) {
                if (node instanceof ValueNode) {
                    ValueNode valueNode = (ValueNode) node;
                    if (valueNode.stamp(NodeView.DEFAULT) == PlaceholderStamp.singleton()) {
                        curPlaceholderStampedNodes.add(valueNode);
                    }
                }

                if (node instanceof StateSplit) {
                    StateSplit stateSplit = (StateSplit) node;
                    FrameState frameState = stateSplit.stateAfter();
                    if (stateSplit.hasSideEffect()) {
                        curSideEffectNodes.add((StateSplit) node);
                    }
                    if (frameState != null) {
                        stateSplit.setStateAfter(null);
                    }
                }
                if (node instanceof DeoptimizingNode) {
                    DeoptimizingNode deoptNode = (DeoptimizingNode) node;
                    if (deoptNode.canDeoptimize()) {
                        curDeoptNodes.add(deoptNode);
                    }
                }
            }

            new DeadCodeEliminationPhase(Required).apply(snippetCopy);

            assert checkAllVarargPlaceholdersAreDeleted(parameterCount, placeholders);

            new FloatingReadPhase(true, true).apply(snippetCopy);
            new RemoveValueProxyPhase().apply(snippetCopy);

            MemoryAnchorNode anchor = snippetCopy.add(new MemoryAnchorNode());
            snippetCopy.start().replaceAtUsages(InputType.Memory, anchor);

            this.snippet = snippetCopy;

            StartNode entryPointNode = snippet.start();
            if (anchor.hasNoUsages()) {
                anchor.safeDelete();
                this.memoryAnchor = null;
            } else {
                // Find out if all the return memory maps point to the anchor (i.e., there's no kill
                // anywhere)
                boolean needsMemoryMaps = false;
                for (ReturnNode retNode : snippet.getNodes(ReturnNode.TYPE)) {
                    MemoryMapNode memoryMap = retNode.getMemoryMap();
                    if (memoryMap.getLocations().size() > 1 || memoryMap.getLastLocationAccess(LocationIdentity.any()) != anchor) {
                        needsMemoryMaps = true;
                        break;
                    }
                }
                boolean needsAnchor;
                if (needsMemoryMaps) {
                    needsAnchor = true;
                } else {
                    // Check that all those memory maps where the only usages of the anchor
                    needsAnchor = anchor.usages().filter(isNotA(MemoryMapNode.class)).isNotEmpty();
                    // Remove the useless memory map
                    MemoryMapNode memoryMap = null;
                    for (ReturnNode retNode : snippet.getNodes(ReturnNode.TYPE)) {
                        if (memoryMap == null) {
                            memoryMap = retNode.getMemoryMap();
                        } else {
                            assert memoryMap == retNode.getMemoryMap();
                        }
                        retNode.setMemoryMap(null);
                    }
                    if (memoryMap != null) {
                        memoryMap.safeDelete();
                    }
                }
                if (needsAnchor) {
                    snippetCopy.addAfterFixed(snippetCopy.start(), anchor);
                    this.memoryAnchor = anchor;
                } else {
                    anchor.safeDelete();
                    this.memoryAnchor = null;
                }
            }
            debug.dump(DebugContext.INFO_LEVEL, snippet, "SnippetTemplate after fixing memory anchoring");

            List<ReturnNode> returnNodes = snippet.getNodes(ReturnNode.TYPE).snapshot();
            if (returnNodes.isEmpty()) {
                this.returnNode = null;
            } else if (returnNodes.size() == 1) {
                this.returnNode = returnNodes.get(0);
            } else {
                AbstractMergeNode merge = snippet.add(new MergeNode());
                List<MemoryMapNode> memMaps = new ArrayList<>();
                for (ReturnNode retNode : returnNodes) {
                    MemoryMapNode memoryMapNode = retNode.getMemoryMap();
                    if (memoryMapNode != null) {
                        memMaps.add(memoryMapNode);
                    }
                }

                ValueNode returnValue = InliningUtil.mergeReturns(merge, returnNodes);
                this.returnNode = snippet.add(new ReturnNode(returnValue));
                if (!memMaps.isEmpty()) {
                    MemoryMapImpl mmap = FloatingReadPhase.mergeMemoryMaps(merge, memMaps);
                    MemoryMapNode memoryMap = snippet.unique(new MemoryMapNode(mmap.getMap()));
                    this.returnNode.setMemoryMap(memoryMap);
                    for (MemoryMapNode mm : memMaps) {
                        if (mm != memoryMap && mm.isAlive()) {
                            assert mm.hasNoUsages();
                            GraphUtil.killWithUnusedFloatingInputs(mm);
                        }
                    }
                }
                merge.setNext(this.returnNode);
            }

            assert verifyIntrinsicsProcessed(snippetCopy);

            this.sideEffectNodes = curSideEffectNodes;
            this.deoptNodes = curDeoptNodes;
            this.placeholderStampedNodes = curPlaceholderStampedNodes;

            nodes = new ArrayList<>(snippet.getNodeCount());
            for (Node node : snippet.getNodes()) {
                if (node != entryPointNode && node != entryPointNode.stateAfter()) {
                    nodes.add(node);
                }
            }

            if (debug.areMetricsEnabled()) {
                DebugContext.counter("SnippetTemplateNodeCount[%#s]", args).add(debug, nodes.size());
            }
            debug.dump(DebugContext.INFO_LEVEL, snippet, "SnippetTemplate final state");
            this.snippet.freeze();

        } catch (Throwable ex) {
            throw debug.handle(ex);
        }
    }

    private static boolean verifyIntrinsicsProcessed(StructuredGraph snippetCopy) {
        for (MethodCallTargetNode target : snippetCopy.getNodes(MethodCallTargetNode.TYPE)) {
            ResolvedJavaMethod targetMethod = target.targetMethod();
            if (targetMethod != null) {
                assert targetMethod.getAnnotation(Fold.class) == null && targetMethod.getAnnotation(NodeIntrinsic.class) == null : "plugin should have been processed";
            }
        }
        return true;
    }

    public static void explodeLoops(final StructuredGraph snippetCopy, CoreProviders providers) {
        // Do any required loop explosion
        boolean exploded = false;
        do {
            exploded = false;
            ExplodeLoopNode explodeLoop = snippetCopy.getNodes().filter(ExplodeLoopNode.class).first();
            if (explodeLoop != null) { // Earlier canonicalization may have removed the loop
                // altogether
                LoopBeginNode loopBegin = explodeLoop.findLoopBegin();
                if (loopBegin != null) {
                    LoopEx loop = new LoopsData(snippetCopy).loop(loopBegin);
                    Mark mark = snippetCopy.getMark();
                    LoopTransformations.fullUnroll(loop, providers, new CanonicalizerPhase());
                    new CanonicalizerPhase().applyIncremental(snippetCopy, providers, mark, false);
                    loop.deleteUnusedNodes();
                }
                GraphUtil.removeFixedWithUnusedInputs(explodeLoop);
                exploded = true;
            }
        } while (exploded);
    }

    protected Object[] getConstantArgs(Arguments args) {
        Object[] constantArgs = args.values.clone();
        for (int i = 0; i < args.info.getParameterCount(); i++) {
            if (!args.info.isConstantParameter(i)) {
                constantArgs[i] = null;
            } else {
                assert constantArgs[i] != null : "Can't pass raw null through as argument";
            }
        }
        return constantArgs;
    }

    private static boolean checkAllVarargPlaceholdersAreDeleted(int parameterCount, VarargsPlaceholderNode[] placeholders) {
        for (int i = 0; i < parameterCount; i++) {
            if (placeholders[i] != null) {
                assert placeholders[i].isDeleted() : placeholders[i];
            }
        }
        return true;
    }

    private static boolean checkConstantArgument(MetaAccessProvider metaAccess, final ResolvedJavaMethod method, Signature signature, int paramIndex, String name, Object arg, JavaKind kind) {
        ResolvedJavaType type = signature.getParameterType(paramIndex, method.getDeclaringClass()).resolve(method.getDeclaringClass());
        if (metaAccess.lookupJavaType(WordBase.class).isAssignableFrom(type)) {
            assert arg instanceof JavaConstant : method + ": word constant parameters must be passed boxed in a Constant value: " + arg;
            return true;
        }
        if (kind != JavaKind.Object) {
            assert arg != null && kind.toBoxedJavaClass() == arg.getClass() : method + ": wrong value kind for " + name + ": expected " + kind + ", got " +
                            (arg == null ? "null" : arg.getClass().getSimpleName());
        }
        return true;
    }

    private static boolean checkVarargs(MetaAccessProvider metaAccess, final ResolvedJavaMethod method, Signature signature, int i, String name, Varargs varargs) {
        ResolvedJavaType type = (ResolvedJavaType) signature.getParameterType(i, method.getDeclaringClass());
        assert type.isArray() : "varargs parameter must be an array type";
        assert type.getComponentType().isAssignableFrom(metaAccess.lookupJavaType(varargs.componentType)) : "componentType for " + name + " not matching " + type.toJavaName() + " instance: " +
                        varargs.componentType;
        return true;
    }

    /**
     * The graph built from the snippet method.
     */
    private final StructuredGraph snippet;

    private final SnippetInfo info;

    /**
     * The named parameters of this template that must be bound to values during instantiation. For
     * a parameter that is still live after specialization, the value in this map is either a
     * {@link ParameterNode} instance or a {@link ParameterNode} array. For an eliminated parameter,
     * the value is identical to the key.
     */
    private final Object[] parameters;

    /**
     * The return node (if any) of the snippet.
     */
    private final ReturnNode returnNode;

    /**
     * The memory anchor (if any) of the snippet.
     */
    private final MemoryAnchorNode memoryAnchor;

    /**
     * Nodes that inherit the {@link StateSplit#stateAfter()} from the replacee during
     * instantiation.
     */
    private final ArrayList<StateSplit> sideEffectNodes;

    /**
     * Nodes that inherit a deoptimization {@link FrameState} from the replacee during
     * instantiation.
     */
    private final ArrayList<DeoptimizingNode> deoptNodes;

    /**
     * Nodes that have a stamp originating from a {@link Placeholder}.
     */
    private final ArrayList<ValueNode> placeholderStampedNodes;

    /**
     * The nodes to be inlined when this specialization is instantiated.
     */
    private final ArrayList<Node> nodes;

    /**
     * Gets the instantiation-time bindings to this template's parameters.
     *
     * @return the map that will be used to bind arguments to parameters when inlining this template
     */
    private EconomicMap<Node, Node> bind(StructuredGraph replaceeGraph, MetaAccessProvider metaAccess, Arguments args) {
        EconomicMap<Node, Node> replacements = EconomicMap.create(Equivalence.IDENTITY);
        assert args.info.getParameterCount() == parameters.length : "number of args (" + args.info.getParameterCount() + ") != number of parameters (" + parameters.length + ")";
        for (int i = 0; i < parameters.length; i++) {
            Object parameter = parameters[i];
            assert parameter != null : this + " has no parameter named " + args.info.getParameterName(i);
            Object argument = args.values[i];
            if (parameter instanceof ParameterNode) {
                if (argument instanceof ValueNode) {
                    replacements.put((ParameterNode) parameter, (ValueNode) argument);
                } else {
                    JavaKind kind = ((ParameterNode) parameter).getStackKind();
                    assert argument != null || kind == JavaKind.Object : this + " cannot accept null for non-object parameter named " + args.info.getParameterName(i);
                    JavaConstant constant = forBoxed(argument, kind);
                    replacements.put((ParameterNode) parameter, ConstantNode.forConstant(constant, metaAccess, replaceeGraph));
                }
            } else if (parameter instanceof ParameterNode[]) {
                ParameterNode[] params = (ParameterNode[]) parameter;
                Varargs varargs = (Varargs) argument;
                int length = params.length;
                List<?> list = null;
                Object array = null;
                if (varargs.value instanceof List) {
                    list = (List<?>) varargs.value;
                    assert list.size() == length : length + " != " + list.size();
                } else {
                    array = varargs.value;
                    assert array != null && array.getClass().isArray();
                    assert Array.getLength(array) == length : length + " != " + Array.getLength(array);
                }

                for (int j = 0; j < length; j++) {
                    ParameterNode param = params[j];
                    assert param != null;
                    Object value = list != null ? list.get(j) : Array.get(array, j);
                    if (value instanceof ValueNode) {
                        replacements.put(param, (ValueNode) value);
                    } else {
                        JavaConstant constant = forBoxed(value, param.getStackKind());
                        ConstantNode element = ConstantNode.forConstant(constant, metaAccess, replaceeGraph);
                        replacements.put(param, element);
                    }
                }
            } else {
                assert parameter.equals(CONSTANT_PARAMETER) || parameter.equals(UNUSED_PARAMETER) : "unexpected entry for parameter: " + args.info.getParameterName(i) + " -> " + parameter;
            }
        }
        return replacements;
    }

    /**
     * Converts a Java boxed value to a {@link JavaConstant} of the right kind. This adjusts for the
     * limitation that a {@link Local}'s kind is a {@linkplain JavaKind#getStackKind() stack kind}
     * and so cannot be used for re-boxing primitives smaller than an int.
     *
     * @param argument a Java boxed value
     * @param localKind the kind of the {@link Local} to which {@code argument} will be bound
     */
    protected JavaConstant forBoxed(Object argument, JavaKind localKind) {
        assert localKind == localKind.getStackKind();
        if (localKind == JavaKind.Int) {
            return JavaConstant.forBoxedPrimitive(argument);
        }
        return snippetReflection.forBoxed(localKind, argument);
    }

    /**
     * Logic for replacing a snippet-lowered node at its usages with the return value of the
     * snippet. An alternative to the {@linkplain SnippetTemplate#DEFAULT_REPLACER default}
     * replacement logic can be used to handle mismatches between the stamp of the node being
     * lowered and the stamp of the snippet's return value.
     */
    public interface UsageReplacer {
        /**
         * Replaces all usages of {@code oldNode} with direct or indirect usages of {@code newNode}.
         */
        void replace(ValueNode oldNode, ValueNode newNode);
    }

    /**
     * Represents the default {@link UsageReplacer usage replacer} logic which simply delegates to
     * {@link Node#replaceAtUsages(Node)}.
     */
    public static final UsageReplacer DEFAULT_REPLACER = new UsageReplacer() {

        @Override
        public void replace(ValueNode oldNode, ValueNode newNode) {
            if (newNode == null) {
                assert oldNode.hasNoUsages();
            } else {
                oldNode.replaceAtUsages(newNode);
            }
        }
    };

    private boolean assertSnippetKills(ValueNode replacee) {
        if (!replacee.graph().isAfterFloatingReadPhase()) {
            // no floating reads yet, ignore locations created while lowering
            return true;
        }
        if (returnNode == null) {
            // The snippet terminates control flow
            return true;
        }
        MemoryMapNode memoryMap = returnNode.getMemoryMap();
        if (memoryMap == null || memoryMap.isEmpty()) {
            // there are no kills in the snippet graph
            return true;
        }

        EconomicSet<LocationIdentity> kills = EconomicSet.create(Equivalence.DEFAULT);
        kills.addAll(memoryMap.getLocations());

        if (replacee instanceof MemoryCheckpoint.Single) {
            // check if some node in snippet graph also kills the same location
            LocationIdentity locationIdentity = ((MemoryCheckpoint.Single) replacee).getLocationIdentity();
            if (locationIdentity.isAny()) {
                assert !(memoryMap.getLastLocationAccess(any()) instanceof MemoryAnchorNode) : replacee + " kills ANY_LOCATION, but snippet does not";
                // if the replacee kills ANY_LOCATION, the snippet can kill arbitrary locations
                return true;
            }
            assert kills.contains(locationIdentity) : replacee + " kills " + locationIdentity + ", but snippet doesn't contain a kill to this location";
            kills.remove(locationIdentity);
        }
        assert !(replacee instanceof MemoryCheckpoint.Multi) : replacee + " multi not supported (yet)";

        // remove ANY_LOCATION if it's just a kill by the start node
        if (memoryMap.getLastLocationAccess(any()) instanceof MemoryAnchorNode) {
            kills.remove(any());
        }

        // node can only lower to a ANY_LOCATION kill if the replacee also kills ANY_LOCATION
        assert !kills.contains(any()) : "snippet graph contains a kill to ANY_LOCATION, but replacee (" + replacee + ") doesn't kill ANY_LOCATION.  kills: " + kills;

        /*
         * Kills to private locations are safe, since there can be no floating read to these
         * locations except reads that are introduced by the snippet itself or related snippets in
         * the same lowering round. These reads are anchored to a MemoryAnchor at the beginning of
         * their snippet, so they can not float above a kill in another instance of the same
         * snippet.
         */
        for (LocationIdentity p : this.info.privateLocations) {
            kills.remove(p);
        }

        assert kills.isEmpty() : "snippet graph kills non-private locations " + kills + " that replacee (" + replacee + ") doesn't kill";
        return true;
    }

    private static class MemoryInputMap implements MemoryMap {

        private final LocationIdentity locationIdentity;
        private final MemoryNode lastLocationAccess;

        MemoryInputMap(ValueNode replacee) {
            if (replacee instanceof MemoryAccess) {
                MemoryAccess access = (MemoryAccess) replacee;
                locationIdentity = access.getLocationIdentity();
                lastLocationAccess = access.getLastLocationAccess();
            } else {
                locationIdentity = null;
                lastLocationAccess = null;
            }
        }

        @Override
        public MemoryNode getLastLocationAccess(LocationIdentity location) {
            if (locationIdentity != null && locationIdentity.equals(location)) {
                return lastLocationAccess;
            } else {
                return null;
            }
        }

        @Override
        public Collection<LocationIdentity> getLocations() {
            if (locationIdentity == null) {
                return Collections.emptySet();
            } else {
                return Collections.singleton(locationIdentity);
            }
        }
    }

    private class MemoryOutputMap extends MemoryInputMap {

        private final UnmodifiableEconomicMap<Node, Node> duplicates;

        MemoryOutputMap(ValueNode replacee, UnmodifiableEconomicMap<Node, Node> duplicates) {
            super(replacee);
            this.duplicates = duplicates;
        }

        @Override
        public MemoryNode getLastLocationAccess(LocationIdentity locationIdentity) {
            MemoryMapNode memoryMap = returnNode.getMemoryMap();
            assert memoryMap != null : "no memory map stored for this snippet graph (snippet doesn't have a ReturnNode?)";
            MemoryNode lastLocationAccess = memoryMap.getLastLocationAccess(locationIdentity);
            assert lastLocationAccess != null : locationIdentity;
            if (lastLocationAccess == memoryAnchor) {
                return super.getLastLocationAccess(locationIdentity);
            } else {
                return (MemoryNode) duplicates.get(ValueNodeUtil.asNode(lastLocationAccess));
            }
        }

        @Override
        public Collection<LocationIdentity> getLocations() {
            return returnNode.getMemoryMap().getLocations();
        }
    }

    private void rewireMemoryGraph(ValueNode replacee, UnmodifiableEconomicMap<Node, Node> duplicates) {
        if (replacee.graph().isAfterFloatingReadPhase()) {
            // rewire outgoing memory edges
            replaceMemoryUsages(replacee, new MemoryOutputMap(replacee, duplicates));

            if (returnNode != null) {
                ReturnNode ret = (ReturnNode) duplicates.get(returnNode);
                if (ret != null) {
                    MemoryMapNode memoryMap = ret.getMemoryMap();
                    if (memoryMap != null) {
                        ret.setMemoryMap(null);
                        memoryMap.safeDelete();
                    }
                }
            }
            if (memoryAnchor != null) {
                // rewire incoming memory edges
                MemoryAnchorNode memoryDuplicate = (MemoryAnchorNode) duplicates.get(memoryAnchor);
                replaceMemoryUsages(memoryDuplicate, new MemoryInputMap(replacee));

                if (memoryDuplicate.hasNoUsages()) {
                    if (memoryDuplicate.next() != null) {
                        memoryDuplicate.graph().removeFixed(memoryDuplicate);
                    } else {
                        // this was a dummy memory node used when instantiating pure data-flow
                        // snippets: it was not attached to the control flow.
                        memoryDuplicate.safeDelete();
                    }
                }
            }
        }
    }

    private static LocationIdentity getLocationIdentity(Node node) {
        if (node instanceof MemoryAccess) {
            return ((MemoryAccess) node).getLocationIdentity();
        } else if (node instanceof MemoryProxy) {
            return ((MemoryProxy) node).getLocationIdentity();
        } else if (node instanceof MemoryPhiNode) {
            return ((MemoryPhiNode) node).getLocationIdentity();
        } else {
            return null;
        }
    }

    private void replaceMemoryUsages(ValueNode node, MemoryMap map) {
        for (Node usage : node.usages().snapshot()) {
            if (usage instanceof MemoryMapNode) {
                continue;
            }

            LocationIdentity location = getLocationIdentity(usage);
            if (location != null) {
                for (Position pos : usage.inputPositions()) {
                    if (pos.getInputType() == InputType.Memory && pos.get(usage) == node) {
                        MemoryNode replacement = map.getLastLocationAccess(location);
                        if (replacement == null) {
                            assert mayRemoveLocation || LocationIdentity.any().equals(location) ||
                                            CollectionsUtil.anyMatch(info.privateLocations, Predicate.isEqual(location)) : "Snippet " +
                                                            info.method.format("%h.%n") + " contains access to the non-private location " +
                                                            location + ", but replacee doesn't access this location." + map.getLocations();
                        } else {
                            pos.set(usage, replacement.asNode());
                        }
                    }
                }
            }
        }
    }

    /**
     * Replaces a given fixed node with this specialized snippet.
     *
     * @param metaAccess
     * @param replacee the node that will be replaced
     * @param replacer object that replaces the usages of {@code replacee}
     * @param args the arguments to be bound to the flattened positional parameters of the snippet
     * @return the map of duplicated nodes (original -&gt; duplicate)
     */
    @SuppressWarnings("try")
    public UnmodifiableEconomicMap<Node, Node> instantiate(MetaAccessProvider metaAccess, FixedNode replacee, UsageReplacer replacer, Arguments args) {
        return instantiate(metaAccess, replacee, replacer, args, true);
    }

    /**
     * Replaces a given fixed node with this specialized snippet.
     *
     * @param metaAccess
     * @param replacee the node that will be replaced
     * @param replacer object that replaces the usages of {@code replacee}
     * @param args the arguments to be bound to the flattened positional parameters of the snippet
     * @param killReplacee is true, the replacee node is deleted
     * @return the map of duplicated nodes (original -&gt; duplicate)
     */
    @SuppressWarnings("try")
    public UnmodifiableEconomicMap<Node, Node> instantiate(MetaAccessProvider metaAccess, FixedNode replacee, UsageReplacer replacer, Arguments args, boolean killReplacee) {
        DebugContext debug = replacee.getDebug();
        assert assertSnippetKills(replacee);
        try (DebugCloseable a = args.info.instantiationTimer.start(debug)) {
            args.info.instantiationCounter.increment(debug);
            // Inline the snippet nodes, replacing parameters with the given args in the process
            StartNode entryPointNode = snippet.start();
            FixedNode firstCFGNode = entryPointNode.next();
            StructuredGraph replaceeGraph = replacee.graph();
            EconomicMap<Node, Node> replacements = bind(replaceeGraph, metaAccess, args);
            replacements.put(entryPointNode, AbstractBeginNode.prevBegin(replacee));
            UnmodifiableEconomicMap<Node, Node> duplicates = inlineSnippet(replacee, debug, replaceeGraph, replacements);

            // Re-wire the control flow graph around the replacee
            FixedNode firstCFGNodeDuplicate = (FixedNode) duplicates.get(firstCFGNode);
            replacee.replaceAtPredecessor(firstCFGNodeDuplicate);

            rewireFrameStates(replacee, duplicates);

            updateStamps(replacee, duplicates);

            rewireMemoryGraph(replacee, duplicates);

            // Replace all usages of the replacee with the value returned by the snippet
            ValueNode returnValue = null;
            if (returnNode != null && !(replacee instanceof ControlSinkNode)) {
                ReturnNode returnDuplicate = (ReturnNode) duplicates.get(returnNode);
                returnValue = returnDuplicate.result();
                if (returnValue == null && replacee.usages().isNotEmpty() && replacee instanceof MemoryCheckpoint) {
                    replacer.replace(replacee, null);
                } else {
                    assert returnValue != null || replacee.hasNoUsages();
                    replacer.replace(replacee, returnValue);
                }
                if (returnDuplicate.isAlive()) {
                    FixedNode next = null;
                    if (replacee instanceof FixedWithNextNode) {
                        FixedWithNextNode fwn = (FixedWithNextNode) replacee;
                        next = fwn.next();
                        fwn.setNext(null);
                    }
                    returnDuplicate.replaceAndDelete(next);
                }
            }

            if (killReplacee) {
                // Remove the replacee from its graph
                GraphUtil.killCFG(replacee);
            }

            debug.dump(DebugContext.DETAILED_LEVEL, replaceeGraph, "After lowering %s with %s", replacee, this);
            return duplicates;
        }
    }

    private UnmodifiableEconomicMap<Node, Node> inlineSnippet(Node replacee, DebugContext debug, StructuredGraph replaceeGraph, EconomicMap<Node, Node> replacements) {
        Mark mark = replaceeGraph.getMark();
        try (InliningLog.UpdateScope scope = replaceeGraph.getInliningLog().openUpdateScope((oldNode, newNode) -> {
            InliningLog log = replaceeGraph.getInliningLog();
            if (oldNode == null) {
                log.trackNewCallsite(newNode);
            }
        })) {
            UnmodifiableEconomicMap<Node, Node> duplicates = replaceeGraph.addDuplicates(nodes, snippet, snippet.getNodeCount(), replacements);
            if (scope != null) {
                replaceeGraph.getInliningLog().addLog(duplicates, snippet.getInliningLog());
            }
            NodeSourcePosition position = replacee.getNodeSourcePosition();
            InliningUtil.updateSourcePosition(replaceeGraph, duplicates, mark, position, true);
            debug.dump(DebugContext.DETAILED_LEVEL, replaceeGraph, "After inlining snippet %s", snippet.method());
            return duplicates;
        }
    }

    private void propagateStamp(Node node) {
        if (node instanceof PhiNode) {
            PhiNode phi = (PhiNode) node;
            if (phi.inferStamp()) {
                for (Node usage : node.usages()) {
                    propagateStamp(usage);
                }
            }
        }
    }

    private void updateStamps(ValueNode replacee, UnmodifiableEconomicMap<Node, Node> duplicates) {
        for (ValueNode node : placeholderStampedNodes) {
            ValueNode dup = (ValueNode) duplicates.get(node);
            Stamp replaceeStamp = replacee.stamp(NodeView.DEFAULT);
            if (node instanceof Placeholder) {
                Placeholder placeholderDup = (Placeholder) dup;
                placeholderDup.makeReplacement(replaceeStamp);
            } else {
                dup.setStamp(replaceeStamp);
            }
        }
        for (ParameterNode paramNode : snippet.getNodes(ParameterNode.TYPE)) {
            for (Node usage : paramNode.usages()) {
                Node usageDup = duplicates.get(usage);
                propagateStamp(usageDup);
            }
        }
    }

    /**
     * Gets a copy of the specialized graph.
     */
    public StructuredGraph copySpecializedGraph(DebugContext debugForCopy) {
        return (StructuredGraph) snippet.copy(debugForCopy);
    }

    /**
     * Replaces a given floating node with this specialized snippet.
     *
     * @param metaAccess
     * @param replacee the node that will be replaced
     * @param replacer object that replaces the usages of {@code replacee}
     * @param tool lowering tool used to insert the snippet into the control-flow
     * @param args the arguments to be bound to the flattened positional parameters of the snippet
     */
    @SuppressWarnings("try")
    public void instantiate(MetaAccessProvider metaAccess, FloatingNode replacee, UsageReplacer replacer, LoweringTool tool, Arguments args) {
        DebugContext debug = replacee.getDebug();
        assert assertSnippetKills(replacee);
        try (DebugCloseable a = args.info.instantiationTimer.start(debug)) {
            args.info.instantiationCounter.increment(debug);

            // Inline the snippet nodes, replacing parameters with the given args in the process
            StartNode entryPointNode = snippet.start();
            FixedNode firstCFGNode = entryPointNode.next();
            StructuredGraph replaceeGraph = replacee.graph();
            EconomicMap<Node, Node> replacements = bind(replaceeGraph, metaAccess, args);
            replacements.put(entryPointNode, tool.getCurrentGuardAnchor().asNode());
            UnmodifiableEconomicMap<Node, Node> duplicates = inlineSnippet(replacee, debug, replaceeGraph, replacements);

            FixedWithNextNode lastFixedNode = tool.lastFixedNode();
            assert lastFixedNode != null && lastFixedNode.isAlive() : replaceeGraph + " lastFixed=" + lastFixedNode;
            FixedNode next = lastFixedNode.next();
            lastFixedNode.setNext(null);
            FixedNode firstCFGNodeDuplicate = (FixedNode) duplicates.get(firstCFGNode);
            replaceeGraph.addAfterFixed(lastFixedNode, firstCFGNodeDuplicate);

            // floating nodes are not state-splits not need to re-wire frame states
            assert !(replacee instanceof StateSplit);
            updateStamps(replacee, duplicates);

            rewireMemoryGraph(replacee, duplicates);

            // Replace all usages of the replacee with the value returned by the snippet
            ReturnNode returnDuplicate = (ReturnNode) duplicates.get(returnNode);
            ValueNode returnValue = returnDuplicate.result();
            assert returnValue != null || replacee.hasNoUsages();
            replacer.replace(replacee, returnValue);

            if (returnDuplicate.isAlive()) {
                returnDuplicate.replaceAndDelete(next);
            }

            debug.dump(DebugContext.DETAILED_LEVEL, replaceeGraph, "After lowering %s with %s", replacee, this);
        }
    }

    /**
     * Replaces a given floating node with this specialized snippet.
     *
     * This snippet must be pure data-flow
     *
     * @param metaAccess
     * @param replacee the node that will be replaced
     * @param replacer object that replaces the usages of {@code replacee}
     * @param args the arguments to be bound to the flattened positional parameters of the snippet
     */
    @SuppressWarnings("try")
    public void instantiate(MetaAccessProvider metaAccess, FloatingNode replacee, UsageReplacer replacer, Arguments args) {
        DebugContext debug = replacee.getDebug();
        assert assertSnippetKills(replacee);
        try (DebugCloseable a = args.info.instantiationTimer.start(debug)) {
            args.info.instantiationCounter.increment(debug);

            // Inline the snippet nodes, replacing parameters with the given args in the process
            StartNode entryPointNode = snippet.start();
            assert entryPointNode.next() == (memoryAnchor == null ? returnNode : memoryAnchor) : entryPointNode.next();
            StructuredGraph replaceeGraph = replacee.graph();
            EconomicMap<Node, Node> replacements = bind(replaceeGraph, metaAccess, args);
            MemoryAnchorNode anchorDuplicate = null;
            if (memoryAnchor != null) {
                anchorDuplicate = replaceeGraph.add(new MemoryAnchorNode());
                replacements.put(memoryAnchor, anchorDuplicate);
            }
            List<Node> floatingNodes = new ArrayList<>(nodes.size() - 2);
            for (Node n : nodes) {
                if (n != entryPointNode && n != returnNode) {
                    floatingNodes.add(n);
                }
            }
            UnmodifiableEconomicMap<Node, Node> duplicates = inlineSnippet(replacee, debug, replaceeGraph, replacements);

            // floating nodes are not state-splits not need to re-wire frame states
            assert !(replacee instanceof StateSplit);
            updateStamps(replacee, duplicates);

            rewireMemoryGraph(replacee, duplicates);
            assert anchorDuplicate == null || anchorDuplicate.isDeleted();

            // Replace all usages of the replacee with the value returned by the snippet
            ValueNode returnValue = (ValueNode) duplicates.get(returnNode.result());
            replacer.replace(replacee, returnValue);

            debug.dump(DebugContext.DETAILED_LEVEL, replaceeGraph, "After lowering %s with %s", replacee, this);
        }
    }

    protected void rewireFrameStates(ValueNode replacee, UnmodifiableEconomicMap<Node, Node> duplicates) {
        if (replacee.graph().getGuardsStage().areFrameStatesAtSideEffects() && replacee instanceof StateSplit) {
            for (StateSplit sideEffectNode : sideEffectNodes) {
                assert ((StateSplit) replacee).hasSideEffect();
                Node sideEffectDup = duplicates.get(sideEffectNode.asNode());
                ((StateSplit) sideEffectDup).setStateAfter(((StateSplit) replacee).stateAfter());
            }
        } else if (replacee.graph().getGuardsStage().areFrameStatesAtDeopts() && replacee instanceof DeoptimizingNode) {
            DeoptimizingNode replaceeDeopt = (DeoptimizingNode) replacee;

            FrameState stateBefore = null;
            FrameState stateDuring = null;
            FrameState stateAfter = null;
            if (replaceeDeopt.canDeoptimize()) {
                if (replaceeDeopt instanceof DeoptimizingNode.DeoptBefore) {
                    stateBefore = ((DeoptimizingNode.DeoptBefore) replaceeDeopt).stateBefore();
                }
                if (replaceeDeopt instanceof DeoptimizingNode.DeoptDuring) {
                    stateDuring = ((DeoptimizingNode.DeoptDuring) replaceeDeopt).stateDuring();
                }
                if (replaceeDeopt instanceof DeoptimizingNode.DeoptAfter) {
                    stateAfter = ((DeoptimizingNode.DeoptAfter) replaceeDeopt).stateAfter();
                }
            }

            for (DeoptimizingNode deoptNode : deoptNodes) {
                DeoptimizingNode deoptDup = (DeoptimizingNode) duplicates.get(deoptNode.asNode());
                if (deoptDup.canDeoptimize()) {
                    if (deoptDup instanceof DeoptimizingNode.DeoptBefore) {
                        ((DeoptimizingNode.DeoptBefore) deoptDup).setStateBefore(stateBefore);
                    }
                    if (deoptDup instanceof DeoptimizingNode.DeoptDuring) {
                        // compute a state "during" for a DeoptDuring inside the snippet depending
                        // on what kind of states we had on the node we are replacing.
                        // If the original node had a state "during" already, we just use that,
                        // otherwise we need to find a strategy to compute a state during based on
                        // some other state (before or after).
                        DeoptimizingNode.DeoptDuring deoptDupDuring = (DeoptimizingNode.DeoptDuring) deoptDup;
                        if (stateDuring != null) {
                            deoptDupDuring.setStateDuring(stateDuring);
                        } else if (stateAfter != null) {
                            deoptDupDuring.computeStateDuring(stateAfter);
                        } else if (stateBefore != null) {
                            assert !deoptDupDuring.hasSideEffect() : "can't use stateBefore as stateDuring for state split " + deoptDupDuring;
                            deoptDupDuring.setStateDuring(stateBefore);
                        }
                    }
                    if (deoptDup instanceof DeoptimizingNode.DeoptAfter) {
                        DeoptimizingNode.DeoptAfter deoptDupAfter = (DeoptimizingNode.DeoptAfter) deoptDup;
                        if (stateAfter != null) {
                            deoptDupAfter.setStateAfter(stateAfter);
                        } else {
                            assert !deoptDupAfter.hasSideEffect() : "can't use stateBefore as stateAfter for state split " + deoptDupAfter;
                            deoptDupAfter.setStateAfter(stateBefore);
                        }

                    }
                }
            }
        }
    }

    @Override
    public String toString() {
        StringBuilder buf = new StringBuilder(snippet.toString()).append('(');
        String sep = "";
        for (int i = 0; i < parameters.length; i++) {
            String name = "[" + i + "]";
            Object value = parameters[i];
            buf.append(sep);
            sep = ", ";
            if (value == null) {
                buf.append("<null> ").append(name);
            } else if (value.equals(UNUSED_PARAMETER)) {
                buf.append("<unused> ").append(name);
            } else if (value.equals(CONSTANT_PARAMETER)) {
                buf.append("<constant> ").append(name);
            } else if (value instanceof ParameterNode) {
                ParameterNode param = (ParameterNode) value;
                buf.append(param.getStackKind().getJavaName()).append(' ').append(name);
            } else {
                ParameterNode[] params = (ParameterNode[]) value;
                String kind = params.length == 0 ? "?" : params[0].getStackKind().getJavaName();
                buf.append(kind).append('[').append(params.length).append("] ").append(name);
            }
        }
        return buf.append(')').toString();
    }

    private static boolean checkTemplate(MetaAccessProvider metaAccess, Arguments args, ResolvedJavaMethod method, Signature signature) {
        int offset = args.info.hasReceiver() ? 1 : 0;
        for (int i = offset; i < args.info.getParameterCount(); i++) {
            if (args.info.isConstantParameter(i)) {
                JavaKind kind = signature.getParameterKind(i - offset);
                assert checkConstantArgument(metaAccess, method, signature, i - offset, args.info.getParameterName(i), args.values[i], kind);

            } else if (args.info.isVarargsParameter(i)) {
                assert args.values[i] instanceof Varargs;
                Varargs varargs = (Varargs) args.values[i];
                assert checkVarargs(metaAccess, method, signature, i, args.info.getParameterName(i), varargs);
            }
        }
        return true;
    }

    public void setMayRemoveLocation(boolean mayRemoveLocation) {
        this.mayRemoveLocation = mayRemoveLocation;
    }
}