/*
* 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.instrumentation.test;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.RootCallTarget;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.TruffleLanguage.Registration;
import com.oracle.truffle.api.frame.FrameSlot;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.Instrumentable;
import com.oracle.truffle.api.instrumentation.ProvidedTags;
import com.oracle.truffle.api.instrumentation.StandardTags;
import com.oracle.truffle.api.instrumentation.test.InstrumentationTest.ReturnLanguageEnv;
import com.oracle.truffle.api.instrumentation.test.InstrumentationTestLanguage.BlockNode;
import com.oracle.truffle.api.instrumentation.test.InstrumentationTestLanguage.DefineNode;
import com.oracle.truffle.api.instrumentation.test.InstrumentationTestLanguage.ExpressionNode;
import com.oracle.truffle.api.interop.ForeignAccess;
import com.oracle.truffle.api.interop.MessageResolution;
import com.oracle.truffle.api.interop.Resolve;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.nodes.DirectCallNode;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.nodes.LoopNode;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.source.SourceSection;
/**
* <p>
* Minimal test language for instrumentation that enables to define a hierarchy of nodes with one or
* multiple {@link SourceSection#getTags() source section tags}. If the DEFINE tag is used then the
* first argument is an identifier and all following arguments are contents of a function. If CALL
* is used then the first argument is used as identifier for a previously defined target. For the
* tag LOOP the first argument is used for the number of iterations all body nodes should get
* executed.
* </p>
*
* <p>
* Can eval expressions with the following syntax:
* </p>
* <code>
* Statement ::= ident {":" ident} ["(" Statement {"," Statement} ")"
* </code>
*
* <p>
* Example for calling to a defined function 'foo' that loops 100 times over a statement with two
* sub expressions:
* </p>
* <code>
* ROOT(
* DEFINE(foo,
* LOOP(100, STATEMENT(EXPRESSION,EXPRESSION))
* ),
* STATEMENT:CALL(foo)
* )
* </code>
* <p>
* Other statements are:
* <ul>
* <li><code>ARGUMENT(name)</code> - copies a frame argument to the named slot</li>
* <li><code>VARIABLE(name, value)</code> - defines a variable</li>
* <li><code>CONSTANT(value)</code> - defines a constant value</li>
* <li><code>PRINT(OUT, text)</code> or <code>PRINT(ERR, text)</code> - prints a text to standard
* output resp. error output.</li>
* </ul>
* </p>
*/
@Registration(mimeType = InstrumentationTestLanguage.MIME_TYPE, name = "InstrumentTestLang", version = "2.0")
@ProvidedTags({ExpressionNode.class, DefineNode.class, LoopNode.class,
StandardTags.StatementTag.class, StandardTags.CallTag.class, StandardTags.RootTag.class, BlockNode.class, StandardTags.RootTag.class})
public class InstrumentationTestLanguage extends TruffleLanguage<Context>
implements SpecialService {
public static final String MIME_TYPE = "application/x-truffle-instrumentation-test-language";
public static final String FILENAME_EXTENSION = ".titl";
public static final Class<?> EXPRESSION = ExpressionNode.class;
public static final Class<?> DEFINE = DefineNode.class;
public static final Class<?> LOOP = LoopNode.class;
public static final Class<?> STATEMENT = StandardTags.StatementTag.class;
public static final Class<?> CALL = StandardTags.CallTag.class;
public static final Class<?> ROOT = StandardTags.RootTag.class;
public static final Class<?> BLOCK = BlockNode.class;
public static final Class<?>[] TAGS = new Class<?>[]{EXPRESSION, DEFINE, LOOP, STATEMENT, CALL, BLOCK, ROOT};
public static final String[] TAG_NAMES = new String[]{"EXPRESSION", "DEFINE", "LOOP", "STATEMENT", "CALL", "BLOCK", "ROOT", "CONSTANT", "VARIABLE", "ARGUMENT", "PRINT"};
// used to test that no getSourceSection calls happen in certain situations
private static int rootSourceSectionQueryCount;
public InstrumentationTestLanguage() {
}
@Override
public String fileExtension() {
return FILENAME_EXTENSION;
}
@Override
protected Context createContext(TruffleLanguage.Env env) {
Object envReturner = env.getConfig().get(ReturnLanguageEnv.KEY);
if (envReturner != null) {
((ReturnLanguageEnv) envReturner).env = env;
}
Object[] sharedContext = (Object[]) env.getConfig().get("context");
if (sharedContext == null || sharedContext[0] == null) {
Source initSource = (Source) env.getConfig().get("initSource");
Boolean runInitAfterExec = (Boolean) env.getConfig().get("runInitAfterExec");
Context c = new Context(env.out(), env.err(), initSource, runInitAfterExec);
if (sharedContext != null) {
sharedContext[0] = c;
}
return c;
} else {
return forkContext((Context) sharedContext[0]);
}
}
@Override
protected void initializeContext(Context context) throws Exception {
super.initializeContext(context);
Source code = context.initSource;
if (code != null) {
SourceSection outer = code.createSection(0, code.getLength());
BaseNode node = parse(code);
RootCallTarget rct = Truffle.getRuntime().createCallTarget(new InstrumentationTestRootNode(this, "", outer, node));
rct.call();
if (context.runInitAfterExec) {
context.afterTarget = rct;
}
}
}
protected Context forkContext(Context context) {
return context;
}
@Override
protected CallTarget parse(ParsingRequest request) throws Exception {
Source code = request.getSource();
SourceSection outer = code.createSection(0, code.getLength());
BaseNode node;
try {
node = parse(code);
} catch (LanguageError e) {
throw new IOException(e);
}
RootCallTarget afterTarget = getContextReference().get().afterTarget;
return Truffle.getRuntime().createCallTarget(new InstrumentationTestRootNode(this, "", outer, afterTarget, node));
}
public BaseNode parse(Source code) {
return new Parser(this, code).parse();
}
private static final class Parser {
private static final char EOF = (char) -1;
private final InstrumentationTestLanguage lang;
private final Source source;
private final String code;
private int current;
Parser(InstrumentationTestLanguage lang, Source source) {
this.lang = lang;
this.source = source;
this.code = source.getCode();
}
public BaseNode parse() {
BaseNode statement = statement();
if (follows() != EOF) {
error("eof expected");
}
return statement;
}
private BaseNode statement() {
skipWhiteSpace();
int startIndex = current;
if (current() == EOF) {
return null;
}
skipWhiteSpace();
String tag = ident().trim().intern();
if (!isValidTag(tag)) {
throw new LanguageError(String.format("Illegal tag \"%s\".", tag));
}
int argumentIndex = 0;
int numberOfIdents = 0;
if (tag.equals("DEFINE") || tag.equals("ARGUMENT") || tag.equals("CALL") || tag.equals("LOOP") || tag.equals("CONSTANT")) {
numberOfIdents = 1;
} else if (tag.equals("VARIABLE") || tag.equals("PRINT")) {
numberOfIdents = 2;
}
String[] idents = new String[numberOfIdents];
List<BaseNode> children = new ArrayList<>();
if (follows() == '(') {
skipWhiteSpace();
if (current() == '(') {
next();
skipWhiteSpace();
int argIndex = 0;
while (current() != ')') {
if (argIndex < numberOfIdents) {
skipWhiteSpace();
idents[argIndex] = ident();
} else {
children.add(statement());
}
skipWhiteSpace();
if (current() != ',') {
break;
}
next();
argIndex++;
}
if (current() != ')') {
error("missing closing bracket");
}
next();
}
}
for (String ident : idents) {
if (ident == null) {
throw new LanguageError(numberOfIdents + " non-child parameters required for " + tag);
}
}
SourceSection sourceSection = source.createSection(startIndex, current - startIndex);
BaseNode[] childArray = children.toArray(new BaseNode[children.size()]);
BaseNode node = createNode(tag, idents, sourceSection, childArray);
if (tag.equals("ARGUMENT")) {
((ArgumentNode) node).setIndex(argumentIndex++);
}
node.setSourceSection(sourceSection);
return node;
}
private static boolean isValidTag(String tag) {
for (int i = 0; i < TAG_NAMES.length; i++) {
String allowedTag = TAG_NAMES[i];
if (tag == allowedTag) {
return true;
}
}
return false;
}
private BaseNode createNode(String tag, String[] idents, SourceSection sourceSection, BaseNode[] childArray) throws AssertionError {
switch (tag) {
case "DEFINE":
return new DefineNode(lang, idents[0], sourceSection, childArray);
case "ARGUMENT":
return new ArgumentNode(idents[0], childArray);
case "CALL":
return new CallNode(idents[0], childArray);
case "LOOP":
return new LoopNode(parseIdent(idents[0]), childArray);
case "BLOCK":
return new BlockNode(childArray);
case "EXPRESSION":
return new ExpressionNode(childArray);
case "ROOT":
return new InstrumentableRootNode(childArray);
case "STATEMENT":
return new StatementNode(childArray);
case "CONSTANT":
return new ConstantNode(idents[0], childArray);
case "VARIABLE":
return new VariableNode(idents[0], idents[1], childArray);
case "PRINT":
return new PrintNode(idents[0], idents[1], childArray);
default:
throw new AssertionError();
}
}
private void error(String message) {
throw new LanguageError(String.format("error at %s. char %s: %s", current, current(), message));
}
private String ident() {
StringBuilder builder = new StringBuilder();
char c;
while ((c = current()) != EOF && Character.isJavaIdentifierPart(c)) {
builder.append(c);
next();
}
if (builder.length() == 0) {
error("expected ident");
}
return builder.toString();
}
private void skipWhiteSpace() {
while (Character.isWhitespace(current())) {
next();
}
}
private char follows() {
for (int i = current; i < code.length(); i++) {
if (!Character.isWhitespace(code.charAt(i))) {
return code.charAt(i);
}
}
return EOF;
}
private void next() {
current++;
}
private char current() {
if (current >= code.length()) {
return EOF;
}
return code.charAt(current);
}
}
private static class InstrumentationTestRootNode extends RootNode {
private final String name;
private final SourceSection sourceSection;
private final RootCallTarget afterTarget;
@Children private final BaseNode[] expressions;
protected InstrumentationTestRootNode(InstrumentationTestLanguage lang, String name, SourceSection sourceSection, BaseNode... expressions) {
this(lang, name, sourceSection, null, expressions);
}
protected InstrumentationTestRootNode(InstrumentationTestLanguage lang, String name, SourceSection sourceSection, RootCallTarget afterTarget, BaseNode... expressions) {
super(lang);
this.name = name;
this.sourceSection = sourceSection;
this.afterTarget = afterTarget;
this.expressions = expressions;
}
@Override
public SourceSection getSourceSection() {
rootSourceSectionQueryCount++;
return sourceSection;
}
@Override
protected boolean isInstrumentable() {
return true;
}
@Override
@ExplodeLoop
public Object execute(VirtualFrame frame) {
Object returnValue = Null.INSTANCE;
for (int i = 0; i < expressions.length; i++) {
BaseNode baseNode = expressions[i];
if (baseNode != null) {
returnValue = baseNode.execute(frame);
}
}
if (afterTarget != null) {
afterTarget.call();
}
return returnValue;
}
@Override
public String getName() {
return name;
}
@Override
public String toString() {
return "Root[" + name + "]";
}
}
static final class ExpressionNode extends InstrumentedNode {
ExpressionNode(BaseNode[] children) {
super(children);
}
}
@Instrumentable(factory = InstrumentedNodeWrapper.class)
public abstract static class InstrumentedNode extends BaseNode {
@Children private final BaseNode[] children;
public InstrumentedNode(BaseNode[] children) {
this.children = children;
}
public InstrumentedNode(@SuppressWarnings("unused") InstrumentedNode delegate) {
this.children = null;
}
@Override
@ExplodeLoop
public Object execute(VirtualFrame frame) {
Object returnValue = Null.INSTANCE;
for (BaseNode child : children) {
returnValue = child.execute(frame);
}
return returnValue;
}
@Override
protected final boolean isTaggedWith(Class<?> tag) {
if (tag == StandardTags.RootTag.class) {
return this instanceof InstrumentableRootNode;
} else if (tag == StandardTags.CallTag.class) {
return this instanceof CallNode;
} else if (tag == StandardTags.StatementTag.class) {
return this instanceof StatementNode;
}
return getClass() == tag;
}
}
static final class BlockNode extends InstrumentedNode {
BlockNode(BaseNode[] children) {
super(children);
}
}
private static final class InstrumentableRootNode extends InstrumentedNode {
InstrumentableRootNode(BaseNode[] children) {
super(children);
}
}
private static final class StatementNode extends InstrumentedNode {
StatementNode(BaseNode[] children) {
super(children);
}
}
static class DefineNode extends BaseNode {
private final String identifier;
private final CallTarget target;
DefineNode(InstrumentationTestLanguage lang, String identifier, SourceSection source, BaseNode[] children) {
this.identifier = identifier;
this.target = Truffle.getRuntime().createCallTarget(new InstrumentationTestRootNode(lang, identifier, source, children));
}
@Override
public Object execute(VirtualFrame frame) {
defineFunction();
return null;
}
@TruffleBoundary
private void defineFunction() {
Context context = getRootNode().getLanguage(InstrumentationTestLanguage.class).getContextReference().get();
if (context.callTargets.containsKey(identifier)) {
if (context.callTargets.get(identifier) != target) {
throw new IllegalArgumentException("Identifier redefinition not supported.");
}
}
context.callTargets.put(this.identifier, target);
}
}
private static class CallNode extends InstrumentedNode {
@Child private DirectCallNode callNode;
private final String identifier;
CallNode(String identifier, BaseNode[] children) {
super(children);
this.identifier = identifier;
}
@Override
public Object execute(VirtualFrame frame) {
if (callNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
Context context = getRootNode().getLanguage(InstrumentationTestLanguage.class).getContextReference().get();
CallTarget target = context.callTargets.get(identifier);
callNode = insert(Truffle.getRuntime().createDirectCallNode(target));
}
return callNode.call(new Object[0]);
}
}
private static class ConstantNode extends InstrumentedNode {
private final Object constant;
ConstantNode(String identifier, BaseNode[] children) {
super(children);
this.constant = parseIdent(identifier);
}
@Override
public Object execute(VirtualFrame frame) {
return constant;
}
}
private static Object parseIdent(String identifier) {
if (identifier.equals("infinity")) {
return Double.POSITIVE_INFINITY;
}
if (identifier.equals("true")) {
return true;
} else if (identifier.equals("false")) {
return false;
}
return Integer.parseInt(identifier);
}
private static class VariableNode extends InstrumentedNode {
private final String name;
private final Object value;
@CompilationFinal private FrameSlot slot;
VariableNode(String name, String value, BaseNode[] children) {
super(children);
this.name = name;
this.value = parseIdent(value);
}
@Override
public Object execute(VirtualFrame frame) {
if (slot == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
slot = frame.getFrameDescriptor().findOrAddFrameSlot(name);
}
frame.setObject(slot, value);
super.execute(frame);
return value;
}
}
private static class ArgumentNode extends InstrumentedNode {
private final String name;
@CompilationFinal private FrameSlot slot;
@CompilationFinal private int index;
ArgumentNode(String name, BaseNode[] children) {
super(children);
this.name = name;
}
void setIndex(int index) {
this.index = index;
}
@Override
public Object execute(VirtualFrame frame) {
if (slot == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
slot = frame.getFrameDescriptor().findOrAddFrameSlot(name);
}
Object[] args = frame.getArguments();
Object value;
if (args.length <= index) {
value = Null.INSTANCE;
} else {
value = args[index];
}
frame.setObject(slot, value);
super.execute(frame);
return value;
}
}
static class LoopNode extends InstrumentedNode {
private final int loopCount;
private final boolean infinite;
LoopNode(Object loopCount, BaseNode[] children) {
super(children);
boolean inf = false;
if (loopCount instanceof Double) {
if (((Double) loopCount).isInfinite()) {
inf = true;
}
this.loopCount = ((Double) loopCount).intValue();
} else if (loopCount instanceof Integer) {
this.loopCount = (int) loopCount;
} else {
throw new LanguageError("Invalid loop count " + loopCount);
}
this.infinite = inf;
}
@Override
public Object execute(VirtualFrame frame) {
Object returnValue = null;
for (int i = 0; infinite || i < loopCount; i++) {
returnValue = super.execute(frame);
}
return returnValue;
}
}
static class PrintNode extends InstrumentedNode {
enum Output {
OUT,
ERR
}
private final Output where;
private final String what;
@CompilationFinal private PrintWriter writer;
PrintNode(String where, String what, BaseNode[] children) {
super(children);
if (what == null) {
this.where = Output.OUT;
this.what = where;
} else {
this.where = Output.valueOf(where);
this.what = what;
}
}
@Override
public Object execute(VirtualFrame frame) {
if (writer == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
Context context = getRootNode().getLanguage(InstrumentationTestLanguage.class).getContextReference().get();
switch (where) {
case OUT:
writer = new PrintWriter(new OutputStreamWriter(context.out));
break;
case ERR:
writer = new PrintWriter(new OutputStreamWriter(context.err));
break;
default:
throw new AssertionError(where);
}
}
writer.write(what);
writer.flush();
return null;
}
}
public abstract static class BaseNode extends Node {
private SourceSection sourceSection;
public void setSourceSection(SourceSection sourceSection) {
this.sourceSection = sourceSection;
}
@Override
public SourceSection getSourceSection() {
return sourceSection;
}
public abstract Object execute(VirtualFrame frame);
}
@SuppressWarnings("serial")
private static class LanguageError extends RuntimeException {
LanguageError(String format) {
super(format);
}
}
@Override
protected Object findExportedSymbol(Context context, String globalName, boolean onlyExplicit) {
TruffleObject functionObject = context.callFunctionObjects.get(globalName);
if (functionObject == null) {
CallTarget ct = context.callTargets.get(globalName);
if (ct == null) {
return null;
}
functionObject = new Function(ct);
context.callFunctionObjects.put(globalName, functionObject);
}
return functionObject;
}
@Override
protected Object getLanguageGlobal(Context context) {
return context;
}
@Override
protected boolean isObjectOfLanguage(Object object) {
return false;
}
@Override
protected Object findMetaObject(Context context, Object obj) {
if (obj instanceof Integer || obj instanceof Long) {
return "Integer";
}
if (obj instanceof Boolean) {
return "Boolean";
}
if (obj != null && obj.equals(Double.POSITIVE_INFINITY)) {
return "Infinity";
}
return null;
}
@Override
protected SourceSection findSourceLocation(Context context, Object obj) {
if (obj instanceof Integer || obj instanceof Long) {
return Source.newBuilder("source integer").name("integer").mimeType(MIME_TYPE).build().createSection(1);
}
if (obj instanceof Boolean) {
return Source.newBuilder("source boolean").name("boolean").mimeType(MIME_TYPE).build().createSection(1);
}
if (obj != null && obj.equals(Double.POSITIVE_INFINITY)) {
return Source.newBuilder("source infinity").name("double").mimeType(MIME_TYPE).build().createSection(1);
}
return null;
}
public static int getRootSourceSectionQueryCount() {
return rootSourceSectionQueryCount;
}
static final class Function implements TruffleObject {
private final CallTarget ct;
Function(CallTarget ct) {
this.ct = ct;
}
@Override
public ForeignAccess getForeignAccess() {
return FunctionMessageResolutionForeign.ACCESS;
}
public static boolean isInstance(TruffleObject obj) {
return obj instanceof Function;
}
@MessageResolution(receiverType = Function.class)
static final class FunctionMessageResolution {
@Resolve(message = "EXECUTE")
public abstract static class FunctionExecuteNode extends Node {
public Object access(Function receiver, Object[] arguments) {
return receiver.ct.call(arguments);
}
}
@Resolve(message = "IS_EXECUTABLE")
public abstract static class FunctionIsExecutableNode extends Node {
public Object access(Object receiver) {
return receiver instanceof Function;
}
}
}
}
static final class Null implements TruffleObject {
static final Null INSTANCE = new Null();
private Null() {
}
public static boolean isInstance(TruffleObject obj) {
return obj instanceof Null;
}
@Override
public String toString() {
return "Null";
}
@Override
public ForeignAccess getForeignAccess() {
return NullMessageResolutionForeign.ACCESS;
}
@MessageResolution(receiverType = Null.class)
static final class NullMessageResolution {
@Resolve(message = "IS_NULL")
public abstract static class NullIsNullNode extends Node {
public boolean access(Null aNull) {
return Null.INSTANCE == aNull;
}
}
}
}
}
class Context {
final Map<String, CallTarget> callTargets = new HashMap<>();
final Map<String, TruffleObject> callFunctionObjects = new HashMap<>();
final OutputStream out;
final OutputStream err;
final Source initSource;
final boolean runInitAfterExec;
RootCallTarget afterTarget;
Context(OutputStream out, OutputStream err, Source initSource, Boolean runInitAfterExec) {
this.out = out;
this.err = err;
this.initSource = initSource;
this.runInitAfterExec = runInitAfterExec != null && runInitAfterExec;
}
}