changeset 13455:69d2e4baa215

Truffle: new infrastructure related to instrumentation, and in particular debugging: support for managing Source objects; framework for generalized "instrumentation proxy nodes" (to be inserted into ASTs with no runtime cost when inactive), and "probes" (which can be attached to proxy nodes to receive event notification); a rudimentary interface and abstract implementation for a "debug manager" (mostly a placeholder at this point); and the beginning of a language-agnostic ExecutionContext interface.
author Michael Van De Vanter <michael.van.de.vanter@oracle.com>
date Tue, 17 Dec 2013 20:22:45 -0800
parents dfb780080923
children 9c88b1138240
files graal/com.oracle.truffle.api/src/com/oracle/truffle/api/DebugManager.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/ExecutionContext.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/Source.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/SourceSection.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/instrument/EmptyProbe.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/instrument/InstrumentationNode.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/instrument/InstrumentationProbeEvents.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/instrument/InstrumentationProbeNode.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/instrument/InstrumentationProxyNode.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/source/SourceLineLocation.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/source/SourceManager.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/source/TextMap.java graal/com.oracle.truffle.sl.test/src/com/oracle/truffle/sl/test/AbstractTest.java graal/com.oracle.truffle.sl.test/src/com/oracle/truffle/sl/test/FibonacciTest.java graal/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLScript.java graal/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SimpleLanguage.java graal/com.oracle.truffle.sl/src/com/oracle/truffle/sl/runtime/SLContext.java
diffstat 17 files changed, 1556 insertions(+), 37 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/DebugManager.java	Tue Dec 17 20:22:45 2013 -0800
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2013, 2013, 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;
+
+import com.oracle.truffle.api.source.*;
+
+/**
+ * Language-agnostic access to AST-based debugging support.
+ * <p>
+ * <strong>WARNING:</strong> this interface is under development and will change substantially.
+ */
+public interface DebugManager {
+
+    /**
+     * Sets a breakpoint at a line-based location.
+     */
+    LineBreakpoint setBreakpoint(SourceLineLocation lineLocation);
+
+    /**
+     * Sets a breakpoint at a line-based location with a boolean expression in the guest language to
+     * serve as a break condition.
+     */
+    LineBreakpoint setConditionalBreakpoint(SourceLineLocation lineLocation, String condition);
+
+    /**
+     * Gets a list of current breakpoints.
+     */
+    LineBreakpoint[] getBreakpoints();
+
+    /**
+     * Removes a breakpoint at a line-based location.
+     */
+    void removeBreakpoint(SourceLineLocation lineLocation);
+
+    /**
+     * Description of a line-based breakpoint.
+     */
+    interface LineBreakpoint {
+
+        SourceLineLocation getSourceLineLocation();
+
+        String getDebugStatus();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/ExecutionContext.java	Tue Dec 17 20:22:45 2013 -0800
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2013, 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;
+
+/**
+ * Information about the runtime context of a Truffle program.
+ */
+public interface ExecutionContext {
+
+    /**
+     * Gets the name of the language, possibly with version number. in short enough form that it
+     * might be used for an interactive prompt.
+     */
+    String getLanguageShortName();
+
+    /**
+     * Gets access to debugging services, {@code null} if not enabled in this context.
+     */
+    DebugManager getDebugManager();
+
+}
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/Source.java	Thu Dec 12 14:56:52 2013 -0800
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/Source.java	Tue Dec 17 20:22:45 2013 -0800
@@ -24,8 +24,10 @@
  */
 package com.oracle.truffle.api;
 
+import java.io.*;
+
 /**
- * Represents the source code of a guest language program.
+ * Represents a unit (typically a file) of guest language source code.
  */
 public interface Source {
 
@@ -38,9 +40,52 @@
     String getName();
 
     /**
-     * Returns the guest language source code represented by this source object.
-     * 
-     * @return the source code as a String object
+     * The normalized, canonical name of the file.
+     */
+    String getPath();
+
+    /**
+     * Access to the source contents.
+     */
+    Reader getReader();
+
+    /**
+     * Access to the source contents.
+     */
+    InputStream getInputStream();
+
+    /**
+     * Return the complete text of the code.
      */
     String getCode();
+
+    /**
+     * Given a 1-based line number, return the text in the line, not including a possible
+     * terminating newline.
+     */
+    String getCode(int lineNumber);
+
+    /**
+     * The number of text lines in the source, including empty lines; characters at the end of the
+     * source without a terminating newline count as a line.
+     */
+    int getLineCount();
+
+    /**
+     * Given a 0-based character offset, return the 1-based number of the line that includes the
+     * position.
+     */
+    int getLineNumber(int offset);
+
+    /**
+     * Given a 1-based line number, return the 0-based offset of the first character in the line.
+     */
+    int getLineStartOffset(int lineNumber);
+
+    /**
+     * The number of characters (not counting a possible terminating newline) in a (1-based)
+     * numbered line.
+     */
+    int getLineLength(int lineNumber);
+
 }
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/SourceSection.java	Thu Dec 12 14:56:52 2013 -0800
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/SourceSection.java	Tue Dec 17 20:22:45 2013 -0800
@@ -92,4 +92,44 @@
      */
     String getCode();
 
+    /**
+     * Singleton instance with no content.
+     */
+    SourceSection NULL = new SourceSection() {
+
+        public Source getSource() {
+            return null;
+        }
+
+        public int getStartLine() {
+            return 0;
+        }
+
+        public int getStartColumn() {
+            return 0;
+        }
+
+        public int getCharIndex() {
+            return 0;
+        }
+
+        @Override
+        public int getCharLength() {
+            return 0;
+        }
+
+        public int getCharEndIndex() {
+            return 0;
+        }
+
+        public String getIdentifier() {
+            return null;
+        }
+
+        public String getCode() {
+            return null;
+        }
+
+    };
+
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/instrument/EmptyProbe.java	Tue Dec 17 20:22:45 2013 -0800
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2013, 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.nodes.instrument;
+
+import com.oracle.truffle.api.nodes.instrument.InstrumentationProbeNode.DefaultProbeNode;
+
+/**
+ * An "probe" that does nothing, used for testing. It relies on the
+ * {@link InstrumentationProbeNode.DefaultProbeNode} implementation to override every event with an
+ * empty method.
+ */
+public class EmptyProbe extends DefaultProbeNode {
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/instrument/InstrumentationNode.java	Tue Dec 17 20:22:45 2013 -0800
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2013, 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.nodes.instrument;
+
+/**
+ * Marker interface for all Truffle <strong>instrumentation nodes</strong>: nodes that are do not
+ * appear in Truffle ASTs as part of a language's execution semantics.
+ * <p>
+ * In documentation related to instrumentation nodes, these are distinguished by referring to all
+ * other nodes (i.e. ones that <em>do</em> implement language semantics) as <em>AST nodes</em>.
+ */
+public interface InstrumentationNode {
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/instrument/InstrumentationProbeEvents.java	Tue Dec 17 20:22:45 2013 -0800
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2013, 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.nodes.instrument;
+
+import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.nodes.*;
+
+/**
+ * Marker interface for all Truffle <strong>instrumentation nodes</strong>: nodes that are do not
+ * appear in Truffle ASTs as part of a language's execution semantics.
+ * <p>
+ * In documentation related to instrumentation nodes, these are distinguished by referring to all
+ * other nodes (i.e. ones that <em>do</em> implement language semantics) as <em>AST nodes</em>.
+ */
+public interface InstrumentationProbeEvents {
+
+    /**
+     * Notifies a probe that receiver that an AST node's execute method has just been entered.
+     * Callers should assure that a matching call to {@link #leave(Node, VirtualFrame, Object)}
+     * always follows.
+     * 
+     * @param astNode The AST node on which the execute method is being called
+     * @param frame The frame being passed to the execute method
+     */
+    void enter(Node astNode, VirtualFrame frame);
+
+    /**
+     * Notifies a probe that an AST Node's void-valued execute method is about to exit.
+     * <p>
+     * Callers should assure (via {@code try/finally}) that a matching call to this method always
+     * follows a call to {@link #enter(Node, VirtualFrame)}.
+     * 
+     * @param astNode The AST node on which the execute method is being called
+     * @param frame The frame that was passed to the execute method
+     */
+    void leave(Node astNode, VirtualFrame frame);
+
+    /**
+     * Notifies a probe that an AST Node's boolean-valued execute method is about to exit.
+     * <p>
+     * Callers should assure (via {@code try/finally}) that a matching call to this method always
+     * follows a call to {@link #enter(Node, VirtualFrame)}.
+     * 
+     * @param astNode The AST node on which the execute method is being called
+     * @param frame The frame that was passed to the execute method
+     * @param result The result of the call to the execute method.
+     */
+    void leave(Node astNode, VirtualFrame frame, boolean result);
+
+    /**
+     * Notifies a probe that an AST Node's byte-valued execute method is about to exit.
+     * <p>
+     * Callers should assure (via {@code try/finally}) that a matching call to this method always
+     * follows a call to {@link #enter(Node, VirtualFrame)}.
+     * 
+     * @param astNode The AST node on which the execute method is being called
+     * @param frame The frame that was passed to the execute method
+     * @param result The result of the call to the execute method.
+     */
+    void leave(Node astNode, VirtualFrame frame, byte result);
+
+    /**
+     * Notifies a probe that an AST Node's short-valued execute method is about to exit.
+     * <p>
+     * Callers should assure (via {@code try/finally}) that a matching call to this method always
+     * follows a call to {@link #enter(Node, VirtualFrame)}.
+     * 
+     * @param astNode The AST node on which the execute method is being called
+     * @param frame The frame that was passed to the execute method
+     * @param result The result of the call to the execute method.
+     */
+    void leave(Node astNode, VirtualFrame frame, short result);
+
+    /**
+     * Notifies a probe that an AST Node's integer-valued execute method is about to exit.
+     * <p>
+     * Callers should assure (via {@code try/finally}) that a matching call to this method always
+     * follows a call to {@link #enter(Node, VirtualFrame)}.
+     * 
+     * @param astNode The AST node on which the execute method is being called
+     * @param frame The frame that was passed to the execute method
+     * @param result The result of the call to the execute method.
+     */
+    void leave(Node astNode, VirtualFrame frame, int result);
+
+    /**
+     * Notifies a probe that an AST Node's long-valued execute method is about to exit.
+     * <p>
+     * Callers should assure (via {@code try/finally}) that a matching call to this method always
+     * follows a call to {@link #enter(Node, VirtualFrame)}.
+     * 
+     * @param astNode The AST node on which the execute method is being called
+     * @param frame The frame that was passed to the execute method
+     * @param result The result of the call to the execute method.
+     */
+    void leave(Node astNode, VirtualFrame frame, long result);
+
+    /**
+     * Notifies a probe that an AST Node's float-valued execute method is about to exit.
+     * <p>
+     * Callers should assure (via {@code try/finally}) that a matching call to this method always
+     * follows a call to {@link #enter(Node, VirtualFrame)}.
+     * 
+     * @param astNode The AST node on which the execute method is being called
+     * @param frame The frame that was passed to the execute method
+     * @param result The result of the call to the execute method.
+     */
+    void leave(Node astNode, VirtualFrame frame, float result);
+
+    /**
+     * Notifies a probe that an AST Node's double-valued execute method is about to exit.
+     * <p>
+     * Callers should assure (via {@code try/finally}) that a matching call to this method always
+     * follows a call to {@link #enter(Node, VirtualFrame)}.
+     * 
+     * @param astNode The AST node on which the execute method is being called
+     * @param frame The frame that was passed to the execute method
+     * @param result The result of the call to the execute method.
+     */
+    void leave(Node astNode, VirtualFrame frame, double result);
+
+    /**
+     * Notifies a probe that an AST Node's char-valued execute method is about to exit.
+     * <p>
+     * Callers should assure (via {@code try/finally}) that a matching call to this method always
+     * follows a call to {@link #enter(Node, VirtualFrame)}.
+     * 
+     * @param astNode The AST node on which the execute method is being called
+     * @param frame The frame that was passed to the execute method
+     * @param result The result of the call to the execute method.
+     */
+    void leave(Node astNode, VirtualFrame frame, char result);
+
+    /**
+     * Notifies a probe that an AST Node's object-valued execute method is about to exit.
+     * <p>
+     * Callers should assure (via {@code try/finally}) that a matching call to this method always
+     * follows a call to {@link #enter(Node, VirtualFrame)}.
+     * 
+     * @param astNode The AST node on which the execute method is being called
+     * @param frame The frame that was passed to the execute method
+     * @param result The result of the call to the execute method.
+     */
+    void leave(Node astNode, VirtualFrame frame, Object result);
+
+    /**
+     * Notifies a probe that an AST Node's execute method is about to leave under exceptional
+     * conditions, returning no value.
+     * <p>
+     * Callers should assure (via {@code try/finally}) that a matching call to this method always
+     * follows a call to {@link #enter(Node, VirtualFrame)}.
+     * 
+     * @param astNode The AST node on which the execute method is being called
+     * @param frame The frame that was passed to the execute method
+     * @param e the exception associated with the unusual return
+     */
+    void leaveExceptional(Node astNode, VirtualFrame frame, Exception e);
+
+    /**
+     * Notifies a probe that an AST node is about to be replaced with another.
+     * 
+     * @param oldAstNode the AST node currently in the tree
+     * @param newAstNode the AST replacement node
+     * @param reason explanation for the replacement
+     */
+    void replace(Node oldAstNode, Node newAstNode, String reason);
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/instrument/InstrumentationProbeNode.java	Tue Dec 17 20:22:45 2013 -0800
@@ -0,0 +1,412 @@
+/*
+ * Copyright (c) 2013, 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.nodes.instrument;
+
+import com.oracle.truffle.api.*;
+import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.nodes.*;
+
+/**
+ * A <strong>probe</strong>: a Truffle instrumentation node that holds code to perform some action
+ * when notified (via a {@linkplain InstrumentationProxyNode proxy node} in the AST) of a
+ * {@linkplain InstrumentationProbeEvents probe event} taking place at the AST node.
+ * <p>
+ * Probes are only active when attached to a {@linkplain ProbeChain "probe chain"} that is referred
+ * to by one or more {@linkplain InstrumentationProxyNode proxy nodes} in an AST.
+ */
+public abstract class InstrumentationProbeNode extends Node implements InstrumentationNode, InstrumentationProbeEvents {
+
+    /**
+     * Next in chain.
+     */
+    @Child protected InstrumentationProbeNode next;
+
+    protected InstrumentationProbeNode() {
+    }
+
+    protected int countProbes() {
+        return next == null ? 0 : next.countProbes() + 1;
+    }
+
+    /**
+     * Add a probe to the end of this probe chain.
+     */
+    protected void internalAppendProbe(InstrumentationProbeNode newProbeNode) {
+        if (next == null) {
+            this.next = adoptChild(newProbeNode);
+        } else {
+            next.internalAppendProbe(newProbeNode);
+        }
+    }
+
+    protected void internalRemoveProbe(InstrumentationProbeNode oldProbeNode) {
+        if (next == null) {
+            throw new RuntimeException("Couldn't find probe to remove: " + oldProbeNode);
+        } else if (next == oldProbeNode) {
+            if (oldProbeNode.next == null) {
+                this.next = null;
+            } else {
+                this.next = adoptChild(oldProbeNode.next);
+            }
+        } else {
+            next.internalRemoveProbe(oldProbeNode);
+        }
+    }
+
+    /**
+     * Passes up the chain notification that a probe has changed its execution state in a way that
+     * invalidates fast path code. Assumes that there is an instance of {@link ProbeChain} at the
+     * head of the chain.
+     */
+    @CompilerDirectives.SlowPath
+    protected void notifyProbeChanged(InstrumentationProbeNode probeNode) {
+        final InstrumentationProbeNode parent = (InstrumentationProbeNode) getParent();
+        parent.notifyProbeChanged(probeNode);
+    }
+
+    // TODO (mlvdv) making the internal*() methods public is a workaround for a bug/limitation in
+    // the Truffle compiler; they are intended to be private.
+
+    public void internalEnter(Node astNode, VirtualFrame frame) {
+        enter(astNode, frame);
+        if (next != null) {
+            next.internalEnter(astNode, frame);
+        }
+    }
+
+    public void internalLeave(Node astNode, VirtualFrame frame) {
+        leave(astNode, frame);
+        if (next != null) {
+            next.internalLeave(astNode, frame);
+        }
+    }
+
+    public void internalLeave(Node astNode, VirtualFrame frame, boolean result) {
+        leave(astNode, frame, result);
+        if (next != null) {
+            next.internalLeave(astNode, frame, result);
+        }
+    }
+
+    public void internalLeave(Node astNode, VirtualFrame frame, byte result) {
+        leave(astNode, frame, result);
+        if (next != null) {
+            next.internalLeave(astNode, frame, result);
+        }
+    }
+
+    public void internalLeave(Node astNode, VirtualFrame frame, short result) {
+        leave(astNode, frame, result);
+        if (next != null) {
+            next.internalLeave(astNode, frame, result);
+        }
+    }
+
+    public void internalLeave(Node astNode, VirtualFrame frame, int result) {
+        leave(astNode, frame, result);
+        if (next != null) {
+            next.internalLeave(astNode, frame, result);
+        }
+    }
+
+    public void internalLeave(Node astNode, VirtualFrame frame, long result) {
+        leave(astNode, frame, result);
+        if (next != null) {
+            next.internalLeave(astNode, frame, result);
+        }
+    }
+
+    public void internalLeave(Node astNode, VirtualFrame frame, char result) {
+        leave(astNode, frame, result);
+        if (next != null) {
+            next.internalLeave(astNode, frame, result);
+        }
+    }
+
+    public void internalLeave(Node astNode, VirtualFrame frame, float result) {
+        leave(astNode, frame, result);
+        if (next != null) {
+            next.internalLeave(astNode, frame, result);
+        }
+    }
+
+    public void internalLeave(Node astNode, VirtualFrame frame, double result) {
+        leave(astNode, frame, result);
+        if (next != null) {
+            next.internalLeave(astNode, frame, result);
+        }
+    }
+
+    public void internalLeave(Node astNode, VirtualFrame frame, Object result) {
+        leave(astNode, frame, result);
+        if (next != null) {
+            next.internalLeave(astNode, frame, result);
+        }
+    }
+
+    public void internalLeaveExceptional(Node astNode, VirtualFrame frame, Exception e) {
+        leaveExceptional(astNode, frame, null);
+        if (next != null) {
+            next.internalLeaveExceptional(astNode, frame, e);
+        }
+    }
+
+    public void internalReplace(Node oldAstNode, Node newAstNode, String reason) {
+        replace(oldAstNode, newAstNode, reason);
+        if (next != null) {
+            next.internalReplace(oldAstNode, newAstNode, reason);
+        }
+    }
+
+    /**
+     * A probe implementation that implements all of {@link InstrumentationProbeEvents} with empty
+     * methods; concrete subclasses can override only the methods for which something is to be done.
+     */
+    public static class DefaultProbeNode extends InstrumentationProbeNode {
+
+        public void enter(Node astNode, VirtualFrame frame) {
+        }
+
+        public void leave(Node astNode, VirtualFrame frame) {
+        }
+
+        public void leave(Node astNode, VirtualFrame frame, boolean result) {
+        }
+
+        public void leave(Node astNode, VirtualFrame frame, byte result) {
+        }
+
+        public void leave(Node astNode, VirtualFrame frame, short result) {
+        }
+
+        public void leave(Node astNode, VirtualFrame frame, int result) {
+        }
+
+        public void leave(Node astNode, VirtualFrame frame, long result) {
+        }
+
+        public void leave(Node astNode, VirtualFrame frame, char result) {
+        }
+
+        public void leave(Node astNode, VirtualFrame frame, float result) {
+        }
+
+        public void leave(Node astNode, VirtualFrame frame, double result) {
+        }
+
+        public void leave(Node astNode, VirtualFrame frame, Object result) {
+        }
+
+        public void leaveExceptional(Node astNode, VirtualFrame frame, Exception e) {
+        }
+
+        public void replace(Node oldAstNode, Node newAstNode, String reason) {
+        }
+
+    }
+
+    /**
+     * Holder of a chain of {@linkplain InstrumentationProbeNode probes}: manages the
+     * {@link Assumption} that the chain has not changed since checked checked.
+     */
+    public static final class ProbeChain extends DefaultProbeNode {
+
+        @CompilerDirectives.CompilationFinal private Assumption probeUnchanged;
+
+        /**
+         * Source information about the node to which this probe chain is attached; it isn't
+         * otherwise available. A probe chain is shared by every copy made during runtime, so there
+         * is no parent pointer.
+         */
+        @SuppressWarnings("unused") private final SourceSection sourceSection;
+
+        private final String description; // for debugging
+
+        /**
+         * Creates a new, empty chain of {@linkplain InstrumentationProbeNode probes}, to which
+         * probes can be added/removed, and all of which will be notified of
+         * {@linkplain InstrumentationProbeEvents events} when the chain is notified.
+         */
+        public ProbeChain(SourceSection sourceSection, String description) {
+            this.probeUnchanged = Truffle.getRuntime().createAssumption();
+            this.sourceSection = sourceSection;
+            this.description = description;
+            this.next = null;
+        }
+
+        public int probeCount() {
+            return countProbes();
+        }
+
+        public String getDescription() {
+            return description;
+        }
+
+        @Override
+        protected int countProbes() {
+            // The head of the chain does not itself hold a probe
+            return next == null ? 0 : next.countProbes();
+        }
+
+        @Override
+        protected void notifyProbeChanged(InstrumentationProbeNode probeNode) {
+            probeUnchanged.invalidate();
+            probeUnchanged = Truffle.getRuntime().createAssumption();
+        }
+
+        @CompilerDirectives.SlowPath
+        public void appendProbe(InstrumentationProbeNode newProbeNode) {
+            probeUnchanged.invalidate();
+            super.internalAppendProbe(newProbeNode);
+            probeUnchanged = Truffle.getRuntime().createAssumption();
+        }
+
+        @CompilerDirectives.SlowPath
+        public void removeProbe(InstrumentationProbeNode oldProbeNode) {
+            probeUnchanged.invalidate();
+            super.internalRemoveProbe(oldProbeNode);
+            probeUnchanged = Truffle.getRuntime().createAssumption();
+        }
+
+        public void notifyEnter(Node astNode, VirtualFrame frame) {
+            if (next != null) {
+                if (!probeUnchanged.isValid()) {
+                    CompilerDirectives.transferToInterpreter();
+                }
+                next.internalEnter(astNode, frame);
+            }
+        }
+
+        public void notifyLeave(Node astNode, VirtualFrame frame) {
+            if (next != null) {
+                if (!probeUnchanged.isValid()) {
+                    CompilerDirectives.transferToInterpreter();
+                }
+                next.internalLeave(astNode, frame);
+            }
+        }
+
+        public void notifyLeave(Node astNode, VirtualFrame frame, boolean result) {
+            if (next != null) {
+                if (!probeUnchanged.isValid()) {
+                    CompilerDirectives.transferToInterpreter();
+                }
+                next.internalLeave(astNode, frame, result);
+            }
+        }
+
+        public void notifyLeave(Node astNode, VirtualFrame frame, byte result) {
+            if (next != null) {
+                if (!probeUnchanged.isValid()) {
+                    CompilerDirectives.transferToInterpreter();
+                }
+                next.internalLeave(astNode, frame, result);
+            }
+        }
+
+        public void notifyLeave(Node astNode, VirtualFrame frame, short result) {
+            if (next != null) {
+                if (!probeUnchanged.isValid()) {
+                    CompilerDirectives.transferToInterpreter();
+                }
+                next.internalLeave(astNode, frame, result);
+            }
+        }
+
+        public void notifyLeave(Node astNode, VirtualFrame frame, int result) {
+            if (next != null) {
+                if (!probeUnchanged.isValid()) {
+                    CompilerDirectives.transferToInterpreter();
+                }
+                next.internalLeave(astNode, frame, result);
+            }
+        }
+
+        public void notifyLeave(Node astNode, VirtualFrame frame, long result) {
+            if (next != null) {
+                if (!probeUnchanged.isValid()) {
+                    CompilerDirectives.transferToInterpreter();
+                }
+                next.internalLeave(astNode, frame, result);
+            }
+        }
+
+        public void notifyLeave(Node astNode, VirtualFrame frame, char result) {
+            if (next != null) {
+                if (!probeUnchanged.isValid()) {
+                    CompilerDirectives.transferToInterpreter();
+                }
+                next.internalLeave(astNode, frame, result);
+            }
+        }
+
+        public void notifyLeave(Node astNode, VirtualFrame frame, float result) {
+            if (next != null) {
+                if (!probeUnchanged.isValid()) {
+                    CompilerDirectives.transferToInterpreter();
+                }
+                next.internalLeave(astNode, frame, result);
+            }
+        }
+
+        public void notifyLeave(Node astNode, VirtualFrame frame, double result) {
+            if (next != null) {
+                if (!probeUnchanged.isValid()) {
+                    CompilerDirectives.transferToInterpreter();
+                }
+                next.internalLeave(astNode, frame, result);
+            }
+        }
+
+        public void notifyLeave(Node astNode, VirtualFrame frame, Object result) {
+            if (next != null) {
+                if (!probeUnchanged.isValid()) {
+                    CompilerDirectives.transferToInterpreter();
+                }
+                next.internalLeave(astNode, frame, result);
+            }
+        }
+
+        public void notifyLeaveExceptional(Node astNode, VirtualFrame frame, Exception e) {
+            if (next != null) {
+                if (!probeUnchanged.isValid()) {
+                    CompilerDirectives.transferToInterpreter();
+                }
+                next.internalLeaveExceptional(astNode, frame, e);
+            }
+        }
+
+        public void notifyReplace(Node oldAstNode, Node newAstNode, String reason) {
+            if (next != null) {
+                if (!probeUnchanged.isValid()) {
+                    CompilerDirectives.transferToInterpreter();
+                }
+                next.internalReplace(oldAstNode, newAstNode, reason);
+            }
+        }
+
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/instrument/InstrumentationProxyNode.java	Tue Dec 17 20:22:45 2013 -0800
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2013, 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.nodes.instrument;
+
+import com.oracle.truffle.api.nodes.instrument.InstrumentationProbeNode.ProbeChain;
+
+/**
+ * Interface implemented by language-specific Truffle <strong>proxy nodes</strong>: nodes that do
+ * not participate in the language's execution semantics, but which are inserted into an AST so that
+ * tools (e.g. tracers, analyzers, debuggers) can be notified of AST interpretation events and
+ * possibly intervene.
+ * <p>
+ * Language-specific proxy nodes call notification methods on an attached {@linkplain ProbeChain
+ * probe chain} which passes along {@linkplain InstrumentationProbeEvents events} to any
+ * {@linkplain InstrumentationProbeNode probes} that might have been attached.
+ */
+public interface InstrumentationProxyNode extends InstrumentationNode {
+
+    /**
+     * Gets the chain of probes to which events at this node are delegated. Note that a chain of
+     * probes may be used by more than one proxy.
+     */
+    ProbeChain getProbeChain();
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/source/SourceLineLocation.java	Tue Dec 17 20:22:45 2013 -0800
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2013, 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.source;
+
+import com.oracle.truffle.api.*;
+
+/**
+ * A specification for a location in guest language source, expressed as a line number in a specific
+ * instance of {@link Source}, suitable for hash table keys with equality defined in terms of
+ * content.
+ */
+public class SourceLineLocation implements Comparable {
+
+    private final Source source;
+    private final int line;
+
+    public SourceLineLocation(Source source, int line) {
+        assert source != null;
+        assert source != SourceSection.NULL;
+        this.source = source;
+        this.line = line;
+    }
+
+    public Source getSource() {
+        return source;
+    }
+
+    public int getLine() {
+        return line;
+    }
+
+    @Override
+    public String toString() {
+        return "SourceLine [" + source.getName() + ", " + line + "]";
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + line;
+        result = prime * result + source.hashCode();
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof SourceLineLocation)) {
+            return false;
+        }
+        SourceLineLocation other = (SourceLineLocation) obj;
+        if (line != other.line) {
+            return false;
+        }
+        return source.equals(other.source);
+    }
+
+    @Override
+    public int compareTo(Object o) {
+        final SourceLineLocation other = (SourceLineLocation) o;
+        final int nameOrder = source.getName().compareTo(other.source.getName());
+        if (nameOrder != 0) {
+            return nameOrder;
+        }
+        return Integer.compare(line, other.line);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/source/SourceManager.java	Tue Dec 17 20:22:45 2013 -0800
@@ -0,0 +1,323 @@
+/*
+ * Copyright (c) 2013, 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.source;
+
+import java.io.*;
+import java.util.*;
+
+import com.oracle.truffle.api.*;
+
+/**
+ * A representation of source code information, suitable for hash table keys with equality defined
+ * in terms of content. There are three kinds of sources supported at present.
+ * <ul>
+ * <li><strong>File:</strong>Each file is represented as a canonical object, indexed by the
+ * absolute, canonical path name of the file. The textual contents of the file may be supplied when
+ * the object is created, or it may be read lazily. Only one lazy attempt will be made to read a
+ * file, and failure will result silently in null content.</li>
+ * <li><strong>Literal Source:</strong> A named text string, whose contents are supplied concretely
+ * (possibly via an {@link InputStream}), can also be used as a source. These are represented as
+ * value objects whose equality depends on both name and contents.</li>
+ * <li><strong>Fake Files:</strong> A named text string used for testing; its contents can be
+ * retrieved by name, unlike literal sources.</li>
+ * </ul>
+ */
+public final class SourceManager {
+
+    // Only files and fake files are indexed.
+    private final Map<String, SourceImpl> sourceMap = new HashMap<>();
+
+    public SourceManager() {
+
+    }
+
+    /**
+     * Gets the canonical representation of a source file, whose contents will be read lazily and
+     * then cached.
+     * 
+     * @param reset forces any existing {@link Source} cache to be cleared, forcing a re-read.
+     */
+    public Source get(String fileName, boolean reset) {
+        SourceImpl source = sourceMap.get(fileName);
+        if (source == null) {
+            String path = findPath(fileName);
+            if (path == null) {
+                throw new RuntimeException("Can't find file " + fileName);
+            }
+            source = sourceMap.get(path);
+            if (source == null) {
+                source = new FileSourceImpl(fileName, path);
+                sourceMap.put(path, source);
+            }
+        } else {
+            if (reset) {
+                source.reset();
+            }
+        }
+        return source;
+    }
+
+    /**
+     * Gets the canonical representation of a source file, whose contents will be read lazily and
+     * then cached.
+     */
+    public Source get(String fileName) {
+        return get(fileName, false);
+    }
+
+    /**
+     * Creates a source from literal text.
+     */
+    @SuppressWarnings("static-method")
+    public Source get(String name, String code) {
+        assert code != null;
+        return new LiteralSourceImpl(name, code);
+    }
+
+    /**
+     * Creates a source whose contents will be read immediately and cached.
+     */
+    @SuppressWarnings("static-method")
+    public Source get(String name, InputStream stream) throws IOException {
+        InputStreamReader reader = new InputStreamReader(stream);
+        return new LiteralSourceImpl(name, readCode(reader));
+    }
+
+    /**
+     * Creates a source from literal text, but which acts as a file and can be retrieved by name
+     * (unlike other literal sources); intended for testing.
+     */
+    public Source getFakeFile(String name, String code) {
+        final SourceImpl source = new LiteralSourceImpl(name, code);
+        sourceMap.put(name, source);
+        return source;
+    }
+
+    // If it names a real file, get the (canonical) normalized absolute path.
+    private static String findPath(String name) {
+        final File file = new File(name);
+        if (file.exists()) {
+            try {
+                return file.getCanonicalPath();
+            } catch (IOException e) {
+            }
+        }
+        return null;
+    }
+
+    private static String readCode(Reader reader) throws IOException {
+        final StringBuilder builder = new StringBuilder();
+        final char[] buffer = new char[1024];
+
+        while (true) {
+            final int n = reader.read(buffer);
+            if (n == -1) {
+                break;
+            }
+            builder.append(buffer, 0, n);
+        }
+
+        return builder.toString();
+    }
+
+    private abstract static class SourceImpl implements Source {
+
+        protected TextMap textMap = null;
+
+        protected abstract void reset();
+
+        public final InputStream getInputStream() {
+            return new ByteArrayInputStream(getCode().getBytes());
+        }
+
+        /**
+         * Gets the text (not including a possible terminating newline) in a (1-based) numbered
+         * line.
+         */
+        public final String getCode(int lineNumber) {
+            checkTextMap();
+            final int offset = textMap.lineStartOffset(lineNumber);
+            final int length = textMap.lineLength(lineNumber);
+            return getCode().substring(offset, offset + length);
+        }
+
+        /**
+         * The number of text lines in the source.
+         */
+        public final int getLineCount() {
+            return checkTextMap().lineCount();
+        }
+
+        /**
+         * The 1-based number of the line that includes a 0-based character offset.
+         */
+        public final int getLineNumber(int offset) {
+            return checkTextMap().offsetToLine(offset);
+        }
+
+        /**
+         * The 0-based character offset at the start of a (1-based) numbered line.
+         */
+        public final int getLineStartOffset(int lineNumber) {
+            return checkTextMap().lineStartOffset(lineNumber);
+        }
+
+        /**
+         * The number of characters (not counting a possible terminating newline) in a (1-based)
+         * numbered line.
+         */
+        public final int getLineLength(int lineNumber) {
+            return checkTextMap().lineLength(lineNumber);
+        }
+
+        private TextMap checkTextMap() {
+            if (textMap == null) {
+                final String code = getCode();
+                if (code == null) {
+                    throw new RuntimeException("can't read file " + getName());
+                }
+                textMap = new TextMap(code);
+            }
+            return textMap;
+        }
+    }
+
+    public static class LiteralSourceImpl extends SourceImpl {
+
+        private final String name; // Name used originally to describe the source
+        private final String code;
+
+        public LiteralSourceImpl(String name, String code) {
+            this.name = name;
+            this.code = code;
+        }
+
+        @Override
+        public String getName() {
+            return name;
+        }
+
+        @Override
+        public String getCode() {
+            return code;
+        }
+
+        @Override
+        public String getPath() {
+            return name;
+        }
+
+        @Override
+        public Reader getReader() {
+            return new StringReader(code);
+        }
+
+        @Override
+        protected void reset() {
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + name.hashCode();
+            result = prime * result + (code == null ? 0 : code.hashCode());
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null) {
+                return false;
+            }
+            if (!(obj instanceof LiteralSourceImpl)) {
+                return false;
+            }
+            LiteralSourceImpl other = (LiteralSourceImpl) obj;
+            return name.equals(other.name) && code.equals(other.code);
+        }
+
+    }
+
+    private static class FileSourceImpl extends SourceImpl {
+
+        private final String name; // Name used originally to describe the source
+        private String code = null;
+        private final String path;  // Normalized path description of an actual file
+        private boolean readAttempted;
+
+        public FileSourceImpl(String name, String path) {
+            this.name = name;
+            this.path = path;
+            this.readAttempted = false;
+
+        }
+
+        @Override
+        public String getName() {
+            return name;
+        }
+
+        @Override
+        public String getCode() {
+            if (code == null && !readAttempted) {
+                readAttempted = true;
+                try {
+                    code = readCode(getReader());
+                } catch (IOException e) {
+                }
+            }
+            return code;
+        }
+
+        @Override
+        public String getPath() {
+            return path;
+        }
+
+        @Override
+        public Reader getReader() {
+            if (code != null) {
+                return new StringReader(code);
+            }
+            try {
+                return new BufferedReader(new FileReader(path));
+            } catch (FileNotFoundException e) {
+                throw new RuntimeException("Can't find file " + path);
+            }
+        }
+
+        @Override
+        protected void reset() {
+            this.code = null;
+            this.readAttempted = false;
+        }
+
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/source/TextMap.java	Tue Dec 17 20:22:45 2013 -0800
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2013, 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.source;
+
+import java.util.*;
+
+/**
+ * A utility for converting between coordinate systems in a string of text interspersed with newline
+ * characters. The coordinate systems are:
+ * <ul>
+ * <li>0-based character offset from the beginning of the text, where newline characters count as a
+ * single character and the first character in the text occupies position 0.</li>
+ * <li>1-based position in the 2D space of lines and columns, in which the first position in the
+ * text is at (1,1).</li>
+ * </ul>
+ * <p>
+ * This utility is based on positions occupied by characters, not text stream positions as in a text
+ * editor. The distinction shows up in editors where you can put the cursor just past the last
+ * character in a buffer; this is necessary, among other reasons, so that you can put the edit
+ * cursor in a new (empty) buffer. For the purposes of this utility, however, there are no character
+ * positions in an empty text string and there are no lines in an empty text string.
+ * <p>
+ * A newline character designates the end of a line and occupies a column position.
+ * <p>
+ * If the text ends with a character other than a newline, then the characters following the final
+ * newline character count as a line, even though not newline-terminated.
+ * <p>
+ * <strong>Limitations:</strong>
+ * <ul>
+ * <li>Does not handle multiple character encodings correctly.</li>
+ * <li>Treats tabs as occupying 1 column.</li>
+ * <li>Does not handle multiple-character line termination sequences correctly.</li>
+ * </ul>
+ */
+public final class TextMap {
+
+    // 0-based offsets of newline characters in the text, with sentinel
+    private final int[] nlOffsets;
+
+    // The number of characters in the text, including newlines (which count as 1).
+    private final int textLength;
+
+    // Is the final text character a newline?
+    final boolean finalNL;
+
+    /**
+     * Constructs map permitting translation between 0-based character offsets and 1-based
+     * lines/columns.
+     */
+    public TextMap(String text) {
+        this.textLength = text.length();
+        final ArrayList<Integer> lines = new ArrayList<>();
+        lines.add(0);
+        int offset = 0;
+
+        while (offset < text.length()) {
+            final int nlIndex = text.indexOf('\n', offset);
+            if (nlIndex >= 0) {
+                offset = nlIndex + 1;
+                lines.add(offset);
+            } else {
+                break;
+            }
+        }
+        lines.add(Integer.MAX_VALUE);
+
+        nlOffsets = new int[lines.size()];
+        for (int line = 0; line < lines.size(); line++) {
+            nlOffsets[line] = lines.get(line);
+        }
+
+        finalNL = textLength > 0 && (textLength == nlOffsets[nlOffsets.length - 2]);
+    }
+
+    /**
+     * Converts 0-based character offset to 1-based number of the line containing the character.
+     * 
+     * @throws IllegalArgumentException if the offset is outside the string.
+     */
+    public int offsetToLine(int offset) throws IllegalArgumentException {
+        if (offset < 0 || offset >= textLength) {
+            throw new IllegalArgumentException("offset out of bounds");
+        }
+        int line = 1;
+        while (offset >= nlOffsets[line]) {
+            line++;
+        }
+        return line;
+    }
+
+    /**
+     * Converts 0-based character offset to 1-based number of the column occupied by the character.
+     * <p>
+     * Tabs are not expanded; they occupy 1 column.
+     * 
+     * @throws IllegalArgumentException if the offset is outside the string.
+     */
+    public int offsetToCol(int offset) throws IllegalArgumentException {
+        return 1 + offset - nlOffsets[offsetToLine(offset) - 1];
+    }
+
+    /**
+     * The number of lines in the text; if characters appear after the final newline, then they also
+     * count as a line, even though not newline-terminated.
+     */
+    public int lineCount() {
+        if (textLength == 0) {
+            return 0;
+        }
+        return finalNL ? nlOffsets.length - 2 : nlOffsets.length - 1;
+    }
+
+    /**
+     * Converts 1-based line number to the 0-based offset of the line's first character; this would
+     * be the offset of a newline if the line is empty.
+     * 
+     * @throws IllegalArgumentException if there is no such line in the text.
+     */
+    public int lineStartOffset(int line) throws IllegalArgumentException {
+        if (textLength == 0 || lineOutOfRange(line)) {
+            throw new IllegalArgumentException("line out of bounds");
+        }
+        return nlOffsets[line - 1];
+    }
+
+    /**
+     * Gets the number of characters in a line, identified by 1-based line number; <em>does not</em>
+     * include the final newline, if any.
+     * 
+     * @throws IllegalArgumentException if there is no such line in the text.
+     */
+    public int lineLength(int line) throws IllegalArgumentException {
+        if (textLength == 0 || lineOutOfRange(line)) {
+            throw new IllegalArgumentException("line out of bounds");
+        }
+        if (line == nlOffsets.length - 1 && !finalNL) {
+            return textLength - nlOffsets[line - 1];
+        }
+        return (nlOffsets[line] - nlOffsets[line - 1]) - 1;
+
+    }
+
+    /**
+     * Is the line number out of range.
+     */
+    private boolean lineOutOfRange(int line) {
+        return line <= 0 || line >= nlOffsets.length || (line == nlOffsets.length - 1 && finalNL);
+    }
+
+}
--- a/graal/com.oracle.truffle.sl.test/src/com/oracle/truffle/sl/test/AbstractTest.java	Thu Dec 12 14:56:52 2013 -0800
+++ b/graal/com.oracle.truffle.sl.test/src/com/oracle/truffle/sl/test/AbstractTest.java	Tue Dec 17 20:22:45 2013 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2013, 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
@@ -50,17 +50,14 @@
     }
 
     protected static void executeSL(String[] input, String[] expectedOutput, boolean useConsole) {
-        InputStream in = new ByteArrayInputStream(concat(input).getBytes());
-
         ByteArrayOutputStream out = new ByteArrayOutputStream();
         PrintStream printer = new PrintStream(useConsole ? new SplitOutputStream(out, System.err) : out);
         PrintStream origErr = System.err;
         System.setErr(printer);
 
-        SimpleLanguage.run(in, printer, REPEATS, false);
+        SimpleLanguage.run("(test)", concat(input), printer, REPEATS, false);
 
         System.setErr(origErr);
         Assert.assertEquals(repeat(concat(expectedOutput), REPEATS), new String(out.toByteArray()));
     }
-
 }
--- a/graal/com.oracle.truffle.sl.test/src/com/oracle/truffle/sl/test/FibonacciTest.java	Thu Dec 12 14:56:52 2013 -0800
+++ b/graal/com.oracle.truffle.sl.test/src/com/oracle/truffle/sl/test/FibonacciTest.java	Tue Dec 17 20:22:45 2013 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2013, 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
@@ -26,6 +26,7 @@
 
 import org.junit.*;
 
+import com.oracle.truffle.api.*;
 import com.oracle.truffle.sl.*;
 import com.oracle.truffle.sl.runtime.*;
 
@@ -75,7 +76,9 @@
         for (String line : INPUT) {
             s.append(line).append("\n");
         }
-        SLScript script = SLScript.create(new SLContext(System.out), s.toString());
+        final SLContext context = new SLContext(System.out);
+        final Source source = context.getSourceManager().get("(fib test)", s.toString());
+        SLScript script = SLScript.create(context, source);
         Integer reference = test(TEST_VALUE);
         for (int i = 0; i < ITERATIONS; i++) {
             if (!reference.equals(script.run(TEST_VALUE))) {
--- a/graal/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLScript.java	Thu Dec 12 14:56:52 2013 -0800
+++ b/graal/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLScript.java	Tue Dec 17 20:22:45 2013 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2013, 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
@@ -22,8 +22,6 @@
  */
 package com.oracle.truffle.sl;
 
-import java.io.*;
-
 import javax.script.*;
 
 import com.oracle.truffle.api.*;
@@ -57,24 +55,11 @@
         return main.toString();
     }
 
-    public static SLScript create(SLContext context, String input) throws ScriptException {
-        return create(context, new ByteArrayInputStream(input.getBytes()));
-
-    }
-
-    public static SLScript create(SLContext context, InputStream input) throws ScriptException {
+    public static SLScript create(SLContext context, Source source) throws ScriptException {
         SLNodeFactory factory = new SLNodeFactory(context);
-        Parser parser = new Parser(new Scanner(input), factory);
+        Parser parser = new Parser(new Scanner(source.getInputStream()), factory);
         factory.setParser(parser);
-        factory.setSource(new Source() {
-            public String getName() {
-                return "Unknown";
-            }
-
-            public String getCode() {
-                return null;
-            }
-        });
+        factory.setSource(source);
         String error = parser.ParseErrors();
         if (!error.isEmpty()) {
             throw new ScriptException(String.format("Error(s) parsing script: %s", error));
--- a/graal/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SimpleLanguage.java	Thu Dec 12 14:56:52 2013 -0800
+++ b/graal/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SimpleLanguage.java	Tue Dec 17 20:22:45 2013 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2013, 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
@@ -35,21 +35,41 @@
 
     private static final Object[] NO_ARGUMENTS = new Object[0];
 
-    public static void main(String[] args) throws IOException {
-        run(new FileInputStream(args[0]), System.out, 10, true);
+    public static void main(String[] args) {
+        run(args[0], System.out, 10, true);
     }
 
-    public static void run(InputStream input, PrintStream printOutput, int repeats, boolean log) {
+    public static void run(String name, String input, PrintStream printOutput, int repeats, boolean log) {
         if (log) {
             // CheckStyle: stop system..print check
             System.out.printf("== running on %s\n", Truffle.getRuntime().getName());
             // CheckStyle: resume system..print check
         }
 
-        SLContext context = new SLContext(printOutput);
+        final SLContext context = new SLContext(printOutput);
+        final Source source = context.getSourceManager().get(name, input);
+
+        run(context, source, printOutput, repeats, log);
+    }
+
+    public static void run(String fileName, PrintStream printOutput, int repeats, boolean log) {
+        if (log) {
+            // CheckStyle: stop system..print check
+            System.out.printf("== running on %s\n", Truffle.getRuntime().getName());
+            // CheckStyle: resume system..print check
+        }
+
+        final SLContext context = new SLContext(printOutput);
+        final Source source = context.getSourceManager().get(fileName);
+
+        run(context, source, printOutput, repeats, log);
+    }
+
+    public static void run(SLContext context, Source source, PrintStream printOutput, int repeats, boolean log) {
+
         SLScript script;
         try {
-            script = SLScript.create(context, input);
+            script = SLScript.create(context, source);
         } catch (ScriptException e) {
             // TODO temporary hack
             throw new RuntimeException(e);
--- a/graal/com.oracle.truffle.sl/src/com/oracle/truffle/sl/runtime/SLContext.java	Thu Dec 12 14:56:52 2013 -0800
+++ b/graal/com.oracle.truffle.sl/src/com/oracle/truffle/sl/runtime/SLContext.java	Tue Dec 17 20:22:45 2013 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2013, 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
@@ -24,17 +24,20 @@
 
 import java.io.*;
 
+import com.oracle.truffle.api.source.*;
 import com.oracle.truffle.sl.builtins.*;
 
 public final class SLContext {
 
     private final PrintStream printOutput;
     private final SLFunctionRegistry functionRegistry;
+    private final SourceManager sourceManager;
 
     public SLContext(PrintStream print) {
         this.printOutput = print;
         this.functionRegistry = new SLFunctionRegistry();
         DefaultBuiltins.install(this);
+        this.sourceManager = new SourceManager();
     }
 
     public PrintStream getPrintOutput() {
@@ -45,4 +48,8 @@
         return functionRegistry;
     }
 
+    public SourceManager getSourceManager() {
+        return sourceManager;
+    }
+
 }