com.oracle.truffle.api.instrument.Instrument Maven / Gradle / Ivy
Show all versions of truffle-api Show documentation
/*
* Copyright (c) 2013, 2015, 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.instrument;
import com.oracle.truffle.api.*;
import com.oracle.truffle.api.frame.*;
import com.oracle.truffle.api.instrument.InstrumentationNode.TruffleEvents;
import com.oracle.truffle.api.nodes.*;
import com.oracle.truffle.api.source.*;
// TODO (mlvdv) these statics should not be global. Move them to some kind of context.
// TODO (mlvdv) migrate factory (together with Probe)? break out nested classes?
/**
* A binding between:
*
* - A {@link Probe}: a source of execution events taking place at a program location in
* an executing Truffle AST, and
* - A listener: a consumer of execution events on behalf of an external client.
*
*
* Client-oriented documentation for the use of Instruments is available online at https://wiki.openjdk.java.net/display/Graal/Listening+for+Execution+Events
*
* The implementation of Instruments is complicated by the requirement that Truffle be able to clone
* ASTs at any time. In particular, any instrumentation-supporting Nodes that have been attached to
* an AST must be cloned along with the AST: AST clones are not permitted to share Nodes.
*
* AST cloning is intended to be as transparent as possible to clients. This is encouraged
* by providing the {@link SimpleInstrumentListener} for clients that need know nothing more than
* the properties associated with a Probe: it's {@link SourceSection} and any associated instances
* of {@link SyntaxTag}.
*
* AST cloning is not transparent to clients that use the
* {@link StandardInstrumentListener}, since those event methods identify the concrete Node instance
* (and thus the AST instance) where the event takes place.
*
*
Implementation Notes: the Life Cycle of an {@link Instrument} at a {@link Probe}
*
*
* - A new Instrument is created in permanent association with a client-provided
* listener.
*
* - Multiple Instruments may share a single listener.
*
* - An Instrument does nothing until it is {@linkplain Probe#attach(Instrument) attached} to a
* Probe, at which time the Instrument begins routing execution events from the Probe's AST location
* to the Instrument's listener.
*
* - Neither Instruments nor Probes are {@link Node}s.
*
* - A Probe has a single source-based location in an AST, but manages a separate
* instrumentation chain of Nodes at the equivalent location in each clone of the AST.
* - When a probed AST is cloned, the instrumentation chain associated with each Probe is cloned
* along with the rest of the AST.
*
* - When a new Instrument (for example an instance created by
* {@link Instrument#create(com.oracle.truffle.api.instrument.SimpleInstrumentListener, java.lang.String)}
* is attached to a Probe, the Instrument inserts a new instance of its private Node type into
* each of the instrument chains managed by the Probe, i.e. one node instance per existing
* clone of the AST.
*
* - If an Instrument is attached to a Probe in an AST that subsequently gets cloned, then the
* Instrument's private Node type will be cloned along with the rest of the the AST.
* - Each Instrument's private Node type is a dynamic inner class whose only state is in the
* shared (outer) Instrument instance; that state includes a reference to the Instrument's listener.
*
*
* - When an Instrument that has been attached to a Probe is {@linkplain #dispose() disposed}, the
* Instrument searches every instrument chain associated with the Probe and removes the instance of
* its private Node type.
*
* - Attaching and disposing an Instrument at a Probe deoptimizes any compilations of the
* AST.
*
*
*
* @see Probe
* @see TruffleEvents
*/
public abstract class Instrument {
/**
* Creates a Simple Instrument: this Instrument routes execution events to a
* client-provided listener.
*
* @param listener a listener for execution events
* @param instrumentInfo optional description of the instrument's role, intended for debugging.
* @return a new instrument, ready for attachment at a probe
*/
public static Instrument create(SimpleInstrumentListener listener, String instrumentInfo) {
return new SimpleInstrument(listener, instrumentInfo);
}
/**
* Creates a Standard Instrument: this Instrument routes execution events, together
* with access to Truffle execution state, to a client-provided listener.
*
* @param standardListener a listener for execution events and execution state
* @param instrumentInfo optional description of the instrument's role, intended for debugging.
* @return a new instrument, ready for attachment at a probe
*/
public static Instrument create(StandardInstrumentListener standardListener, String instrumentInfo) {
return new StandardInstrument(standardListener, instrumentInfo);
}
/**
* Creates an Advanced Instrument: this Instrument executes efficiently, subject to
* full Truffle optimization, a client-provided AST fragment every time the Probed node is
* entered.
*
* Any {@link RuntimeException} thrown by execution of the fragment is caught by the framework
* and reported to the listener; there is no other notification.
*
* @param resultListener optional client callback for results/failure notification
* @param rootFactory provider of AST fragments on behalf of the client
* @param requiredResultType optional requirement, any non-assignable result is reported to the
* the listener, if any, as a failure
* @param instrumentInfo optional description of the instrument's role, intended for debugging.
* @return a new instrument, ready for attachment at a probe
*/
public static Instrument create(AdvancedInstrumentResultListener resultListener, AdvancedInstrumentRootFactory rootFactory, Class> requiredResultType, String instrumentInfo) {
return new AdvancedInstrument(resultListener, rootFactory, requiredResultType, instrumentInfo);
}
// TODO (mlvdv) experimental
/**
* For implementation testing.
*/
public static Instrument create(TruffleOptListener listener) {
return new TruffleOptInstrument(listener, null);
}
/**
* Has this instrument been disposed? stays true once set.
*/
private boolean isDisposed = false;
protected Probe probe = null;
/**
* Optional documentation, mainly for debugging.
*/
private final String instrumentInfo;
private Instrument(String instrumentInfo) {
this.instrumentInfo = instrumentInfo;
}
/**
* Gets the {@link Probe} to which this Instrument is currently attached: {@code null} if not
* yet attached to a Probe or if this Instrument has been {@linkplain #dispose() disposed}.
*/
public Probe getProbe() {
return probe;
}
/**
* Removes this Instrument from the Probe to which it attached and renders this Instrument
* inert.
*
* @throws IllegalStateException if this instrument has already been disposed
*/
public void dispose() throws IllegalStateException {
if (isDisposed) {
throw new IllegalStateException("Attempt to dispose an already disposed Instrumennt");
}
if (probe != null) {
// It's attached
probe.disposeInstrument(this);
probe = null;
}
this.isDisposed = true;
}
/**
* For internal implementation only.
*/
void setAttachedTo(Probe probe) {
this.probe = probe;
}
/**
* Has this instrument been disposed and rendered unusable?
*/
boolean isDisposed() {
return isDisposed;
}
abstract AbstractInstrumentNode addToChain(AbstractInstrumentNode nextNode);
/**
* Removes this instrument from an instrument chain.
*/
abstract AbstractInstrumentNode removeFromChain(AbstractInstrumentNode instrumentNode);
/**
* An instrument that propagates events to an instance of {@link SimpleInstrumentListener}.
*/
private static final class SimpleInstrument extends Instrument {
/**
* Tool-supplied listener for events.
*/
private final SimpleInstrumentListener simpleListener;
private SimpleInstrument(SimpleInstrumentListener simpleListener, String instrumentInfo) {
super(instrumentInfo);
this.simpleListener = simpleListener;
}
@Override
AbstractInstrumentNode addToChain(AbstractInstrumentNode nextNode) {
return new SimpleInstrumentNode(nextNode);
}
@Override
AbstractInstrumentNode removeFromChain(AbstractInstrumentNode instrumentNode) {
boolean found = false;
if (instrumentNode != null) {
if (instrumentNode.getInstrument() == this) {
// Found the match at the head of the chain
return instrumentNode.nextInstrumentNode;
}
// Match not at the head of the chain; remove it.
found = instrumentNode.removeFromChain(SimpleInstrument.this);
}
if (!found) {
throw new IllegalStateException("Couldn't find instrument node to remove: " + this);
}
return instrumentNode;
}
/**
* Node that implements a {@link SimpleInstrument} in a particular AST.
*/
@NodeInfo(cost = NodeCost.NONE)
private final class SimpleInstrumentNode extends AbstractInstrumentNode {
private SimpleInstrumentNode(AbstractInstrumentNode nextNode) {
super(nextNode);
}
public void enter(Node node, VirtualFrame vFrame) {
SimpleInstrument.this.simpleListener.enter(SimpleInstrument.this.probe);
if (nextInstrumentNode != null) {
nextInstrumentNode.enter(node, vFrame);
}
}
public void returnVoid(Node node, VirtualFrame vFrame) {
SimpleInstrument.this.simpleListener.returnVoid(SimpleInstrument.this.probe);
if (nextInstrumentNode != null) {
nextInstrumentNode.returnVoid(node, vFrame);
}
}
public void returnValue(Node node, VirtualFrame vFrame, Object result) {
SimpleInstrument.this.simpleListener.returnValue(SimpleInstrument.this.probe, result);
if (nextInstrumentNode != null) {
nextInstrumentNode.returnValue(node, vFrame, result);
}
}
public void returnExceptional(Node node, VirtualFrame vFrame, Exception exception) {
SimpleInstrument.this.simpleListener.returnExceptional(SimpleInstrument.this.probe, exception);
if (nextInstrumentNode != null) {
nextInstrumentNode.returnExceptional(node, vFrame, exception);
}
}
public String instrumentationInfo() {
final String info = getInstrumentInfo();
return info != null ? info : simpleListener.getClass().getSimpleName();
}
}
}
/**
* An instrument that propagates events to an instance of {@link StandardInstrumentListener}.
*/
private static final class StandardInstrument extends Instrument {
/**
* Tool-supplied listener for AST events.
*/
private final StandardInstrumentListener standardListener;
private StandardInstrument(StandardInstrumentListener standardListener, String instrumentInfo) {
super(instrumentInfo);
this.standardListener = standardListener;
}
@Override
AbstractInstrumentNode addToChain(AbstractInstrumentNode nextNode) {
return new StandardInstrumentNode(nextNode);
}
@Override
AbstractInstrumentNode removeFromChain(AbstractInstrumentNode instrumentNode) {
boolean found = false;
if (instrumentNode != null) {
if (instrumentNode.getInstrument() == this) {
// Found the match at the head of the chain
return instrumentNode.nextInstrumentNode;
}
// Match not at the head of the chain; remove it.
found = instrumentNode.removeFromChain(StandardInstrument.this);
}
if (!found) {
throw new IllegalStateException("Couldn't find instrument node to remove: " + this);
}
return instrumentNode;
}
/**
* Node that implements a {@link StandardInstrument} in a particular AST.
*/
@NodeInfo(cost = NodeCost.NONE)
private final class StandardInstrumentNode extends AbstractInstrumentNode {
private StandardInstrumentNode(AbstractInstrumentNode nextNode) {
super(nextNode);
}
public void enter(Node node, VirtualFrame vFrame) {
standardListener.enter(StandardInstrument.this.probe, node, vFrame);
if (nextInstrumentNode != null) {
nextInstrumentNode.enter(node, vFrame);
}
}
public void returnVoid(Node node, VirtualFrame vFrame) {
standardListener.returnVoid(StandardInstrument.this.probe, node, vFrame);
if (nextInstrumentNode != null) {
nextInstrumentNode.returnVoid(node, vFrame);
}
}
public void returnValue(Node node, VirtualFrame vFrame, Object result) {
standardListener.returnValue(StandardInstrument.this.probe, node, vFrame, result);
if (nextInstrumentNode != null) {
nextInstrumentNode.returnValue(node, vFrame, result);
}
}
public void returnExceptional(Node node, VirtualFrame vFrame, Exception exception) {
standardListener.returnExceptional(StandardInstrument.this.probe, node, vFrame, exception);
if (nextInstrumentNode != null) {
nextInstrumentNode.returnExceptional(node, vFrame, exception);
}
}
public String instrumentationInfo() {
final String info = getInstrumentInfo();
return info != null ? info : standardListener.getClass().getSimpleName();
}
}
}
/**
* An instrument that allows clients to provide an AST fragment to be executed directly from
* within a Probe's instrumentation chain, and thus directly in the executing Truffle
* AST with potential for full optimization.
*/
private static final class AdvancedInstrument extends Instrument {
private final AdvancedInstrumentResultListener resultListener;
private final AdvancedInstrumentRootFactory rootFactory;
private final Class> requiredResultType;
private AdvancedInstrument(AdvancedInstrumentResultListener resultListener, AdvancedInstrumentRootFactory rootFactory, Class> requiredResultType, String instrumentInfo) {
super(instrumentInfo);
this.resultListener = resultListener;
this.rootFactory = rootFactory;
this.requiredResultType = requiredResultType;
}
@Override
AbstractInstrumentNode addToChain(AbstractInstrumentNode nextNode) {
return new AdvancedInstrumentNode(nextNode);
}
@Override
AbstractInstrumentNode removeFromChain(AbstractInstrumentNode instrumentNode) {
boolean found = false;
if (instrumentNode != null) {
if (instrumentNode.getInstrument() == this) {
// Found the match at the head of the chain
return instrumentNode.nextInstrumentNode;
}
// Match not at the head of the chain; remove it.
found = instrumentNode.removeFromChain(AdvancedInstrument.this);
}
if (!found) {
throw new IllegalStateException("Couldn't find instrument node to remove: " + this);
}
return instrumentNode;
}
/**
* Node that implements a {@link AdvancedInstrument} in a particular AST.
*/
@NodeInfo(cost = NodeCost.NONE)
private final class AdvancedInstrumentNode extends AbstractInstrumentNode {
@Child private AdvancedInstrumentRoot instrumentRoot;
private AdvancedInstrumentNode(AbstractInstrumentNode nextNode) {
super(nextNode);
}
public void enter(Node node, VirtualFrame vFrame) {
if (instrumentRoot == null) {
try {
final AdvancedInstrumentRoot newInstrumentRoot = AdvancedInstrument.this.rootFactory.createInstrumentRoot(AdvancedInstrument.this.probe, node);
if (newInstrumentRoot != null) {
instrumentRoot = newInstrumentRoot;
adoptChildren();
AdvancedInstrument.this.probe.invalidateProbeUnchanged();
}
} catch (RuntimeException ex) {
if (resultListener != null) {
resultListener.notifyFailure(node, vFrame, ex);
}
}
}
if (instrumentRoot != null) {
try {
final Object result = instrumentRoot.executeRoot(node, vFrame);
if (resultListener != null) {
checkResultType(result);
resultListener.notifyResult(node, vFrame, result);
}
} catch (RuntimeException ex) {
if (resultListener != null) {
resultListener.notifyFailure(node, vFrame, ex);
}
}
}
if (nextInstrumentNode != null) {
nextInstrumentNode.enter(node, vFrame);
}
}
private void checkResultType(Object result) {
if (requiredResultType == null) {
return;
}
if (result == null) {
throw new RuntimeException("Instrument result null: " + requiredResultType.getSimpleName() + " is required");
}
if (!(requiredResultType.isAssignableFrom(result.getClass()))) {
throw new RuntimeException("Instrument result " + result.toString() + " not assignable to " + requiredResultType.getSimpleName());
}
}
public void returnVoid(Node node, VirtualFrame vFrame) {
if (nextInstrumentNode != null) {
nextInstrumentNode.returnVoid(node, vFrame);
}
}
public void returnValue(Node node, VirtualFrame vFrame, Object result) {
if (nextInstrumentNode != null) {
nextInstrumentNode.returnValue(node, vFrame, result);
}
}
public void returnExceptional(Node node, VirtualFrame vFrame, Exception exception) {
if (nextInstrumentNode != null) {
nextInstrumentNode.returnExceptional(node, vFrame, exception);
}
}
public String instrumentationInfo() {
final String info = getInstrumentInfo();
return info != null ? info : rootFactory.getClass().getSimpleName();
}
}
}
public interface TruffleOptListener {
void notifyIsCompiled(boolean isCompiled);
}
private static final class TruffleOptInstrument extends Instrument {
private final TruffleOptListener toolOptListener;
private TruffleOptInstrument(TruffleOptListener listener, String instrumentInfo) {
super(instrumentInfo);
this.toolOptListener = listener;
}
@Override
AbstractInstrumentNode addToChain(AbstractInstrumentNode nextNode) {
return new TruffleOptInstrumentNode(nextNode);
}
@Override
AbstractInstrumentNode removeFromChain(AbstractInstrumentNode instrumentNode) {
boolean found = false;
if (instrumentNode != null) {
if (instrumentNode.getInstrument() == this) {
// Found the match at the head of the chain
return instrumentNode.nextInstrumentNode;
}
// Match not at the head of the chain; remove it.
found = instrumentNode.removeFromChain(TruffleOptInstrument.this);
}
if (!found) {
throw new IllegalStateException("Couldn't find instrument node to remove: " + this);
}
return instrumentNode;
}
@NodeInfo(cost = NodeCost.NONE)
private final class TruffleOptInstrumentNode extends AbstractInstrumentNode {
private boolean isCompiled;
private TruffleOptInstrumentNode(AbstractInstrumentNode nextNode) {
super(nextNode);
this.isCompiled = CompilerDirectives.inCompiledCode();
}
public void enter(Node node, VirtualFrame vFrame) {
if (this.isCompiled != CompilerDirectives.inCompiledCode()) {
this.isCompiled = CompilerDirectives.inCompiledCode();
TruffleOptInstrument.this.toolOptListener.notifyIsCompiled(this.isCompiled);
}
if (nextInstrumentNode != null) {
nextInstrumentNode.enter(node, vFrame);
}
}
public void returnVoid(Node node, VirtualFrame vFrame) {
if (nextInstrumentNode != null) {
nextInstrumentNode.returnVoid(node, vFrame);
}
}
public void returnValue(Node node, VirtualFrame vFrame, Object result) {
if (nextInstrumentNode != null) {
nextInstrumentNode.returnValue(node, vFrame, result);
}
}
public void returnExceptional(Node node, VirtualFrame vFrame, Exception exception) {
if (nextInstrumentNode != null) {
nextInstrumentNode.returnExceptional(node, vFrame, exception);
}
}
public String instrumentationInfo() {
final String info = getInstrumentInfo();
return info != null ? info : toolOptListener.getClass().getSimpleName();
}
}
}
@NodeInfo(cost = NodeCost.NONE)
abstract class AbstractInstrumentNode extends Node implements TruffleEvents, InstrumentationNode {
@Child protected AbstractInstrumentNode nextInstrumentNode;
protected AbstractInstrumentNode(AbstractInstrumentNode nextNode) {
this.nextInstrumentNode = nextNode;
}
@Override
public boolean isInstrumentable() {
return false;
}
/**
* Gets the instrument that created this node.
*/
private Instrument getInstrument() {
return Instrument.this;
}
/**
* Removes the node from this chain that was added by a particular instrument, assuming that
* the head of the chain is not the one to be replaced. This is awkward, but is required
* because {@link Node#replace(Node)} won't take a {@code null} argument. This doesn't work
* for the tail of the list, which would be replacing itself with null. So the replacement
* must be directed the parent of the node being removed.
*/
private boolean removeFromChain(Instrument instrument) {
assert getInstrument() != instrument;
if (nextInstrumentNode == null) {
return false;
}
if (nextInstrumentNode.getInstrument() == instrument) {
// Next is the one to remove
if (nextInstrumentNode.nextInstrumentNode == null) {
// Next is at the tail; just forget
nextInstrumentNode = null;
} else {
// Replace next with its successor
nextInstrumentNode.replace(nextInstrumentNode.nextInstrumentNode);
}
return true;
}
return nextInstrumentNode.removeFromChain(instrument);
}
protected String getInstrumentInfo() {
return Instrument.this.instrumentInfo;
}
}
}