From ea784a2bbad2e9034550df44e8c86343310cf7be Mon Sep 17 00:00:00 2001
From: stepan <stepan.sindelar@oracle.com>
Date: Wed, 4 Apr 2018 10:14:37 +0200
Subject: [PATCH] Change the AST structure so that args are copied in
 RootTagged node

* New interface RootBodyNode implemented by FunctionBodyNode
* FunctionBodyNode saves args and then executes the actual body node
* New tag FunctionBodyBlockTag that tags BlockStatements that are function bodies
* FastR debugger that used to listen to RootTags (originally BlockNodes that had FunctionDefinitionNode as parent) is now listening to FunctionBodyBlockTags
---
 .../com/oracle/truffle/r/engine/REngine.java  |  35 +++-
 .../r/engine/RRuntimeASTAccessImpl.java       |  71 ++++----
 .../r/engine/TruffleRLanguageImpl.java        |   3 +-
 .../nodes/builtin/helpers/DebugHandling.java  |  26 +--
 .../nodes/builtin/helpers/TraceHandling.java  |   6 +-
 .../truffle/r/nodes/test/SpecialCallTest.java |   4 +-
 .../truffle/r/nodes/control/BlockNode.java    |   2 +-
 .../r/nodes/function/FunctionBodyNode.java    | 155 ++++++++++++++++++
 .../function/FunctionDefinitionNode.java      | 104 +-----------
 .../truffle/r/nodes/function/RCallNode.java   |   5 +
 .../instrumentation/RInstrumentation.java     |   5 +-
 .../r/nodes/instrumentation/RSyntaxTags.java  |  19 ++-
 .../truffle/r/runtime/RootBodyNode.java       |  60 +++++++
 .../truffle/r/runtime/RootWithBody.java       |  12 +-
 .../instrumentation/RRootBodyNodeWrapper.java |  91 ++++++++++
 .../truffle/r/test/tck/FastRDebugTest.java    |   4 +-
 16 files changed, 437 insertions(+), 165 deletions(-)
 create mode 100644 com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/FunctionBodyNode.java
 create mode 100644 com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RootBodyNode.java
 create mode 100644 com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/nodes/instrumentation/RRootBodyNodeWrapper.java

diff --git a/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/REngine.java b/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/REngine.java
index 371e16066c..1fb2aa2354 100644
--- a/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/REngine.java
+++ b/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/REngine.java
@@ -43,6 +43,7 @@ import com.oracle.truffle.api.interop.java.JavaInterop;
 import com.oracle.truffle.api.nodes.DirectCallNode;
 import com.oracle.truffle.api.nodes.ExecutableNode;
 import com.oracle.truffle.api.nodes.ExplodeLoop;
+import com.oracle.truffle.api.nodes.Node;
 import com.oracle.truffle.api.nodes.RootNode;
 import com.oracle.truffle.api.profiles.ValueProfile;
 import com.oracle.truffle.api.source.Source;
@@ -78,6 +79,7 @@ import com.oracle.truffle.r.runtime.RRuntime;
 import com.oracle.truffle.r.runtime.RSource;
 import com.oracle.truffle.r.runtime.ReturnException;
 import com.oracle.truffle.r.runtime.RootWithBody;
+import com.oracle.truffle.r.runtime.RootBodyNode;
 import com.oracle.truffle.r.runtime.ThreadTimings;
 import com.oracle.truffle.r.runtime.Utils;
 import com.oracle.truffle.r.runtime.Utils.DebugExitException;
@@ -326,7 +328,7 @@ final class REngine implements Engine, Engine.Timings {
             statements[i] = list.get(i).asRNode();
         }
         return new ExecutableNode(context.getLanguage()) {
-            @Child R2Foreign toForeignNode = R2Foreign.create();
+            @Child private R2Foreign toForeignNode = R2Foreign.create();
 
             @Override
             public Object execute(VirtualFrame frame) {
@@ -529,14 +531,14 @@ final class REngine implements Engine, Engine.Timings {
         private final boolean topLevel;
         private final boolean suppressWarnings;
 
-        @Child private RNode body;
+        @Child private RootBodyNode body;
         @Child private GetVisibilityNode visibility = GetVisibilityNode.create();
         @Child private SetVisibilityNode setVisibility = SetVisibilityNode.create();
 
         protected AnonymousRootNode(REngine engine, RNode body, String description, boolean printResult, boolean topLevel) {
             super(engine.context.getLanguage());
             this.suppressWarnings = engine.suppressWarnings;
-            this.body = body;
+            this.body = new AnonymousBodyNode(body);
             this.description = description;
             this.printResult = printResult;
             this.topLevel = topLevel;
@@ -544,12 +546,12 @@ final class REngine implements Engine, Engine.Timings {
 
         @Override
         public SourceSection getSourceSection() {
-            return body.getSourceSection();
+            return getBody().getSourceSection();
         }
 
         @Override
         public boolean isInternal() {
-            return RSyntaxNode.isInternal(body.asRSyntaxNode().getLazySourceSection());
+            return RSyntaxNode.isInternal(getBody().getLazySourceSection());
         }
 
         private VirtualFrame prepareFrame(VirtualFrame frame) {
@@ -620,6 +622,29 @@ final class REngine implements Engine, Engine.Timings {
             return false;
         }
 
+        @Override
+        public RSyntaxNode getBody() {
+            return body.getBody().asRSyntaxNode();
+        }
+    }
+
+    private static final class AnonymousBodyNode extends Node implements RootBodyNode {
+        @Child private RNode body;
+
+        AnonymousBodyNode(RNode body) {
+            this.body = body;
+        }
+
+        @Override
+        public Object visibleExecute(VirtualFrame frame) {
+            return body.visibleExecute(frame);
+        }
+
+        @Override
+        public SourceSection getSourceSection() {
+            return body.getSourceSection();
+        }
+
         @Override
         public RNode getBody() {
             return body;
diff --git a/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/RRuntimeASTAccessImpl.java b/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/RRuntimeASTAccessImpl.java
index 08cbe18cd4..63c73a1f26 100644
--- a/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/RRuntimeASTAccessImpl.java
+++ b/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/RRuntimeASTAccessImpl.java
@@ -33,6 +33,9 @@ import com.oracle.truffle.api.CompilerAsserts;
 import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
 import com.oracle.truffle.api.frame.Frame;
 import com.oracle.truffle.api.frame.FrameInstance.FrameAccess;
+import com.oracle.truffle.api.instrumentation.StandardTags.CallTag;
+import com.oracle.truffle.api.instrumentation.StandardTags.RootTag;
+import com.oracle.truffle.api.instrumentation.StandardTags.StatementTag;
 import com.oracle.truffle.api.nodes.Node;
 import com.oracle.truffle.api.nodes.RootNode;
 import com.oracle.truffle.r.launcher.RCommand;
@@ -54,6 +57,8 @@ import com.oracle.truffle.r.nodes.function.ClassHierarchyNode;
 import com.oracle.truffle.r.nodes.function.FunctionDefinitionNode;
 import com.oracle.truffle.r.nodes.function.RCallNode;
 import com.oracle.truffle.r.nodes.instrumentation.RInstrumentation;
+import com.oracle.truffle.r.nodes.instrumentation.RSyntaxTags.FunctionBodyBlockTag;
+import com.oracle.truffle.r.nodes.instrumentation.RSyntaxTags.LoopTag;
 import com.oracle.truffle.r.runtime.ArgumentsSignature;
 import com.oracle.truffle.r.runtime.RArguments;
 import com.oracle.truffle.r.runtime.RCaller;
@@ -61,7 +66,7 @@ import com.oracle.truffle.r.runtime.RDeparse;
 import com.oracle.truffle.r.runtime.RError;
 import com.oracle.truffle.r.runtime.RError.ShowCallerOf;
 import com.oracle.truffle.r.runtime.RRuntimeASTAccess;
-import com.oracle.truffle.r.runtime.RootWithBody;
+import com.oracle.truffle.r.runtime.RootBodyNode;
 import com.oracle.truffle.r.runtime.Utils;
 import com.oracle.truffle.r.runtime.context.Engine;
 import com.oracle.truffle.r.runtime.context.RContext;
@@ -265,45 +270,49 @@ class RRuntimeASTAccessImpl implements RRuntimeASTAccess {
 
     @Override
     public boolean isTaggedWith(Node node, Class<?> tag) {
+        if (node instanceof RootBodyNode) {
+            return (tag == RootTag.class);
+        }
+        if (node instanceof RootNode) {
+            // roots don't have any tags
+            return false;
+        }
         if (!(node instanceof RSyntaxNode)) {
             return false;
         }
         if (isInternalChild(node)) {
             return false;
         }
-        String className = tag.getSimpleName();
-        switch (className) {
-            case "CallTag":
-                return node instanceof RCallNode;
-
-            case "StatementTag": {
-                Node parent = ((RInstrumentableNode) node).unwrapParent();
-                if (node instanceof BlockNode) {
-                    // TODO we may reconsider this
-                    return false;
-                }
-                // Most likely
-                if (parent instanceof BlockNode) {
-                    return true;
-                } else {
-                    // single statement block, variable parent
-                    // note: RepeatingNode is not a RSyntaxElement but the body of a loop is
-                    // under the repeating node !
-                    return parent instanceof FunctionDefinitionNode || parent instanceof RootWithBody || parent instanceof IfNode || AbstractLoopNode.isLoopBody(node);
-                }
+        if (tag == CallTag.class) {
+            return node instanceof RCallNode;
+        }
+        if (tag == FunctionBodyBlockTag.class) {
+            return node instanceof BlockNode && ((BlockNode) node).unwrapParent() instanceof RootBodyNode;
+        }
+        if (tag == LoopTag.class) {
+            return node instanceof AbstractLoopNode;
+        }
+        if (tag == StatementTag.class) {
+            if (node instanceof BlockNode) {
+                // so that the stepping location is not the block itself, but the first statement in
+                // the block, note that the FastR's own debugging and tracing mechanism uses
+                // FunctionBodyBlockTag to recognize function bodies.
+                return false;
             }
-
-            case "RootTag": {
-                Node parent = ((RInstrumentableNode) node).unwrapParent();
-                return parent instanceof FunctionDefinitionNode || parent instanceof RootWithBody;
+            // How to recognize statement from some node inside a statement (e.g. expression)?
+            Node parent = ((RInstrumentableNode) node).unwrapParent();
+            if (parent instanceof BlockNode) {
+                // It's in a block of statements
+                return true;
+            } else {
+                // single statement block: as function body, if/else body, loop body
+                // note: RepeatingNode is not a RSyntaxElement but the body of a loop is
+                // under the repeating node !
+                return parent instanceof RootBodyNode || parent instanceof IfNode || AbstractLoopNode.isLoopBody(node);
             }
-
-            case "LoopTag":
-                return node instanceof AbstractLoopNode;
-
-            default:
-                return false;
         }
+        // TODO: ExpressionTag: (!statement && !loop && !if && !call && !root)??
+        return false;
     }
 
     private static boolean isInternalChild(Node node) {
diff --git a/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/TruffleRLanguageImpl.java b/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/TruffleRLanguageImpl.java
index e84096fba5..494ff445ab 100644
--- a/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/TruffleRLanguageImpl.java
+++ b/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/TruffleRLanguageImpl.java
@@ -39,6 +39,7 @@ import com.oracle.truffle.r.engine.interop.RForeignAccessFactoryImpl;
 import com.oracle.truffle.r.nodes.RASTBuilder;
 import com.oracle.truffle.r.nodes.builtin.RBuiltinPackages;
 import com.oracle.truffle.r.nodes.instrumentation.RSyntaxTags;
+import com.oracle.truffle.r.nodes.instrumentation.RSyntaxTags.FunctionBodyBlockTag;
 import com.oracle.truffle.r.runtime.ExitException;
 import com.oracle.truffle.r.runtime.FastROptions;
 import com.oracle.truffle.r.runtime.RAccuracyInfo;
@@ -57,7 +58,7 @@ import com.oracle.truffle.r.runtime.ffi.RFFIFactory;
 import com.oracle.truffle.r.runtime.nodes.RBaseNode;
 
 @TruffleLanguage.Registration(name = "R", id = "R", version = "3.3.2", mimeType = {RRuntime.R_APP_MIME, RRuntime.R_TEXT_MIME}, interactive = true)
-@ProvidedTags({StandardTags.CallTag.class, StandardTags.StatementTag.class, StandardTags.RootTag.class, RSyntaxTags.LoopTag.class})
+@ProvidedTags({StandardTags.CallTag.class, StandardTags.StatementTag.class, StandardTags.RootTag.class, RSyntaxTags.LoopTag.class, FunctionBodyBlockTag.class})
 public final class TruffleRLanguageImpl extends TruffleRLanguage {
 
     private final HashMap<String, RFunction> builtinFunctionCache = new HashMap<>();
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/helpers/DebugHandling.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/helpers/DebugHandling.java
index 70bda866de..e5d0ee8f42 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/helpers/DebugHandling.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/helpers/DebugHandling.java
@@ -46,6 +46,7 @@ import com.oracle.truffle.r.nodes.control.AbstractLoopNode;
 import com.oracle.truffle.r.nodes.function.FunctionDefinitionNode;
 import com.oracle.truffle.r.nodes.instrumentation.RInstrumentation;
 import com.oracle.truffle.r.nodes.instrumentation.RSyntaxTags;
+import com.oracle.truffle.r.nodes.instrumentation.RSyntaxTags.FunctionBodyBlockTag;
 import com.oracle.truffle.r.runtime.JumpToTopLevelException;
 import com.oracle.truffle.r.runtime.RArguments;
 import com.oracle.truffle.r.runtime.RDeparse;
@@ -76,7 +77,7 @@ import com.oracle.truffle.r.runtime.nodes.RSyntaxVisitor;
  * Three different listener classes are defined:
  * <ul>
  * <li>{@link FunctionStatementsEventListener}: attaches to function bodies and handles the special
- * behavior on entry/exit</li>
+ * behavior on entry/exit</li>. Function body is distinguished with tag {@link FunctionBodyBlockTag}
  * <li>{@link StatementEventListener}: attaches to all {@code StandardTags.StatementTag} nodes and
  * handles "n" and "s" browser commands</li>
  * <li>{@link LoopStatementEventListener}: attaches to {@link AbstractLoopNode} instances and
@@ -89,7 +90,7 @@ import com.oracle.truffle.r.runtime.nodes.RSyntaxVisitor;
  * mode and reset it's state on return. This is handled as follows:
  * <ol>
  * <li>On a step-into, attach a {@link StepIntoInstrumentListener} with a filter that matches all
- * functions and the {@code StandardTags.RootTag} tag</li>
+ * functions and their bodies like {@link FunctionStatementsEventListener}.</li>
  * <li>On entry to that listener instrument/enable the function we have entered (if necessary) for
  * one-time (unless already)</li>
  * <li>Dispose the {@link StepIntoInstrumentListener} and continue, which will then stop at the
@@ -219,7 +220,7 @@ public class DebugHandling {
         }
 
         @CompilationFinal private boolean disabled;
-        CyclicAssumption disabledUnchangedAssumption = new CyclicAssumption("debug event disabled state unchanged");
+        private final CyclicAssumption disabledUnchangedAssumption = new CyclicAssumption("debug event disabled state unchanged");
 
         boolean disabled() {
             return disabled || RContext.getInstance().stateInstrumentation.debugGloballyDisabled();
@@ -317,7 +318,7 @@ public class DebugHandling {
         @TruffleBoundary
         private void attachStepInto() {
             FunctionStatementsEventListener parentListener = getFunctionStatementsEventListener(functionDefinitionNode);
-            parentListener.stepIntoInstrument = RInstrumentation.getInstrumenter().attachExecutionEventListener(SourceSectionFilter.newBuilder().tagIs(StandardTags.RootTag.class).build(),
+            parentListener.stepIntoInstrument = RInstrumentation.getInstrumenter().attachExecutionEventListener(SourceSectionFilter.newBuilder().tagIs(FunctionBodyBlockTag.class).build(),
                             new StepIntoInstrumentListener(parentListener));
 
         }
@@ -430,8 +431,10 @@ public class DebugHandling {
 
         public void attach() {
 
+            // Note: BlockStatement is not tagged as a STATEMENT, but there is FastR specific
+            // FunctionBodyBlockTag
             Instrumenter instrumenter = RInstrumentation.getInstrumenter();
-            SourceSectionFilter.Builder functionBuilder = RInstrumentation.createFunctionFilter(functionDefinitionNode, StandardTags.RootTag.class);
+            SourceSectionFilter.Builder functionBuilder = RInstrumentation.createFunctionFilter(functionDefinitionNode, FunctionBodyBlockTag.class);
             setBinding(instrumenter.attachExecutionEventListener(functionBuilder.build(), this));
 
             // Next attach statement handler to all STATEMENTs except LOOPs
@@ -470,7 +473,7 @@ public class DebugHandling {
                     accept(element.getSyntaxBody());
                     return null;
                 }
-            }.accept(functionDefinitionNode);
+            }.accept(functionDefinitionNode.getBody());
         }
 
         @Override
@@ -524,8 +527,9 @@ public class DebugHandling {
             CompilerDirectives.transferToInterpreter();
             print("debugging in: ", false);
             printCall(frame);
-            printNode(context.getInstrumentedNode(), true);
-            browserInteract(context.getInstrumentedNode(), frame);
+            Node node = context.getInstrumentedNode();
+            printNode(node, true);
+            browserInteract(node, frame);
         }
 
         @Override
@@ -582,6 +586,7 @@ public class DebugHandling {
             String callString = RContext.getRRuntimeASTAccess().getCallerSource(RArguments.getCall(frame));
             print(callString, true);
         }
+
     }
 
     @TruffleBoundary
@@ -621,10 +626,7 @@ public class DebugHandling {
                 // in case we did a step into that never called a function
                 clearStepInstrument();
                 RBaseNode node = (RBaseNode) context.getInstrumentedNode();
-                if (node.hasTag(StandardTags.RootTag.class)) {
-                    // already handled
-                    return;
-                }
+                assert !node.hasTag(StandardTags.RootTag.class) : "root is not a statement";
                 printNode(node, false);
                 browserInteract(node, frame);
             }
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/helpers/TraceHandling.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/helpers/TraceHandling.java
index 72c9913923..142a26bbdd 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/helpers/TraceHandling.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/helpers/TraceHandling.java
@@ -33,9 +33,9 @@ import com.oracle.truffle.api.instrumentation.EventBinding;
 import com.oracle.truffle.api.instrumentation.EventContext;
 import com.oracle.truffle.api.instrumentation.ExecutionEventListener;
 import com.oracle.truffle.api.instrumentation.SourceSectionFilter;
-import com.oracle.truffle.api.instrumentation.StandardTags;
 import com.oracle.truffle.api.utilities.CyclicAssumption;
 import com.oracle.truffle.r.nodes.instrumentation.RInstrumentation;
+import com.oracle.truffle.r.nodes.instrumentation.RSyntaxTags.FunctionBodyBlockTag;
 import com.oracle.truffle.r.runtime.FastROptions;
 import com.oracle.truffle.r.runtime.RArguments;
 import com.oracle.truffle.r.runtime.RCaller;
@@ -88,7 +88,7 @@ public class TraceHandling {
         if (FastROptions.TraceCalls.getBooleanValue()) {
             PrimitiveFunctionEntryEventListener fser = new PrimitiveFunctionEntryEventListener();
             SourceSectionFilter.Builder builder = SourceSectionFilter.newBuilder();
-            builder.tagIs(StandardTags.RootTag.class);
+            builder.tagIs(FunctionBodyBlockTag.class);
             SourceSectionFilter filter = builder.build();
             RInstrumentation.getInstrumenter().attachExecutionEventListener(filter, fser);
             setOutputHandler();
@@ -122,7 +122,7 @@ public class TraceHandling {
     private abstract static class TraceEventListener implements ExecutionEventListener {
 
         @CompilationFinal private boolean disabled;
-        CyclicAssumption disabledUnchangedAssumption = new CyclicAssumption("trace event disabled state unchanged");
+        private final CyclicAssumption disabledUnchangedAssumption = new CyclicAssumption("trace event disabled state unchanged");
 
         protected TraceEventListener() {
         }
diff --git a/com.oracle.truffle.r.nodes.test/src/com/oracle/truffle/r/nodes/test/SpecialCallTest.java b/com.oracle.truffle.r.nodes.test/src/com/oracle/truffle/r/nodes/test/SpecialCallTest.java
index 243a5e9e83..89a959f4b3 100644
--- a/com.oracle.truffle.r.nodes.test/src/com/oracle/truffle/r/nodes/test/SpecialCallTest.java
+++ b/com.oracle.truffle.r.nodes.test/src/com/oracle/truffle/r/nodes/test/SpecialCallTest.java
@@ -52,7 +52,7 @@ public class SpecialCallTest extends TestBase {
         public int special;
 
         CountCallsVisitor(RootCallTarget callTarget) {
-            accept(((RootWithBody) callTarget.getRootNode()).getBody().asRSyntaxNode());
+            accept(((RootWithBody) callTarget.getRootNode()).getBody());
         }
 
         @Override
@@ -108,7 +108,7 @@ public class SpecialCallTest extends TestBase {
 
         void print(RootCallTarget callTarget) {
             System.out.println();
-            accept(((RootWithBody) callTarget.getRootNode()).getBody().asRSyntaxNode());
+            accept(((RootWithBody) callTarget.getRootNode()).getBody());
         }
 
         @Override
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/control/BlockNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/control/BlockNode.java
index 184b48c6d5..bce05cd02c 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/control/BlockNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/control/BlockNode.java
@@ -39,7 +39,7 @@ import com.oracle.truffle.r.runtime.nodes.RSyntaxNode;
  */
 public final class BlockNode extends OperatorNode {
 
-    @Children protected final RNode[] sequence;
+    @Children private final RNode[] sequence;
     @Child private SetVisibilityNode visibility;
 
     public BlockNode(SourceSection src, RSyntaxLookup operator, RNode[] sequence) {
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/FunctionBodyNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/FunctionBodyNode.java
new file mode 100644
index 0000000000..949b918b94
--- /dev/null
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/FunctionBodyNode.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2018, 2018, 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.
+ *
+ * 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.r.nodes.function;
+
+import com.oracle.truffle.api.CompilerDirectives;
+import com.oracle.truffle.api.frame.FrameDescriptor;
+import com.oracle.truffle.api.frame.FrameSlot;
+import com.oracle.truffle.api.frame.FrameSlotKind;
+import com.oracle.truffle.api.frame.VirtualFrame;
+import com.oracle.truffle.api.nodes.Node;
+import com.oracle.truffle.api.profiles.BranchProfile;
+import com.oracle.truffle.api.source.SourceSection;
+import com.oracle.truffle.r.runtime.RArguments;
+import com.oracle.truffle.r.runtime.RArguments.DispatchArgs;
+import com.oracle.truffle.r.runtime.RArguments.S3Args;
+import com.oracle.truffle.r.runtime.RArguments.S4Args;
+import com.oracle.truffle.r.runtime.RRuntime;
+import com.oracle.truffle.r.runtime.RootBodyNode;
+import com.oracle.truffle.r.runtime.env.frame.FrameSlotChangeMonitor;
+import com.oracle.truffle.r.runtime.nodes.RNode;
+
+public final class FunctionBodyNode extends Node implements RootBodyNode {
+
+    @Child private RNode body;
+    @Child private RNode saveArguments;
+    @Child private SetupS3ArgsNode setupS3Args;
+    @Child private SetupS4ArgsNode setupS4Args;
+
+    public FunctionBodyNode(RNode saveArguments, RNode body) {
+        this.body = body;
+        this.saveArguments = saveArguments;
+    }
+
+    @Override
+    public Object visibleExecute(VirtualFrame frame) {
+        setupDispatchSlots(frame);
+        saveArguments.execute(frame);
+        return body.visibleExecute(frame);
+    }
+
+    @Override
+    public RNode getBody() {
+        return body;
+    }
+
+    @Override
+    public SourceSection getSourceSection() {
+        return body.getSourceSection();
+    }
+
+    private void setupDispatchSlots(VirtualFrame frame) {
+        DispatchArgs dispatchArgs = RArguments.getDispatchArgs(frame);
+        if (dispatchArgs == null) {
+            return;
+        }
+        if (dispatchArgs instanceof S3Args) {
+            S3Args s3Args = (S3Args) dispatchArgs;
+            if (setupS3Args == null) {
+                CompilerDirectives.transferToInterpreterAndInvalidate();
+                setupS3Args = insert(new SetupS3ArgsNode(frame.getFrameDescriptor()));
+            }
+            setupS3Args.execute(frame, s3Args);
+        } else {
+            S4Args s4Args = (S4Args) dispatchArgs;
+            if (setupS4Args == null) {
+                CompilerDirectives.transferToInterpreterAndInvalidate();
+                setupS4Args = insert(new SetupS4ArgsNode(frame.getFrameDescriptor()));
+            }
+            setupS4Args.execute(frame, s4Args);
+        }
+    }
+
+    private abstract static class SetupDispatchNode extends Node {
+        // S3/S4 slots
+        private final FrameSlot dotGenericSlot;
+        private final FrameSlot dotMethodSlot;
+
+        final BranchProfile invalidateFrameSlotProfile = BranchProfile.create();
+
+        SetupDispatchNode(FrameDescriptor frameDescriptor) {
+            dotGenericSlot = FrameSlotChangeMonitor.findOrAddFrameSlot(frameDescriptor, RRuntime.R_DOT_GENERIC, FrameSlotKind.Object);
+            dotMethodSlot = FrameSlotChangeMonitor.findOrAddFrameSlot(frameDescriptor, RRuntime.R_DOT_METHOD, FrameSlotKind.Object);
+        }
+
+        void executeDispatchArgs(VirtualFrame frame, DispatchArgs args) {
+            FrameSlotChangeMonitor.setObjectAndInvalidate(frame, dotGenericSlot, args.generic, false, invalidateFrameSlotProfile);
+            FrameSlotChangeMonitor.setObjectAndInvalidate(frame, dotMethodSlot, args.method, false, invalidateFrameSlotProfile);
+        }
+    }
+
+    private static final class SetupS3ArgsNode extends SetupDispatchNode {
+        // S3 slots
+        private final FrameSlot dotClassSlot;
+        private final FrameSlot dotGenericCallEnvSlot;
+        private final FrameSlot dotGenericCallDefSlot;
+        private final FrameSlot dotGroupSlot;
+
+        SetupS3ArgsNode(FrameDescriptor frameDescriptor) {
+            super(frameDescriptor);
+            dotClassSlot = FrameSlotChangeMonitor.findOrAddFrameSlot(frameDescriptor, RRuntime.R_DOT_CLASS, FrameSlotKind.Object);
+            dotGenericCallEnvSlot = FrameSlotChangeMonitor.findOrAddFrameSlot(frameDescriptor, RRuntime.R_DOT_GENERIC_CALL_ENV, FrameSlotKind.Object);
+            dotGenericCallDefSlot = FrameSlotChangeMonitor.findOrAddFrameSlot(frameDescriptor, RRuntime.R_DOT_GENERIC_DEF_ENV, FrameSlotKind.Object);
+            dotGroupSlot = FrameSlotChangeMonitor.findOrAddFrameSlot(frameDescriptor, RRuntime.R_DOT_GROUP, FrameSlotKind.Object);
+        }
+
+        void execute(VirtualFrame frame, S3Args args) {
+            super.executeDispatchArgs(frame, args);
+            FrameSlotChangeMonitor.setObjectAndInvalidate(frame, dotClassSlot, args.clazz, false, invalidateFrameSlotProfile);
+            FrameSlotChangeMonitor.setObjectAndInvalidate(frame, dotGenericCallEnvSlot, args.callEnv, false, invalidateFrameSlotProfile);
+            FrameSlotChangeMonitor.setObjectAndInvalidate(frame, dotGenericCallDefSlot, args.defEnv, false, invalidateFrameSlotProfile);
+            FrameSlotChangeMonitor.setObjectAndInvalidate(frame, dotGroupSlot, args.group, false, invalidateFrameSlotProfile);
+        }
+    }
+
+    private static final class SetupS4ArgsNode extends SetupDispatchNode {
+        // S4 slots
+        private final FrameSlot dotDefinedSlot;
+        private final FrameSlot dotTargetSlot;
+        private final FrameSlot dotMethodsSlot;
+
+        SetupS4ArgsNode(FrameDescriptor frameDescriptor) {
+            super(frameDescriptor);
+            dotDefinedSlot = FrameSlotChangeMonitor.findOrAddFrameSlot(frameDescriptor, RRuntime.R_DOT_DEFINED, FrameSlotKind.Object);
+            dotTargetSlot = FrameSlotChangeMonitor.findOrAddFrameSlot(frameDescriptor, RRuntime.R_DOT_TARGET, FrameSlotKind.Object);
+            dotMethodsSlot = FrameSlotChangeMonitor.findOrAddFrameSlot(frameDescriptor, RRuntime.R_DOT_METHODS, FrameSlotKind.Object);
+        }
+
+        void execute(VirtualFrame frame, S4Args args) {
+            super.executeDispatchArgs(frame, args);
+            FrameSlotChangeMonitor.setObjectAndInvalidate(frame, dotDefinedSlot, args.defined, false, invalidateFrameSlotProfile);
+            FrameSlotChangeMonitor.setObjectAndInvalidate(frame, dotTargetSlot, args.target, false, invalidateFrameSlotProfile);
+            FrameSlotChangeMonitor.setObjectAndInvalidate(frame, dotMethodsSlot, args.methods, false, invalidateFrameSlotProfile);
+        }
+    }
+}
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/FunctionDefinitionNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/FunctionDefinitionNode.java
index fbb1754702..34712bf494 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/FunctionDefinitionNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/FunctionDefinitionNode.java
@@ -35,7 +35,6 @@ import com.oracle.truffle.api.frame.FrameSlot;
 import com.oracle.truffle.api.frame.FrameSlotKind;
 import com.oracle.truffle.api.frame.FrameSlotTypeException;
 import com.oracle.truffle.api.frame.VirtualFrame;
-import com.oracle.truffle.api.nodes.Node;
 import com.oracle.truffle.api.nodes.NodeUtil;
 import com.oracle.truffle.api.nodes.NodeUtil.NodeCountFilter;
 import com.oracle.truffle.api.profiles.BranchProfile;
@@ -55,16 +54,13 @@ import com.oracle.truffle.r.runtime.ArgumentsSignature;
 import com.oracle.truffle.r.runtime.ExitException;
 import com.oracle.truffle.r.runtime.JumpToTopLevelException;
 import com.oracle.truffle.r.runtime.RArguments;
-import com.oracle.truffle.r.runtime.RArguments.DispatchArgs;
-import com.oracle.truffle.r.runtime.RArguments.S3Args;
-import com.oracle.truffle.r.runtime.RArguments.S4Args;
 import com.oracle.truffle.r.runtime.RDeparse;
 import com.oracle.truffle.r.runtime.RError;
 import com.oracle.truffle.r.runtime.RErrorHandling;
 import com.oracle.truffle.r.runtime.RInternalError;
-import com.oracle.truffle.r.runtime.RRuntime;
 import com.oracle.truffle.r.runtime.RType;
 import com.oracle.truffle.r.runtime.ReturnException;
+import com.oracle.truffle.r.runtime.RootBodyNode;
 import com.oracle.truffle.r.runtime.Utils.DebugExitException;
 import com.oracle.truffle.r.runtime.builtins.RBuiltinDescriptor;
 import com.oracle.truffle.r.runtime.context.RContext;
@@ -89,7 +85,7 @@ public final class FunctionDefinitionNode extends RRootNode implements RSyntaxNo
 
     private final FormalArguments formalArguments;
 
-    @Child private RNode body;
+    @Child private RootBodyNode body;
 
     /**
      * This exists for debugging purposes. It is set initially when the function is defined to
@@ -108,7 +104,6 @@ public final class FunctionDefinitionNode extends RRootNode implements RSyntaxNo
 
     private final RNodeClosureCache closureCache = new RNodeClosureCache();
 
-    @Child private RNode saveArguments;
     @Child private FrameSlotNode onExitSlot;
     @Child private InlineCacheNode onExitExpressionCache;
     private final ConditionProfile onExitProfile = ConditionProfile.createBinaryProfile();
@@ -121,9 +116,6 @@ public final class FunctionDefinitionNode extends RRootNode implements RSyntaxNo
 
     @Child private PostProcessArgumentsNode argPostProcess;
 
-    @Child private SetupS3ArgsNode setupS3Args;
-    @Child private SetupS4ArgsNode setupS4Args;
-
     private final boolean needsSplitting;
 
     @CompilationFinal private boolean containsDispatch;
@@ -153,8 +145,7 @@ public final class FunctionDefinitionNode extends RRootNode implements RSyntaxNo
         assert FrameSlotChangeMonitor.isValidFrameDescriptor(frameDesc);
         assert src != null;
         this.sourceSectionR = src;
-        this.saveArguments = saveArguments;
-        this.body = body.asRNode();
+        this.body = new FunctionBodyNode(saveArguments, body.asRNode());
         this.name = name;
         this.onExitSlot = FrameSlotNode.createInitialized(frameDesc, RFrameSlot.OnExit, false);
         this.needsSplitting = needsAnyBuiltinSplitting();
@@ -269,7 +260,7 @@ public final class FunctionDefinitionNode extends RRootNode implements RSyntaxNo
     }
 
     public RSyntaxNode getBody() {
-        return body.asRSyntaxNode();
+        return body.getBody().asRSyntaxNode();
     }
 
     public PostProcessArgumentsNode getArgPostProcess() {
@@ -281,8 +272,6 @@ public final class FunctionDefinitionNode extends RRootNode implements RSyntaxNo
         boolean runOnExitHandlers = true;
         try {
             verifyEnclosingAssumptions(frame);
-            setupDispatchSlots(frame);
-            saveArguments.execute(frame);
             Object result = body.visibleExecute(frame);
             normalExit.enter();
             if (CompilerDirectives.inInterpreter() && result == null) {
@@ -398,91 +387,6 @@ public final class FunctionDefinitionNode extends RRootNode implements RSyntaxNo
         }
     }
 
-    private abstract static class SetupDispatchNode extends Node {
-        // S3/S4 slots
-        private final FrameSlot dotGenericSlot;
-        private final FrameSlot dotMethodSlot;
-
-        final BranchProfile invalidateFrameSlotProfile = BranchProfile.create();
-
-        SetupDispatchNode(FrameDescriptor frameDescriptor) {
-            dotGenericSlot = FrameSlotChangeMonitor.findOrAddFrameSlot(frameDescriptor, RRuntime.R_DOT_GENERIC, FrameSlotKind.Object);
-            dotMethodSlot = FrameSlotChangeMonitor.findOrAddFrameSlot(frameDescriptor, RRuntime.R_DOT_METHOD, FrameSlotKind.Object);
-        }
-
-        void execute(VirtualFrame frame, DispatchArgs args) {
-            FrameSlotChangeMonitor.setObjectAndInvalidate(frame, dotGenericSlot, args.generic, false, invalidateFrameSlotProfile);
-            FrameSlotChangeMonitor.setObjectAndInvalidate(frame, dotMethodSlot, args.method, false, invalidateFrameSlotProfile);
-        }
-    }
-
-    private static final class SetupS3ArgsNode extends SetupDispatchNode {
-        // S3 slots
-        private final FrameSlot dotClassSlot;
-        private final FrameSlot dotGenericCallEnvSlot;
-        private final FrameSlot dotGenericCallDefSlot;
-        private final FrameSlot dotGroupSlot;
-
-        SetupS3ArgsNode(FrameDescriptor frameDescriptor) {
-            super(frameDescriptor);
-            dotClassSlot = FrameSlotChangeMonitor.findOrAddFrameSlot(frameDescriptor, RRuntime.R_DOT_CLASS, FrameSlotKind.Object);
-            dotGenericCallEnvSlot = FrameSlotChangeMonitor.findOrAddFrameSlot(frameDescriptor, RRuntime.R_DOT_GENERIC_CALL_ENV, FrameSlotKind.Object);
-            dotGenericCallDefSlot = FrameSlotChangeMonitor.findOrAddFrameSlot(frameDescriptor, RRuntime.R_DOT_GENERIC_DEF_ENV, FrameSlotKind.Object);
-            dotGroupSlot = FrameSlotChangeMonitor.findOrAddFrameSlot(frameDescriptor, RRuntime.R_DOT_GROUP, FrameSlotKind.Object);
-        }
-
-        void execute(VirtualFrame frame, S3Args args) {
-            super.execute(frame, args);
-            FrameSlotChangeMonitor.setObjectAndInvalidate(frame, dotClassSlot, args.clazz, false, invalidateFrameSlotProfile);
-            FrameSlotChangeMonitor.setObjectAndInvalidate(frame, dotGenericCallEnvSlot, args.callEnv, false, invalidateFrameSlotProfile);
-            FrameSlotChangeMonitor.setObjectAndInvalidate(frame, dotGenericCallDefSlot, args.defEnv, false, invalidateFrameSlotProfile);
-            FrameSlotChangeMonitor.setObjectAndInvalidate(frame, dotGroupSlot, args.group, false, invalidateFrameSlotProfile);
-        }
-    }
-
-    private static final class SetupS4ArgsNode extends SetupDispatchNode {
-        // S4 slots
-        private final FrameSlot dotDefinedSlot;
-        private final FrameSlot dotTargetSlot;
-        private final FrameSlot dotMethodsSlot;
-
-        SetupS4ArgsNode(FrameDescriptor frameDescriptor) {
-            super(frameDescriptor);
-            dotDefinedSlot = FrameSlotChangeMonitor.findOrAddFrameSlot(frameDescriptor, RRuntime.R_DOT_DEFINED, FrameSlotKind.Object);
-            dotTargetSlot = FrameSlotChangeMonitor.findOrAddFrameSlot(frameDescriptor, RRuntime.R_DOT_TARGET, FrameSlotKind.Object);
-            dotMethodsSlot = FrameSlotChangeMonitor.findOrAddFrameSlot(frameDescriptor, RRuntime.R_DOT_METHODS, FrameSlotKind.Object);
-        }
-
-        void execute(VirtualFrame frame, S4Args args) {
-            super.execute(frame, args);
-            FrameSlotChangeMonitor.setObjectAndInvalidate(frame, dotDefinedSlot, args.defined, false, invalidateFrameSlotProfile);
-            FrameSlotChangeMonitor.setObjectAndInvalidate(frame, dotTargetSlot, args.target, false, invalidateFrameSlotProfile);
-            FrameSlotChangeMonitor.setObjectAndInvalidate(frame, dotMethodsSlot, args.methods, false, invalidateFrameSlotProfile);
-        }
-    }
-
-    private void setupDispatchSlots(VirtualFrame frame) {
-        DispatchArgs dispatchArgs = RArguments.getDispatchArgs(frame);
-        if (dispatchArgs == null) {
-            return;
-        }
-        if (dispatchArgs instanceof S3Args) {
-            S3Args s3Args = (S3Args) dispatchArgs;
-            if (setupS3Args == null) {
-                CompilerDirectives.transferToInterpreterAndInvalidate();
-                setupS3Args = insert(new SetupS3ArgsNode(frame.getFrameDescriptor()));
-            }
-            setupS3Args.execute(frame, s3Args);
-        } else {
-            S4Args s4Args = (S4Args) dispatchArgs;
-            if (setupS4Args == null) {
-                CompilerDirectives.transferToInterpreterAndInvalidate();
-                setupS4Args = insert(new SetupS4ArgsNode(frame.getFrameDescriptor()));
-            }
-            setupS4Args.execute(frame, s4Args);
-        }
-    }
-
     private static RPairList getCurrentOnExitList(VirtualFrame frame, FrameSlot slot) {
         try {
             return (RPairList) FrameSlotChangeMonitor.getObject(slot, frame);
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/RCallNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/RCallNode.java
index 6f987abcbb..1474a417c3 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/RCallNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/RCallNode.java
@@ -787,6 +787,11 @@ public abstract class RCallNode extends RCallBaseNode implements RSyntaxNode, RS
             return arg;
         }
 
+        @Override
+        public SourceSection getSourceSection() {
+            return arg.getSourceSection();
+        }
+
         @Override
         public Object execute(VirtualFrame frame) {
             try {
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/instrumentation/RInstrumentation.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/instrumentation/RInstrumentation.java
index 56581f056d..e83d262ee7 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/instrumentation/RInstrumentation.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/instrumentation/RInstrumentation.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2018, 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
@@ -30,6 +30,7 @@ import com.oracle.truffle.api.instrumentation.StandardTags;
 import com.oracle.truffle.api.source.Source;
 import com.oracle.truffle.api.source.SourceSection;
 import com.oracle.truffle.r.nodes.function.FunctionDefinitionNode;
+import com.oracle.truffle.r.nodes.instrumentation.RSyntaxTags.FunctionBodyBlockTag;
 import com.oracle.truffle.r.runtime.FastROptions;
 import com.oracle.truffle.r.runtime.RSource;
 import com.oracle.truffle.r.runtime.context.RContext;
@@ -102,7 +103,7 @@ public class RInstrumentation {
     public static SourceSectionFilter.Builder createFunctionStartFilter(RFunction func) {
         FunctionDefinitionNode fdn = (FunctionDefinitionNode) func.getRootNode();
         SourceSectionFilter.Builder builder = SourceSectionFilter.newBuilder();
-        builder.tagIs(StandardTags.RootTag.class);
+        builder.tagIs(FunctionBodyBlockTag.class);
         builder.sourceSectionEquals(fdn.getBody().getSourceSection());
         return builder;
     }
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/instrumentation/RSyntaxTags.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/instrumentation/RSyntaxTags.java
index 75e32fd080..becb1090f3 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/instrumentation/RSyntaxTags.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/instrumentation/RSyntaxTags.java
@@ -24,6 +24,7 @@ package com.oracle.truffle.r.nodes.instrumentation;
 
 import com.oracle.truffle.api.instrumentation.StandardTags;
 import com.oracle.truffle.api.instrumentation.Tag;
+import com.oracle.truffle.r.runtime.RootBodyNode;
 
 public class RSyntaxTags {
 
@@ -33,6 +34,22 @@ public class RSyntaxTags {
         }
     }
 
+    /**
+     * Marks a block of statements that is the body of a function, the difference to
+     * {@link com.oracle.truffle.api.instrumentation.StandardTags.RootTag} is that the
+     * {@code RootTag} is supposed to save arguments and then invoke the actual body tagged with
+     * this tag.
+     *
+     * More technically, this tag tags {@link com.oracle.truffle.r.nodes.control.BlockNode}s that
+     * have parent of type {@link RootBodyNode}.
+     */
+    @Tag.Identifier("FUNCTIONBODYBLOCK")
+    public static final class FunctionBodyBlockTag extends Tag {
+        private FunctionBodyBlockTag() {
+            // no instances
+        }
+    }
+
     @SuppressWarnings("unchecked") public static final Class<? extends Tag>[] ALL_TAGS = (Class<? extends Tag>[]) new Class<?>[]{StandardTags.CallTag.class, StandardTags.StatementTag.class,
-                    StandardTags.RootTag.class, LoopTag.class};
+                    StandardTags.RootTag.class, LoopTag.class, FunctionBodyBlockTag.class};
 }
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RootBodyNode.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RootBodyNode.java
new file mode 100644
index 0000000000..68031a62f3
--- /dev/null
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RootBodyNode.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2018, 2018, 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.
+ *
+ * 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.r.runtime;
+
+import com.oracle.truffle.api.frame.VirtualFrame;
+import com.oracle.truffle.api.instrumentation.ProbeNode;
+import com.oracle.truffle.api.instrumentation.StandardTags.RootTag;
+import com.oracle.truffle.api.instrumentation.Tag;
+import com.oracle.truffle.r.runtime.nodes.RInstrumentableNode;
+import com.oracle.truffle.r.runtime.nodes.RNode;
+import com.oracle.truffle.r.runtime.nodes.instrumentation.RRootBodyNodeWrapper;
+
+/**
+ * Marks a node that represents the body of a {@link com.oracle.truffle.api.nodes.RootNode}, such
+ * nodes are tagged with {@link com.oracle.truffle.api.instrumentation.StandardTags.RootTag} and
+ * should inherit {@link RNode}.
+ *
+ * The {@link RootBodyNode} task is to save the arguments from frame's arguments array to the local
+ * variables and then invoke the actual body statement accessible via {@link #getBody()}.
+ */
+public interface RootBodyNode extends RInstrumentableNode {
+    RNode getBody();
+
+    Object visibleExecute(VirtualFrame frame);
+
+    @Override
+    default boolean isInstrumentable() {
+        return true;
+    }
+
+    @Override
+    default boolean hasTag(Class<? extends Tag> tag) {
+        return tag == RootTag.class;
+    }
+
+    @Override
+    default WrapperNode createWrapper(ProbeNode probe) {
+        return new RRootBodyNodeWrapper(this, probe);
+    }
+}
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RootWithBody.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RootWithBody.java
index b2736e10e5..2af8f9a54a 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RootWithBody.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RootWithBody.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, 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,12 +22,16 @@
  */
 package com.oracle.truffle.r.runtime;
 
-import com.oracle.truffle.r.runtime.nodes.RNode;
+import com.oracle.truffle.r.runtime.nodes.RSyntaxNode;
 
 /**
- * Used for testing.
+ * Used to allow access to the body field for testing purposes.
  */
 public interface RootWithBody {
 
-    RNode getBody();
+    /**
+     * Should return the real body, i.e. what is wrapped by {@link RootBodyNode}.
+     */
+    RSyntaxNode getBody();
+
 }
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/nodes/instrumentation/RRootBodyNodeWrapper.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/nodes/instrumentation/RRootBodyNodeWrapper.java
new file mode 100644
index 0000000000..37ca385062
--- /dev/null
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/nodes/instrumentation/RRootBodyNodeWrapper.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2018, 2018, 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.
+ *
+ * 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.r.runtime.nodes.instrumentation;
+
+import com.oracle.truffle.api.frame.VirtualFrame;
+import com.oracle.truffle.api.instrumentation.InstrumentableNode;
+import com.oracle.truffle.api.instrumentation.ProbeNode;
+import com.oracle.truffle.api.nodes.Node;
+import com.oracle.truffle.api.nodes.NodeCost;
+import com.oracle.truffle.api.nodes.NodeInfo;
+import com.oracle.truffle.api.source.SourceSection;
+import com.oracle.truffle.r.runtime.RootBodyNode;
+import com.oracle.truffle.r.runtime.nodes.RNode;
+
+@NodeInfo(cost = NodeCost.NONE)
+public final class RRootBodyNodeWrapper extends Node implements RootBodyNode, InstrumentableNode.WrapperNode {
+    @Child private RootBodyNode delegate;
+    @Child private ProbeNode probeNode;
+
+    public RRootBodyNodeWrapper(RootBodyNode delegate, ProbeNode probeNode) {
+        assert delegate != null;
+        assert !(delegate instanceof RRootBodyNodeWrapper);
+        this.delegate = delegate;
+        this.probeNode = probeNode;
+    }
+
+    @Override
+    public Node getDelegateNode() {
+        return (Node) delegate;
+    }
+
+    @Override
+    public ProbeNode getProbeNode() {
+        return probeNode;
+    }
+
+    @Override
+    public Object visibleExecute(VirtualFrame frame) {
+        Object returnValue;
+        for (;;) {
+            boolean wasOnReturnExecuted = false;
+            try {
+                probeNode.onEnter(frame);
+                returnValue = delegate.visibleExecute(frame);
+                wasOnReturnExecuted = true;
+                probeNode.onReturnValue(frame, returnValue);
+                break;
+            } catch (Throwable t) {
+                Object result = probeNode.onReturnExceptionalOrUnwind(frame, t, wasOnReturnExecuted);
+                if (result == ProbeNode.UNWIND_ACTION_REENTER) {
+                    continue;
+                } else if (result != null) {
+                    returnValue = result;
+                    break;
+                }
+                throw t;
+            }
+        }
+        return returnValue;
+    }
+
+    @Override
+    public RNode getBody() {
+        return delegate.getBody();
+    }
+
+    @Override
+    public SourceSection getSourceSection() {
+        return ((Node) delegate).getSourceSection();
+    }
+}
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/tck/FastRDebugTest.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/tck/FastRDebugTest.java
index 00e515d31f..a18d440f52 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/tck/FastRDebugTest.java
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/tck/FastRDebugTest.java
@@ -39,7 +39,6 @@ import java.util.concurrent.atomic.AtomicInteger;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 
 import com.oracle.truffle.api.debug.Breakpoint;
@@ -498,7 +497,6 @@ public class FastRDebugTest {
     }
 
     @Test
-    @Ignore
     public void testReenterArgumentsAndValues() throws Throwable {
         // Test that after a re-enter, arguments are kept and variables are cleared.
         final Source source = sourceFromText("" +
@@ -719,7 +717,7 @@ public class FastRDebugTest {
             }
             assertNotNull("identifier \"" + expectedIdentifier + "\" not found", value);
             String valueStr = value.as(String.class);
-            assertEquals(expectedValueStr, valueStr);
+            assertEquals(line + ": " + code + "; identifier: '" + expectedIdentifier + "'", expectedValueStr, valueStr);
         }
 
         if (!run.isEmpty()) {
-- 
GitLab