diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RArguments.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RArguments.java
index 107504369fa3ffdf4f92751bf52f9637bfcf9d10..18afbe3002ddb38c49dbd68b21ec951290e1cb2f 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RArguments.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RArguments.java
@@ -47,7 +47,7 @@ import com.oracle.truffle.r.runtime.env.frame.FrameSlotChangeMonitor;
  *                            +--------------------+
  * INDEX_FUNCTION          -> | RFunction          |
  *                            +--------------------+
- * INDEX_CALL_SRC          -> | SourceSection      |
+ * INDEX_CALL              -> | RCaller            |
  *                            +--------------------+
  * INDEX_CALLER_FRAME   ->    | MaterializedFrame  |
  *                            +--------------------+
@@ -81,11 +81,7 @@ import com.oracle.truffle.r.runtime.env.frame.FrameSlotChangeMonitor;
  * to how the supplied arguments were permuted. The purpose of this slot is to store the names in the
  * original signature (especially positional vs. named) for later use in UseMethod.
  *
- * N.B. The depth is always a monotonically increasing value and unique across the active set of stack frames.
- * Promise evaluation requires some special support as the stack must reflect the "logical" stack depth,
- * else code like {@code sys.frames} does not work correctly, but it must be possible to access the initial frame
- * that was associated with the promise else condition handling does not work correctly. Accordingly the
- * stack frames associated with a promise evaluation maintain the INDEX_PROMISE_FRAME field for that access.
+ * @see RCaller
  */
 // @formatter:on
 public final class RArguments {
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RCaller.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RCaller.java
index 18d4cc48176558fea78d0267258f79073cb3a33f..99bd051e3fd64774249b9d2078c5acb95c294820 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RCaller.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RCaller.java
@@ -41,6 +41,42 @@ import com.oracle.truffle.r.runtime.nodes.RSyntaxNode;
  * NOTE: It is important to create new caller instances for each stack frame, so that
  * {@link ReturnException#getTarget()} can uniquely identify the target frame.
  * 
+ * Example:
+ *
+ * <pre>
+ *     foo <- function(a) a
+ *     bar <- function() foo(promiseFun(1))
+ *     promiseFun <- function(a) a+1
+ *     bar()
+ * </pre>
+ *
+ * When {@code promiseFun} is entered (which is at the point where {@code a} is evaluated in
+ * {@code foo}), the stack frames will look like:
+ *
+ * <pre>
+ *   promiseFun      (depth = 3, parent = bar, payload = "promiseFun(1)")
+ *   internal frame  (depth = 2, parent = global, payload = RCaller:bar)  <-- this may not be a real Truffle execution frame!
+ *   foo             (depth = 2, parent = bar, payload = "foo(promiseFun(1))")
+ *   bar             (depth = 1, parent = global, payload = "bar()")
+ *   global          (depth = 0, parent = null, payload = null)
+ * </pre>
+ *
+ * Where the 'internal frame' wraps the frame of bar so that the promise code can access all the
+ * local variables of bar, but the {@link RCaller} can be different: the depth that would normally
+ * be 1 is 2, and parent and payload are different (see docs of {@link #isPromise()}). The purpose
+ * of {@link #payload} in such frames is that we can use it to reach the actual frame where the
+ * promise is logically evaluated, should the promise call some stack introspection built-in, e.g.
+ * {@code parent.frame()}. The reason why depths is 2 is that any consecutive function call uses
+ * current depth + 1 as the new depth and we need the new depth to be 3.
+ *
+ * Note that the 'internal frame' may not be on the real execution stack (i.e. not iterable by
+ * Truffle). The {@code InlineCacheNode} directly injects the AST of the promise into the calling
+ * function AST (foo in this case), but passes the 'internal frame' to the execute method instead of
+ * the current {@code VirtualFrame} (so that the injected AST thinks that it is executed inside bar
+ * and not foo). If the cache is full, then the {@code InlineCacheNode} creates a new
+ * {@link com.oracle.truffle.api.CallTarget} and calls it with 'internal frame', in which case the
+ * 'internal frame' appears in Truffle frames iteration.
+ *
  * @see RArguments
  */
 public final class RCaller {