diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/FrameFunctions.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/FrameFunctions.java
index 30b8da14968b3e46595c9622bbcfeb677d395ebc..7baf39bb7d472663a413ba1c8264c101bf0df70c 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/FrameFunctions.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/FrameFunctions.java
@@ -55,6 +55,7 @@ import com.oracle.truffle.r.nodes.function.PromiseNode.VarArgNode;
 import com.oracle.truffle.r.nodes.function.PromiseNode.VarArgsPromiseNode;
 import com.oracle.truffle.r.nodes.function.RCallNode;
 import com.oracle.truffle.r.runtime.ArgumentsSignature;
+import com.oracle.truffle.r.runtime.CallerFrameClosure;
 import com.oracle.truffle.r.runtime.HasSignature;
 import com.oracle.truffle.r.runtime.RArguments;
 import com.oracle.truffle.r.runtime.RCaller;
@@ -164,7 +165,6 @@ public class FrameFunctions {
                             return current;
                         }
                     }
-                    notifyRCallNodes(actualFrame, RArguments.getCall(frame));
                 }
                 return Utils.getStackFrame(access, actualFrame);
             }
@@ -172,21 +172,13 @@ public class FrameFunctions {
 
         private static Frame getCallerFrame(Frame current) {
             Object callerFrame = RArguments.getCallerFrame(current);
-            return callerFrame instanceof Frame ? (Frame) callerFrame : null;
-        }
-
-        @TruffleBoundary
-        private static void notifyRCallNodes(int actualFrame, RCaller caller) {
-            RCaller currentCaller = caller;
-            for (int i = 0; i < ITERATE_LEVELS; i++) {
-                if (currentCaller == null || currentCaller.getDepth() <= actualFrame) {
-                    break;
-                }
-                if (currentCaller.isValidCaller() && !currentCaller.isPromise() && currentCaller.getSyntaxNode() instanceof RCallNode) {
-                    ((RCallNode) currentCaller.getSyntaxNode()).setNeedsCallerFrame();
-                }
-                currentCaller = currentCaller.getParent();
+            if (callerFrame instanceof CallerFrameClosure) {
+                CallerFrameClosure closure = (CallerFrameClosure) callerFrame;
+                closure.setNeedsCallerFrame();
+                return closure.getMaterializedCallerFrame();
             }
+            assert callerFrame instanceof Frame;
+            return (Frame) callerFrame;
         }
     }
 
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/CallMatcherNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/CallMatcherNode.java
index fe03a9b9a5f5f2e6606b012a513f4578757b619e..5ec1cab2849f619f1d6e083541e6d07bbf1246f2 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/CallMatcherNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/CallMatcherNode.java
@@ -16,6 +16,7 @@ import java.util.function.Supplier;
 import com.oracle.truffle.api.CompilerDirectives;
 import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
 import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import com.oracle.truffle.api.frame.MaterializedFrame;
 import com.oracle.truffle.api.frame.VirtualFrame;
 import com.oracle.truffle.api.nodes.ExplodeLoop;
 import com.oracle.truffle.api.nodes.NodeCost;
@@ -32,6 +33,7 @@ import com.oracle.truffle.r.nodes.function.visibility.SetVisibilityNode;
 import com.oracle.truffle.r.runtime.ArgumentsSignature;
 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.RCaller;
 import com.oracle.truffle.r.runtime.RInternalError;
 import com.oracle.truffle.r.runtime.RVisibility;
@@ -263,8 +265,11 @@ public abstract class CallMatcherNode extends RBaseNode {
                     String genFunctionName = functionName == null ? function.getName() : functionName;
                     Supplier<RSyntaxElement> argsSupplier = RCallerHelper.createFromArguments(genFunctionName, preparePermutation, suppliedArguments, suppliedSignature);
                     RCaller caller = genFunctionName == null ? RCaller.createInvalid(frame, parent) : RCaller.create(frame, parent, argsSupplier);
+                    MaterializedFrame callerFrame = dispatchArgs instanceof S3Args ? ((S3Args) dispatchArgs).callEnv : null;
                     try {
-                        return call.execute(frame, cachedFunction, caller, reorderedArgs, matchedArgs.getSignature(), cachedFunction.getEnclosingFrame(), dispatchArgs);
+                        return call.execute(frame, cachedFunction, caller, callerFrame, parent == null || parent.getDepth() == 0, reorderedArgs, matchedArgs.getSignature(),
+                                        cachedFunction.getEnclosingFrame(),
+                                        dispatchArgs);
                     } finally {
                         visibility.executeAfterCall(frame, caller);
                     }
@@ -343,8 +348,9 @@ public abstract class CallMatcherNode extends RBaseNode {
             RCaller caller = genFunctionName == null ? RCaller.createInvalid(frame, parent)
                             : RCaller.create(frame, RCallerHelper.createFromArguments(genFunctionName,
                                             new RArgsValuesAndNames(reorderedArgs.getArguments(), ArgumentsSignature.empty(reorderedArgs.getLength()))));
+            MaterializedFrame callerFrame = (dispatchArgs instanceof S3Args) ? ((S3Args) dispatchArgs).callEnv : null;
             try {
-                return call.execute(frame, function, caller, reorderedArgs.getArguments(), reorderedArgs.getSignature(), function.getEnclosingFrame(), dispatchArgs);
+                return call.execute(frame, function, caller, callerFrame, reorderedArgs.getArguments(), reorderedArgs.getSignature(), function.getEnclosingFrame(), dispatchArgs);
             } finally {
                 visibility.executeAfterCall(frame, caller);
             }
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/GetCallerFrameNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/GetCallerFrameNode.java
index 1583d0222e7a1d285baad422132c346c1874ebef..b3ca25d93dec0cf40f7046dbaebd39592176445e 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/GetCallerFrameNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/GetCallerFrameNode.java
@@ -22,7 +22,6 @@
  */
 package com.oracle.truffle.r.nodes.function;
 
-import com.oracle.truffle.api.CompilerAsserts;
 import com.oracle.truffle.api.CompilerDirectives;
 import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
 import com.oracle.truffle.api.frame.Frame;
@@ -30,9 +29,9 @@ import com.oracle.truffle.api.frame.FrameInstance.FrameAccess;
 import com.oracle.truffle.api.frame.MaterializedFrame;
 import com.oracle.truffle.api.nodes.NodeCost;
 import com.oracle.truffle.api.profiles.BranchProfile;
+import com.oracle.truffle.r.runtime.CallerFrameClosure;
 import com.oracle.truffle.r.runtime.RArguments;
 import com.oracle.truffle.r.runtime.RError;
-import com.oracle.truffle.r.runtime.CallerFrameClosure;
 import com.oracle.truffle.r.runtime.Utils;
 import com.oracle.truffle.r.runtime.nodes.RBaseNode;
 
@@ -48,7 +47,6 @@ public final class GetCallerFrameNode extends RBaseNode {
 
     public MaterializedFrame execute(Frame frame) {
         Object callerFrameObject = RArguments.getCallerFrame(frame);
-        MaterializedFrame mCallerFrame;
         if (callerFrameObject instanceof CallerFrameClosure) {
             if (!slowPathInitialized) {
                 CompilerDirectives.transferToInterpreterAndInvalidate();
@@ -62,14 +60,14 @@ public final class GetCallerFrameNode extends RBaseNode {
             // if interpreted, we will have a materialized frame in the closure
             MaterializedFrame materializedCallerFrame = closure.getMaterializedCallerFrame();
             if (materializedCallerFrame != null) {
-                CompilerAsserts.neverPartOfCompilation();
+                assert CompilerDirectives.inInterpreter() || materializedCallerFrame == null;
                 return materializedCallerFrame;
             }
             RError.performanceWarning("slow caller frame access");
             // for now, get it on the very slow path
             Frame callerFrame = Utils.getCallerFrame(frame, FrameAccess.MATERIALIZE);
             if (callerFrame != null) {
-                mCallerFrame = callerFrame.materialize();
+                callerFrameObject = callerFrame.materialize();
             }
         }
         if (callerFrameObject == null) {
@@ -78,11 +76,9 @@ public final class GetCallerFrameNode extends RBaseNode {
             // must be the top level case.
             topLevelProfile.enter();
             return frame.materialize();
-        } else {
-            mCallerFrame = (MaterializedFrame) callerFrameObject;
         }
         assert callerFrameObject instanceof MaterializedFrame;
-        return mCallerFrame;
+        return (MaterializedFrame) callerFrameObject;
     }
 
 }
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 f1996378bde793255c0bc50eef27cb8fa4343908..61bbb2e68208a2078cc75d36b59bb4bffa4952e7 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
@@ -986,7 +986,8 @@ public abstract class RCallNode extends RCallBaseNode implements RSyntaxNode, RS
                 }
             }
 
-            return call.execute(frame, function, originalCall.createCaller(frame, function), orderedArguments.getArguments(), orderedArguments.getSignature(), function.getEnclosingFrame(), s3Args);
+            return call.execute(frame, function, originalCall.createCaller(frame, function), orderedArguments.getArguments(), orderedArguments.getSignature(), function.getEnclosingFrame(),
+                            s3Args);
         }
     }
 
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/call/CallRFunctionBaseNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/call/CallRFunctionBaseNode.java
index 4f8ca2212e387c8966beee4f1da48f13b0159958..6580ae783689e776968c484aae1c23df6a6d8610 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/call/CallRFunctionBaseNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/call/CallRFunctionBaseNode.java
@@ -1,3 +1,25 @@
+/*
+ * Copyright (c) 2017, 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.call;
 
 import com.oracle.truffle.api.Assumption;
@@ -12,6 +34,7 @@ public abstract class CallRFunctionBaseNode extends Node {
 
     protected final Assumption needsNoCallerFrame = Truffle.getRuntime().createAssumption("no caller frame");
     protected final CallerFrameClosure invalidateNoCallerFrame = new InvalidateNoCallerFrame(needsNoCallerFrame);
+    private static final CallerFrameClosure DUMMY = new DummyCallerFrameClosure();
 
     public boolean setNeedsCallerFrame() {
         boolean value = !needsNoCallerFrame.isValid();
@@ -19,17 +42,50 @@ public abstract class CallRFunctionBaseNode extends Node {
         return value;
     }
 
+    private Object getCallerFrameClosure(MaterializedFrame callerFrame) {
+        if (CompilerDirectives.inInterpreter()) {
+            return new InvalidateNoCallerFrame(needsNoCallerFrame, callerFrame);
+        }
+        return invalidateNoCallerFrame;
+    }
+
     private Object getCallerFrameClosure(VirtualFrame callerFrame) {
         if (CompilerDirectives.inInterpreter()) {
-            return new InvalidateNoCallerFrame(needsNoCallerFrame, callerFrame.materialize());
+            return new InvalidateNoCallerFrame(needsNoCallerFrame, callerFrame != null ? callerFrame.materialize() : null);
         }
         return invalidateNoCallerFrame;
     }
 
+    protected final Object getCallerFrameObject(VirtualFrame curFrame, MaterializedFrame callerFrame, boolean topLevel) {
+        if (needsNoCallerFrame.isValid()) {
+            return getCallerFrameClosure(callerFrame);
+        } else {
+            if (callerFrame != null) {
+                return callerFrame;
+            } else if (topLevel) {
+                return DUMMY;
+            }
+            return curFrame.materialize();
+        }
+    }
+
     protected final Object getCallerFrameObject(VirtualFrame callerFrame) {
         return needsNoCallerFrame.isValid() ? getCallerFrameClosure(callerFrame) : callerFrame.materialize();
     }
 
+    private static final class DummyCallerFrameClosure extends CallerFrameClosure {
+
+        @Override
+        public void setNeedsCallerFrame() {
+        }
+
+        @Override
+        public MaterializedFrame getMaterializedCallerFrame() {
+            return null;
+        }
+
+    }
+
     public static final class InvalidateNoCallerFrame extends CallerFrameClosure {
 
         private final Assumption needsNoCallerFrame;
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/call/CallRFunctionCachedNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/call/CallRFunctionCachedNode.java
index 43fd6ecc99ca99dba05e16744f0ad1da6159c160..b94e7a9a1a6f6a518d7b3bc892e8dd26885cfc04 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/call/CallRFunctionCachedNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/call/CallRFunctionCachedNode.java
@@ -61,6 +61,12 @@ public abstract class CallRFunctionCachedNode extends CallRFunctionBaseNode {
         return execute(frame, function.getTarget(), callArgs, call);
     }
 
+    public final Object execute(VirtualFrame frame, RFunction function, RCaller call, MaterializedFrame callerFrame, Object[] evaluatedArgs,
+                    ArgumentsSignature suppliedSignature, MaterializedFrame enclosingFrame, DispatchArgs dispatchArgs) {
+        Object[] callArgs = RArguments.create(function, call, getCallerFrameObject(frame, callerFrame, false), evaluatedArgs, suppliedSignature, enclosingFrame, dispatchArgs);
+        return execute(frame, function.getTarget(), callArgs, call);
+    }
+
     protected abstract Object execute(VirtualFrame frame, CallTarget target, Object[] arguments, RCaller caller);
 
     protected static DirectCallNode createDirectCallNode(CallTarget target) {
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/call/CallRFunctionNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/call/CallRFunctionNode.java
index 432d72b8379bb61837877f537cc3148c5ff297df..89f202ced9792e15e82029bcc0c3ba56263a133c 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/call/CallRFunctionNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/call/CallRFunctionNode.java
@@ -50,9 +50,19 @@ public final class CallRFunctionNode extends CallRFunctionBaseNode {
         return new CallRFunctionNode(callTarget);
     }
 
+    public Object execute(VirtualFrame frame, RFunction function, RCaller caller, MaterializedFrame candidate, boolean topLevel, Object[] evaluatedArgs, ArgumentsSignature suppliedSignature,
+                    MaterializedFrame enclosingFrame, DispatchArgs dispatchArgs) {
+        Object[] callArgs = RArguments.create(function, caller, getCallerFrameObject(frame, candidate, topLevel), evaluatedArgs, suppliedSignature, enclosingFrame, dispatchArgs);
+        try {
+            return callNode.call(callArgs);
+        } finally {
+            visibility.executeAfterCall(frame, caller);
+        }
+    }
+
     public Object execute(VirtualFrame frame, RFunction function, RCaller caller, Object[] evaluatedArgs, ArgumentsSignature suppliedSignature,
                     MaterializedFrame enclosingFrame, DispatchArgs dispatchArgs) {
-        Object[] callArgs = RArguments.create(function, caller, getCallerFrameObject(frame), evaluatedArgs, suppliedSignature, enclosingFrame, dispatchArgs);
+        Object[] callArgs = RArguments.create(function, caller, getCallerFrameObject(frame, null, true), evaluatedArgs, suppliedSignature, enclosingFrame, dispatchArgs);
         try {
             return callNode.call(callArgs);
         } finally {