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

org.pkl.thirdparty.truffle.api.dsl.Introspection Maven / Gradle / Ivy

Go to download

Shaded fat Jar for pkl-config-java, a Java config library based on the Pkl config language.

There is a newer version: 0.27.1
Show newest version
/*
 * Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * The Universal Permissive License (UPL), Version 1.0
 *
 * Subject to the condition set forth below, permission is hereby granted to any
 * person obtaining a copy of this software, associated documentation and/or
 * data (collectively the "Software"), free of charge and under any and all
 * copyright rights in the Software, and any and all patent rights owned or
 * freely licensable by each licensor hereunder covering either (i) the
 * unmodified Software as contributed to or provided by such licensor, or (ii)
 * the Larger Works (as defined below), to deal in both
 *
 * (a) the Software, and
 *
 * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
 * one is included with the Software each a "Larger Work" to which the Software
 * is contributed by such licensors),
 *
 * without restriction, including without limitation the rights to copy, create
 * derivative works of, display, perform, and distribute the Software and make,
 * use, sell, offer for sale, import, export, have made, and have sold the
 * Software and the Larger Work(s), and to sublicense the foregoing rights on
 * either these or other terms.
 *
 * This license is subject to the following condition:
 *
 * The above copyright notice and either this complete permission notice or at a
 * minimum a reference to the UPL must be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.pkl.thirdparty.truffle.api.dsl;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

import org.pkl.thirdparty.truffle.api.dsl.Introspection.SpecializationInfo;
import org.pkl.thirdparty.truffle.api.nodes.Node;

/**
 * Contains introspection utilities for Truffle DSL. The contained utilities are only usable if the
 * operation node is annotated with {@link Introspectable}.
 * 

* Introspection is useful for using testing the node declaration and verifying that particular * specializations become active. *

* Example for using introspection in unit testing: * * {@codesnippet org.pkl.thirdparty.truffle.api.dsl.IntrospectionSnippets.NegateNode} * * @since 0.22 * @see Introspectable */ public final class Introspection { private static final List> EMPTY_CACHED = Collections.unmodifiableList(Arrays.asList(Collections.emptyList())); private static final List> NO_CACHED = Collections.emptyList(); private final Object[] data; Introspection(Object[] data) { this.data = data; } /** * Returns true if the given node is introspectable. If something is introspectable * is determined by if the node is generated by Truffle DSL, if is annotated with * {@link Introspectable} and if the DSL implementation supports introspection. * * @param node a DSL generated node * @return true if the given node is introspectable * @since 0.22 */ public static boolean isIntrospectable(Node node) { return node instanceof Provider; } /** * Returns introspection information for the first specialization that matches a given method * name. A node must declare at least one specialization and must be annotated with * {@link Introspectable} otherwise an {@link IllegalArgumentException} is thrown. If multiple * specializations with the same method name are declared then an undefined specialization is * going to be returned. In such cases disambiguate them by renaming the specialzation method * name. The returned introspection information is not updated when the state of the given * operation node is updated. The implementation of this method might be slow, do not use it in * performance critical code. *

* For a node was {@link GenerateInline inlined} use * {@link #getSpecialization(Node, Node, String)}. * * @param node a introspectable DSL operation with at least one specialization * @param methodName the Java method name of the specialization to introspect * @return introspection info for the method * @see Introspection example usage * @since 0.22 */ public static SpecializationInfo getSpecialization(Node node, String methodName) { try { return getIntrospectionData(node).getSpecialization(methodName); } catch (IllegalStateException e) { throw new IllegalStateException("Failed to provide introspection data for node class " + node.getClass() + " and method " + methodName + ".", e); } } /** * Like {@link #getSpecialization(Node, String)} but must be used for nodes that were * {@link GenerateInline inlined}. * * @param inlineParent the inlined parent node. * @param node a introspectable DSL operation with at least one specialization * @param methodName the Java method name of the specialization to introspect * @return introspection info for the method * @see Introspection example usage * @since 23.0 */ public static SpecializationInfo getSpecialization(Node inlineParent, Node node, String methodName) { try { return getIntrospectionData(inlineParent, node).getSpecialization(methodName); } catch (IllegalStateException e) { throw new IllegalStateException( "Failed to provide introspection data for node class " + node.getClass() + " and inlinig parent " + inlineParent.getClass().getName() + " and method " + methodName + ".", e); } } /** * Returns introspection information for all declared specializations as unmodifiable list. A * given node must declare at least one specialization and must be annotated with * {@link Introspectable} otherwise an {@link IllegalArgumentException} is thrown. The returned * introspection information is not updated when the state of the given operation node is * updated. The implementation of this method might be slow, do not use it in performance * critical code. *

* For a node was {@link GenerateInline inlined} use * {@link #getSpecialization(Node, Node, String)}. * * @param node a introspectable DSL operation with at least one specialization * @see Introspection example usage * @since 0.22 */ public static List getSpecializations(Node node) { try { return getIntrospectionData(node).getSpecializations(); } catch (IllegalStateException e) { throw new IllegalStateException("Failed to provide introspection data for node class " + node.getClass() + ".", e); } } /** * Like {@link #getSpecializations(Node)} but must be used for nodes that were * {@link GenerateInline inlined}. * * @param inlineParent the inlined parent node. * @param node a introspectable DSL operation with at least one specialization * @see Introspection example usage * @since 23.0 */ public static List getSpecializations(Node inlineParent, Node node) { try { return getIntrospectionData(inlineParent, node).getSpecializations(); } catch (IllegalStateException e) { throw new IllegalStateException( "Failed to provide introspection data for node class " + node.getClass() + " and inlinig parent " + inlineParent.getClass().getName() + ".", e); } } private static Introspection getIntrospectionData(Node node) { Objects.requireNonNull(node); if (!(node instanceof Provider)) { throw new IllegalArgumentException(String.format("Provided node is not introspectable. Annotate with @%s to make a node introspectable.", Introspectable.class.getSimpleName())); } return ((Provider) node).getIntrospectionData(); } private static Introspection getIntrospectionData(Node inlineParent, Node node) { Objects.requireNonNull(inlineParent); Objects.requireNonNull(node); if (!(node instanceof Provider)) { throw new IllegalArgumentException(String.format("Provided node is not introspectable. Annotate with @%s to make a node introspectable.", Introspectable.class.getSimpleName())); } return ((Provider) node).getIntrospectionData(inlineParent); } /** * Represents dynamic introspection information of a specialization of a DSL operation. * * @since 0.22 */ public static final class SpecializationInfo { private final String methodName; private final byte state; /* 0b000000 */ private final List> cachedData; SpecializationInfo(String methodName, byte state, List> cachedData) { this.methodName = methodName; this.state = state; this.cachedData = cachedData; } /** * Returns the method name of the introspected specialization. Please note that the returned * method name might not be unique for a given node. * * @since 0.22 */ public String getMethodName() { return methodName; } /** * Returns true if the specialization was active at the time when the * introspection was performed. * * @since 0.22 */ public boolean isActive() { return (state & 0b1) != 0; } /** * Returns true if the specialization was excluded at the time when the * introspection was performed. * * @since 0.22 */ public boolean isExcluded() { return (state & 0b10) != 0; } /** * Returns the number of dynamic specialization instances that are active for this * specialization. * * @since 0.22 */ public int getInstances() { return cachedData.size(); } /** * Returns the cached state for a given specialization instance. The provided instance index * must be greater or equal 0 and smaller {@link #getInstances()}. The returned * list is unmodifiable and never null. * * @since 0.22 */ public List getCachedData(int instanceIndex) { if (instanceIndex < 0 || instanceIndex >= cachedData.size()) { throw new IllegalArgumentException("Invalid specialization index"); } return cachedData.get(instanceIndex); } /** * {@inheritDoc} * * @since 21.1 */ @Override public String toString() { StringBuilder cacheInfo = new StringBuilder(); for (int i = 0; i < getInstances(); i++) { List cacheData = getCachedData(i); cacheInfo.append(", cache[").append(i).append("] = {"); String sep = ""; for (Object object : cacheData) { cacheInfo.append(sep); if (object == null) { cacheInfo.append("null"); } else if (object instanceof Number) { cacheInfo.append(object); } else { cacheInfo.append(object.getClass().getSimpleName() + "@" + Integer.toHexString(hashCode())); } sep = ", "; } cacheInfo.append("}"); } return "SpecializationInfo[name=" + methodName + ", active=" + isActive() + ", excluded=" + isExcluded() + ", instances=" + getInstances() + cacheInfo + "]"; } } /** * Internal marker interface for DSL generated code to access reflection information. A DSL user * must not refer to this type manually. * * @since 0.22 */ public interface Provider { /** * Returns internal reflection data in undefined format. A DSL user must not call this * method. * * @since 0.22 */ default Introspection getIntrospectionData() { throw new UnsupportedOperationException( "Introspection provider for regular nodes is not implemented. Use Introspection.getSpecializations(Node, Node) with inlinedParent parameter instead."); } /** * Returns internal reflection data in undefined format. A DSL user must not call this * method. * * @since 23.0 */ default Introspection getIntrospectionData(@SuppressWarnings("unused") Node inlinedParent) { return getIntrospectionData(); } /** * Factory method to create {@link Node} introspection data. The factory is used to create * {@link Introspection} data to be returned from the {@link #getIntrospectionData()} * method. The format of the data parameters is internal, thus this method * shall only be used by the nodes generated by the DSL processor. A DSL user must not call * this method. * * @param data introspection data in an internal format * @return wrapped data to be used by * {@link Introspection#getSpecializations(org.pkl.thirdparty.truffle.api.nodes.Node)} and * similar methods * @since 0.22 */ static Introspection create(Object... data) { return new Introspection(data); } } SpecializationInfo getSpecialization(String methodName) { checkVersion(); for (int i = 1; i < data.length; i++) { Object[] fieldData = getIntrospectionData(data[i]); if (methodName.equals(fieldData[0])) { return createSpecialization(fieldData); } } return null; } List getSpecializations() { checkVersion(); List specializations = new ArrayList<>(); for (int i = 1; i < data.length; i++) { specializations.add(createSpecialization(getIntrospectionData(data[i]))); } return Collections.unmodifiableList(specializations); } private void checkVersion() { int version = -1; if (data.length > 0 && data[0] instanceof Integer) { Object objectVersion = data[0]; version = (int) objectVersion; } if (version != 0) { throw new IllegalStateException("Unsupported introspection data version: " + version); } } private static Object[] getIntrospectionData(Object specializationData) { if (!(specializationData instanceof Object[])) { throw new IllegalStateException("Invalid introspection data: expected object array"); } Object[] fieldData = (Object[]) specializationData; if (fieldData.length < 3) { throw new IllegalStateException("Invalid introspection data: invalid array length"); } else if (!(fieldData[0] instanceof String)) { throw new IllegalStateException("Invalid introspection data: expected string at index 0"); } else if (!(fieldData[1] instanceof Byte)) { throw new IllegalStateException("Invalid introspection data: expected byte at index 1"); } else if ((fieldData[2] != null && !(fieldData[2] instanceof List))) { throw new IllegalStateException("Invalid introspection data: expected list or null at index 2"); } return fieldData; } @SuppressWarnings("unchecked") private static SpecializationInfo createSpecialization(Object[] fieldData) { String id = (String) fieldData[0]; byte state = (byte) fieldData[1]; List> cachedData = (List>) fieldData[2]; if (cachedData == null || cachedData.isEmpty()) { if ((state & 0b01) != 0) { cachedData = EMPTY_CACHED; } else { cachedData = NO_CACHED; } } else { for (int i = 0; i < cachedData.size(); i++) { cachedData.set(i, Collections.unmodifiableList(cachedData.get(i))); } } return new SpecializationInfo(id, state, cachedData); } } @SuppressWarnings({"null", "unused"}) @SuppressFBWarnings("") class IntrospectionSnippets { // BEGIN: org.pkl.thirdparty.truffle.api.dsl.IntrospectionSnippets.NegateNode @Introspectable abstract static class NegateNode extends Node { abstract Object execute(Object o); @Specialization(guards = "cachedvalue == value", limit = "1") protected static int doInt(int value, @Cached("value") int cachedvalue) { return -cachedvalue; } @Specialization(replaces = "doInt") protected static int doGeneric(int value) { return -value; } } public void testUsingIntrospection() { NegateNode node = null; // NegateNodeGen.create(); SpecializationInfo info; node.execute(1); info = Introspection.getSpecialization(node, "doInt"); assert info.getInstances() == 1; node.execute(1); info = Introspection.getSpecialization(node, "doInt"); assert info.getInstances() == 1; node.execute(2); info = Introspection.getSpecialization(node, "doInt"); assert info.getInstances() == 0; info = Introspection.getSpecialization(node, "doGeneric"); assert info.getInstances() == 1; } // END: org.pkl.thirdparty.truffle.api.dsl.IntrospectionSnippets.NegateNode }