/* * Copyright (c) 2016, 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. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * 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 com.oracle.truffle.api.dsl; import java.util.ArrayList; import java.util.Collections; import java.util.List; import com.oracle.truffle.api.nodes.Node; import java.util.Arrays; /** * Contains introspection utilities for Truffle DSL. The contained utilities are only usable if the * operation node is annotated with {@link Introspectable}. * <p> * Introspection is useful for using testing the node declaration and verifying that particular * specializations become active. * <p> * Example for using introspection in unit testing: * * {@codesnippet com.oracle.truffle.api.dsl.test.IntrospectionTest} * * @since 0.22 * @see Introspectable */ public final class Introspection { private static final List<List<Object>> EMPTY_CACHED = Collections.unmodifiableList(Arrays.asList(Collections.emptyList())); private static final List<List<Object>> NO_CACHED = Collections.emptyList(); private final Object[] data; Introspection(Object[] data) { this.data = data; } /** * Returns <code>true</code> 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. * * @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) { return getIntrospectionData(node).getSpecialization(methodName); } /** * 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. * * @param node a introspectable DSL operation with at least one specialization * @see Introspection example usage * @since 0.22 */ public static List<SpecializationInfo> getSpecializations(Node node) { return getIntrospectionData(node).getSpecializations(); } private static Introspection getIntrospectionData(Node 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(); } /** * 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<excluded><active> */ private final List<List<Object>> cachedData; SpecializationInfo(String methodName, byte state, List<List<Object>> 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 <code>true</code> if the specialization was active at the time when the * introspection was performed. * * @since 0.22 */ public boolean isActive() { return (state & 0b1) != 0; } /** * Returns <code>true</code> 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 <code>0</code> and smaller {@link #getInstances()}. The returned * list is unmodifiable and never <code>null</code>. * * @since 0.22 */ public List<Object> getCachedData(int instanceIndex) { if (instanceIndex < 0 || instanceIndex >= cachedData.size()) { throw new IllegalArgumentException("Invalid specialization index"); } return cachedData.get(instanceIndex); } } /** * 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 */ Introspection 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 <code>data</code> 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(com.oracle.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<SpecializationInfo> getSpecializations() { checkVersion(); List<SpecializationInfo> 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; Object objectVersion = data[0]; if (data.length > 0 && objectVersion instanceof Integer) { 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."); } Object[] fieldData = (Object[]) specializationData; if (fieldData.length < 3 || !(fieldData[0] instanceof String) // || !(fieldData[1] instanceof Byte) // || (fieldData[2] != null && !(fieldData[2] instanceof List))) { throw new IllegalStateException("Invalid introspection data."); } return fieldData; } @SuppressWarnings("unchecked") private static SpecializationInfo createSpecialization(Object[] fieldData) { String id = (String) fieldData[0]; byte state = (byte) fieldData[1]; List<List<Object>> cachedData = (List<List<Object>>) 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); } }