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 d8a9b08e1d65070421b6f68f3c75b93add39e594..649f4d84aa04ea9937d99cfd4e68f2924c6b3321 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
@@ -158,7 +158,7 @@ final class REngine implements Engine, Engine.Timings {
 
     private void initializeShared() {
         suppressWarnings = true;
-        MaterializedFrame baseFrame = RRuntime.createNonFunctionFrame().materialize();
+        MaterializedFrame baseFrame = RRuntime.createNonFunctionFrame("base");
         REnvironment.baseInitialize(baseFrame, globalFrame);
         RBuiltinPackages.loadBase(baseFrame);
         RGraphics.initialize();
@@ -489,7 +489,7 @@ final class REngine implements Engine, Engine.Timings {
             fbn = new BodyNode(body);
         }
         FrameDescriptor descriptor = new FrameDescriptor();
-        FrameSlotChangeMonitor.initializeFunctionFrameDescriptor(descriptor);
+        FrameSlotChangeMonitor.initializeFunctionFrameDescriptor("<eval>", descriptor);
         FunctionDefinitionNode rootNode = new FunctionDefinitionNode(sourceSection, descriptor, fbn, FormalArguments.NO_ARGS, description, true, true, null);
         RootCallTarget callTarget = Truffle.getRuntime().createCallTarget(rootNode);
         return callTarget;
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 444300b2e745820bd51faba406013d3c0577642a..e806f440e4b6142e02d36ee44d9e8ca2d3506efb 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
@@ -123,7 +123,7 @@ public class RRuntimeASTAccessImpl implements RRuntimeASTAccess {
             // obj is RSymbol or a primitive value.
             // A symbol needs to be converted back to a ReadVariableNode
             if (obj instanceof RSymbol) {
-                return ReadVariableNode.create(((RSymbol) obj).getName(), false);
+                return ReadVariableNode.create(((RSymbol) obj).getName());
             } else {
                 return ConstantNode.create(obj);
             }
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/AsFunction.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/AsFunction.java
index 1103c240c2ed417c17dfcaf2ef43750c300ba42b..ea3ee7e70305219cc47a8bed5922cc6345fd33bc 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/AsFunction.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/AsFunction.java
@@ -115,7 +115,8 @@ public abstract class AsFunction extends RBuiltinNode {
         }
 
         FrameDescriptor descriptor = new FrameDescriptor();
-        FrameSlotChangeMonitor.initializeFunctionFrameDescriptor(descriptor);
+        FrameSlotChangeMonitor.initializeFunctionFrameDescriptor("<as.function.default>", descriptor);
+        FrameSlotChangeMonitor.initializeEnclosingFrame(descriptor, envir.getFrame());
         FunctionDefinitionNode rootNode = new FunctionDefinitionNode(sourceSection, descriptor, fbn, formals, "from AsFunction", false, null);
         RootCallTarget callTarget = Truffle.getRuntime().createCallTarget(rootNode);
         boolean containsDispatch = ((FunctionDefinitionNode) callTarget.getRootNode()).containsDispatch();
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/EnvFunctions.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/EnvFunctions.java
index 2089de76caf6795195eee644529605ca0b613427..4f1006826fdd376be23177ac68256d60249bfdaa 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/EnvFunctions.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/EnvFunctions.java
@@ -22,23 +22,56 @@
  */
 package com.oracle.truffle.r.nodes.builtin.base;
 
-import static com.oracle.truffle.r.runtime.RBuiltinKind.*;
+import static com.oracle.truffle.r.runtime.RBuiltinKind.INTERNAL;
+import static com.oracle.truffle.r.runtime.RBuiltinKind.PRIMITIVE;
 
-import com.oracle.truffle.api.*;
+import com.oracle.truffle.api.CompilerDirectives;
 import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
-import com.oracle.truffle.api.dsl.*;
-import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.RootCallTarget;
+import com.oracle.truffle.api.Truffle;
+import com.oracle.truffle.api.dsl.Fallback;
+import com.oracle.truffle.api.dsl.NodeChild;
+import com.oracle.truffle.api.dsl.Specialization;
+import com.oracle.truffle.api.frame.Frame;
 import com.oracle.truffle.api.frame.FrameInstance.FrameAccess;
-import com.oracle.truffle.api.profiles.*;
-import com.oracle.truffle.r.nodes.builtin.*;
+import com.oracle.truffle.api.frame.MaterializedFrame;
+import com.oracle.truffle.api.frame.VirtualFrame;
+import com.oracle.truffle.api.profiles.BranchProfile;
+import com.oracle.truffle.api.profiles.ConditionProfile;
+import com.oracle.truffle.r.nodes.RRootNode;
+import com.oracle.truffle.r.nodes.builtin.RBuiltinNode;
+import com.oracle.truffle.r.nodes.builtin.RInvisibleBuiltinNode;
 import com.oracle.truffle.r.nodes.builtin.base.EnvFunctionsFactory.CopyNodeGen;
-import com.oracle.truffle.r.nodes.function.*;
+import com.oracle.truffle.r.nodes.function.FunctionDefinitionNode;
+import com.oracle.truffle.r.nodes.function.PromiseHelperNode;
 import com.oracle.truffle.r.nodes.function.PromiseHelperNode.PromiseDeoptimizeFrameNode;
-import com.oracle.truffle.r.runtime.*;
-import com.oracle.truffle.r.runtime.data.*;
-import com.oracle.truffle.r.runtime.data.model.*;
-import com.oracle.truffle.r.runtime.env.*;
-import com.oracle.truffle.r.runtime.nodes.*;
+import com.oracle.truffle.r.runtime.RArguments;
+import com.oracle.truffle.r.runtime.RBuiltin;
+import com.oracle.truffle.r.runtime.RError;
+import com.oracle.truffle.r.runtime.RInternalError;
+import com.oracle.truffle.r.runtime.RRuntime;
+import com.oracle.truffle.r.runtime.Utils;
+import com.oracle.truffle.r.runtime.VirtualEvalFrame;
+import com.oracle.truffle.r.runtime.data.RAttributable;
+import com.oracle.truffle.r.runtime.data.RAttributeProfiles;
+import com.oracle.truffle.r.runtime.data.RDataFactory;
+import com.oracle.truffle.r.runtime.data.RFunction;
+import com.oracle.truffle.r.runtime.data.RLanguage;
+import com.oracle.truffle.r.runtime.data.RList;
+import com.oracle.truffle.r.runtime.data.RMissing;
+import com.oracle.truffle.r.runtime.data.RNull;
+import com.oracle.truffle.r.runtime.data.RPromise;
+import com.oracle.truffle.r.runtime.data.RStringVector;
+import com.oracle.truffle.r.runtime.data.RSymbol;
+import com.oracle.truffle.r.runtime.data.model.RAbstractDoubleVector;
+import com.oracle.truffle.r.runtime.data.model.RAbstractIntVector;
+import com.oracle.truffle.r.runtime.data.model.RAbstractLogicalVector;
+import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector;
+import com.oracle.truffle.r.runtime.data.model.RAbstractVector;
+import com.oracle.truffle.r.runtime.env.REnvironment;
+import com.oracle.truffle.r.runtime.env.frame.FrameSlotChangeMonitor;
+import com.oracle.truffle.r.runtime.nodes.RBaseNode;
+import com.oracle.truffle.r.runtime.nodes.RNode;
 
 /**
  * Encapsulates all the builtins related to R environments as nested static classes.
@@ -169,7 +202,6 @@ public class EnvFunctions {
             env.setParent(parent);
             return env;
         }
-
     }
 
     @RBuiltin(name = "is.environment", kind = PRIMITIVE, parameterNames = {"x"})
@@ -235,8 +267,14 @@ public class EnvFunctions {
         @Specialization
         @TruffleBoundary
         protected Object updateEnvironment(RFunction fun, REnvironment env) {
-            fun.setEnclosingFrame(env.getFrame());
-            return fun;
+            MaterializedFrame enclosingFrame = env.getFrame();
+            assert !(enclosingFrame instanceof VirtualEvalFrame);
+
+            RRootNode root = (RRootNode) fun.getTarget().getRootNode();
+            root = root.duplicateWithNewFrameDescriptor();
+            RootCallTarget target = Truffle.getRuntime().createCallTarget(root);
+            FrameSlotChangeMonitor.initializeEnclosingFrame(target.getRootNode().getFrameDescriptor(), enclosingFrame);
+            return RDataFactory.createFunction(fun.getName(), target, null, enclosingFrame, fun.getFastPath(), ((FunctionDefinitionNode) target.getRootNode()).containsDispatch());
         }
 
         @SuppressWarnings("unused")
@@ -293,15 +331,11 @@ public class EnvFunctions {
         }
 
         @Specialization
+        @TruffleBoundary
         protected REnvironment newEnv(byte hash, REnvironment parent, int size) {
             controlVisibility();
-            return createEnvironment(parent, RRuntime.fromLogical(hash), size);
-        }
-
-        @TruffleBoundary
-        private static REnvironment createEnvironment(REnvironment parent, boolean hash, int size) {
-            REnvironment env = RDataFactory.createNewEnv(null, hash, size);
-            env.setParent(parent);
+            REnvironment env = RDataFactory.createNewEnv(null, RRuntime.fromLogical(hash), size);
+            RArguments.initializeEnclosingFrame(env.getFrame(), parent.getFrame());
             return env;
         }
     }
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Identical.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Identical.java
index dd1e5027457cb5d0f91fc0740836053e714cfd24..68f75c9d98b62dea975c220f8c92a37e9de3d75a 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Identical.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Identical.java
@@ -188,7 +188,6 @@ public abstract class Identical extends RBuiltinNode {
         return RRuntime.LOGICAL_TRUE;
     }
 
-    @SuppressWarnings("unused")
     @Specialization
     protected byte doInternalIdenticalGeneric(RList x, RList y, byte numEq, byte singleNA, byte attribAsSet, byte ignoreBytecode, byte ignoreEnvironment) {
         if (!recursive) {
@@ -241,7 +240,6 @@ public abstract class Identical extends RBuiltinNode {
         return RRuntime.LOGICAL_FALSE;
     }
 
-    @SuppressWarnings("unused")
     @Specialization
     protected byte doInternalIdenticalGeneric(RS4Object x, RS4Object y, Object numEq, Object singleNA, Object attribAsSet, Object ignoreBytecode, Object ignoreEnvironment) {
         if (!recursive) {
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Lapply.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Lapply.java
index 19e003c76728be88674da6ce224893a0272908de..533e7ba147f617f1c01ef12c2ba008bfc3689369 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Lapply.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Lapply.java
@@ -107,7 +107,7 @@ public abstract class Lapply extends RBuiltinNode {
                 throw RInternalError.shouldNotReachHere();
             }
             REnvironment env = RDataFactory.createInternalEnv();
-            env.safePut("i", RDataFactory.createLanguage(ReadVariableNode.create(INDEX_NAME, false)));
+            env.safePut("i", RDataFactory.createLanguage(ReadVariableNode.create(INDEX_NAME)));
             return indexNode.substitute(env).asRNode();
         }
 
@@ -128,7 +128,7 @@ public abstract class Lapply extends RBuiltinNode {
                 readVectorElementName = null;
             }
 
-            ReadVariableNode readVector = ReadVariableNode.createAnonymous(VECTOR_ELEMENT);
+            ReadVariableNode readVector = ReadVariableNode.create(VECTOR_ELEMENT);
 
             // The remaining parameters are passed from {@code ...}. The call node will take
             // care of matching.
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Mapply.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Mapply.java
index 316dfbb8dba268a774712b26a743dec107ba08d3..4feef08b251d28d0d3a69b55a560591aaa3153f4 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Mapply.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Mapply.java
@@ -161,7 +161,7 @@ public abstract class Mapply extends RBuiltinNode {
 
             RSyntaxNode[] readVectorElementNodes = new RSyntaxNode[elementNodeArray.length];
             for (int i = 0; i < readVectorElementNodes.length; i++) {
-                readVectorElementNodes[i] = ReadVariableNode.create(elementNodeArray[i].vectorElementName, false);
+                readVectorElementNodes[i] = ReadVariableNode.create(elementNodeArray[i].vectorElementName);
             }
             ArgumentsSignature argsSig = ArgumentsSignature.empty(readVectorElementNodes.length);
             // Errors can be thrown from the modified call so a SourceSection is required
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/PrintFunctions.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/PrintFunctions.java
index 652ae4b0820b31737d58a4cbc5610c3b230eae37..8548085db3135f95c5df12601582e4ec9c254c9f 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/PrintFunctions.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/PrintFunctions.java
@@ -33,12 +33,10 @@ import com.oracle.truffle.api.dsl.Specialization;
 import com.oracle.truffle.api.frame.VirtualFrame;
 import com.oracle.truffle.api.nodes.DirectCallNode;
 import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode;
-import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode.ReadKind;
 import com.oracle.truffle.r.nodes.builtin.RInvisibleBuiltinNode;
 import com.oracle.truffle.r.runtime.RBuiltin;
 import com.oracle.truffle.r.runtime.RError;
 import com.oracle.truffle.r.runtime.RRuntime;
-import com.oracle.truffle.r.runtime.RType;
 import com.oracle.truffle.r.runtime.Utils;
 import com.oracle.truffle.r.runtime.conn.StdConnections;
 import com.oracle.truffle.r.runtime.context.RContext;
@@ -80,7 +78,7 @@ public class PrintFunctions {
         }
 
         ReadVariableNode createShowFind() {
-            return ReadVariableNode.create("show", RType.Function, ReadKind.Normal);
+            return ReadVariableNode.createFunctionLookup(null, "show");
         }
 
         RFunction createShowFunction(ReadVariableNode showFind) {
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Recall.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Recall.java
index 8a6c435abd7eec1c265949a08e22a233c2622c51..274768e590f4a14695fc31382a580b7e0f258863 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Recall.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Recall.java
@@ -22,18 +22,23 @@
  */
 package com.oracle.truffle.r.nodes.builtin.base;
 
-import static com.oracle.truffle.r.runtime.RBuiltinKind.*;
+import static com.oracle.truffle.r.runtime.RBuiltinKind.INTERNAL;
 
-import com.oracle.truffle.api.dsl.*;
-import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.dsl.Specialization;
+import com.oracle.truffle.api.frame.Frame;
 import com.oracle.truffle.api.frame.FrameInstance.FrameAccess;
-import com.oracle.truffle.api.profiles.*;
-import com.oracle.truffle.r.nodes.access.variables.*;
-import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode.*;
-import com.oracle.truffle.r.nodes.builtin.*;
-import com.oracle.truffle.r.nodes.function.*;
-import com.oracle.truffle.r.runtime.*;
-import com.oracle.truffle.r.runtime.data.*;
+import com.oracle.truffle.api.frame.VirtualFrame;
+import com.oracle.truffle.api.profiles.BranchProfile;
+import com.oracle.truffle.r.nodes.access.variables.LocalReadVariableNode;
+import com.oracle.truffle.r.nodes.builtin.RBuiltinNode;
+import com.oracle.truffle.r.nodes.function.CallMatcherNode;
+import com.oracle.truffle.r.runtime.ArgumentsSignature;
+import com.oracle.truffle.r.runtime.RArguments;
+import com.oracle.truffle.r.runtime.RBuiltin;
+import com.oracle.truffle.r.runtime.RError;
+import com.oracle.truffle.r.runtime.Utils;
+import com.oracle.truffle.r.runtime.data.RArgsValuesAndNames;
+import com.oracle.truffle.r.runtime.data.RFunction;
 
 /**
  * The {@code Recall} {@code .Internal}.
@@ -43,7 +48,7 @@ public abstract class Recall extends RBuiltinNode {
 
     private final BranchProfile errorProfile = BranchProfile.create();
 
-    @Child private ReadVariableNode readArgs = ReadVariableNode.create(ArgumentsSignature.VARARG_NAME, RType.Any, ReadKind.UnforcedSilentLocal);
+    @Child private LocalReadVariableNode readArgs = LocalReadVariableNode.create(ArgumentsSignature.VARARG_NAME, false);
     @Child private CallMatcherNode call = CallMatcherNode.create(false, false);
 
     @Specialization
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/S3DispatchFunctions.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/S3DispatchFunctions.java
index 5cd047cdbefb7c375f272fc42194adbdcd0a883c..591ef9b77327ad4ed98de103c0195e5622545c0c 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/S3DispatchFunctions.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/S3DispatchFunctions.java
@@ -24,8 +24,7 @@ import com.oracle.truffle.api.frame.VirtualFrame;
 import com.oracle.truffle.api.profiles.BranchProfile;
 import com.oracle.truffle.api.profiles.ConditionProfile;
 import com.oracle.truffle.api.profiles.ValueProfile;
-import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode;
-import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode.ReadKind;
+import com.oracle.truffle.r.nodes.access.variables.LocalReadVariableNode;
 import com.oracle.truffle.r.nodes.builtin.RBuiltinNode;
 import com.oracle.truffle.r.nodes.function.CallMatcherNode;
 import com.oracle.truffle.r.nodes.function.ClassHierarchyNode;
@@ -44,7 +43,6 @@ import com.oracle.truffle.r.runtime.RArguments.S3Args;
 import com.oracle.truffle.r.runtime.RBuiltin;
 import com.oracle.truffle.r.runtime.RError;
 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.Utils;
 import com.oracle.truffle.r.runtime.data.RArgsValuesAndNames;
@@ -158,11 +156,11 @@ public abstract class S3DispatchFunctions extends RBuiltinNode {
     @RBuiltin(name = "NextMethod", kind = SUBSTITUTE, parameterNames = {"generic", "object", "..."})
     public abstract static class NextMethod extends S3DispatchFunctions {
 
-        @Child private ReadVariableNode rvnGroup = ReadVariableNode.create(RRuntime.RDotGroup, RType.Character, ReadKind.UnforcedSilentLocal);
-        @Child private ReadVariableNode rvnClass = ReadVariableNode.create(RRuntime.RDotClass, RType.Character, ReadKind.UnforcedSilentLocal);
-        @Child private ReadVariableNode rvnGeneric = ReadVariableNode.create(RRuntime.RDotGeneric, RType.Character, ReadKind.UnforcedSilentLocal);
-        @Child private ReadVariableNode rvnCall = ReadVariableNode.create(RRuntime.RDotGenericCallEnv, RType.Any, ReadKind.UnforcedSilentLocal);
-        @Child private ReadVariableNode rvnDef = ReadVariableNode.create(RRuntime.RDotGenericDefEnv, RType.Any, ReadKind.UnforcedSilentLocal);
+        @Child private LocalReadVariableNode rvnGroup = LocalReadVariableNode.create(RRuntime.RDotGroup, false);
+        @Child private LocalReadVariableNode rvnClass = LocalReadVariableNode.create(RRuntime.RDotClass, false);
+        @Child private LocalReadVariableNode rvnGeneric = LocalReadVariableNode.create(RRuntime.RDotGeneric, false);
+        @Child private LocalReadVariableNode rvnCall = LocalReadVariableNode.create(RRuntime.RDotGenericCallEnv, false);
+        @Child private LocalReadVariableNode rvnDef = LocalReadVariableNode.create(RRuntime.RDotGenericDefEnv, false);
 
         @Child private CombineSignaturesNode combineSignatures;
         @Child private CollectArgumentsNode collectArguments = CollectArgumentsNodeGen.create();
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/StandardGeneric.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/StandardGeneric.java
index 106fbfccff203153f69dcd6662677bd13477744c..097062b22e2c2456b3765376d19babb967b89459 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/StandardGeneric.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/StandardGeneric.java
@@ -12,22 +12,22 @@
  */
 package com.oracle.truffle.r.nodes.builtin.base;
 
-import static com.oracle.truffle.r.runtime.RBuiltinKind.*;
+import static com.oracle.truffle.r.runtime.RBuiltinKind.PRIMITIVE;
 
 import com.oracle.truffle.api.CompilerDirectives;
 import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
 import com.oracle.truffle.api.Truffle;
-import com.oracle.truffle.api.dsl.*;
+import com.oracle.truffle.api.dsl.Specialization;
 import com.oracle.truffle.api.frame.MaterializedFrame;
 import com.oracle.truffle.api.frame.VirtualFrame;
 import com.oracle.truffle.api.nodes.DirectCallNode;
 import com.oracle.truffle.api.profiles.BranchProfile;
 import com.oracle.truffle.api.profiles.ConditionProfile;
+import com.oracle.truffle.r.nodes.access.variables.LocalReadVariableNode;
 import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode;
-import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode.ReadKind;
 import com.oracle.truffle.r.nodes.attributes.AttributeAccess;
 import com.oracle.truffle.r.nodes.attributes.AttributeAccessNodeGen;
-import com.oracle.truffle.r.nodes.builtin.*;
+import com.oracle.truffle.r.nodes.builtin.RBuiltinNode;
 import com.oracle.truffle.r.nodes.function.signature.RArgumentsNode;
 import com.oracle.truffle.r.nodes.objects.CollectGenericArgumentsNode;
 import com.oracle.truffle.r.nodes.objects.CollectGenericArgumentsNodeGen;
@@ -36,10 +36,23 @@ import com.oracle.truffle.r.nodes.objects.DispatchGenericNodeGen;
 import com.oracle.truffle.r.nodes.unary.CastIntegerScalarNode;
 import com.oracle.truffle.r.nodes.unary.CastStringScalarNode;
 import com.oracle.truffle.r.nodes.unary.CastStringScalarNodeGen;
-import com.oracle.truffle.r.runtime.*;
+import com.oracle.truffle.r.runtime.ArgumentsSignature;
+import com.oracle.truffle.r.runtime.RArguments;
+import com.oracle.truffle.r.runtime.RBuiltin;
+import com.oracle.truffle.r.runtime.RCaller;
+import com.oracle.truffle.r.runtime.RError;
+import com.oracle.truffle.r.runtime.RRuntime;
 import com.oracle.truffle.r.runtime.context.RContext;
-import com.oracle.truffle.r.runtime.data.*;
-import com.oracle.truffle.r.runtime.data.model.*;
+import com.oracle.truffle.r.runtime.data.RAttributable;
+import com.oracle.truffle.r.runtime.data.RAttributeProfiles;
+import com.oracle.truffle.r.runtime.data.RAttributes;
+import com.oracle.truffle.r.runtime.data.RDataFactory;
+import com.oracle.truffle.r.runtime.data.RFunction;
+import com.oracle.truffle.r.runtime.data.RList;
+import com.oracle.truffle.r.runtime.data.RMissing;
+import com.oracle.truffle.r.runtime.data.RNull;
+import com.oracle.truffle.r.runtime.data.RStringVector;
+import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector;
 import com.oracle.truffle.r.runtime.env.REnvironment;
 import com.oracle.truffle.r.runtime.nodes.RNode;
 
@@ -52,11 +65,11 @@ public abstract class StandardGeneric extends RBuiltinNode {
     @Child private AttributeAccess genericAttrAccess;
     @Child private FrameFunctions.SysFunction sysFunction;
     @Child private CastStringScalarNode castStringScalar = CastStringScalarNodeGen.create();
-    @Child private ReadVariableNode readMTableFirst = ReadVariableNode.create(RRuntime.DOT_ALL_MTABLE, RType.Any, ReadKind.SilentLocal);
-    @Child private ReadVariableNode readMTableSecond = ReadVariableNode.create(RRuntime.DOT_ALL_MTABLE, RType.Any, ReadKind.SilentLocal);
-    @Child private ReadVariableNode readSigLength = ReadVariableNode.create(RRuntime.DOT_SIG_LENGTH, RType.Any, ReadKind.SilentLocal);
-    @Child private ReadVariableNode readSigARgs = ReadVariableNode.create(RRuntime.DOT_SIG_ARGS, RType.Any, ReadKind.SilentLocal);
-    @Child private ReadVariableNode getMethodsTableFind = ReadVariableNode.create(".getMethodsTable", RType.Function, ReadKind.Normal);
+    @Child private LocalReadVariableNode readMTableFirst = LocalReadVariableNode.create(RRuntime.DOT_ALL_MTABLE, true);
+    @Child private LocalReadVariableNode readMTableSecond = LocalReadVariableNode.create(RRuntime.DOT_ALL_MTABLE, true);
+    @Child private LocalReadVariableNode readSigLength = LocalReadVariableNode.create(RRuntime.DOT_SIG_LENGTH, true);
+    @Child private LocalReadVariableNode readSigARgs = LocalReadVariableNode.create(RRuntime.DOT_SIG_ARGS, true);
+    @Child private ReadVariableNode getMethodsTableFind = ReadVariableNode.createFunctionLookup(null, ".getMethodsTable");
     @Child private DirectCallNode getMethodsTableCall;
     @Child private RArgumentsNode argsNode = RArgumentsNode.create();
     @Child private CastIntegerScalarNode castIntScalar = CastIntegerScalarNode.create();
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateSlot.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateSlot.java
index a2c946ae120a024be18e96fed7ea7187c3c71adc..d94f0cbbfec8ec32bbe0e02619a62e6a14a57b6e 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateSlot.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateSlot.java
@@ -16,21 +16,25 @@ package com.oracle.truffle.r.nodes.builtin.base;
 import com.oracle.truffle.api.CompilerDirectives;
 import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
 import com.oracle.truffle.api.Truffle;
-import com.oracle.truffle.api.dsl.*;
+import com.oracle.truffle.api.dsl.Specialization;
 import com.oracle.truffle.api.frame.VirtualFrame;
 import com.oracle.truffle.api.nodes.DirectCallNode;
 import com.oracle.truffle.api.profiles.ConditionProfile;
 import com.oracle.truffle.r.nodes.access.ConstantNode;
 import com.oracle.truffle.r.nodes.access.UpdateSlotNode;
 import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode;
-import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode.ReadKind;
-import com.oracle.truffle.r.nodes.builtin.*;
+import com.oracle.truffle.r.nodes.builtin.RBuiltinNode;
 import com.oracle.truffle.r.nodes.function.ClassHierarchyNode;
 import com.oracle.truffle.r.nodes.function.ClassHierarchyNodeGen;
 import com.oracle.truffle.r.nodes.function.RCallNode;
 import com.oracle.truffle.r.nodes.function.WrapArgumentNode;
 import com.oracle.truffle.r.nodes.function.signature.RArgumentsNode;
-import com.oracle.truffle.r.runtime.*;
+import com.oracle.truffle.r.runtime.ArgumentsSignature;
+import com.oracle.truffle.r.runtime.RArguments;
+import com.oracle.truffle.r.runtime.RBuiltin;
+import com.oracle.truffle.r.runtime.RBuiltinKind;
+import com.oracle.truffle.r.runtime.RCaller;
+import com.oracle.truffle.r.runtime.RError;
 import com.oracle.truffle.r.runtime.context.RContext;
 import com.oracle.truffle.r.runtime.data.RDataFactory;
 import com.oracle.truffle.r.runtime.data.RFunction;
@@ -45,7 +49,7 @@ public abstract class UpdateSlot extends RBuiltinNode {
     @Child private ClassHierarchyNode objClassHierarchy;
     @Child private ClassHierarchyNode valClassHierarchy;
     @Child UpdateSlotNode updateSlotNode = com.oracle.truffle.r.nodes.access.UpdateSlotNodeGen.create(null, null, null);
-    @Child ReadVariableNode checkAtAssignmentFind = ReadVariableNode.create("checkAtAssignment", RType.Function, ReadKind.Normal);
+    @Child ReadVariableNode checkAtAssignmentFind = ReadVariableNode.createFunctionLookup(null, "checkAtAssignment");
     @Child DirectCallNode checkAtAssignmentCall;
     @Child private RArgumentsNode argsNode = RArgumentsNode.create();
     private final ConditionProfile cached = ConditionProfile.createBinaryProfile();
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/RASTUtils.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/RASTUtils.java
index 857e18ba40803c684a626e865c367ffbbf043235..07a443120adb8d89796e7f3058c9a51481a22c4c 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/RASTUtils.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/RASTUtils.java
@@ -73,7 +73,7 @@ public class RASTUtils {
      */
     @TruffleBoundary
     public static ReadVariableNode createReadVariableNode(String name) {
-        return ReadVariableNode.createForced(null, name, RType.Any);
+        return ReadVariableNode.create(name);
     }
 
     /**
@@ -131,7 +131,7 @@ public class RASTUtils {
     public static Object checkForRSymbol(Object expr) {
         if (expr instanceof RSymbol) {
             String symbolName = ((RSymbol) expr).getName();
-            return RDataFactory.createLanguage(ReadVariableNode.create(symbolName, false));
+            return RDataFactory.createLanguage(ReadVariableNode.create(symbolName));
         } else {
             return expr;
         }
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/RRootNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/RRootNode.java
index 5e4b1a989927b250228ee2b80c10ab9c550d866b..ad0e414d0dae7279dc743c1f22d7876290822008 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/RRootNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/RRootNode.java
@@ -22,17 +22,19 @@
  */
 package com.oracle.truffle.r.nodes;
 
-import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
 import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
-import com.oracle.truffle.api.frame.*;
-import com.oracle.truffle.api.nodes.*;
-import com.oracle.truffle.api.source.*;
-import com.oracle.truffle.api.profiles.*;
-import com.oracle.truffle.r.nodes.builtin.*;
-import com.oracle.truffle.r.nodes.function.*;
-import com.oracle.truffle.r.runtime.*;
-import com.oracle.truffle.r.runtime.context.*;
-import com.oracle.truffle.r.runtime.env.frame.*;
+import com.oracle.truffle.api.frame.FrameDescriptor;
+import com.oracle.truffle.api.frame.VirtualFrame;
+import com.oracle.truffle.api.nodes.RootNode;
+import com.oracle.truffle.api.profiles.ConditionProfile;
+import com.oracle.truffle.api.source.SourceSection;
+import com.oracle.truffle.r.nodes.builtin.RBuiltinNode;
+import com.oracle.truffle.r.nodes.function.FormalArguments;
+import com.oracle.truffle.r.nodes.function.FunctionDefinitionNode;
+import com.oracle.truffle.r.runtime.ArgumentsSignature;
+import com.oracle.truffle.r.runtime.HasSignature;
+import com.oracle.truffle.r.runtime.RArguments;
+import com.oracle.truffle.r.runtime.context.RContext;
 import com.oracle.truffle.r.runtime.nodes.RSyntaxNode;
 
 /**
@@ -41,7 +43,6 @@ import com.oracle.truffle.r.runtime.nodes.RSyntaxNode;
  */
 public abstract class RRootNode extends RootNode implements HasSignature {
 
-    @CompilationFinal protected boolean checkSingletonFrame = true;
     private final ConditionProfile irregularFrameProfile = ConditionProfile.createBinaryProfile();
 
     /**
@@ -62,12 +63,10 @@ public abstract class RRootNode extends RootNode implements HasSignature {
         }
     }
 
+    public abstract RRootNode duplicateWithNewFrameDescriptor();
+
     protected void verifyEnclosingAssumptions(VirtualFrame vf) {
         RArguments.setIsIrregular(vf, irregularFrameProfile.profile(RArguments.getIsIrregular(vf)));
-
-        if (checkSingletonFrame) {
-            checkSingletonFrame = FrameSlotChangeMonitor.checkSingletonFrame(vf);
-        }
     }
 
     /**
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/RTruffleVisitor.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/RTruffleVisitor.java
index d5f01e14e6ce7f60437106ee5a48e5449ad17dee..cdfd9d7a27620f3ded27be7a3ac9a6d9ee6b9732 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/RTruffleVisitor.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/RTruffleVisitor.java
@@ -91,7 +91,6 @@ import com.oracle.truffle.r.runtime.FastROptions;
 import com.oracle.truffle.r.runtime.RGroupGenerics;
 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.data.FastPathFactory;
 import com.oracle.truffle.r.runtime.data.RDataFactory;
 import com.oracle.truffle.r.runtime.data.REmpty;
@@ -110,6 +109,7 @@ public final class RTruffleVisitor extends BasicVisitor<RSyntaxNode> {
     public RFunction transformFunction(String name, Function func, MaterializedFrame enclosingFrame) {
         RootCallTarget callTarget = createFunctionCallTarget(func);
         FastPathFactory fastPath = EvaluatedArgumentsVisitor.process(func);
+        FrameSlotChangeMonitor.initializeEnclosingFrame(callTarget.getRootNode().getFrameDescriptor(), enclosingFrame);
         return RDataFactory.createFunction(name, callTarget, null, enclosingFrame, fastPath, ((FunctionDefinitionNode) callTarget.getRootNode()).containsDispatch());
     }
 
@@ -188,7 +188,7 @@ public final class RTruffleVisitor extends BasicVisitor<RSyntaxNode> {
                 return GroupDispatchNode.create(callName, callSource, signature, nodes);
             }
             SourceSection varSource = ASTNode.adjustedSource(callSource, callSource.getCharIndex(), callName.length());
-            lhs = ReadVariableNode.createForced(varSource, callName, RType.Function);
+            lhs = ReadVariableNode.createForcedFunctionLookup(varSource, callName);
         } else {
             lhs = call.getLhsNode().accept(this).asRNode();
         }
@@ -273,8 +273,8 @@ public final class RTruffleVisitor extends BasicVisitor<RSyntaxNode> {
         }
 
         FrameDescriptor descriptor = new FrameDescriptor();
-        FrameSlotChangeMonitor.initializeFunctionFrameDescriptor(descriptor);
         String description = getFunctionDescription(func);
+        FrameSlotChangeMonitor.initializeFunctionFrameDescriptor(description != null && !description.isEmpty() ? description : "<function>", descriptor);
         FunctionDefinitionNode rootNode = new FunctionDefinitionNode(func.getSource(), descriptor, new FunctionBodyNode(saveArguments, statements), formals, description, false, argPostProcess);
         return Truffle.getRuntime().createCallTarget(rootNode);
     }
@@ -357,7 +357,7 @@ public final class RTruffleVisitor extends BasicVisitor<RSyntaxNode> {
         nodes[nodes.length - 1] = rhs;
         names[nodes.length - 1] = "value";
 
-        return RCallNode.createCallNotSyntax(ReadVariableNode.createForced(null, isSubset ? "[<-" : "[[<-", RType.Function), ArgumentsSignature.get(names), nodes);
+        return RCallNode.createCallNotSyntax(ReadVariableNode.createForcedFunctionLookup(null, isSubset ? "[<-" : "[[<-"), ArgumentsSignature.get(names), nodes);
     }
 
     private int tempNamesCount;
@@ -560,12 +560,12 @@ public final class RTruffleVisitor extends BasicVisitor<RSyntaxNode> {
     public RSyntaxNode visit(FieldAccess access) {
         SourceSection callSource = access.getSource();
         RSyntaxNode lhs = access.getLhs().accept(this);
-        ReadVariableNode function = ReadVariableNode.createForced(callSource, access.isAt() ? "@" : "$", RType.Function);
+        ReadVariableNode function = ReadVariableNode.createForcedFunctionLookup(callSource, access.isAt() ? "@" : "$");
         return RCallNode.createCall(callSource, function, ArgumentsSignature.empty(2), lhs, ConstantNode.create(callSource, access.getFieldName()));
     }
 
     private static RCallNode createFieldUpdate(SourceSection source, RSyntaxNode receiver, RSyntaxNode rhs, String fieldName, boolean at) {
-        ReadVariableNode function = ReadVariableNode.createForced(source, at ? "@<-" : "$<-", RType.Function);
+        ReadVariableNode function = ReadVariableNode.createForcedFunctionLookup(source, at ? "@<-" : "$<-");
         return RCallNode.createCall(source, function, ArgumentsSignature.empty(3), receiver, ConstantNode.create(source, fieldName), rhs);
     }
 
@@ -584,7 +584,7 @@ public final class RTruffleVisitor extends BasicVisitor<RSyntaxNode> {
             argNodes[i] = visit(arguments.get(i));
         }
         argNodes[arguments.size()] = rhs;
-        ReadVariableNode function = ReadVariableNode.createForced(null, funName, RType.Function);
+        ReadVariableNode function = ReadVariableNode.createForcedFunctionLookup(null, funName);
         return RCallNode.createCall(null, function, ArgumentsSignature.get(names), argNodes);
     }
 
@@ -601,8 +601,8 @@ public final class RTruffleVisitor extends BasicVisitor<RSyntaxNode> {
 
                 String tmpSymbol = createTempName();
                 String rhsSymbol = createTempName();
-                ReadVariableNode rhsAccess = ReadVariableNode.createAnonymous(rhsSymbol);
-                ReadVariableNode tmpVarAccess = ReadVariableNode.createAnonymous(tmpSymbol);
+                ReadVariableNode rhsAccess = ReadVariableNode.create(rhsSymbol);
+                ReadVariableNode tmpVarAccess = ReadVariableNode.create(tmpSymbol);
 
                 RSyntaxNode updateOp = updateFunction.apply(tmpVarAccess, rhsAccess);
                 RNode assignFromTemp = WriteVariableNode.createAnonymous(vSymbol, updateOp.asRNode(), WriteVariableNode.Mode.INVISIBLE, isSuper);
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/AccessEnclosingFrameNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/AccessEnclosingFrameNode.java
index 129161a0b2f79fb6990f5eb9aec19cff4258bb73..65b7e9f903c0cfa52e7785f2e7eb7db3f12e87c5 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/AccessEnclosingFrameNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/AccessEnclosingFrameNode.java
@@ -22,17 +22,15 @@
  */
 package com.oracle.truffle.r.nodes.access;
 
-import com.oracle.truffle.api.dsl.*;
-import com.oracle.truffle.api.frame.*;
-import com.oracle.truffle.r.runtime.*;
-import com.oracle.truffle.r.runtime.nodes.*;
+import com.oracle.truffle.api.frame.MaterializedFrame;
+import com.oracle.truffle.api.frame.VirtualFrame;
+import com.oracle.truffle.api.nodes.Node;
+import com.oracle.truffle.r.runtime.RArguments;
 
-public abstract class AccessEnclosingFrameNode extends RNode {
+public final class AccessEnclosingFrameNode extends Node {
 
-    public abstract MaterializedFrame executeMaterializedFrame(VirtualFrame frame);
-
-    @Specialization
-    protected MaterializedFrame doMaterializedFrame(VirtualFrame frame) {
+    @SuppressWarnings("static-method")
+    public MaterializedFrame execute(VirtualFrame frame) {
         return RArguments.getEnclosingFrame(frame);
     }
 }
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/FrameSlotNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/FrameSlotNode.java
index 94239a4ecdc4ec0ac015d8b4ba359168580b2027..efd6559640699bdb625b38f0b2194f7b697fce40 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/FrameSlotNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/FrameSlotNode.java
@@ -65,7 +65,7 @@ public abstract class FrameSlotNode extends RBaseNode {
         FrameSlotNode newNode;
         FrameSlot frameSlot;
         if (createIfAbsent) {
-            frameSlot = findOrAddFrameSlot(frameDescriptor, identifier);
+            frameSlot = findOrAddFrameSlot(frameDescriptor, identifier, FrameSlotKind.Illegal);
         } else {
             frameSlot = frameDescriptor.findFrameSlot(identifier);
         }
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/ReadVariadicComponentNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/ReadVariadicComponentNode.java
index 5d88c4f1b3d61d0969934cc1bf21f007c38341df..7d10c1dd1e42962d4eb318c7b564741a439837f1 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/ReadVariadicComponentNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/ReadVariadicComponentNode.java
@@ -22,25 +22,31 @@
  */
 package com.oracle.truffle.r.nodes.access;
 
-import com.oracle.truffle.api.*;
-import com.oracle.truffle.api.frame.*;
-import com.oracle.truffle.api.source.*;
-import com.oracle.truffle.api.profiles.*;
-import com.oracle.truffle.r.nodes.access.variables.*;
-import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode.ReadKind;
-import com.oracle.truffle.r.nodes.function.*;
-import com.oracle.truffle.r.runtime.*;
+import com.oracle.truffle.api.CompilerDirectives;
+import com.oracle.truffle.api.frame.VirtualFrame;
+import com.oracle.truffle.api.profiles.BranchProfile;
+import com.oracle.truffle.api.source.SourceSection;
+import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode;
+import com.oracle.truffle.r.nodes.function.PromiseHelperNode;
+import com.oracle.truffle.r.runtime.ArgumentsSignature;
 import com.oracle.truffle.r.runtime.RDeparse.State;
-import com.oracle.truffle.r.runtime.data.*;
-import com.oracle.truffle.r.runtime.env.*;
-import com.oracle.truffle.r.runtime.nodes.*;
+import com.oracle.truffle.r.runtime.RError;
+import com.oracle.truffle.r.runtime.RInternalError;
+import com.oracle.truffle.r.runtime.RType;
+import com.oracle.truffle.r.runtime.VisibilityController;
+import com.oracle.truffle.r.runtime.data.RArgsValuesAndNames;
+import com.oracle.truffle.r.runtime.data.RMissing;
+import com.oracle.truffle.r.runtime.data.RPromise;
+import com.oracle.truffle.r.runtime.env.REnvironment;
+import com.oracle.truffle.r.runtime.nodes.RNode;
+import com.oracle.truffle.r.runtime.nodes.RSyntaxNode;
 
 /**
  * An {@link RNode} that handles accesses to components of the variadic argument (..1, ..2, etc.).
  */
-public class ReadVariadicComponentNode extends RNode implements RSyntaxNode {
+public class ReadVariadicComponentNode extends RNode implements RSyntaxNode, VisibilityController {
 
-    @Child private ReadVariableNode lookup = ReadVariableNode.create(ArgumentsSignature.VARARG_NAME, RType.Any, ReadKind.Silent);
+    @Child private ReadVariableNode lookup = ReadVariableNode.createSilent(ArgumentsSignature.VARARG_NAME, RType.Any);
     @Child private PromiseHelperNode promiseHelper;
 
     private final int index;
@@ -55,6 +61,8 @@ public class ReadVariadicComponentNode extends RNode implements RSyntaxNode {
 
     @Override
     public Object execute(VirtualFrame frame) {
+        controlVisibility();
+
         Object args = lookup.execute(frame);
         if (args == null) {
             errorBranch.enter();
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/WriteLocalFrameVariableNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/WriteLocalFrameVariableNode.java
index 62dacb8c91350f0160d49aeb9cebad595472f579..6661538c78d0e09294cb713a8f28b95f94ff11e9 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/WriteLocalFrameVariableNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/WriteLocalFrameVariableNode.java
@@ -84,7 +84,7 @@ public abstract class WriteLocalFrameVariableNode extends BaseWriteVariableNode
 
         private void resolveAndSet(VirtualFrame frame, Object value, FrameSlotKind initialKind) {
             CompilerDirectives.transferToInterpreterAndInvalidate();
-            // it's slow path (unconditional replace) so toStrin() is fine as well
+            // it's slow path (unconditional replace) so toString() is fine as well
             if (getName().toString().isEmpty()) {
                 throw RError.error(this, RError.Message.ZERO_LENGTH_VARIABLE);
             }
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/WriteSuperFrameVariableNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/WriteSuperFrameVariableNode.java
index 540cb609f97f4f13c6190ea2ef9eea443e33e0c7..34a9fc76e2ed3b46acb245323f6389be11586780 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/WriteSuperFrameVariableNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/WriteSuperFrameVariableNode.java
@@ -114,11 +114,12 @@ public abstract class WriteSuperFrameVariableNode extends WriteSuperFrameVariabl
             final WriteSuperFrameVariableNodeHelper writeNode;
             if (REnvironment.isGlobalEnvFrame(enclosingFrame)) {
                 /*
-                 * we've reached the global scope, do unconditional write // if this is the first
-                 * node in the chain, needs the rhs and enclosingFrame // nodes
+                 * we've reached the global scope, do unconditional write. if this is the first node
+                 * in the chain, needs the rhs and enclosingFrame nodes
                  */
-                AccessEnclosingFrameNode enclosingFrameNode = RArguments.getEnclosingFrame(frame) == enclosingFrame ? AccessEnclosingFrameNodeGen.create() : null;
-                writeNode = WriteSuperFrameVariableNodeGen.create(getRhs(), enclosingFrameNode, FrameSlotNode.create(findOrAddFrameSlot(enclosingFrame.getFrameDescriptor(), symbol)), getName(), mode);
+                AccessEnclosingFrameNode enclosingFrameNode = RArguments.getEnclosingFrame(frame) == enclosingFrame ? new AccessEnclosingFrameNode() : null;
+                writeNode = WriteSuperFrameVariableNodeGen.create(getRhs(), enclosingFrameNode,
+                                FrameSlotNode.create(findOrAddFrameSlot(enclosingFrame.getFrameDescriptor(), symbol, FrameSlotKind.Illegal)), getName(), mode);
             } else {
                 WriteSuperFrameVariableNode actualWriteNode = WriteSuperFrameVariableNodeGen.create(null, null, FrameSlotNode.create(symbol), this.getName(), mode);
                 writeNode = new WriteSuperFrameVariableConditionalNode(actualWriteNode, new UnresolvedWriteSuperFrameVariableNode(symbol, null, mode), getRhs());
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/variables/LocalReadVariableNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/variables/LocalReadVariableNode.java
new file mode 100644
index 0000000000000000000000000000000000000000..21d2bd4a966e6d3a5ade7cee1686b1bad7c480bf
--- /dev/null
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/variables/LocalReadVariableNode.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2013, 2015, 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.access.variables;
+
+import com.oracle.truffle.api.Assumption;
+import com.oracle.truffle.api.CompilerDirectives;
+import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
+import com.oracle.truffle.api.frame.Frame;
+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.InvalidAssumptionException;
+import com.oracle.truffle.api.nodes.Node;
+import com.oracle.truffle.api.profiles.ConditionProfile;
+import com.oracle.truffle.api.profiles.ValueProfile;
+import com.oracle.truffle.r.nodes.function.PromiseHelperNode;
+import com.oracle.truffle.r.runtime.RError;
+import com.oracle.truffle.r.runtime.data.RMissing;
+import com.oracle.truffle.r.runtime.data.RPromise;
+
+public final class LocalReadVariableNode extends Node {
+
+    @Child private PromiseHelperNode promiseHelper;
+
+    private final Object identifier;
+    private final boolean forceResult;
+
+    @CompilationFinal private boolean[] seenValueKinds;
+    @CompilationFinal private ValueProfile valueProfile;
+    @CompilationFinal private ConditionProfile isNullProfile;
+    @CompilationFinal private ConditionProfile isMissingProfile;
+    @CompilationFinal private ConditionProfile isPromiseProfile;
+
+    @CompilationFinal private FrameSlot frameSlot;
+    @CompilationFinal private Assumption notInFrame;
+
+    public static LocalReadVariableNode create(Object identifier, boolean forceResult) {
+        return new LocalReadVariableNode(identifier, forceResult);
+    }
+
+    private LocalReadVariableNode(Object identifier, boolean forceResult) {
+        this.identifier = identifier;
+        this.forceResult = forceResult;
+    }
+
+    public Object getIdentifier() {
+        return identifier;
+    }
+
+    public Object execute(VirtualFrame frame) {
+        return execute(frame, frame);
+    }
+
+    public Object execute(VirtualFrame frame, Frame variableFrame) {
+        if (frameSlot == null && notInFrame == null) {
+            CompilerDirectives.transferToInterpreterAndInvalidate();
+            if (identifier.toString().isEmpty()) {
+                throw RError.error(RError.NO_NODE, RError.Message.ZERO_LENGTH_VARIABLE);
+            }
+            frameSlot = variableFrame.getFrameDescriptor().findFrameSlot(identifier);
+            notInFrame = frameSlot == null ? variableFrame.getFrameDescriptor().getNotInFrameAssumption(identifier) : null;
+        }
+        // check if the slot is missing / wrong type in current frame
+        if (frameSlot == null) {
+            try {
+                notInFrame.check();
+            } catch (InvalidAssumptionException e) {
+                frameSlot = variableFrame.getFrameDescriptor().findFrameSlot(identifier);
+                notInFrame = frameSlot == null ? variableFrame.getFrameDescriptor().getNotInFrameAssumption(identifier) : null;
+            }
+        }
+        if (frameSlot == null) {
+            return null;
+        }
+        Object result = null;
+        if (isMissingProfile == null) {
+            seenValueKinds = new boolean[FrameSlotKind.values().length];
+            valueProfile = ValueProfile.createClassProfile();
+            isNullProfile = ConditionProfile.createBinaryProfile();
+            isMissingProfile = ConditionProfile.createBinaryProfile();
+        }
+        result = valueProfile.profile(ReadVariableNode.profiledGetValue(seenValueKinds, variableFrame, frameSlot));
+        if (isNullProfile.profile(result == null) || isMissingProfile.profile(result == RMissing.instance)) {
+            return null;
+        }
+        if (forceResult) {
+            if (isPromiseProfile == null) {
+                CompilerDirectives.transferToInterpreterAndInvalidate();
+                isPromiseProfile = ConditionProfile.createBinaryProfile();
+            }
+            if (isPromiseProfile.profile(result instanceof RPromise)) {
+                if (promiseHelper == null) {
+                    CompilerDirectives.transferToInterpreterAndInvalidate();
+                    promiseHelper = insert(new PromiseHelperNode());
+                }
+                result = promiseHelper.evaluate(frame, (RPromise) result);
+            }
+        }
+        return result;
+    }
+}
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/variables/ReadVariableNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/variables/ReadVariableNode.java
index 118ab397931e3ad8fca6f4cf764fae28117cbd48..32e5dfd5096bd8f0b5beda499470abb251322cc3 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/variables/ReadVariableNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/variables/ReadVariableNode.java
@@ -22,23 +22,55 @@
  */
 package com.oracle.truffle.r.nodes.access.variables;
 
-import java.util.*;
+import java.util.ArrayList;
 
-import com.oracle.truffle.api.*;
+import com.oracle.truffle.api.Assumption;
+import com.oracle.truffle.api.CompilerAsserts;
+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.*;
-import com.oracle.truffle.api.nodes.*;
-import com.oracle.truffle.api.source.*;
-import com.oracle.truffle.api.profiles.*;
-import com.oracle.truffle.r.nodes.*;
-import com.oracle.truffle.r.nodes.function.*;
-import com.oracle.truffle.r.runtime.*;
-import com.oracle.truffle.r.runtime.data.*;
-import com.oracle.truffle.r.runtime.data.model.*;
-import com.oracle.truffle.r.runtime.env.*;
-import com.oracle.truffle.r.runtime.env.frame.*;
-import com.oracle.truffle.r.runtime.nodes.*;
+import com.oracle.truffle.api.frame.Frame;
+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.FrameSlotTypeException;
+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.InvalidAssumptionException;
+import com.oracle.truffle.api.nodes.NodeUtil;
+import com.oracle.truffle.api.nodes.SlowPathException;
+import com.oracle.truffle.api.profiles.BranchProfile;
+import com.oracle.truffle.api.profiles.ConditionProfile;
+import com.oracle.truffle.api.profiles.ValueProfile;
+import com.oracle.truffle.api.source.SourceSection;
+import com.oracle.truffle.r.nodes.RASTUtils;
+import com.oracle.truffle.r.nodes.function.PromiseHelperNode;
+import com.oracle.truffle.r.runtime.AnonymousFrameVariable;
+import com.oracle.truffle.r.runtime.ArgumentsSignature;
+import com.oracle.truffle.r.runtime.FastROptions;
+import com.oracle.truffle.r.runtime.RArguments;
+import com.oracle.truffle.r.runtime.RDeparse;
+import com.oracle.truffle.r.runtime.RError;
+import com.oracle.truffle.r.runtime.RInternalError;
+import com.oracle.truffle.r.runtime.RRuntime;
+import com.oracle.truffle.r.runtime.RSerialize;
+import com.oracle.truffle.r.runtime.RType;
+import com.oracle.truffle.r.runtime.StableValue;
+import com.oracle.truffle.r.runtime.VisibilityController;
+import com.oracle.truffle.r.runtime.data.RArgsValuesAndNames;
+import com.oracle.truffle.r.runtime.data.RDataFactory;
+import com.oracle.truffle.r.runtime.data.RFunction;
+import com.oracle.truffle.r.runtime.data.RMissing;
+import com.oracle.truffle.r.runtime.data.RNull;
+import com.oracle.truffle.r.runtime.data.RPromise;
+import com.oracle.truffle.r.runtime.data.model.RAbstractVector;
+import com.oracle.truffle.r.runtime.env.REnvironment;
+import com.oracle.truffle.r.runtime.env.frame.FrameSlotChangeMonitor;
+import com.oracle.truffle.r.runtime.env.frame.FrameSlotChangeMonitor.FrameAndSlotLookupResult;
+import com.oracle.truffle.r.runtime.env.frame.FrameSlotChangeMonitor.LookupResult;
+import com.oracle.truffle.r.runtime.nodes.RNode;
+import com.oracle.truffle.r.runtime.nodes.RSyntaxNode;
 
 /**
  * This node is used to read a variable from the local or enclosing environments. It specializes to
@@ -47,73 +79,49 @@ import com.oracle.truffle.r.runtime.nodes.*;
  */
 public final class ReadVariableNode extends RNode implements RSyntaxNode, VisibilityController {
 
-    public static enum ReadKind {
+    private static final int MAX_INVALIDATION_COUNT = 2;
+
+    private enum ReadKind {
         Normal,
-        Unforced,
         // return null (instead of throwing an error) if not found
         Silent,
         // copy semantics
         Copying,
         // start the lookup in the enclosing frame
         Super,
-        // lookup only within the current frame
-        SilentLocal,
-        UnforcedSilentLocal,
-        // whether a promise should be forced to check its type or not
+        // whether a promise should be forced to check its type or not during lookup
         ForcedTypeCheck;
     }
 
-    public static ReadVariableNode create(String name, RType mode, ReadKind kind) {
-        return new ReadVariableNode(name, mode, kind, true);
-    }
-
-    public static ReadVariableNode create(String name, boolean shouldCopyValue) {
-        return new ReadVariableNode(name, RType.Any, shouldCopyValue ? ReadKind.Copying : ReadKind.Normal, true);
+    public static ReadVariableNode create(String name) {
+        return new ReadVariableNode(name, RType.Any, ReadKind.Normal);
     }
 
     public static ReadVariableNode create(SourceSection src, String name, boolean shouldCopyValue) {
-        ReadVariableNode rvn = new ReadVariableNode(name, RType.Any, shouldCopyValue ? ReadKind.Copying : ReadKind.Normal, true);
+        ReadVariableNode rvn = new ReadVariableNode(name, RType.Any, shouldCopyValue ? ReadKind.Copying : ReadKind.Normal);
         rvn.assignSourceSection(src);
         return rvn;
     }
 
-    public static ReadVariableNode createForRefCount(Object name) {
-        return new ReadVariableNode(name, RType.Any, ReadKind.UnforcedSilentLocal, false);
+    public static ReadVariableNode createSilent(String name, RType mode) {
+        return new ReadVariableNode(name, mode, ReadKind.Silent);
     }
 
-    public static ReadVariableNode createAnonymous(String name) {
-        return new ReadVariableNode(name, RType.Any, ReadKind.Normal, true);
+    public static ReadVariableNode createSuperLookup(SourceSection src, String name) {
+        ReadVariableNode rvn = new ReadVariableNode(name, RType.Any, ReadKind.Super);
+        rvn.assignSourceSection(src);
+        return rvn;
     }
 
-    /**
-     * Creates a function lookup for the given identifier.
-     */
     public static ReadVariableNode createFunctionLookup(SourceSection src, String identifier) {
-        ReadVariableNode result = new ReadVariableNode(identifier, RType.Function, ReadKind.Normal, true);
+        ReadVariableNode result = new ReadVariableNode(identifier, RType.Function, ReadKind.Normal);
         result.assignSourceSection(src);
         return result;
     }
 
-    public static ReadVariableNode createSuperLookup(SourceSection src, String name) {
-        ReadVariableNode rvn = new ReadVariableNode(name, RType.Any, ReadKind.Super, true);
-        rvn.assignSourceSection(src);
-        return rvn;
-    }
-
-    /**
-     * Creates every {@link ReadVariableNode} out there.
-     *
-     * @param src A {@link SourceSection} for the variable
-     * @param name The symbol the {@link ReadVariableNode} is meant to resolve
-     * @param mode The mode of the variable
-     *
-     * @return The appropriate implementation of {@link ReadVariableNode}
-     */
-    public static ReadVariableNode createForced(SourceSection src, String name, RType mode) {
-        ReadVariableNode result = new ReadVariableNode(name, mode, ReadKind.ForcedTypeCheck, true);
-        if (src != null) {
-            result.assignSourceSection(src);
-        }
+    public static ReadVariableNode createForcedFunctionLookup(SourceSection src, String name) {
+        ReadVariableNode result = new ReadVariableNode(name, RType.Function, ReadKind.ForcedTypeCheck);
+        result.assignSourceSection(src);
         return result;
     }
 
@@ -121,30 +129,26 @@ public final class ReadVariableNode extends RNode implements RSyntaxNode, Visibi
     @CompilationFinal private FrameLevel read;
     @CompilationFinal private boolean needsCopying;
 
-    private final ConditionProfile isPromiseProfile;
+    private final ConditionProfile isPromiseProfile = ConditionProfile.createBinaryProfile();
     private final ConditionProfile copyProfile;
     private final BranchProfile unexpectedMissingProfile = BranchProfile.create();
     private final ValueProfile superEnclosingFrameProfile = ValueProfile.createClassProfile();
-    private final ConditionProfile isNullValueProfile = ConditionProfile.createBinaryProfile();
-    private final ValueProfile valueProfile = ValueProfile.createClassProfile();
 
     private final Object identifier;
     private final String identifierAsString;
     private final RType mode;
     private final ReadKind kind;
-    private final boolean visibilityChange;
+    private int invalidationCount;
 
     @CompilationFinal private final boolean[] seenValueKinds = new boolean[FrameSlotKind.values().length];
 
-    private ReadVariableNode(Object identifier, RType mode, ReadKind kind, boolean visibilityChange) {
+    private ReadVariableNode(Object identifier, RType mode, ReadKind kind) {
         this.identifier = identifier;
         this.identifierAsString = identifier.toString().intern();
         this.mode = mode;
         this.kind = kind;
-        this.visibilityChange = visibilityChange;
 
-        isPromiseProfile = kind == ReadKind.UnforcedSilentLocal && mode == RType.Any ? null : ConditionProfile.createBinaryProfile();
-        copyProfile = kind != ReadKind.Copying ? null : ConditionProfile.createBinaryProfile();
+        this.copyProfile = kind != ReadKind.Copying ? null : ConditionProfile.createBinaryProfile();
     }
 
     public String getIdentifier() {
@@ -155,14 +159,6 @@ public final class ReadVariableNode extends RNode implements RSyntaxNode, Visibi
         return mode;
     }
 
-    public ReadKind getKind() {
-        return kind;
-    }
-
-    private boolean seenValueKind(FrameSlotKind slotKind) {
-        return seenValueKinds[slotKind.ordinal()];
-    }
-
     @Override
     public void deparseImpl(RDeparse.State state) {
         state.startNodeDeparse(this);
@@ -218,7 +214,7 @@ public final class ReadVariableNode extends RNode implements RSyntaxNode, Visibi
     }
 
     private Object executeInternal(VirtualFrame frame, Frame variableFrame) {
-        if (visibilityChange) {
+        if (kind != ReadKind.Silent) {
             controlVisibility();
         }
 
@@ -237,7 +233,7 @@ public final class ReadVariableNode extends RNode implements RSyntaxNode, Visibi
                     result = read.execute(frame, variableFrame);
                 } catch (InvalidAssumptionException | LayoutChangedException | FrameSlotTypeException e2) {
                     if (iterations > 10) {
-                        throw new RInternalError("too many iterations during RVN initialization");
+                        throw new RInternalError("too many iterations during RVN initialization: " + identifier);
                     }
                     continue;
                 }
@@ -247,7 +243,7 @@ public final class ReadVariableNode extends RNode implements RSyntaxNode, Visibi
         if (needsCopying && copyProfile.profile(result instanceof RAbstractVector)) {
             result = ((RAbstractVector) result).copy();
         }
-        if (kind != ReadKind.UnforcedSilentLocal && kind != ReadKind.Unforced && isPromiseProfile.profile(result instanceof RPromise)) {
+        if (isPromiseProfile.profile(result instanceof RPromise)) {
             if (promiseHelper == null) {
                 CompilerDirectives.transferToInterpreterAndInvalidate();
                 promiseHelper = insert(new PromiseHelperNode());
@@ -289,17 +285,14 @@ public final class ReadVariableNode extends RNode implements RSyntaxNode, Visibi
         private final FrameSlot slot;
         private final ConditionProfile isNullProfile = ConditionProfile.createBinaryProfile();
 
-        public Mismatch(FrameLevel next, FrameSlot slot) {
+        private Mismatch(FrameLevel next, FrameSlot slot) {
             this.next = next;
             this.slot = slot;
         }
 
         @Override
         public Object execute(VirtualFrame frame, Frame variableFrame) throws InvalidAssumptionException, LayoutChangedException, FrameSlotTypeException {
-            Object value = profiledGetValue(variableFrame, slot);
-            if ((kind == ReadKind.UnforcedSilentLocal || kind == ReadKind.SilentLocal) && value == RMissing.instance) {
-                return null;
-            }
+            Object value = profiledGetValue(seenValueKinds, variableFrame, slot);
             if (checkType(frame, value, isNullProfile)) {
                 CompilerDirectives.transferToInterpreterAndInvalidate();
                 throw new LayoutChangedException();
@@ -318,7 +311,7 @@ public final class ReadVariableNode extends RNode implements RSyntaxNode, Visibi
         private final Assumption assumption;
         private final Object value;
 
-        public DescriptorStableMatch(Assumption assumption, Object value) {
+        private DescriptorStableMatch(Assumption assumption, Object value) {
             this.assumption = assumption;
             this.value = value;
         }
@@ -340,16 +333,13 @@ public final class ReadVariableNode extends RNode implements RSyntaxNode, Visibi
         private final FrameSlot slot;
         private final ConditionProfile isNullProfile = ConditionProfile.createBinaryProfile();
 
-        public Match(FrameSlot slot) {
+        private Match(FrameSlot slot) {
             this.slot = slot;
         }
 
         @Override
         public Object execute(VirtualFrame frame, Frame variableFrame) throws LayoutChangedException, FrameSlotTypeException {
-            Object value = profiledGetValue(variableFrame, slot);
-            if ((kind == ReadKind.UnforcedSilentLocal || kind == ReadKind.SilentLocal) && value == RMissing.instance) {
-                return null;
-            }
+            Object value = profiledGetValue(seenValueKinds, variableFrame, slot);
             if (!checkType(frame, value, isNullProfile)) {
                 throw new LayoutChangedException();
             }
@@ -366,7 +356,7 @@ public final class ReadVariableNode extends RNode implements RSyntaxNode, Visibi
 
         @Override
         public Object execute(VirtualFrame frame) {
-            if (kind == ReadKind.Silent || kind == ReadKind.SilentLocal || kind == ReadKind.UnforcedSilentLocal) {
+            if (kind == ReadKind.Silent) {
                 return null;
             } else {
                 throw RError.error(RError.NO_NODE, mode == RType.Function ? RError.Message.UNKNOWN_FUNCTION : RError.Message.UNKNOWN_OBJECT, identifier);
@@ -379,20 +369,19 @@ public final class ReadVariableNode extends RNode implements RSyntaxNode, Visibi
         }
     }
 
-    private static final class NextFrameFromDescriptorLevel extends DescriptorLevel {
+    private static final class SingletonFrameLevel extends DescriptorLevel {
 
         private final FrameLevel next;
-        private final StableValue<MaterializedFrame> enclosingFrameAssumption;
+        private final MaterializedFrame singletonFrame;
 
-        public NextFrameFromDescriptorLevel(FrameLevel next, StableValue<MaterializedFrame> enclosingFrameAssumption) {
+        private SingletonFrameLevel(FrameLevel next, MaterializedFrame singletonFrame) {
             this.next = next;
-            this.enclosingFrameAssumption = enclosingFrameAssumption;
+            this.singletonFrame = singletonFrame;
         }
 
         @Override
         public Object execute(VirtualFrame frame) throws InvalidAssumptionException, LayoutChangedException, FrameSlotTypeException {
-            enclosingFrameAssumption.getAssumption().check();
-            return next.execute(frame, enclosingFrameAssumption.getValue());
+            return next.execute(frame, singletonFrame);
         }
 
         @Override
@@ -407,7 +396,7 @@ public final class ReadVariableNode extends RNode implements RSyntaxNode, Visibi
         private final FrameDescriptor nextDescriptor;
         private final ValueProfile frameProfile = ValueProfile.createClassProfile();
 
-        public NextFrameLevel(FrameLevel next, FrameDescriptor nextDescriptor) {
+        private NextFrameLevel(FrameLevel next, FrameDescriptor nextDescriptor) {
             this.next = next;
             this.nextDescriptor = nextDescriptor;
         }
@@ -442,7 +431,7 @@ public final class ReadVariableNode extends RNode implements RSyntaxNode, Visibi
         private final FrameLevel next;
         @CompilationFinal private final Assumption[] assumptions;
 
-        public MultiAssumptionLevel(FrameLevel next, Assumption[] assumptions) {
+        private MultiAssumptionLevel(FrameLevel next, Assumption... assumptions) {
             this.next = next;
             this.assumptions = assumptions;
         }
@@ -462,135 +451,214 @@ public final class ReadVariableNode extends RNode implements RSyntaxNode, Visibi
         }
     }
 
-    private FrameLevel initialize(VirtualFrame frame, Frame variableFrame) {
-        if (identifier.toString().isEmpty()) {
-            throw RError.error(RError.NO_NODE, RError.Message.ZERO_LENGTH_VARIABLE);
-        }
-
-        class ReadVariableLevel {
-            public final FrameDescriptor descriptor;
-            public final FrameSlot slot;
-            public final StableValue<Object> valueAssumption;
-
-            public final StableValue<FrameDescriptor> enclosingDescriptorAssumption;
-            public final StableValue<MaterializedFrame> enclosingFrameAssumption;
-
-            public final MaterializedFrame nextFrame;
+    private final class Polymorphic extends FrameLevel {
 
-            public ReadVariableLevel(FrameDescriptor descriptor, FrameSlot slot, StableValue<Object> valueAssumption, StableValue<FrameDescriptor> enclosingDescriptorAssumption,
-                            StableValue<MaterializedFrame> enclosingFrameAssumption, MaterializedFrame nextFrame) {
-                this.descriptor = descriptor;
-                this.slot = slot;
-                this.valueAssumption = valueAssumption;
-                this.enclosingDescriptorAssumption = enclosingDescriptorAssumption;
-                this.enclosingFrameAssumption = enclosingFrameAssumption;
-                this.nextFrame = nextFrame;
-            }
+        private final ConditionProfile isNullProfile = ConditionProfile.createBinaryProfile();
+        @CompilationFinal private FrameSlot frameSlot;
+        @CompilationFinal private Assumption notInFrame;
 
-            public FrameDescriptor nextDescriptor() {
-                return nextFrame == null ? null : nextFrame.getFrameDescriptor();
-            }
+        private Polymorphic(Frame variableFrame) {
+            frameSlot = variableFrame.getFrameDescriptor().findFrameSlot(identifier);
+            notInFrame = frameSlot == null ? variableFrame.getFrameDescriptor().getNotInFrameAssumption(identifier) : null;
         }
 
-        ArrayList<ReadVariableLevel> levels = new ArrayList<>();
-
-        Frame current = variableFrame;
-        FrameDescriptor currentDescriptor = current.getFrameDescriptor();
-        boolean match = false;
-        do {
-            // see if the current frame has a value of the given name
-            FrameSlot frameSlot = currentDescriptor.findFrameSlot(identifier);
-            StableValue<Object> valueAssumption = null;
+        @Override
+        public Object execute(VirtualFrame frame, Frame variableFrame) throws LayoutChangedException, FrameSlotTypeException {
+            // check if the slot is missing / wrong type in current frame
+            if (frameSlot == null) {
+                try {
+                    notInFrame.check();
+                } catch (InvalidAssumptionException e) {
+                    frameSlot = variableFrame.getFrameDescriptor().findFrameSlot(identifier);
+                    notInFrame = frameSlot == null ? variableFrame.getFrameDescriptor().getNotInFrameAssumption(identifier) : null;
+                }
+            }
             if (frameSlot != null) {
-                Object value = getValue(current, frameSlot);
-                valueAssumption = FrameSlotChangeMonitor.getStableValueAssumption(currentDescriptor, frameSlot, value);
-                if ((kind == ReadKind.UnforcedSilentLocal || kind == ReadKind.SilentLocal) && value == RMissing.instance) {
-                    match = false;
-                } else {
-                    match = checkTypeSlowPath(frame, value);
+                Object value = variableFrame.getValue(frameSlot);
+                if (checkType(frame, value, isNullProfile)) {
+                    return value;
                 }
             }
-
-            // figure out how to get to the next frame or descriptor
-            MaterializedFrame next = RArguments.getEnclosingFrame(current);
-            FrameDescriptor nextDescriptor = next == null ? null : next.getFrameDescriptor();
-            StableValue<MaterializedFrame> enclosingFrameAssumption = FrameSlotChangeMonitor.getOrInitializeEnclosingFrameAssumption(current, currentDescriptor, null, next);
-            StableValue<FrameDescriptor> enclosingDescriptorAssumption = FrameSlotChangeMonitor.getOrInitializeEnclosingFrameDescriptorAssumption(current, currentDescriptor, nextDescriptor);
-
-            levels.add(new ReadVariableLevel(currentDescriptor, frameSlot, valueAssumption, enclosingDescriptorAssumption, enclosingFrameAssumption, next));
-
-            current = next;
-            currentDescriptor = nextDescriptor;
-        } while (kind != ReadKind.UnforcedSilentLocal && kind != ReadKind.SilentLocal && current != null && !match);
-
-        FrameLevel lastLevel = null;
-
-        boolean complex = false;
-        ListIterator<ReadVariableLevel> iter = levels.listIterator(levels.size());
-        if (match) {
-            ReadVariableLevel level = levels.get(levels.size() - 1);
-            if (level.valueAssumption != null) {
-                Assumption assumption = level.valueAssumption.getAssumption();
-                Object value = level.valueAssumption.getValue();
-                if (kind != ReadKind.UnforcedSilentLocal && value instanceof RPromise) {
-                    RPromise promise = (RPromise) value;
-                    Object promiseValue = PromiseHelperNode.evaluateSlowPath(frame, promise);
-                    if (promiseValue instanceof RFunction) {
-                        value = promiseValue;
-                    }
+            // search enclosing frames if necessary
+            MaterializedFrame current = RArguments.getEnclosingFrame(variableFrame);
+            while (current != null) {
+                Object value = getValue(current);
+                if (checkType(frame, value, isNullProfile)) {
+                    return value;
                 }
-                lastLevel = new DescriptorStableMatch(assumption, value);
+                current = RArguments.getEnclosingFrame(current);
+            }
+            if (kind == ReadKind.Silent) {
+                return null;
             } else {
-                complex = true;
-                lastLevel = new Match(level.slot);
+                throw RError.error(RError.NO_NODE, mode == RType.Function ? RError.Message.UNKNOWN_FUNCTION : RError.Message.UNKNOWN_OBJECT, identifier);
             }
-            iter.previous();
-        } else {
-            lastLevel = new Unknown();
         }
 
-        ArrayList<Assumption> assumptions = new ArrayList<>();
-        while (iter.hasPrevious()) {
-            ReadVariableLevel level = iter.previous();
-            if (lastLevel instanceof DescriptorLevel) {
-                if (level.enclosingDescriptorAssumption != null) {
-                    assumptions.add(level.enclosingDescriptorAssumption.getAssumption());
-                } else {
-                    complex = true;
-                    lastLevel = new NextFrameLevel(lastLevel, level.nextDescriptor());
-                }
+        @TruffleBoundary
+        private Object getValue(MaterializedFrame current) {
+            FrameSlot slot = current.getFrameDescriptor().findFrameSlot(identifier);
+            return slot == null ? null : current.getValue(slot);
+        }
+
+        @Override
+        public String toString() {
+            return "P";
+        }
+    }
+
+    private final class LookupLevel extends DescriptorLevel {
+
+        private final LookupResult lookup;
+
+        private LookupLevel(LookupResult lookup) {
+            this.lookup = lookup;
+        }
+
+        @Override
+        public Object execute(VirtualFrame frame) throws InvalidAssumptionException, LayoutChangedException, FrameSlotTypeException {
+            Object value;
+            if (lookup instanceof FrameAndSlotLookupResult) {
+                FrameAndSlotLookupResult frameAndSlotLookupResult = (FrameAndSlotLookupResult) lookup;
+                value = profiledGetValue(seenValueKinds, frameAndSlotLookupResult.getFrame(), frameAndSlotLookupResult.getSlot());
             } else {
-                if (level.enclosingFrameAssumption != null) {
-                    lastLevel = new NextFrameFromDescriptorLevel(lastLevel, level.enclosingFrameAssumption);
-                } else {
-                    complex = true;
-                    lastLevel = new NextFrameLevel(lastLevel, level.nextDescriptor());
-                }
+                value = lookup.getValue();
             }
+            if (kind != ReadKind.Silent && value == null) {
+                throw RError.error(RError.NO_NODE, mode == RType.Function ? RError.Message.UNKNOWN_FUNCTION : RError.Message.UNKNOWN_OBJECT, identifier);
+            }
+            return value;
+        }
+    }
 
-            if (level.slot == null) {
-                if (lastLevel instanceof DescriptorLevel) {
-                    assumptions.add(level.descriptor.getNotInFrameAssumption(identifier));
-                } else {
-                    assumptions.add(level.descriptor.getNotInFrameAssumption(identifier));
+    private FrameLevel initialize(VirtualFrame frame, Frame variableFrame) {
+        if (identifier.toString().isEmpty()) {
+            throw RError.error(RError.NO_NODE, RError.Message.ZERO_LENGTH_VARIABLE);
+        }
+
+        /*
+         * Check whether we need to go to the polymorphic case, which will not rely on any frame
+         * descriptor assumptions (apart from the first frame).
+         */
+        if (++invalidationCount > MAX_INVALIDATION_COUNT) {
+            RError.performanceWarning("polymorphic (slow path) lookup of symbol \"" + identifier + "\"");
+            return new Polymorphic(variableFrame);
+        }
+
+        /*
+         * Check whether we can fulfill the lookup by only looking at the current frame, and thus
+         * without additional dependencies on frame descriptor layouts.
+         */
+        FrameSlot localSlot = variableFrame.getFrameDescriptor().findFrameSlot(identifier);
+        // non-local reads can only be handled in a simple way if they are successful
+        if (localSlot != null && checkTypeSlowPath(frame, getValue(seenValueKinds, variableFrame, localSlot))) {
+            return new Match(localSlot);
+        }
+
+        /*
+         * Check whether the frame descriptor information available in FrameSlotChangeMonitor is
+         * enough to handle this lookup. This has the advantage of not depending on a specific
+         * "enclosing frame descriptor" chain, so that attaching/detaching environments does not
+         * necessarily invalidate lookups.
+         */
+        LookupResult lookup = FrameSlotChangeMonitor.lookup(variableFrame, identifier);
+        if (lookup != null) {
+            try {
+                if (lookup.getValue() == null) {
+                    return new LookupLevel(lookup);
                 }
-            } else {
-                if (level.valueAssumption != null && lastLevel instanceof DescriptorLevel) {
-                    assumptions.add(level.valueAssumption.getAssumption());
-                } else {
-                    complex = true;
-                    lastLevel = new Mismatch(lastLevel, level.slot);
+                if (checkTypeSlowPath(frame, lookup.getValue())) {
+                    return new LookupLevel(lookup);
                 }
+            } catch (InvalidAssumptionException e) {
+                // immediately invalidated...
             }
         }
+
+        /*
+         * If everything else fails: build the lookup from scratch, by recursively building
+         * assumptions and checks.
+         */
+        ArrayList<Assumption> assumptions = new ArrayList<>();
+        FrameLevel lastLevel = createLevels(frame, variableFrame, assumptions);
         if (!assumptions.isEmpty()) {
             lastLevel = new MultiAssumptionLevel(lastLevel, assumptions.toArray(new Assumption[assumptions.size()]));
         }
 
-        if (FastROptions.PrintComplexLookups.getBooleanValue() && levels.size() > 1 && complex) {
+        if (FastROptions.PrintComplexLookups.getBooleanValue()) {
             System.out.println(identifier + " " + lastLevel);
         }
+        return lastLevel;
+    }
 
+    /**
+     * This function returns a "recipe" to find the value of this lookup, starting at varibleFrame.
+     * It may record assumptions into the given ArrayList of assumptions. The result will be a
+     * linked list of {@link FrameLevel} instances.
+     */
+    private FrameLevel createLevels(VirtualFrame frame, Frame variableFrame, ArrayList<Assumption> assumptions) {
+        if (variableFrame == null) {
+            // this means that we've arrived at the empty env during lookup
+            return new Unknown();
+        }
+        // see if the current frame has a value of the given name
+        FrameDescriptor currentDescriptor = variableFrame.getFrameDescriptor();
+        FrameSlot frameSlot = currentDescriptor.findFrameSlot(identifier);
+        if (frameSlot != null) {
+            Object value = getValue(seenValueKinds, variableFrame, frameSlot);
+            if (checkTypeSlowPath(frame, value)) {
+                StableValue<Object> valueAssumption = FrameSlotChangeMonitor.getStableValueAssumption(currentDescriptor, frameSlot, value);
+                if (valueAssumption != null) {
+                    Assumption assumption = valueAssumption.getAssumption();
+                    assert value == valueAssumption.getValue() || value.equals(valueAssumption.getValue()) : value + " vs. " + valueAssumption.getValue();
+                    if (value instanceof RPromise) {
+                        RPromise promise = (RPromise) value;
+                        Object promiseValue = PromiseHelperNode.evaluateSlowPath(frame, promise);
+                        if (promiseValue instanceof RFunction) {
+                            value = promiseValue;
+                        }
+                    }
+                    return new DescriptorStableMatch(assumption, value);
+                } else {
+                    return new Match(frameSlot);
+                }
+            }
+        }
+
+        // the identifier wasn't found in the current frame: try the next one
+        MaterializedFrame next = RArguments.getEnclosingFrame(variableFrame);
+        FrameLevel lastLevel = createLevels(frame, next, assumptions);
+
+        /*
+         * Here we look at the type of the recursive lookup result, to see if we need only a
+         * specific FrameDescriptor (DescriptorLevel) or the actual frame (FrameLevel).
+         */
+
+        if (!(lastLevel instanceof DescriptorLevel)) {
+            MaterializedFrame singleton = FrameSlotChangeMonitor.getSingletonFrame(next.getFrameDescriptor());
+            if (singleton != null) {
+                // use singleton frames to get from a frame descriptor to an actual frame
+                lastLevel = new SingletonFrameLevel(lastLevel, singleton);
+            }
+        }
+
+        StableValue<FrameDescriptor> enclosingDescriptorAssumption = FrameSlotChangeMonitor.getEnclosingFrameDescriptorAssumption(currentDescriptor);
+        if (lastLevel instanceof DescriptorLevel && enclosingDescriptorAssumption != null) {
+            assumptions.add(enclosingDescriptorAssumption.getAssumption());
+        } else {
+            lastLevel = new NextFrameLevel(lastLevel, next == null ? null : next.getFrameDescriptor());
+        }
+
+        if (frameSlot == null) {
+            assumptions.add(currentDescriptor.getNotInFrameAssumption(identifier));
+        } else {
+            StableValue<Object> valueAssumption = FrameSlotChangeMonitor.getStableValueAssumption(currentDescriptor, frameSlot, getValue(seenValueKinds, variableFrame, frameSlot));
+            if (valueAssumption != null && lastLevel instanceof DescriptorLevel) {
+                assumptions.add(valueAssumption.getAssumption());
+            } else {
+                lastLevel = new Mismatch(lastLevel, frameSlot);
+            }
+        }
         return lastLevel;
     }
 
@@ -653,7 +721,7 @@ public final class ReadVariableNode extends RNode implements RSyntaxNode, Visibi
         return null;
     }
 
-    private Object getValue(Frame variableFrame, FrameSlot frameSlot) {
+    private static Object getValue(boolean[] seenValueKinds, Frame variableFrame, FrameSlot frameSlot) {
         Object value = variableFrame.getValue(frameSlot);
         if (variableFrame.isObject(frameSlot)) {
             seenValueKinds[FrameSlotKind.Object.ordinal()] = true;
@@ -667,18 +735,23 @@ public final class ReadVariableNode extends RNode implements RSyntaxNode, Visibi
         return value;
     }
 
-    private Object profiledGetValue(Frame variableFrame, FrameSlot frameSlot) throws FrameSlotTypeException {
-        if (seenValueKind(FrameSlotKind.Object) && variableFrame.isObject(frameSlot)) {
-            Object result = variableFrame.getObject(frameSlot);
-            return isNullValueProfile.profile(result == null) ? null : valueProfile.profile(result);
-        } else if (seenValueKind(FrameSlotKind.Byte) && variableFrame.isByte(frameSlot)) {
-            return variableFrame.getByte(frameSlot);
-        } else if (seenValueKind(FrameSlotKind.Int) && variableFrame.isInt(frameSlot)) {
-            return variableFrame.getInt(frameSlot);
-        } else if (seenValueKind(FrameSlotKind.Double) && variableFrame.isDouble(frameSlot)) {
-            return variableFrame.getDouble(frameSlot);
-        } else {
-            throw new FrameSlotTypeException();
+    static Object profiledGetValue(boolean[] seenValueKinds, Frame variableFrame, FrameSlot frameSlot) {
+        try {
+            if (seenValueKinds[FrameSlotKind.Object.ordinal()] && variableFrame.isObject(frameSlot)) {
+                return variableFrame.getObject(frameSlot);
+            } else if (seenValueKinds[FrameSlotKind.Byte.ordinal()] && variableFrame.isByte(frameSlot)) {
+                return variableFrame.getByte(frameSlot);
+            } else if (seenValueKinds[FrameSlotKind.Int.ordinal()] && variableFrame.isInt(frameSlot)) {
+                return variableFrame.getInt(frameSlot);
+            } else if (seenValueKinds[FrameSlotKind.Double.ordinal()] && variableFrame.isDouble(frameSlot)) {
+                return variableFrame.getDouble(frameSlot);
+            } else {
+                CompilerDirectives.transferToInterpreterAndInvalidate();
+                // re-profile to widen the set of expected types
+                return getValue(seenValueKinds, variableFrame, frameSlot);
+            }
+        } catch (FrameSlotTypeException e) {
+            throw new RInternalError(e, "unexpected frame slot type mismatch");
         }
     }
 
@@ -773,4 +846,8 @@ public final class ReadVariableNode extends RNode implements RSyntaxNode, Visibi
     public static String getSlowPathEvaluationName() {
         return slowPathEvaluationName.get();
     }
+
+    public boolean isForceForTypeCheck() {
+        return kind == ReadKind.ForcedTypeCheck;
+    }
 }
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/builtin/RBuiltinNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/builtin/RBuiltinNode.java
index abf25dc6adbd826251d160da2e1c18edb93431b7..8a5f58ec773d5c847ec6dd085519c8875b97f1df 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/builtin/RBuiltinNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/builtin/RBuiltinNode.java
@@ -218,7 +218,7 @@ public abstract class RBuiltinNode extends RNode implements RSyntaxNode, Visibil
         // Setup
         FrameDescriptor frameDescriptor = new FrameDescriptor();
         RBuiltinRootNode root = new RBuiltinRootNode(node, formals, frameDescriptor);
-        FrameSlotChangeMonitor.initializeFunctionFrameDescriptor(frameDescriptor);
+        FrameSlotChangeMonitor.initializeFunctionFrameDescriptor(builtin.getName(), frameDescriptor);
         return Truffle.getRuntime().createCallTarget(root);
     }
 
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/builtin/RBuiltinRootNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/builtin/RBuiltinRootNode.java
index c1b14a97dde3c78597f7cb39b107597e8158d206..4964dbb441a3b501b716a5aabc44010b06151e75 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/builtin/RBuiltinRootNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/builtin/RBuiltinRootNode.java
@@ -30,6 +30,7 @@ import com.oracle.truffle.r.nodes.function.*;
 import com.oracle.truffle.r.runtime.*;
 import com.oracle.truffle.r.runtime.RDeparse.State;
 import com.oracle.truffle.r.runtime.env.REnvironment;
+import com.oracle.truffle.r.runtime.env.frame.FrameSlotChangeMonitor;
 import com.oracle.truffle.r.runtime.nodes.*;
 
 public final class RBuiltinRootNode extends RRootNode implements RSyntaxNode {
@@ -41,6 +42,13 @@ public final class RBuiltinRootNode extends RRootNode implements RSyntaxNode {
         this.builtin = builtin;
     }
 
+    @Override
+    public RRootNode duplicateWithNewFrameDescriptor() {
+        FrameDescriptor frameDescriptor = new FrameDescriptor();
+        FrameSlotChangeMonitor.initializeFunctionFrameDescriptor(builtin.getBuiltin().getName(), frameDescriptor);
+        return new RBuiltinRootNode((RBuiltinNode) builtin.deepCopy(), getFormalArguments(), frameDescriptor);
+    }
+
     @Override
     public Object execute(VirtualFrame frame) {
         verifyEnclosingAssumptions(frame);
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/control/ForNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/control/ForNode.java
index 79d514333e01ca9fdb437c183737f695d9f851b8..2daa99a8d845f2f32b5b54c263b03d9a201baa07 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/control/ForNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/control/ForNode.java
@@ -54,7 +54,7 @@ public final class ForNode extends AbstractLoopNode implements VisibilityControl
 
         this.writeIndexNode = WriteVariableNode.createAnonymous(indexName, null, Mode.REGULAR);
         this.writeRangeNode = WriteVariableNode.createAnonymous(rangeName, range, Mode.REGULAR);
-        this.writeLengthNode = WriteVariableNode.createAnonymous(lengthName, RLengthNodeGen.create(ReadVariableNode.create(rangeName, false)), Mode.REGULAR);
+        this.writeLengthNode = WriteVariableNode.createAnonymous(lengthName, RLengthNodeGen.create(ReadVariableNode.create(rangeName)), Mode.REGULAR);
         this.loopNode = Truffle.getRuntime().createLoopNode(new ForRepeatingNode(this, cvar, body, indexName, lengthName, rangeName));
     }
 
@@ -174,8 +174,8 @@ public final class ForNode extends AbstractLoopNode implements VisibilityControl
             this.writeElementNode = cvar;
             this.body = body;
 
-            this.readIndexNode = ReadVariableNode.createAnonymous(indexName);
-            this.readLengthNode = ReadVariableNode.createAnonymous(lengthName);
+            this.readIndexNode = ReadVariableNode.create(indexName);
+            this.readLengthNode = ReadVariableNode.create(lengthName);
             this.writeIndexNode = WriteVariableNode.createAnonymous(indexName, null, Mode.REGULAR);
             this.loadElement = createIndexedLoad(indexName, rangeName);
             // pre-initialize the profile so that loop exits to not deoptimize
@@ -190,8 +190,8 @@ public final class ForNode extends AbstractLoopNode implements VisibilityControl
                 throw RInternalError.shouldNotReachHere();
             }
             REnvironment env = RDataFactory.createInternalEnv();
-            env.safePut("i", RDataFactory.createLanguage(ReadVariableNode.createAnonymous(indexName)));
-            env.safePut("x", RDataFactory.createLanguage(ReadVariableNode.createAnonymous(rangeName)));
+            env.safePut("i", RDataFactory.createLanguage(ReadVariableNode.create(indexName)));
+            env.safePut("x", RDataFactory.createLanguage(ReadVariableNode.create(rangeName)));
             return indexNode.substitute(env).asRNode();
         }
 
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/ClassHierarchyNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/ClassHierarchyNode.java
index b7eea96f781c65101f1b815deafccf178422a2ad..2adf0b11b54d51d7e8714ab8591e6e424a8967cb 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/ClassHierarchyNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/ClassHierarchyNode.java
@@ -22,18 +22,36 @@
  */
 package com.oracle.truffle.r.nodes.function;
 
-import com.oracle.truffle.api.*;
+import com.oracle.truffle.api.CompilerDirectives;
 import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
-import com.oracle.truffle.api.dsl.*;
+import com.oracle.truffle.api.dsl.Cached;
+import com.oracle.truffle.api.dsl.Fallback;
+import com.oracle.truffle.api.dsl.Specialization;
 import com.oracle.truffle.api.interop.TruffleObject;
-import com.oracle.truffle.api.profiles.*;
+import com.oracle.truffle.api.profiles.BranchProfile;
+import com.oracle.truffle.api.profiles.ConditionProfile;
+import com.oracle.truffle.api.profiles.ValueProfile;
 import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode;
-import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode.ReadKind;
-import com.oracle.truffle.r.nodes.attributes.*;
-import com.oracle.truffle.r.nodes.unary.*;
-import com.oracle.truffle.r.runtime.*;
+import com.oracle.truffle.r.nodes.attributes.AttributeAccess;
+import com.oracle.truffle.r.nodes.attributes.AttributeAccessNodeGen;
+import com.oracle.truffle.r.nodes.unary.CastToVectorNode;
+import com.oracle.truffle.r.nodes.unary.UnaryNode;
+import com.oracle.truffle.r.runtime.RInternalError;
+import com.oracle.truffle.r.runtime.RRuntime;
 import com.oracle.truffle.r.runtime.context.RContext;
-import com.oracle.truffle.r.runtime.data.*;
+import com.oracle.truffle.r.runtime.data.RAttributable;
+import com.oracle.truffle.r.runtime.data.RAttributeStorage;
+import com.oracle.truffle.r.runtime.data.RAttributes;
+import com.oracle.truffle.r.runtime.data.RComplex;
+import com.oracle.truffle.r.runtime.data.RComplexVector;
+import com.oracle.truffle.r.runtime.data.RDataFactory;
+import com.oracle.truffle.r.runtime.data.RDoubleVector;
+import com.oracle.truffle.r.runtime.data.RFunction;
+import com.oracle.truffle.r.runtime.data.RIntVector;
+import com.oracle.truffle.r.runtime.data.RLogicalVector;
+import com.oracle.truffle.r.runtime.data.RNull;
+import com.oracle.truffle.r.runtime.data.RStringVector;
+import com.oracle.truffle.r.runtime.data.RTypedValue;
 import com.oracle.truffle.r.runtime.env.REnvironment;
 import com.oracle.truffle.r.runtime.nodes.RBaseNode;
 
@@ -141,7 +159,7 @@ abstract class S4Class extends RBaseNode {
 
     public abstract RStringVector executeRStringVector(String classAttr);
 
-    @Child private ReadVariableNode sExtendsForS3Find = ReadVariableNode.create(".extendsForS3", RType.Function, ReadKind.Normal);
+    @Child private ReadVariableNode sExtendsForS3Find = ReadVariableNode.createFunctionLookup(null, ".extendsForS3");
     @Child private CastToVectorNode castToVector = CastToVectorNode.create();
 
     @TruffleBoundary
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 72a6e114e8f9519d668baf8679c0ff6a9c5a9289..b59153173da7e361a83bbbca7f01c7750d4849fc 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
@@ -115,18 +115,26 @@ public final class FunctionDefinitionNode extends RRootNode implements RSyntaxNo
     public FunctionDefinitionNode(SourceSection src, FrameDescriptor frameDesc, BodyNode body, FormalArguments formals, String description, boolean substituteFrame, boolean skipExit,
                     PostProcessArgumentsNode argPostProcess) {
         super(src, formals, frameDesc);
+        assert FrameSlotChangeMonitor.isValidFrameDescriptor(frameDesc);
         this.body = body;
         this.uninitializedBody = body;
         this.description = description;
         this.substituteFrame = substituteFrame;
         this.onExitSlot = skipExit ? null : FrameSlotNode.createInitialized(frameDesc, RFrameSlot.OnExit, false);
         this.uuid = FunctionUIDFactory.get().createUID();
-        this.checkSingletonFrame = !substituteFrame;
         this.needsSplitting = needsAnyBuiltinSplitting();
         this.containsDispatch = containsAnyDispatch(body);
         this.argPostProcess = argPostProcess;
     }
 
+    @Override
+    public RRootNode duplicateWithNewFrameDescriptor() {
+        FrameDescriptor frameDesc = new FrameDescriptor();
+        FrameSlotChangeMonitor.initializeFunctionFrameDescriptor(description != null && !description.isEmpty() ? description : "<function>", frameDesc);
+        return new FunctionDefinitionNode(getSourceSection(), frameDesc, (BodyNode) body.deepCopy(), getFormalArguments(), description, substituteFrame, argPostProcess == null ? null
+                        : (PostProcessArgumentsNode) argPostProcess.deepCopy());
+    }
+
     private static boolean containsAnyDispatch(BodyNode body) {
         NodeCountFilter dispatchingMethodsFilter = node -> {
             if (node instanceof ReadVariableNode) {
@@ -332,10 +340,6 @@ public final class FunctionDefinitionNode extends RRootNode implements RSyntaxNo
         return description == null ? "<no source>" : description;
     }
 
-    public String parentToString() {
-        return super.toString();
-    }
-
     @Override
     public boolean isCloningAllowed() {
         return !substituteFrame;
@@ -373,7 +377,10 @@ public final class FunctionDefinitionNode extends RRootNode implements RSyntaxNo
 
     @Override
     public RSyntaxNode substituteImpl(REnvironment env) {
-        return new FunctionDefinitionNode(null, new FrameDescriptor(), (BodyNode) body.substitute(env).asRNode(), getFormalArguments(), null, substituteFrame, argPostProcess);
+        FrameDescriptor frameDesc = new FrameDescriptor();
+
+        FrameSlotChangeMonitor.initializeFunctionFrameDescriptor("<substituted function>", frameDesc);
+        return new FunctionDefinitionNode(null, frameDesc, (BodyNode) body.substitute(env).asRNode(), getFormalArguments(), null, substituteFrame, argPostProcess);
     }
 
     /**
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/FunctionExpressionNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/FunctionExpressionNode.java
index 9658e8317b3d2ecde6ab2e95b8e1b440ca7fcf81..780327236e7ba103c1cf1f5954340fb711439e0a 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/FunctionExpressionNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/FunctionExpressionNode.java
@@ -22,20 +22,27 @@
  */
 package com.oracle.truffle.r.nodes.function;
 
-import com.oracle.truffle.api.*;
+import com.oracle.truffle.api.CompilerDirectives;
 import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
-import com.oracle.truffle.api.frame.*;
-import com.oracle.truffle.api.nodes.*;
-import com.oracle.truffle.api.source.*;
+import com.oracle.truffle.api.RootCallTarget;
+import com.oracle.truffle.api.Truffle;
+import com.oracle.truffle.api.frame.MaterializedFrame;
+import com.oracle.truffle.api.frame.VirtualFrame;
+import com.oracle.truffle.api.source.SourceSection;
 import com.oracle.truffle.r.nodes.function.PromiseHelperNode.PromiseDeoptimizeFrameNode;
-import com.oracle.truffle.r.nodes.function.opt.*;
-import com.oracle.truffle.r.nodes.instrument.*;
-import com.oracle.truffle.r.runtime.*;
-import com.oracle.truffle.r.runtime.data.*;
-import com.oracle.truffle.r.runtime.env.*;
-import com.oracle.truffle.r.runtime.env.frame.*;
-import com.oracle.truffle.r.runtime.gnur.*;
-import com.oracle.truffle.r.runtime.nodes.*;
+import com.oracle.truffle.r.nodes.function.opt.EagerEvalHelper;
+import com.oracle.truffle.r.nodes.instrument.RInstrument;
+import com.oracle.truffle.r.runtime.RDeparse;
+import com.oracle.truffle.r.runtime.RInternalError;
+import com.oracle.truffle.r.runtime.RSerialize;
+import com.oracle.truffle.r.runtime.data.FastPathFactory;
+import com.oracle.truffle.r.runtime.data.RDataFactory;
+import com.oracle.truffle.r.runtime.data.RFunction;
+import com.oracle.truffle.r.runtime.env.REnvironment;
+import com.oracle.truffle.r.runtime.env.frame.FrameSlotChangeMonitor;
+import com.oracle.truffle.r.runtime.gnur.SEXPTYPE;
+import com.oracle.truffle.r.runtime.nodes.RNode;
+import com.oracle.truffle.r.runtime.nodes.RSyntaxNode;
 
 public final class FunctionExpressionNode extends RNode implements RSyntaxNode {
 
@@ -47,17 +54,13 @@ public final class FunctionExpressionNode extends RNode implements RSyntaxNode {
     private final PromiseDeoptimizeFrameNode deoptFrameNode;
     private final FastPathFactory fastPath;
 
-    @CompilationFinal private StableValue<MaterializedFrame> enclosingFrameAssumption;
-    @CompilationFinal private StableValue<FrameDescriptor> enclosingFrameDescriptorAssumption;
+    @CompilationFinal private boolean initialized = false;
 
     private FunctionExpressionNode(SourceSection src, RootCallTarget callTarget, FastPathFactory fastPath) {
         this.fastPath = fastPath;
         assignSourceSection(src);
         this.callTarget = callTarget;
         this.deoptFrameNode = EagerEvalHelper.optExprs() || EagerEvalHelper.optVars() || EagerEvalHelper.optDefault() ? new PromiseDeoptimizeFrameNode() : null;
-
-        this.enclosingFrameAssumption = FrameSlotChangeMonitor.getEnclosingFrameAssumption(callTarget.getRootNode().getFrameDescriptor());
-        this.enclosingFrameDescriptorAssumption = FrameSlotChangeMonitor.getEnclosingFrameDescriptorAssumption(callTarget.getRootNode().getFrameDescriptor());
     }
 
     @Override
@@ -65,37 +68,6 @@ public final class FunctionExpressionNode extends RNode implements RSyntaxNode {
         return executeFunction(frame);
     }
 
-    protected void verifyEnclosingAssumptions(VirtualFrame enclosingFrame, FrameDescriptor descriptor) {
-        if (enclosingFrameAssumption != null) {
-            try {
-                enclosingFrameAssumption.getAssumption().check();
-            } catch (InvalidAssumptionException e) {
-                enclosingFrameAssumption = FrameSlotChangeMonitor.getEnclosingFrameAssumption(descriptor);
-            }
-            if (enclosingFrameAssumption != null) {
-                if (enclosingFrameAssumption.getValue() != enclosingFrame) {
-                    CompilerDirectives.transferToInterpreterAndInvalidate();
-                    enclosingFrameAssumption = FrameSlotChangeMonitor.getOrInitializeEnclosingFrameAssumption(null, descriptor, enclosingFrameAssumption, enclosingFrame.materialize());
-                }
-            }
-        }
-        if (enclosingFrameDescriptorAssumption != null) {
-            try {
-                enclosingFrameDescriptorAssumption.getAssumption().check();
-            } catch (InvalidAssumptionException e) {
-                enclosingFrameDescriptorAssumption = FrameSlotChangeMonitor.getEnclosingFrameDescriptorAssumption(descriptor);
-            }
-            if (enclosingFrameDescriptorAssumption != null) {
-                FrameDescriptor enclosingFrameDescriptor = enclosingFrame.getFrameDescriptor();
-                if (enclosingFrameDescriptorAssumption.getValue() != enclosingFrameDescriptor) {
-                    CompilerDirectives.transferToInterpreterAndInvalidate();
-                    enclosingFrameDescriptorAssumption.getAssumption().invalidate();
-                    enclosingFrameDescriptorAssumption = FrameSlotChangeMonitor.getOrInitializeEnclosingFrameDescriptorAssumption(null, descriptor, enclosingFrameDescriptor);
-                }
-            }
-        }
-    }
-
     @Override
     public RFunction executeFunction(VirtualFrame frame) {
         MaterializedFrame matFrame = frame.materialize();
@@ -103,7 +75,11 @@ public final class FunctionExpressionNode extends RNode implements RSyntaxNode {
             // Deoptimize every promise which is now in this frame, as it might leave it's stack
             deoptFrameNode.deoptimizeFrame(matFrame);
         }
-        verifyEnclosingAssumptions(frame, callTarget.getRootNode().getFrameDescriptor());
+        if (!initialized) {
+            CompilerDirectives.transferToInterpreterAndInvalidate();
+            FrameSlotChangeMonitor.initializeEnclosingFrame(callTarget.getRootNode().getFrameDescriptor(), frame);
+            initialized = true;
+        }
         boolean containsDispatch = ((FunctionDefinitionNode) callTarget.getRootNode()).containsDispatch();
         RFunction func = RDataFactory.createFunction(RFunction.NO_NAME, callTarget, null, matFrame, fastPath, containsDispatch);
         if (RInstrument.instrumentingEnabled()) {
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/GroupDispatchNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/GroupDispatchNode.java
index a4066e46f3ad54313dc1551ce0fe38dc864e2abd..1e55e597b6353013691ce7b30e738d5b3048f4e3 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/GroupDispatchNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/GroupDispatchNode.java
@@ -16,12 +16,11 @@ import com.oracle.truffle.api.CompilerDirectives;
 import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
 import com.oracle.truffle.api.frame.VirtualFrame;
 import com.oracle.truffle.api.nodes.UnexpectedResultException;
-import com.oracle.truffle.api.source.SourceSection;
 import com.oracle.truffle.api.profiles.ConditionProfile;
+import com.oracle.truffle.api.source.SourceSection;
 import com.oracle.truffle.r.nodes.RASTUtils;
 import com.oracle.truffle.r.nodes.access.ConstantNode;
 import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode;
-import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode.ReadKind;
 import com.oracle.truffle.r.nodes.function.S3FunctionLookupNode.NoGenericMethodException;
 import com.oracle.truffle.r.nodes.function.S3FunctionLookupNode.Result;
 import com.oracle.truffle.r.nodes.runtime.RASTDeparse;
@@ -165,7 +164,7 @@ public final class GroupDispatchNode extends RNode implements RSyntaxNode {
         if (callArgsNode.containsVarArgsSymbol()) {
             if (lookupVarArgs == null) {
                 CompilerDirectives.transferToInterpreterAndInvalidate();
-                lookupVarArgs = insert(ReadVariableNode.create(ArgumentsSignature.VARARG_NAME, RType.Any, ReadKind.Silent));
+                lookupVarArgs = insert(ReadVariableNode.createSilent(ArgumentsSignature.VARARG_NAME, RType.Any));
             }
             try {
                 varArgs = lookupVarArgs.executeRArgsValuesAndNames(frame);
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/PostProcessArgumentsNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/PostProcessArgumentsNode.java
index ef4850ed07dbe617c041311e14947f04a8530eeb..d06ae676833dc376872a2e2ad16dd46ac3c014db 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/PostProcessArgumentsNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/PostProcessArgumentsNode.java
@@ -22,14 +22,17 @@
  */
 package com.oracle.truffle.r.nodes.function;
 
-import com.oracle.truffle.api.*;
+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.*;
-import com.oracle.truffle.api.nodes.*;
-import com.oracle.truffle.api.profiles.*;
-import com.oracle.truffle.r.nodes.access.variables.*;
-import com.oracle.truffle.r.runtime.data.*;
-import com.oracle.truffle.r.runtime.nodes.*;
+import com.oracle.truffle.api.frame.VirtualFrame;
+import com.oracle.truffle.api.nodes.ExplodeLoop;
+import com.oracle.truffle.api.nodes.Node;
+import com.oracle.truffle.api.profiles.ConditionProfile;
+import com.oracle.truffle.r.nodes.access.variables.LocalReadVariableNode;
+import com.oracle.truffle.r.runtime.data.RNull;
+import com.oracle.truffle.r.runtime.data.RShareable;
+import com.oracle.truffle.r.runtime.nodes.RNode;
 
 /**
  * Encapsulates the nodes that decrement reference count incremented when the argument node is
@@ -37,7 +40,7 @@ import com.oracle.truffle.r.runtime.nodes.*;
  */
 public final class PostProcessArgumentsNode extends RNode {
 
-    @Children protected final ReadVariableNode[] sequence;
+    @Children protected final LocalReadVariableNode[] sequence;
     @Child private PostProcessArgumentsNode nextOptPostProccessArgNode;
     @CompilationFinal public int transArgsBitSet;
     // the first time this node is cloned (via FunctionDefinitionNode) it's from the trufflerizer -
@@ -46,7 +49,7 @@ public final class PostProcessArgumentsNode extends RNode {
     @CompilationFinal private boolean createClone;
     private final ConditionProfile isRefCountUpdateable = ConditionProfile.createBinaryProfile();
 
-    private PostProcessArgumentsNode(ReadVariableNode[] sequence) {
+    private PostProcessArgumentsNode(LocalReadVariableNode[] sequence) {
         this.sequence = sequence;
         this.createClone = false;
         this.transArgsBitSet = 0;
@@ -54,9 +57,9 @@ public final class PostProcessArgumentsNode extends RNode {
 
     public static PostProcessArgumentsNode create(int length) {
         int maxLength = Math.min(length, ArgumentStatePush.MAX_COUNTED_ARGS);
-        ReadVariableNode[] argReadNodes = new ReadVariableNode[maxLength];
+        LocalReadVariableNode[] argReadNodes = new LocalReadVariableNode[maxLength];
         for (int i = 0; i < maxLength; i++) {
-            argReadNodes[i] = ReadVariableNode.createForRefCount(Integer.valueOf(1 << i));
+            argReadNodes[i] = LocalReadVariableNode.create(Integer.valueOf(1 << i), false);
         }
         return new PostProcessArgumentsNode(argReadNodes);
     }
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/PromiseNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/PromiseNode.java
index 52a6a597ccfba2534927dc325e7a92d1a696a4d8..e8b7167d331d7dd450416aebac61c81eee502f46 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/PromiseNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/PromiseNode.java
@@ -39,7 +39,6 @@ import com.oracle.truffle.api.profiles.ConditionProfile;
 import com.oracle.truffle.r.nodes.RASTUtils;
 import com.oracle.truffle.r.nodes.access.ConstantNode;
 import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode;
-import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode.ReadKind;
 import com.oracle.truffle.r.nodes.function.PromiseHelperNode.PromiseCheckHelperNode;
 import com.oracle.truffle.r.nodes.function.opt.OptConstantPromiseNode;
 import com.oracle.truffle.r.nodes.function.opt.OptForcedEagerPromiseNode;
@@ -299,7 +298,7 @@ public abstract class PromiseNode extends RNode {
         public RArgsValuesAndNames getVarargsAndNames(VirtualFrame frame) {
             if (lookupVarArgs == null) {
                 CompilerDirectives.transferToInterpreterAndInvalidate();
-                lookupVarArgs = insert(ReadVariableNode.create(ArgumentsSignature.VARARG_NAME, RType.Any, ReadKind.Silent));
+                lookupVarArgs = insert(ReadVariableNode.createSilent(ArgumentsSignature.VARARG_NAME, RType.Any));
             }
             RArgsValuesAndNames varArgsAndNames;
             try {
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 9ccc7b3ec224e6977b1f317a8e7c7e2ab62eea7d..1e540bf587491570f0a27295724e02614536eb63 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
@@ -22,32 +22,75 @@
  */
 package com.oracle.truffle.r.nodes.function;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
 
-import com.oracle.truffle.api.*;
+import com.oracle.truffle.api.CallTarget;
+import com.oracle.truffle.api.CompilerAsserts;
+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.*;
-import com.oracle.truffle.api.interop.*;
-import com.oracle.truffle.api.nodes.*;
-import com.oracle.truffle.api.source.*;
-import com.oracle.truffle.api.profiles.*;
-import com.oracle.truffle.r.nodes.*;
-import com.oracle.truffle.r.nodes.access.*;
-import com.oracle.truffle.r.nodes.access.variables.*;
-import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode.ReadKind;
-import com.oracle.truffle.r.nodes.builtin.*;
+import com.oracle.truffle.api.RootCallTarget;
+import com.oracle.truffle.api.Truffle;
+import com.oracle.truffle.api.frame.Frame;
+import com.oracle.truffle.api.frame.FrameSlot;
+import com.oracle.truffle.api.frame.FrameSlotTypeException;
+import com.oracle.truffle.api.frame.MaterializedFrame;
+import com.oracle.truffle.api.frame.VirtualFrame;
+import com.oracle.truffle.api.interop.ForeignAccess;
+import com.oracle.truffle.api.interop.Message;
+import com.oracle.truffle.api.interop.TruffleObject;
+import com.oracle.truffle.api.nodes.DirectCallNode;
+import com.oracle.truffle.api.nodes.IndirectCallNode;
+import com.oracle.truffle.api.nodes.Node;
+import com.oracle.truffle.api.nodes.NodeCloneable;
+import com.oracle.truffle.api.nodes.NodeCost;
+import com.oracle.truffle.api.nodes.NodeInfo;
+import com.oracle.truffle.api.nodes.NodeUtil;
+import com.oracle.truffle.api.nodes.RootNode;
+import com.oracle.truffle.api.profiles.BranchProfile;
+import com.oracle.truffle.api.profiles.ConditionProfile;
+import com.oracle.truffle.api.profiles.ValueProfile;
+import com.oracle.truffle.api.source.SourceSection;
+import com.oracle.truffle.r.nodes.RASTUtils;
+import com.oracle.truffle.r.nodes.RRootNode;
+import com.oracle.truffle.r.nodes.access.ConstantNode;
+import com.oracle.truffle.r.nodes.access.FrameSlotNode;
+import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode;
+import com.oracle.truffle.r.nodes.builtin.RBuiltinNode;
+import com.oracle.truffle.r.nodes.builtin.RBuiltinRootNode;
 import com.oracle.truffle.r.nodes.function.MatchedArguments.MatchedArgumentsNode;
 import com.oracle.truffle.r.nodes.function.S3FunctionLookupNode.Result;
-import com.oracle.truffle.r.nodes.function.signature.*;
-import com.oracle.truffle.r.nodes.runtime.*;
-import com.oracle.truffle.r.runtime.*;
+import com.oracle.truffle.r.nodes.function.signature.RArgumentsNode;
+import com.oracle.truffle.r.nodes.runtime.RASTDeparse;
+import com.oracle.truffle.r.runtime.Arguments;
+import com.oracle.truffle.r.runtime.ArgumentsSignature;
+import com.oracle.truffle.r.runtime.RArguments;
 import com.oracle.truffle.r.runtime.RArguments.S3Args;
+import com.oracle.truffle.r.runtime.RBuiltinKind;
+import com.oracle.truffle.r.runtime.RCaller;
+import com.oracle.truffle.r.runtime.RDeparse;
 import com.oracle.truffle.r.runtime.RDeparse.Func;
-import com.oracle.truffle.r.runtime.data.*;
-import com.oracle.truffle.r.runtime.env.*;
-import com.oracle.truffle.r.runtime.gnur.*;
-import com.oracle.truffle.r.runtime.nodes.*;
+import com.oracle.truffle.r.runtime.RDispatch;
+import com.oracle.truffle.r.runtime.RError;
+import com.oracle.truffle.r.runtime.RInternalError;
+import com.oracle.truffle.r.runtime.RSerialize;
+import com.oracle.truffle.r.runtime.RType;
+import com.oracle.truffle.r.runtime.data.RArgsValuesAndNames;
+import com.oracle.truffle.r.runtime.data.RBuiltinDescriptor;
+import com.oracle.truffle.r.runtime.data.RDataFactory;
+import com.oracle.truffle.r.runtime.data.RFunction;
+import com.oracle.truffle.r.runtime.data.RNull;
+import com.oracle.truffle.r.runtime.data.RPromise;
+import com.oracle.truffle.r.runtime.data.RStringVector;
+import com.oracle.truffle.r.runtime.data.RSymbol;
+import com.oracle.truffle.r.runtime.data.RTypedValue;
+import com.oracle.truffle.r.runtime.env.REnvironment;
+import com.oracle.truffle.r.runtime.gnur.SEXPTYPE;
+import com.oracle.truffle.r.runtime.nodes.RBaseNode;
+import com.oracle.truffle.r.runtime.nodes.RFastPathNode;
+import com.oracle.truffle.r.runtime.nodes.RNode;
+import com.oracle.truffle.r.runtime.nodes.RSyntaxNode;
 
 /**
  * This class denotes a call site to a function,e.g.:
@@ -86,12 +129,12 @@ import com.oracle.truffle.r.runtime.nodes.*;
  *  U = {@link UninitializedCallNode}: Forms the uninitialized end of the function PIC
  *  D = {@link DispatchedCallNode}: Function fixed, no varargs
  *  G = {@link GenericCallNode}: Function arbitrary
- * 
+ *
  *  UV = {@link UninitializedCallNode} with varargs,
  *  UVC = {@link UninitializedVarArgsCacheCallNode} with varargs, for varargs cache
  *  DV = {@link DispatchedVarArgsCallNode}: Function fixed, with cached varargs
  *  DGV = {@link DispatchedGenericVarArgsCallNode}: Function fixed, with arbitrary varargs (generic case)
- * 
+ *
  * (RB = {@link RBuiltinNode}: individual functions that are builtins are represented by this node
  * which is not aware of caching). Due to {@link CachedCallNode} (see below) this is transparent to
  * the cache and just behaves like a D/DGV)
@@ -104,11 +147,11 @@ import com.oracle.truffle.r.runtime.nodes.*;
  * non varargs, max depth:
  * |
  * D-D-D-U
- * 
+ *
  * no varargs, generic (if max depth is exceeded):
  * |
  * D-D-D-D-G
- * 
+ *
  * varargs:
  * |
  * DV-DV-UV         <- function call target identity level cache
@@ -116,7 +159,7 @@ import com.oracle.truffle.r.runtime.nodes.*;
  *    DV
  *    |
  *    UVC           <- varargs signature level cache
- * 
+ *
  * varargs, max varargs depth exceeded:
  * |
  * DV-DV-UV
@@ -128,7 +171,7 @@ import com.oracle.truffle.r.runtime.nodes.*;
  *    DV
  *    |
  *    DGV
- * 
+ *
  * varargs, max function depth exceeded:
  * |
  * DV-DV-DV-DV-GV
@@ -936,7 +979,7 @@ public final class RCallNode extends RNode implements RSyntaxNode {
      */
     private abstract static class VarArgsCacheCallNode extends LeafCallNode {
 
-        @Child private ReadVariableNode lookupVarArgs = ReadVariableNode.create(ArgumentsSignature.VARARG_NAME, RType.Any, ReadKind.Silent);
+        @Child private ReadVariableNode lookupVarArgs = ReadVariableNode.createSilent(ArgumentsSignature.VARARG_NAME, RType.Any);
 
         @Override
         public final Object execute(VirtualFrame frame, RFunction function, S3Args s3Args) {
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/S3FunctionLookupNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/S3FunctionLookupNode.java
index 7e40c71d6d7d340a9ce315de59387c929f715c47..62aec3a72afc2e078d247d511561a33dcb2c6873 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/S3FunctionLookupNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/S3FunctionLookupNode.java
@@ -10,22 +10,41 @@
  */
 package com.oracle.truffle.r.nodes.function;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 
-import com.oracle.truffle.api.*;
+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.CompilerDirectives.ValueType;
-import com.oracle.truffle.api.frame.*;
-import com.oracle.truffle.api.nodes.*;
-import com.oracle.truffle.api.profiles.*;
-import com.oracle.truffle.r.nodes.access.variables.*;
-import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode.ReadKind;
-import com.oracle.truffle.r.runtime.*;
-import com.oracle.truffle.r.runtime.context.*;
-import com.oracle.truffle.r.runtime.data.*;
-import com.oracle.truffle.r.runtime.env.*;
-import com.oracle.truffle.r.runtime.nodes.*;
+import com.oracle.truffle.api.frame.Frame;
+import com.oracle.truffle.api.frame.FrameSlot;
+import com.oracle.truffle.api.frame.FrameSlotTypeException;
+import com.oracle.truffle.api.frame.MaterializedFrame;
+import com.oracle.truffle.api.frame.VirtualFrame;
+import com.oracle.truffle.api.nodes.ControlFlowException;
+import com.oracle.truffle.api.nodes.ExplodeLoop;
+import com.oracle.truffle.api.nodes.NodeCost;
+import com.oracle.truffle.api.nodes.NodeInfo;
+import com.oracle.truffle.api.profiles.BranchProfile;
+import com.oracle.truffle.api.profiles.ConditionProfile;
+import com.oracle.truffle.api.profiles.ValueProfile;
+import com.oracle.truffle.r.nodes.access.variables.LocalReadVariableNode;
+import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode;
+import com.oracle.truffle.r.runtime.ArgumentsSignature;
+import com.oracle.truffle.r.runtime.RError;
+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.context.RContext;
+import com.oracle.truffle.r.runtime.data.RDataFactory;
+import com.oracle.truffle.r.runtime.data.RFunction;
+import com.oracle.truffle.r.runtime.data.RNull;
+import com.oracle.truffle.r.runtime.data.RPromise;
+import com.oracle.truffle.r.runtime.data.RStringVector;
+import com.oracle.truffle.r.runtime.env.REnvironment;
+import com.oracle.truffle.r.runtime.nodes.RBaseNode;
 
 public abstract class S3FunctionLookupNode extends RBaseNode {
     protected static final int MAX_CACHE_DEPTH = 3;
@@ -146,23 +165,31 @@ public abstract class S3FunctionLookupNode extends RBaseNode {
     private static UseMethodFunctionLookupCachedNode specialize(String genericName, RStringVector type, String group, MaterializedFrame callerFrame, MaterializedFrame genericDefFrame,
                     S3FunctionLookupNode next) {
         ArrayList<ReadVariableNode> unsuccessfulReadsCaller = new ArrayList<>();
-        ArrayList<ReadVariableNode> unsuccessfulReadsTable = new ArrayList<>();
+        ArrayList<LocalReadVariableNode> unsuccessfulReadsTable = new ArrayList<>();
         class SuccessfulReads {
             ReadVariableNode successfulRead;
-            boolean successfulReadIsTable;
-            ReadVariableNode methodsTableRead;
+            LocalReadVariableNode successfulReadTable;
+            LocalReadVariableNode methodsTableRead;
         }
         SuccessfulReads reads = new SuccessfulReads();
 
         LookupOperation op = (lookupFrame, name, inMethodsTable) -> {
-            ReadVariableNode read = ReadVariableNode.create(name, RType.Function, inMethodsTable ? ReadKind.SilentLocal : ReadKind.Silent);
-            Object result = read.execute(null, lookupFrame);
-            if (result == null) {
-                (inMethodsTable ? unsuccessfulReadsTable : unsuccessfulReadsCaller).add(read);
+            Object result;
+            if (inMethodsTable) {
+                LocalReadVariableNode read = LocalReadVariableNode.create(name, true);
+                result = read.execute(null, lookupFrame);
+                if (result == null) {
+                    unsuccessfulReadsTable.add(read);
+                } else {
+                    reads.successfulReadTable = read;
+                }
             } else {
-                reads.successfulRead = read;
-                if (inMethodsTable) {
-                    reads.successfulReadIsTable = true;
+                ReadVariableNode read = ReadVariableNode.createSilent(name, RType.Function);
+                result = read.execute(null, lookupFrame);
+                if (result == null) {
+                    unsuccessfulReadsCaller.add(read);
+                } else {
+                    reads.successfulRead = read;
                 }
             }
             return result;
@@ -170,7 +197,7 @@ public abstract class S3FunctionLookupNode extends RBaseNode {
 
         GetMethodsTable getTable = () -> {
             if (genericDefFrame != null) {
-                reads.methodsTableRead = ReadVariableNode.create(RRuntime.RS3MethodsTable, RType.Any, ReadKind.SilentLocal);
+                reads.methodsTableRead = LocalReadVariableNode.create(RRuntime.RS3MethodsTable, true);
                 return reads.methodsTableRead.execute(null, genericDefFrame);
             } else {
                 return null;
@@ -183,11 +210,11 @@ public abstract class S3FunctionLookupNode extends RBaseNode {
 
         if (result != null) {
             cachedNode = new UseMethodFunctionLookupCachedNode(next.throwsError, next.nextMethod, genericName, type, group, null, unsuccessfulReadsCaller, unsuccessfulReadsTable,
-                            reads.methodsTableRead, reads.successfulRead, reads.successfulReadIsTable, result.function, result.clazz, result.targetFunctionName, result.groupMatch, next);
+                            reads.methodsTableRead, reads.successfulRead, reads.successfulReadTable, result.function, result.clazz, result.targetFunctionName, result.groupMatch, next);
         } else {
             RFunction builtin = next.throwsError ? builtin = RContext.lookupBuiltin(genericName) : null;
             cachedNode = new UseMethodFunctionLookupCachedNode(next.throwsError, next.nextMethod, genericName, type, group, builtin, unsuccessfulReadsCaller, unsuccessfulReadsTable,
-                            reads.methodsTableRead, null, false, null, null, null, false, next);
+                            reads.methodsTableRead, null, null, null, null, null, false, next);
         }
         return cachedNode;
     }
@@ -218,10 +245,11 @@ public abstract class S3FunctionLookupNode extends RBaseNode {
 
         @CompilationFinal private final String[] cachedTypeContents;
         @Children private final ReadVariableNode[] unsuccessfulReadsCallerFrame;
-        @Child private ReadVariableNode readS3MethodsTable;
-        @Children private final ReadVariableNode[] unsuccessfulReadsDefFrame;
+        @Child private LocalReadVariableNode readS3MethodsTable;
+        @Children private final LocalReadVariableNode[] unsuccessfulReadsTable;
         // if unsuccessfulReadsDefFrame != null, then this read will go to the def frame
         @Child private ReadVariableNode successfulRead;
+        @Child private LocalReadVariableNode successfulReadTable;
 
         private final String cachedGenericName;
         private final RStringVector cachedType;
@@ -236,11 +264,10 @@ public abstract class S3FunctionLookupNode extends RBaseNode {
         private final ValueProfile methodsTableProfile = ValueProfile.createIdentityProfile();
         private final String cachedGroup;
         private final RFunction builtin;
-        private final boolean successfulReadIsTable;
 
         public UseMethodFunctionLookupCachedNode(boolean throwsError, boolean nextMethod, String genericName, RStringVector type, String group, RFunction builtin,
-                        List<ReadVariableNode> unsuccessfulReadsCaller, List<ReadVariableNode> unsuccessfulReadsDef, ReadVariableNode readS3MethodsTable, ReadVariableNode successfulRead,
-                        boolean successfulReadIsTable, RFunction function, Object clazz, String targetFunctionName, boolean groupMatch, S3FunctionLookupNode next) {
+                        List<ReadVariableNode> unsuccessfulReadsCaller, List<LocalReadVariableNode> unsuccessfulReadsTable, LocalReadVariableNode readS3MethodsTable, ReadVariableNode successfulRead,
+                        LocalReadVariableNode successfulReadTable, RFunction function, Object clazz, String targetFunctionName, boolean groupMatch, S3FunctionLookupNode next) {
             super(throwsError, nextMethod);
             this.cachedGenericName = genericName;
             this.cachedGroup = group;
@@ -250,9 +277,9 @@ public abstract class S3FunctionLookupNode extends RBaseNode {
             this.cachedType = type;
             this.cachedTypeContents = type.getDataCopy();
             this.unsuccessfulReadsCallerFrame = unsuccessfulReadsCaller.toArray(new ReadVariableNode[unsuccessfulReadsCaller.size()]);
-            this.unsuccessfulReadsDefFrame = unsuccessfulReadsDef.toArray(new ReadVariableNode[unsuccessfulReadsDef.size()]);
+            this.unsuccessfulReadsTable = unsuccessfulReadsTable.toArray(new LocalReadVariableNode[unsuccessfulReadsTable.size()]);
             this.successfulRead = successfulRead;
-            this.successfulReadIsTable = successfulReadIsTable;
+            this.successfulReadTable = successfulReadTable;
             this.result = new Result(genericName, function != null ? function : builtin, clazz, targetFunctionName, groupMatch);
         }
 
@@ -270,12 +297,13 @@ public abstract class S3FunctionLookupNode extends RBaseNode {
                     methodsTable = null;
                 } else {
                     methodsTable = (REnvironment) methodsTableProfile.profile(readS3MethodsTable.execute(frame, genericDefFrame));
-                    if (methodsTable != null && !executeReads(unsuccessfulReadsDefFrame, methodsTable.getFrame())) {
+                    if (methodsTable != null && !executeReads(unsuccessfulReadsTable, methodsTable.getFrame())) {
                         break;
                     }
                 }
-                if (successfulRead != null) {
-                    Object actualFunction = successfulRead.execute(null, successfulReadIsTable ? methodsTable.getFrame() : callerFrame);
+                if (successfulRead != null || successfulReadTable != null) {
+
+                    Object actualFunction = successfulRead != null ? successfulRead.execute(null, callerFrame) : successfulReadTable.execute(null, methodsTable.getFrame());
                     if (actualFunction != result.function) {
                         break;
                     }
@@ -305,6 +333,17 @@ public abstract class S3FunctionLookupNode extends RBaseNode {
             return true;
         }
 
+        @ExplodeLoop
+        private static boolean executeReads(LocalReadVariableNode[] reads, Frame callerFrame) {
+            for (LocalReadVariableNode read : reads) {
+                if (read.execute(null, callerFrame) != null) {
+                    CompilerDirectives.transferToInterpreterAndInvalidate();
+                    return false;
+                }
+            }
+            return true;
+        }
+
         private boolean isEqualType(RStringVector type) {
             if (sameIdentityProfile.profile(type == cachedType)) {
                 return true;
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/opt/EagerEvalHelper.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/opt/EagerEvalHelper.java
index 5af9151a4461c3a8cb8ea692deb69e3cf526c608..ce780741f73df59b4d840ebb955eb72746ab15b3 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/opt/EagerEvalHelper.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/opt/EagerEvalHelper.java
@@ -22,13 +22,18 @@
  */
 package com.oracle.truffle.r.nodes.function.opt;
 
-import com.oracle.truffle.r.nodes.access.*;
-import com.oracle.truffle.r.nodes.access.variables.*;
-import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode.ReadKind;
-import com.oracle.truffle.r.nodes.function.*;
-import com.oracle.truffle.r.runtime.*;
-import com.oracle.truffle.r.runtime.data.*;
-import com.oracle.truffle.r.runtime.nodes.*;
+import com.oracle.truffle.r.nodes.access.AccessArgumentNode;
+import com.oracle.truffle.r.nodes.access.ConstantNode;
+import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode;
+import com.oracle.truffle.r.nodes.function.ArgumentStatePush;
+import com.oracle.truffle.r.nodes.function.PromiseNode;
+import com.oracle.truffle.r.nodes.function.RCallNode;
+import com.oracle.truffle.r.runtime.FastROptions;
+import com.oracle.truffle.r.runtime.data.RDataFactory;
+import com.oracle.truffle.r.runtime.data.RStringVector;
+import com.oracle.truffle.r.runtime.nodes.RBaseNode;
+import com.oracle.truffle.r.runtime.nodes.RNode;
+import com.oracle.truffle.r.runtime.nodes.RSyntaxNode;
 
 /**
  * Provides small helper function for eager evaluation of arguments for the use in
@@ -121,7 +126,7 @@ public class EagerEvalHelper {
     public static boolean isVariableArgument(RBaseNode expr) {
         // Do NOT try to optimize anything that might force a Promise, as this might be arbitrary
         // complex (time and space)!
-        return expr instanceof ReadVariableNode && ((ReadVariableNode) expr).getKind() != ReadKind.ForcedTypeCheck;
+        return expr instanceof ReadVariableNode && !((ReadVariableNode) expr).isForceForTypeCheck();
     }
 
     private static boolean isCheapExpressionArgument(@SuppressWarnings("unused") RNode expr) {
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/opt/OptVariablePromiseBaseNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/opt/OptVariablePromiseBaseNode.java
index 91bb53698d06e63c459219b92f9757615a7ef2bc..502f824a5e83954dad2d1249b4799a444905ef6f 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/opt/OptVariablePromiseBaseNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/opt/OptVariablePromiseBaseNode.java
@@ -22,33 +22,38 @@
  */
 package com.oracle.truffle.r.nodes.function.opt;
 
-import com.oracle.truffle.api.*;
-import com.oracle.truffle.api.frame.*;
-import com.oracle.truffle.api.nodes.*;
-import com.oracle.truffle.r.nodes.access.*;
-import com.oracle.truffle.r.nodes.access.variables.*;
-import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode.ReadKind;
-import com.oracle.truffle.r.nodes.function.*;
-import com.oracle.truffle.r.runtime.*;
-import com.oracle.truffle.r.runtime.data.*;
+import com.oracle.truffle.api.Assumption;
+import com.oracle.truffle.api.CompilerDirectives;
+import com.oracle.truffle.api.frame.FrameSlot;
+import com.oracle.truffle.api.frame.VirtualFrame;
+import com.oracle.truffle.api.nodes.InvalidAssumptionException;
+import com.oracle.truffle.r.nodes.access.FrameSlotNode;
+import com.oracle.truffle.r.nodes.access.variables.LocalReadVariableNode;
+import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode;
+import com.oracle.truffle.r.nodes.function.PromiseNode;
+import com.oracle.truffle.r.runtime.RArguments;
+import com.oracle.truffle.r.runtime.RType;
+import com.oracle.truffle.r.runtime.data.RPromise;
 import com.oracle.truffle.r.runtime.data.RPromise.EagerFeedback;
 import com.oracle.truffle.r.runtime.data.RPromise.RPromiseFactory;
-import com.oracle.truffle.r.runtime.env.frame.*;
-import com.oracle.truffle.r.runtime.nodes.*;
+import com.oracle.truffle.r.runtime.env.frame.FrameSlotChangeMonitor;
+import com.oracle.truffle.r.runtime.nodes.RNode;
+import com.oracle.truffle.r.runtime.nodes.RSyntaxNode;
 
 public abstract class OptVariablePromiseBaseNode extends PromiseNode implements EagerFeedback {
     protected final ReadVariableNode originalRvn;
     @Child private FrameSlotNode frameSlotNode;
     @Child private RNode fallback = null;
-    @Child private ReadVariableNode readNode;
+    @Child private LocalReadVariableNode readNode;
     private final int wrapIndex;
 
     public OptVariablePromiseBaseNode(RPromiseFactory factory, ReadVariableNode rvn, int wrapIndex) {
         super(factory);
-        assert rvn.getKind() != ReadKind.ForcedTypeCheck;  // Should be caught by optimization check
+        // Should be caught by optimization check
+        assert !rvn.isForceForTypeCheck() && rvn.getMode() == RType.Any;
         this.originalRvn = rvn;
         this.frameSlotNode = FrameSlotNode.create(rvn.getIdentifier(), false);
-        this.readNode = ReadVariableNode.create(rvn.getIdentifier(), rvn.getMode(), ReadKind.UnforcedSilentLocal);
+        this.readNode = LocalReadVariableNode.create(rvn.getIdentifier(), false);
         this.wrapIndex = wrapIndex;
     }
 
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/signature/CollectArgumentsNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/signature/CollectArgumentsNode.java
index 10f38006443d9c40705c3871cab6d1e761dff47e..9dc6863b819ecf07851fed0935f3835cff4ae9c3 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/signature/CollectArgumentsNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/signature/CollectArgumentsNode.java
@@ -23,16 +23,22 @@
 package com.oracle.truffle.r.nodes.function.signature;
 
 import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
-import com.oracle.truffle.api.dsl.*;
-import com.oracle.truffle.api.frame.*;
-import com.oracle.truffle.api.nodes.*;
-import com.oracle.truffle.api.profiles.*;
-import com.oracle.truffle.r.nodes.access.*;
-import com.oracle.truffle.r.nodes.access.variables.*;
-import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode.ReadKind;
-import com.oracle.truffle.r.runtime.*;
-import com.oracle.truffle.r.runtime.data.*;
-import com.oracle.truffle.r.runtime.nodes.*;
+import com.oracle.truffle.api.dsl.Cached;
+import com.oracle.truffle.api.dsl.Specialization;
+import com.oracle.truffle.api.frame.FrameDescriptor;
+import com.oracle.truffle.api.frame.FrameSlot;
+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.Node;
+import com.oracle.truffle.api.profiles.ConditionProfile;
+import com.oracle.truffle.r.nodes.access.ConstantNode;
+import com.oracle.truffle.r.nodes.access.variables.LocalReadVariableNode;
+import com.oracle.truffle.r.runtime.ArgumentsSignature;
+import com.oracle.truffle.r.runtime.RArguments;
+import com.oracle.truffle.r.runtime.data.RMissing;
+import com.oracle.truffle.r.runtime.data.RPromise;
+import com.oracle.truffle.r.runtime.nodes.RBaseNode;
 
 public abstract class CollectArgumentsNode extends RBaseNode {
 
@@ -42,14 +48,14 @@ public abstract class CollectArgumentsNode extends RBaseNode {
 
     public abstract Object[] execute(VirtualFrame frame, ArgumentsSignature signature);
 
-    protected RNode[] createArgs(ArgumentsSignature signature, VirtualFrame frame) {
-        RNode[] reads = new RNode[signature.getLength()];
+    protected Node[] createArgs(ArgumentsSignature signature, VirtualFrame frame) {
+        Node[] reads = new Node[signature.getLength()];
         for (int i = 0; i < signature.getLength(); i++) {
             Object arg = RArguments.getArgument(frame, i);
             if (arg instanceof RPromise && ((RPromise) arg).isDefault()) {
                 reads[i] = ConstantNode.create(RMissing.instance);
             } else {
-                reads[i] = ReadVariableNode.create(signature.getName(i), RType.Any, ReadKind.UnforcedSilentLocal);
+                reads[i] = LocalReadVariableNode.create(signature.getName(i), false);
             }
         }
         return reads;
@@ -58,10 +64,10 @@ public abstract class CollectArgumentsNode extends RBaseNode {
     @SuppressWarnings("unused")
     @ExplodeLoop
     @Specialization(limit = "CACHE_LIMIT", guards = {"cachedSignature == signature"})
-    protected Object[] combineCached(VirtualFrame frame, ArgumentsSignature signature, @Cached("signature") ArgumentsSignature cachedSignature, @Cached("createArgs(signature, frame)") RNode[] reads) {
+    protected Object[] combineCached(VirtualFrame frame, ArgumentsSignature signature, @Cached("signature") ArgumentsSignature cachedSignature, @Cached("createArgs(signature, frame)") Node[] reads) {
         Object[] result = new Object[reads.length];
         for (int i = 0; i < reads.length; i++) {
-            Object value = reads[i].execute(frame);
+            Object value = reads[i] instanceof ConstantNode ? ((ConstantNode) reads[i]).getValue() : ((LocalReadVariableNode) reads[i]).execute(frame);
             result[i] = valueMissingProfile.profile(value == null) ? RMissing.instance : value;
         }
         return result;
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/objects/CollectGenericArgumentsNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/objects/CollectGenericArgumentsNode.java
index 6222a4005e947c6fe976abe1e7b4334c89d06739..f356ac072b518b6746479d52e84ec86bef14008e 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/objects/CollectGenericArgumentsNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/objects/CollectGenericArgumentsNode.java
@@ -24,17 +24,20 @@ package com.oracle.truffle.r.nodes.objects;
 
 import com.oracle.truffle.api.CompilerAsserts;
 import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
-import com.oracle.truffle.api.dsl.*;
-import com.oracle.truffle.api.frame.*;
-import com.oracle.truffle.api.nodes.*;
-import com.oracle.truffle.api.profiles.*;
-import com.oracle.truffle.r.nodes.access.variables.*;
-import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode.ReadKind;
+import com.oracle.truffle.api.dsl.Specialization;
+import com.oracle.truffle.api.frame.FrameDescriptor;
+import com.oracle.truffle.api.frame.FrameSlot;
+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.SlowPathException;
+import com.oracle.truffle.api.profiles.ConditionProfile;
+import com.oracle.truffle.r.nodes.access.variables.LocalReadVariableNode;
 import com.oracle.truffle.r.nodes.function.ClassHierarchyScalarNode;
 import com.oracle.truffle.r.nodes.function.ClassHierarchyScalarNodeGen;
-import com.oracle.truffle.r.runtime.*;
-import com.oracle.truffle.r.runtime.data.*;
-import com.oracle.truffle.r.runtime.nodes.*;
+import com.oracle.truffle.r.runtime.data.RList;
+import com.oracle.truffle.r.runtime.data.RSymbol;
+import com.oracle.truffle.r.runtime.nodes.RBaseNode;
 
 /*
  * Used to collect arguments of the generic function for S4 method dispatch. Modeled after {@link CollectArgumentsNode}.
@@ -43,7 +46,7 @@ public abstract class CollectGenericArgumentsNode extends RBaseNode {
 
     // TODO: re-do with a multi-element cache? (list comparison will have some cost, though)
 
-    @Children private final ReadVariableNode[] argReads;
+    @Children private final LocalReadVariableNode[] argReads;
     @Children private final ClassHierarchyScalarNode[] classHierarchyNodes;
     @Child private ClassHierarchyScalarNode classHierarchyNodeSlowPath;
 
@@ -52,11 +55,11 @@ public abstract class CollectGenericArgumentsNode extends RBaseNode {
     public abstract String[] execute(VirtualFrame frame, RList arguments, int argLength);
 
     protected CollectGenericArgumentsNode(Object[] arguments, int argLength) {
-        ReadVariableNode[] reads = new ReadVariableNode[argLength];
+        LocalReadVariableNode[] reads = new LocalReadVariableNode[argLength];
         ClassHierarchyScalarNode[] hierarchyNodes = new ClassHierarchyScalarNode[argLength];
         for (int i = 0; i < argLength; i++) {
             RSymbol s = (RSymbol) arguments[i];
-            reads[i] = ReadVariableNode.create(s.getName(), RType.Any, ReadKind.SilentLocal);
+            reads[i] = LocalReadVariableNode.create(s.getName(), true);
             hierarchyNodes[i] = ClassHierarchyScalarNodeGen.create();
         }
         argReads = insert(reads);
@@ -71,9 +74,9 @@ public abstract class CollectGenericArgumentsNode extends RBaseNode {
         }
         String[] result = new String[argReads.length];
         for (int i = 0; i < argReads.length; i++) {
-            String cachedId = argReads[i].getIdentifier();
+            Object cachedId = argReads[i].getIdentifier();
             String id = ((RSymbol) (arguments.getDataAt(0))).getName();
-            assert cachedId == cachedId.intern() && id == id.intern();
+            assert cachedId instanceof String && cachedId == ((String) cachedId).intern() && id == id.intern();
             if (cachedId != id) {
                 throw new SlowPathException();
             }
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/objects/DispatchGeneric.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/objects/DispatchGeneric.java
index 54d4e381e4d07e392a63a8e6e3caa9461962ae3d..3320fd394af480e41c9cc24d12b710c0afffe9e2 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/objects/DispatchGeneric.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/objects/DispatchGeneric.java
@@ -16,16 +16,21 @@ 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.Truffle;
-import com.oracle.truffle.api.dsl.*;
+import com.oracle.truffle.api.dsl.Cached;
+import com.oracle.truffle.api.dsl.Specialization;
 import com.oracle.truffle.api.frame.VirtualFrame;
 import com.oracle.truffle.api.nodes.DirectCallNode;
 import com.oracle.truffle.api.profiles.ConditionProfile;
+import com.oracle.truffle.r.nodes.access.variables.LocalReadVariableNode;
 import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode;
-import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode.ReadKind;
 import com.oracle.truffle.r.nodes.function.signature.RArgumentsNode;
-import com.oracle.truffle.r.runtime.*;
+import com.oracle.truffle.r.runtime.ArgumentsSignature;
+import com.oracle.truffle.r.runtime.RArguments;
+import com.oracle.truffle.r.runtime.RCaller;
 import com.oracle.truffle.r.runtime.context.RContext;
-import com.oracle.truffle.r.runtime.data.*;
+import com.oracle.truffle.r.runtime.data.RDataFactory;
+import com.oracle.truffle.r.runtime.data.RFunction;
+import com.oracle.truffle.r.runtime.data.RStringVector;
 import com.oracle.truffle.r.runtime.env.REnvironment;
 import com.oracle.truffle.r.runtime.nodes.RBaseNode;
 
@@ -63,8 +68,8 @@ public abstract class DispatchGeneric extends RBaseNode {
         }
     }
 
-    protected ReadVariableNode createTableRead(String dispatchString) {
-        return ReadVariableNode.create(dispatchString, RType.Any, ReadKind.SilentLocal);
+    protected LocalReadVariableNode createTableRead(String dispatchString) {
+        return LocalReadVariableNode.create(dispatchString, true);
     }
 
     private Object dispatchInternal(VirtualFrame frame, REnvironment methodsEnv, REnvironment mtable, RStringVector classes, RFunction fdef, String fname, RFunction f) {
@@ -72,7 +77,7 @@ public abstract class DispatchGeneric extends RBaseNode {
         if (method == null) {
             if (inheritForDispatchFind == null) {
                 CompilerDirectives.transferToInterpreterAndInvalidate();
-                inheritForDispatchFind = insert(ReadVariableNode.create(".InheritForDispatch", RType.Function, ReadKind.Normal));
+                inheritForDispatchFind = insert(ReadVariableNode.createFunctionLookup(null, ".InheritForDispatch"));
                 inheritForDispatchFunction = (RFunction) inheritForDispatchFind.execute(null, methodsEnv.getFrame());
                 inheritForDispatchCall = insert(Truffle.getRuntime().createDirectCallNode(inheritForDispatchFunction.getTarget()));
 
@@ -96,7 +101,7 @@ public abstract class DispatchGeneric extends RBaseNode {
     @Specialization(guards = "equalClasses(classes, cachedClasses)")
     protected Object dispatchCached(VirtualFrame frame, REnvironment methodsEnv, REnvironment mtable, RStringVector classes, RFunction fdef, String fname,
                     @Cached("classes") RStringVector cachedClasses, @Cached("createDispatchString(cachedClasses)") String dispatchString,
-                    @Cached("createTableRead(dispatchString)") ReadVariableNode tableRead) {
+                    @Cached("createTableRead(dispatchString)") LocalReadVariableNode tableRead) {
         RFunction method = (RFunction) tableRead.execute(null, mtable.getFrame());
         return dispatchInternal(frame, methodsEnv, mtable, classes, fdef, fname, method);
     }
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/objects/ExecuteMethod.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/objects/ExecuteMethod.java
index d2fc54d4d8f18de86b217857384d2ef6a2b98d52..7664b6065da5a7fbe47b876721b83f9bc409d03b 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/objects/ExecuteMethod.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/objects/ExecuteMethod.java
@@ -14,21 +14,24 @@ package com.oracle.truffle.r.nodes.objects;
 
 import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
 import com.oracle.truffle.api.Truffle;
-import com.oracle.truffle.api.dsl.*;
+import com.oracle.truffle.api.dsl.Specialization;
 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.MaterializedFrame;
 import com.oracle.truffle.api.frame.VirtualFrame;
 import com.oracle.truffle.r.nodes.RRootNode;
-import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode;
-import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode.ReadKind;
+import com.oracle.truffle.r.nodes.access.variables.LocalReadVariableNode;
 import com.oracle.truffle.r.nodes.function.FormalArguments;
 import com.oracle.truffle.r.nodes.function.RMissingHelper;
 import com.oracle.truffle.r.nodes.function.signature.RArgumentsNode;
-import com.oracle.truffle.r.runtime.*;
+import com.oracle.truffle.r.runtime.ArgumentsSignature;
+import com.oracle.truffle.r.runtime.RArguments;
+import com.oracle.truffle.r.runtime.RInternalError;
+import com.oracle.truffle.r.runtime.RRuntime;
 import com.oracle.truffle.r.runtime.context.RContext;
-import com.oracle.truffle.r.runtime.data.*;
+import com.oracle.truffle.r.runtime.data.RArgsValuesAndNames;
+import com.oracle.truffle.r.runtime.data.RFunction;
 import com.oracle.truffle.r.runtime.env.frame.FrameSlotChangeMonitor;
 import com.oracle.truffle.r.runtime.nodes.RBaseNode;
 
@@ -36,11 +39,11 @@ public abstract class ExecuteMethod extends RBaseNode {
 
     public abstract Object executeObject(VirtualFrame frame, RFunction fdef);
 
-    @Child private ReadVariableNode readDefined = ReadVariableNode.create(RRuntime.R_DOT_DEFINED, RType.Any, ReadKind.SilentLocal);
-    @Child private ReadVariableNode readMethod = ReadVariableNode.create(RRuntime.RDotMethod, RType.Any, ReadKind.SilentLocal);
-    @Child private ReadVariableNode readTarget = ReadVariableNode.create(RRuntime.R_DOT_TARGET, RType.Any, ReadKind.SilentLocal);
-    @Child private ReadVariableNode readGeneric = ReadVariableNode.create(RRuntime.RDotGeneric, RType.Any, ReadKind.SilentLocal);
-    @Child private ReadVariableNode readMethods = ReadVariableNode.create(RRuntime.R_DOT_METHODS, RType.Any, ReadKind.SilentLocal);
+    @Child private LocalReadVariableNode readDefined = LocalReadVariableNode.create(RRuntime.R_DOT_DEFINED, true);
+    @Child private LocalReadVariableNode readMethod = LocalReadVariableNode.create(RRuntime.RDotMethod, true);
+    @Child private LocalReadVariableNode readTarget = LocalReadVariableNode.create(RRuntime.R_DOT_TARGET, true);
+    @Child private LocalReadVariableNode readGeneric = LocalReadVariableNode.create(RRuntime.RDotGeneric, true);
+    @Child private LocalReadVariableNode readMethods = LocalReadVariableNode.create(RRuntime.R_DOT_METHODS, true);
     @Child private RArgumentsNode argsNode = RArgumentsNode.create();
 
     @Specialization
@@ -50,7 +53,8 @@ public abstract class ExecuteMethod extends RBaseNode {
                         RArguments.getSignature(frame), null);
         MaterializedFrame newFrame = Truffle.getRuntime().createMaterializedFrame(args);
         FrameDescriptor desc = newFrame.getFrameDescriptor();
-        FrameSlotChangeMonitor.initializeFunctionFrameDescriptor(desc);
+        FrameSlotChangeMonitor.initializeFunctionFrameDescriptor("<executeMethod>", desc);
+        FrameSlotChangeMonitor.initializeEnclosingFrame(newFrame, RArguments.getFunction(frame).getEnclosingFrame());
         FormalArguments formals = ((RRootNode) fdef.getRootNode()).getFormalArguments();
         if (formals != null) {
             ArgumentsSignature signature = formals.getSignature();
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/objects/LoadMethod.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/objects/LoadMethod.java
index 43935e34f9a83ba7196e00794597a8440ba14c26..6bc7bc3189f6162df8a66c3461ef8ef637dcc97b 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/objects/LoadMethod.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/objects/LoadMethod.java
@@ -15,19 +15,23 @@ package com.oracle.truffle.r.nodes.objects;
 import com.oracle.truffle.api.CompilerDirectives;
 import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
 import com.oracle.truffle.api.Truffle;
-import com.oracle.truffle.api.dsl.*;
+import com.oracle.truffle.api.dsl.Specialization;
 import com.oracle.truffle.api.frame.VirtualFrame;
 import com.oracle.truffle.api.nodes.DirectCallNode;
 import com.oracle.truffle.api.profiles.ConditionProfile;
 import com.oracle.truffle.r.nodes.access.WriteLocalFrameVariableNode;
 import com.oracle.truffle.r.nodes.access.WriteVariableNode;
 import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode;
-import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode.ReadKind;
 import com.oracle.truffle.r.nodes.function.signature.RArgumentsNode;
-import com.oracle.truffle.r.runtime.*;
+import com.oracle.truffle.r.runtime.ArgumentsSignature;
+import com.oracle.truffle.r.runtime.RArguments;
+import com.oracle.truffle.r.runtime.RCaller;
+import com.oracle.truffle.r.runtime.RRuntime;
 import com.oracle.truffle.r.runtime.context.RContext;
-import com.oracle.truffle.r.runtime.data.*;
+import com.oracle.truffle.r.runtime.data.RAttributable;
 import com.oracle.truffle.r.runtime.data.RAttributes.RAttribute;
+import com.oracle.truffle.r.runtime.data.RDataFactory;
+import com.oracle.truffle.r.runtime.data.RFunction;
 import com.oracle.truffle.r.runtime.env.REnvironment;
 import com.oracle.truffle.r.runtime.nodes.RBaseNode;
 
@@ -79,7 +83,7 @@ public abstract class LoadMethod extends RBaseNode {
         if (moreAttributes.profile(found < fdef.getAttributes().size())) {
             if (loadMethodFind == null) {
                 CompilerDirectives.transferToInterpreterAndInvalidate();
-                loadMethodFind = insert(ReadVariableNode.create("loadMethod", RType.Function, ReadKind.Normal));
+                loadMethodFind = insert(ReadVariableNode.createFunctionLookup(null, "loadMethod"));
                 loadMethodFunction = (RFunction) loadMethodFind.execute(null, methodsEnv.getFrame());
                 loadMethodCall = insert(Truffle.getRuntime().createDirectCallNode(loadMethodFunction.getTarget()));
 
diff --git a/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jnr/CallRFFIHelper.java b/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jnr/CallRFFIHelper.java
index d587c6940c49dc206a322379f39e23ac4ee3586e..7bf81a338f2f873408e41c6cd432173b1fe094c3 100644
--- a/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jnr/CallRFFIHelper.java
+++ b/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jnr/CallRFFIHelper.java
@@ -563,7 +563,7 @@ public class CallRFFIHelper {
 
     static REnvironment Rf_createNewEnv(REnvironment parent, String name, boolean hashed, int initialSize) {
         REnvironment env = RDataFactory.createNewEnv(name, hashed, initialSize);
-        env.setParent(parent);
+        RArguments.initializeEnclosingFrame(env.getFrame(), parent.getFrame());
         return env;
     }
 
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 a5758daeee6fc5b42c922533577817898fa22928..0e215243f0b60c8fd50d6f80911db3c73069c8fd 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
@@ -268,7 +268,15 @@ public final class RArguments {
         Object[] arguments = frame.getArguments();
         MaterializedFrame oldEnclosingFrame = (MaterializedFrame) arguments[INDEX_ENCLOSING_FRAME];
         arguments[INDEX_ENCLOSING_FRAME] = newEnclosingFrame;
-        FrameSlotChangeMonitor.invalidateEnclosingFrame(frame);
+        FrameSlotChangeMonitor.setEnclosingFrame(frame, newEnclosingFrame, oldEnclosingFrame);
+    }
+
+    public static void initializeEnclosingFrame(Frame frame, MaterializedFrame newEnclosingFrame) {
+        CompilerAsserts.neverPartOfCompilation();
+        Object[] arguments = frame.getArguments();
+        assert arguments[INDEX_ENCLOSING_FRAME] == null;
+        arguments[INDEX_ENCLOSING_FRAME] = newEnclosingFrame;
+        FrameSlotChangeMonitor.initializeEnclosingFrame(frame, newEnclosingFrame);
     }
 
     /**
@@ -283,8 +291,7 @@ public final class RArguments {
         Object[] newArguments = newEnclosingFrame.getArguments();
         newArguments[INDEX_ENCLOSING_FRAME] = oldEnclosingFrame;
         arguments[INDEX_ENCLOSING_FRAME] = newEnclosingFrame;
-        FrameSlotChangeMonitor.invalidateEnclosingFrame(newEnclosingFrame);
-        FrameSlotChangeMonitor.invalidateEnclosingFrame(frame);
+        FrameSlotChangeMonitor.attach(frame, newEnclosingFrame);
     }
 
     /**
@@ -297,7 +304,7 @@ public final class RArguments {
         MaterializedFrame encl = (MaterializedFrame) arguments[INDEX_ENCLOSING_FRAME];
         Object[] enclArguments = encl.getArguments();
         arguments[INDEX_ENCLOSING_FRAME] = enclArguments[INDEX_ENCLOSING_FRAME];
-        FrameSlotChangeMonitor.invalidateEnclosingFrame(frame);
+        FrameSlotChangeMonitor.detach(frame);
     }
 
     /**
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RRuntime.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RRuntime.java
index 312d655519fd4b1a5ea5ad845b809ef56e7c076e..3edcbcab78a9b1cfa03ec4a2bbe09b99bfa736c4 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RRuntime.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RRuntime.java
@@ -172,10 +172,11 @@ public class RRuntime {
      * Create an {@link VirtualFrame} for a non-function environment, e.g., a package frame or the
      * global environment.
      */
-    public static VirtualFrame createNonFunctionFrame() {
+    public static MaterializedFrame createNonFunctionFrame(String name) {
         FrameDescriptor frameDescriptor = new FrameDescriptor();
-        FrameSlotChangeMonitor.initializeNonFunctionFrameDescriptor(frameDescriptor, false);
-        return Truffle.getRuntime().createVirtualFrame(RArguments.createUnitialized(), frameDescriptor);
+        MaterializedFrame frame = Truffle.getRuntime().createMaterializedFrame(RArguments.createUnitialized(), frameDescriptor);
+        FrameSlotChangeMonitor.initializeNonFunctionFrameDescriptor(name, frame);
+        return frame;
     }
 
     public static RComplex createComplexNA() {
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RSerialize.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RSerialize.java
index 5d7bde0495d8ef094cf37f0573ee31e667b1d016..1c67585506a6fbb8b8ee55b0b0a7c3bdce67e28e 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RSerialize.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RSerialize.java
@@ -415,7 +415,7 @@ public class RSerialize {
                     Object enclos = readItem();
                     REnvironment enclosing = enclos == RNull.instance ? REnvironment.baseEnv() : (REnvironment) enclos;
                     final REnvironment.NewEnv env = RDataFactory.createNewEnv(null);
-                    env.setParent(enclosing);
+                    RArguments.initializeEnclosingFrame(env.getFrame(), enclosing.getFrame());
                     /*
                      * We update the env reference as soon as possible, in case the contents of an
                      * environment contain a reference to the env itself.
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RDataFactory.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RDataFactory.java
index e13ed0bcd29d93c9e631b65c0d5b234a191061c0..f50154eed01375953438b9bac0452b60dd3160a3 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RDataFactory.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RDataFactory.java
@@ -23,6 +23,7 @@
 package com.oracle.truffle.r.runtime.data;
 
 import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import com.oracle.truffle.api.*;
 import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
@@ -436,16 +437,18 @@ public final class RDataFactory {
         return traceDataCreated(new RFunction(name, target, builtin, enclosingFrame, fastPath, containsDispatch));
     }
 
+    private static final AtomicInteger environmentCount = new AtomicInteger();
+
     public static REnvironment createInternalEnv() {
-        return traceDataCreated(new REnvironment.NewEnv(RRuntime.createNonFunctionFrame().materialize(), REnvironment.UNNAMED));
+        return traceDataCreated(new REnvironment.NewEnv(RRuntime.createNonFunctionFrame("<internal-env-" + environmentCount.incrementAndGet() + ">"), REnvironment.UNNAMED));
     }
 
     public static REnvironment.NewEnv createNewEnv(String name) {
-        return traceDataCreated(new REnvironment.NewEnv(RRuntime.createNonFunctionFrame().materialize(), name));
+        return traceDataCreated(new REnvironment.NewEnv(RRuntime.createNonFunctionFrame("<new-env-" + environmentCount.incrementAndGet() + ">"), name));
     }
 
     public static REnvironment createNewEnv(String name, boolean hashed, int initialSize) {
-        REnvironment.NewEnv env = new REnvironment.NewEnv(RRuntime.createNonFunctionFrame().materialize(), name);
+        REnvironment.NewEnv env = new REnvironment.NewEnv(RRuntime.createNonFunctionFrame("<new-env-" + environmentCount.incrementAndGet() + ">"), name);
         env.setHashed(hashed);
         env.setInitialSize(initialSize);
         return traceDataCreated(env);
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RFunction.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RFunction.java
index 89af3038071ddaeec86f364003533250435ca4e3..f3632b1d57f423a2f878bbb363ecf5f7cdc01c29 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RFunction.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RFunction.java
@@ -22,14 +22,15 @@
  */
 package com.oracle.truffle.r.runtime.data;
 
-import com.oracle.truffle.api.*;
-import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
-import com.oracle.truffle.api.frame.*;
-import com.oracle.truffle.api.interop.*;
-import com.oracle.truffle.api.nodes.*;
-import com.oracle.truffle.r.runtime.*;
-import com.oracle.truffle.r.runtime.context.*;
-import com.oracle.truffle.r.runtime.env.frame.*;
+import com.oracle.truffle.api.RootCallTarget;
+import com.oracle.truffle.api.frame.MaterializedFrame;
+import com.oracle.truffle.api.interop.ForeignAccess;
+import com.oracle.truffle.api.interop.TruffleObject;
+import com.oracle.truffle.api.nodes.RootNode;
+import com.oracle.truffle.r.runtime.RBuiltin;
+import com.oracle.truffle.r.runtime.RType;
+import com.oracle.truffle.r.runtime.VirtualEvalFrame;
+import com.oracle.truffle.r.runtime.context.RContext;
 
 /**
  * An instance of {@link RFunction} represents a function defined in R. The properties of a function
@@ -46,6 +47,7 @@ import com.oracle.truffle.r.runtime.env.frame.*;
 public final class RFunction extends RAttributeStorage implements RTypedValue, TruffleObject {
 
     public static final String NO_NAME = new String("");
+
     private String name;
     private final RootCallTarget target;
     private final RBuiltinDescriptor builtin;
@@ -53,7 +55,7 @@ public final class RFunction extends RAttributeStorage implements RTypedValue, T
 
     private FastPathFactory fastPath;
 
-    @CompilationFinal private MaterializedFrame enclosingFrame;
+    private final MaterializedFrame enclosingFrame;
 
     RFunction(String name, RootCallTarget target, RBuiltinDescriptor builtin, MaterializedFrame enclosingFrame, FastPathFactory fastPath, boolean containsDispatch) {
         this.target = target;
@@ -101,17 +103,6 @@ public final class RFunction extends RAttributeStorage implements RTypedValue, T
         return enclosingFrame;
     }
 
-    /**
-     * Used by the {@code environment<-} builtin.
-     */
-    public void setEnclosingFrame(MaterializedFrame enclosingFrame) {
-        assert !(enclosingFrame instanceof VirtualEvalFrame);
-        this.enclosingFrame = enclosingFrame;
-        FrameDescriptor descriptor = target.getRootNode().getFrameDescriptor();
-        FrameSlotChangeMonitor.getOrInitializeEnclosingFrameAssumption(null, descriptor, null, enclosingFrame);
-        FrameSlotChangeMonitor.getOrInitializeEnclosingFrameDescriptorAssumption(null, descriptor, enclosingFrame.getFrameDescriptor());
-    }
-
     private static final RStringVector implicitClass = RDataFactory.createStringVectorFromScalar(RType.Function.getName());
 
     @Override
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/env/REnvironment.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/env/REnvironment.java
index 50bb7b6818cf393117c9cabfb1e7c7ef4f59e390..3725f8e90c4eb3b3c6bb38d5ab5d9210b388c7c3 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/env/REnvironment.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/env/REnvironment.java
@@ -153,7 +153,7 @@ public abstract class REnvironment extends RAttributeStorage implements RTypedVa
         }
 
         public static ContextStateImpl newContext(RContext context) {
-            return createContext(context, RRuntime.createNonFunctionFrame().materialize());
+            return createContext(context, RRuntime.createNonFunctionFrame("global"));
         }
     }
 
@@ -278,7 +278,7 @@ public abstract class REnvironment extends RAttributeStorage implements RTypedVa
         namespaceRegistry.safePut("base", baseEnv.namespaceEnv);
 
         Global globalEnv = new Global(initialGlobalFrame);
-        globalEnv.setParent(baseEnv);
+        RArguments.initializeEnclosingFrame(initialGlobalFrame, baseFrame);
         state.setBaseEnv(baseEnv);
         state.setSearchPath(initSearchList(globalEnv));
     }
@@ -326,7 +326,7 @@ public abstract class REnvironment extends RAttributeStorage implements RTypedVa
                 REnvironment e = parentSearchPath.get(1).cloneEnv(globalFrame);
                 // create the new Global with clone top as parent
                 Global newGlobalEnv = new Global(globalFrame);
-                newGlobalEnv.setParent(e);
+                RArguments.initializeEnclosingFrame(globalFrame, e.getFrame());
                 // create new namespaceRegistry and populate it while locating "base"
                 REnvironment newNamespaceRegistry = RDataFactory.createInternalEnv();
                 Base newBaseEnv = null;
@@ -397,7 +397,7 @@ public abstract class REnvironment extends RAttributeStorage implements RTypedVa
         }
         // N.B. Base overrides this method, so we only get here for package environments
         REnvironment newEnv = RDataFactory.createNewEnv(getName());
-        newEnv.setParent(parentClone);
+        RArguments.initializeEnclosingFrame(newEnv.getFrame(), parentClone.getFrame());
         if (attributes != null) {
             newEnv.attributes = attributes.copy();
         }
@@ -588,13 +588,6 @@ public abstract class REnvironment extends RAttributeStorage implements RTypedVa
         throw new DetachException(message);
     }
 
-    /**
-     * Specifically for {@code ls()}, we don't care about the parent, as the use is transient.
-     */
-    public static REnvironment createLsCurrent(MaterializedFrame frame) {
-        return new Function(frame);
-    }
-
     /**
      * Converts a {@link Frame} to an {@link REnvironment}, which necessarily requires the frame to
      * be materialized.
@@ -633,7 +626,7 @@ public abstract class REnvironment extends RAttributeStorage implements RTypedVa
     @TruffleBoundary
     public static REnvironment createFromList(RAttributeProfiles attrProfiles, RList list, REnvironment parent) {
         REnvironment result = RDataFactory.createNewEnv(null);
-        result.setParent(parent);
+        RArguments.initializeEnclosingFrame(result.getFrame(), parent.getFrame());
         RStringVector names = list.getNames(attrProfiles);
         for (int i = 0; i < list.getLength(); i++) {
             try {
@@ -918,7 +911,7 @@ public abstract class REnvironment extends RAttributeStorage implements RTypedVa
 
         @Override
         protected REnvironment cloneEnv(MaterializedFrame globalFrame) {
-            Base newBase = new Base(RRuntime.createNonFunctionFrame().materialize(), globalFrame);
+            Base newBase = new Base(RRuntime.createNonFunctionFrame("base"), globalFrame);
             this.copyBindings(newBase);
             return newBase;
         }
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/env/frame/FrameSlotChangeMonitor.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/env/frame/FrameSlotChangeMonitor.java
index e6394089b36434c38da87a2d2511fdbf6071d453..23b37d8a2a7a6d357813ab84158b75560036f5ac 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/env/frame/FrameSlotChangeMonitor.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/env/frame/FrameSlotChangeMonitor.java
@@ -22,45 +22,401 @@
  */
 package com.oracle.truffle.r.runtime.env.frame;
 
-import java.util.*;
-
-import com.oracle.truffle.api.*;
+import java.lang.ref.WeakReference;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+import com.oracle.truffle.api.Assumption;
+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.*;
-import com.oracle.truffle.api.profiles.*;
-import com.oracle.truffle.r.runtime.*;
-import com.oracle.truffle.r.runtime.data.*;
+import com.oracle.truffle.api.Truffle;
+import com.oracle.truffle.api.frame.Frame;
+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.MaterializedFrame;
+import com.oracle.truffle.api.nodes.InvalidAssumptionException;
+import com.oracle.truffle.api.profiles.BranchProfile;
+import com.oracle.truffle.r.runtime.RArguments;
+import com.oracle.truffle.r.runtime.RInternalError;
+import com.oracle.truffle.r.runtime.StableValue;
 
 /**
- * This is meant to monitor updates performed on {@link FrameSlot}. Each {@link FrameSlot} holds an
- * {@link Assumption} in it's "info" field; it is valid as long as no non-local update has ever
- * taken place.<br/>
- * The background to this rather strange assumption is that non-local reads are very hard to keep
- * track of thanks to R powerful language features. To keep the maintenance for the assumption as
- * cheap as possible, it checks only local reads - which is fast - and does a more costly check on
- * "<<-" but invalidates the assumption as soon as "eval" and the like comes into play.<br/>
+ * This class maintains information about the current hierarchy of environments in the system. This
+ * information is described as assumptions that will be invalidated if the layout changes, and thus
+ * make sure that code is properly deoptimized.
  */
 public final class FrameSlotChangeMonitor {
 
-    private static final int MAX_FUNCTION_INVALIDATION_COUNT = 2;
-    private static final int MAX_INVALIDATION_COUNT = 1;
+    /*
+     * The following classes describe the result of a previous lookup that successfully delivered a
+     * result based on the system's knowledge about the hierarchy of environments and the stable
+     * values of certain bindings. Most function lookups can be answered based only on this
+     * information.
+     *
+     * These lookups are stored for caching and invalidation, i.e., to save on repeated lookups and
+     * to invalidate lookups in case the environment hierarchy changes.
+     */
+
+    public abstract static class LookupResult {
+        protected final Assumption assumption;
+
+        private LookupResult(String identifier) {
+            this.assumption = Truffle.getRuntime().createAssumption("lookup \"" + identifier + "\" (" + this.getClass().getSimpleName() + ")");
+        }
+
+        public boolean isValid() {
+            return assumption.isValid();
+        }
+
+        public abstract Object getValue() throws InvalidAssumptionException;
+
+        public void invalidate() {
+            assumption.invalidate();
+        }
+    }
+
+    public static final class StableValueLookupResult extends LookupResult {
+        private final StableValue<Object> value;
+
+        private StableValueLookupResult(String identifier, StableValue<Object> value) {
+            super(identifier);
+            this.value = value;
+        }
+
+        @Override
+        public boolean isValid() {
+            return super.isValid() && value.getAssumption().isValid();
+        }
+
+        @Override
+        public Object getValue() throws InvalidAssumptionException {
+            assumption.check();
+            StableValue<Object> result = value;
+            result.getAssumption().check();
+            return result.getValue();
+        }
+    }
+
+    public static final class MissingLookupResult extends LookupResult {
+
+        private MissingLookupResult(String identifier) {
+            super(identifier);
+        }
+
+        @Override
+        public Object getValue() throws InvalidAssumptionException {
+            assumption.check();
+            return null;
+        }
+    }
+
+    public static final class FrameAndSlotLookupResult extends LookupResult {
+        private final MaterializedFrame frame;
+        private final FrameSlot slot;
+
+        private FrameAndSlotLookupResult(String identifier, MaterializedFrame frame, FrameSlot slot) {
+            super(identifier);
+            this.frame = frame;
+            this.slot = slot;
+        }
+
+        @Override
+        public Object getValue() {
+            // fast path execution should use getFrame / getSlot
+            CompilerAsserts.neverPartOfCompilation("FrameAndSlotLookupResult.getValue() should not be used in fast path execution");
+            return frame.getValue(slot);
+        }
+
+        public MaterializedFrame getFrame() throws InvalidAssumptionException {
+            assumption.check();
+            return frame;
+        }
+
+        public FrameSlot getSlot() throws InvalidAssumptionException {
+            assumption.check();
+            return slot;
+        }
+    }
+
+    /**
+     * Every frame descriptor in the system will be associated with a FrameDescriptorMetaData
+     * object. For function environments, one frame descriptor corresponds to many actual
+     * environments, while for manually created environment, there is always one frame descriptor
+     * for one environment.
+     */
+    private static final class FrameDescriptorMetaData {
+        private final String name; // name for debug purposes
+        private final WeakReference<MaterializedFrame> singletonFrame;
+        private final Set<FrameDescriptor> subDescriptors = Collections.newSetFromMap(new WeakHashMap<>());
+
+        /**
+         * This set contains all lookups that have been performed "across" this frame descriptor. If
+         * a binding with one of these names is modified, then the lookups in this frame descriptor
+         * and all child frame descriptors need to be checked.
+         */
+        private final Set<Object> previousLookups = new HashSet<>();
+        /**
+         * A set of all lookups that started in this frame descriptor.
+         */
+        private final WeakHashMap<Object, LookupResult> lookupResults = new WeakHashMap<>();
+
+        private StableValue<FrameDescriptor> enclosingFrameDescriptor = new StableValue<>(null, "initial (empty) enclosing frame");
+
+        private FrameDescriptorMetaData(String name, MaterializedFrame singletonFrame) {
+            this.name = name;
+            this.singletonFrame = singletonFrame == null ? null : new WeakReference<>(singletonFrame);
+        }
+
+        public void updateEnclosingFrameDescriptor(FrameDescriptor newEnclosingDescriptor) {
+            enclosingFrameDescriptor.getAssumption().invalidate();
+            enclosingFrameDescriptor = new StableValue<>(newEnclosingDescriptor, "enclosing frame");
+        }
+    }
+
+    private static final WeakHashMap<FrameDescriptor, FrameDescriptorMetaData> frameDescriptors = new WeakHashMap<>();
+
+    /**
+     * This function tries to fulfill the lookup for the given name in the given frame based only on
+     * the static knowledge about the frame descriptor hierarchy and stable bindings. Returns
+     * {@code null} in case this was not possible.
+     */
+    public static synchronized LookupResult lookup(Frame frame, Object identifier) {
+        FrameDescriptorMetaData metaData = getMetaData(frame);
+        LookupResult result = metaData.lookupResults.get(identifier);
+        if (result != null && result.isValid()) {
+            return result;
+        }
+        Frame current = frame;
+        while (true) {
+            FrameSlot slot = current.getFrameDescriptor().findFrameSlot(identifier);
+            if (slot != null) {
+                LookupResult lookupResult;
+                StableValue<Object> stableValue = getFrameSlotInfo(slot).stableValue;
+                if (stableValue != null) {
+                    lookupResult = new StableValueLookupResult(identifier.toString(), stableValue);
+                } else {
+                    FrameDescriptorMetaData currentMetaData = getMetaData(current);
+                    if (currentMetaData.singletonFrame == null) {
+                        return null;
+                    } else {
+                        assert currentMetaData.singletonFrame.get() != null;
+                        lookupResult = new FrameAndSlotLookupResult(identifier.toString(), currentMetaData.singletonFrame.get(), slot);
+                    }
+                }
+                addPreviousLookups(frame, current, identifier);
+                metaData.lookupResults.put(identifier, lookupResult);
+                return lookupResult;
+            }
+            Frame next = RArguments.getEnclosingFrame(current);
+            assert isEnclosingFrameDescriptor(current, next) : "the enclosing frame descriptor assumptions do not match the actual enclosing frame descriptor: " + getMetaData(current).name + " -> " +
+                            getMetaData(next).name;
+            if (next == null) {
+                // leave "current" if we hit the empty env
+                break;
+            }
+            current = next;
+        }
+        // not frame slot found: missing value
+        addPreviousLookups(frame, current, identifier);
+        LookupResult lookupResult = new MissingLookupResult(identifier.toString());
+        metaData.lookupResults.put(identifier, lookupResult);
+        return lookupResult;
+    }
+
+    private static void addPreviousLookups(Frame from, Frame to, Object identifier) {
+        Frame mark = from;
+        while (true) {
+            FrameDescriptorMetaData lookupMetaData = getMetaData(mark);
+            lookupMetaData.previousLookups.add(identifier);
+            if (mark == to) {
+                break;
+            }
+            mark = RArguments.getEnclosingFrame(mark);
+        }
+    }
+
+    private static boolean isEnclosingFrameDescriptor(Frame current, Frame next) {
+        assert current != null;
+        FrameDescriptorMetaData metaData = getMetaData(current);
+        FrameDescriptor nextDesc = next == null ? null : handleBaseNamespaceEnv(next);
+        return metaData.enclosingFrameDescriptor.getValue() == nextDesc;
+    }
+
+    private static synchronized void invalidateNames(FrameDescriptorMetaData metaData, Collection<Object> identifiers) {
+        if (metaData.previousLookups.removeAll(identifiers)) {
+            for (Object identifier : identifiers) {
+                LookupResult result = metaData.lookupResults.remove(identifier);
+                if (result != null) {
+                    result.invalidate();
+                }
+            }
+            for (FrameDescriptor descriptor : metaData.subDescriptors) {
+                FrameDescriptorMetaData sub = getMetaData(descriptor);
+                invalidateNames(sub, identifiers);
+            }
+        }
+    }
+
+    /**
+     * Special handling (return a marker frame descriptor) for the namespace:base environment.
+     */
+    private static FrameDescriptor handleBaseNamespaceEnv(Frame frame) {
+        return frame == null ? null : frame instanceof NSBaseMaterializedFrame ? ((NSBaseMaterializedFrame) frame).getMarkerFrameDescriptor() : frame.getFrameDescriptor();
+    }
+
+    private static FrameDescriptorMetaData getMetaData(FrameDescriptor descriptor) {
+        FrameDescriptorMetaData result = frameDescriptors.get(descriptor);
+        assert result != null : "null metadata for " + descriptor;
+        return result;
+    }
+
+    private static FrameDescriptorMetaData getMetaData(Frame frame) {
+        return getMetaData(handleBaseNamespaceEnv(frame));
+    }
+
+    public static synchronized void initializeEnclosingFrame(FrameDescriptor descriptor, Frame newEnclosingFrame) {
+        CompilerAsserts.neverPartOfCompilation();
+        assert descriptor != null : "initializing enclosing of null descriptor";
+        FrameDescriptorMetaData target = getMetaData(descriptor);
+        assert target != null : "frame descriptor wasn't registered properly";
+
+        FrameDescriptor newEnclosingDescriptor = handleBaseNamespaceEnv(newEnclosingFrame);
+
+        // this function can be called multiple times with the same enclosing descriptor
+        assert target.enclosingFrameDescriptor.getAssumption().isValid();
+        if (target.enclosingFrameDescriptor.getValue() != newEnclosingDescriptor) {
+            assert target.enclosingFrameDescriptor.getValue() == null : "existing enclosing descriptor while initializing " + target.name;
+            assert target.lookupResults.isEmpty() : "existing lookup results while initializing " + target.name;
+
+            target.updateEnclosingFrameDescriptor(newEnclosingDescriptor);
+            if (newEnclosingDescriptor != null) {
+                FrameDescriptorMetaData newEnclosing = getMetaData(newEnclosingDescriptor);
+                newEnclosing.subDescriptors.add(descriptor);
+            }
+        }
+    }
+
+    public static synchronized void initializeEnclosingFrame(Frame frame, Frame newEnclosingFrame) {
+        initializeEnclosingFrame(handleBaseNamespaceEnv(frame), newEnclosingFrame);
+    }
+
+    public static synchronized void setEnclosingFrame(FrameDescriptor descriptor, MaterializedFrame newEnclosingFrame, MaterializedFrame oldEnclosingFrame) {
+        CompilerAsserts.neverPartOfCompilation();
+        FrameDescriptorMetaData target = getMetaData(descriptor);
+        assert target != null : "frame descriptor wasn't registered properly for " + descriptor;
+
+        // invalidate existing lookups
+        invalidateAllNames(target);
+
+        FrameDescriptor oldEnclosingDescriptor = target.enclosingFrameDescriptor.getValue();
+        assert (oldEnclosingDescriptor == null) == (oldEnclosingFrame == null) : "mismatch " + oldEnclosingDescriptor + " / " + oldEnclosingFrame;
+
+        if (oldEnclosingDescriptor != null) {
+            assert oldEnclosingDescriptor == oldEnclosingFrame.getFrameDescriptor() : "mismatch " + oldEnclosingDescriptor + " / " + oldEnclosingFrame.getFrameDescriptor();
+            FrameDescriptorMetaData oldEnclosing = getMetaData(oldEnclosingDescriptor);
+            oldEnclosing.subDescriptors.remove(descriptor);
+        }
+        FrameDescriptor newEnclosingDescriptor = handleBaseNamespaceEnv(newEnclosingFrame);
+        target.updateEnclosingFrameDescriptor(newEnclosingDescriptor);
+
+        if (newEnclosingDescriptor != null) {
+            FrameDescriptorMetaData newEnclosing = getMetaData(newEnclosingDescriptor);
+            assert !newEnclosing.name.equals("global") || !target.name.equals("base");
+            newEnclosing.subDescriptors.add(descriptor);
+        }
+    }
+
+    public static synchronized void setEnclosingFrame(Frame frame, MaterializedFrame newEnclosingFrame, MaterializedFrame oldEnclosingFrame) {
+        setEnclosingFrame(handleBaseNamespaceEnv(frame), newEnclosingFrame, oldEnclosingFrame);
+    }
+
+    private static void invalidateAllNames(FrameDescriptorMetaData target) {
+        for (Map.Entry<Object, LookupResult> entry : target.lookupResults.entrySet()) {
+            entry.getValue().invalidate();
+        }
+        target.lookupResults.clear();
+        target.previousLookups.clear();
+        for (FrameDescriptor sub : target.subDescriptors) {
+            invalidateAllNames(getMetaData(sub));
+        }
+    }
+
+    public static synchronized void detach(Frame frame) {
+        CompilerAsserts.neverPartOfCompilation();
+        FrameDescriptorMetaData position = getMetaData(frame);
+        FrameDescriptor oldEnclosingDescriptor = position.enclosingFrameDescriptor.getValue();
+        FrameDescriptorMetaData oldEnclosing = getMetaData(oldEnclosingDescriptor);
+        FrameDescriptor newEnclosingDescriptor = oldEnclosing.enclosingFrameDescriptor.getValue();
+        FrameDescriptorMetaData newEnclosing = getMetaData(newEnclosingDescriptor);
+
+        assert position.enclosingFrameDescriptor.getAssumption().isValid() && oldEnclosing.enclosingFrameDescriptor.getAssumption().isValid();
+
+        invalidateNames(oldEnclosing, oldEnclosingDescriptor.getIdentifiers());
+
+        position.updateEnclosingFrameDescriptor(newEnclosingDescriptor);
+        oldEnclosing.updateEnclosingFrameDescriptor(null);
+        oldEnclosing.subDescriptors.remove(frame.getFrameDescriptor());
+        newEnclosing.subDescriptors.remove(oldEnclosingDescriptor);
+        newEnclosing.subDescriptors.add(frame.getFrameDescriptor());
+    }
+
+    public static synchronized void attach(Frame frame, Frame newEnclosingFrame) {
+        CompilerAsserts.neverPartOfCompilation();
+        FrameDescriptorMetaData position = getMetaData(frame);
+        FrameDescriptorMetaData newEnclosing = getMetaData(newEnclosingFrame);
+        assert position.enclosingFrameDescriptor.getAssumption().isValid();
+        FrameDescriptor oldEnclosingDescriptor = position.enclosingFrameDescriptor.getValue();
+        FrameDescriptorMetaData oldEnclosing = getMetaData(oldEnclosingDescriptor);
+
+        invalidateAllNames(newEnclosing);
+        invalidateNames(position, newEnclosingFrame.getFrameDescriptor().getIdentifiers());
+
+        position.enclosingFrameDescriptor.getAssumption().invalidate();
+        position.enclosingFrameDescriptor = new StableValue<>(newEnclosingFrame.getFrameDescriptor(), "enclosing frame");
+        newEnclosing.enclosingFrameDescriptor.getAssumption().invalidate();
+        newEnclosing.enclosingFrameDescriptor = new StableValue<>(oldEnclosingDescriptor, "enclosing frame");
+        assert frame.getFrameDescriptor() == handleBaseNamespaceEnv(frame);
+        assert !newEnclosing.name.equals("global") || !position.name.equals("base");
+        newEnclosing.subDescriptors.add(frame.getFrameDescriptor());
+        oldEnclosing.subDescriptors.remove(frame.getFrameDescriptor());
+        oldEnclosing.subDescriptors.add(newEnclosingFrame.getFrameDescriptor());
+    }
+
+    private static final int MAX_INVALIDATION_COUNT = 2;
+    private static final int MAX_GLOBAL_ENV_INVALIDATION_COUNT = 1;
 
     @SuppressWarnings("unused")
     private static void out(String format, Object... args) {
-// System.out.println(String.format(format, args));
+        // System.out.println(String.format(format, args));
     }
 
     private static final class FrameSlotInfoImpl {
-        @CompilationFinal private StableValue<Object> stableValue;
+        /**
+         * This is meant to monitor updates performed on {@link FrameSlot}. Each {@link FrameSlot}
+         * holds an {@link Assumption} in it's "info" field; it is valid as long as no non-local
+         * update has ever taken place.<br/>
+         * The background to this rather strange assumption is that non-local reads are very hard to
+         * keep track of thanks to R powerful language features. To keep the maintenance for the
+         * assumption as cheap as possible, it checks only local reads - which is fast - and does a
+         * more costly check on "<<-" but invalidates the assumption as soon as "eval" and the like
+         * comes into play.<br/>
+         */
         private final Assumption nonLocalModifiedAssumption = Truffle.getRuntime().createAssumption();
-        private final Object identifier;
+
+        @CompilationFinal private StableValue<Object> stableValue;
         private int invalidationCount;
 
-        public FrameSlotInfoImpl(boolean isSingletonFrame, Object identifier) {
-            this.identifier = identifier;
+        public FrameSlotInfoImpl(boolean isSingletonFrame, boolean isGlobalEnv, Object identifier) {
             if (isSingletonFrame) {
                 stableValue = new StableValue<>(null, identifier.toString());
-                invalidationCount = 0;
+                invalidationCount = isGlobalEnv ? MAX_GLOBAL_ENV_INVALIDATION_COUNT : MAX_INVALIDATION_COUNT;
             } else {
                 stableValue = null;
             }
@@ -70,37 +426,26 @@ public final class FrameSlotChangeMonitor {
             return stableValue != null;
         }
 
-        public void setValue(Object value) {
+        public void setValue(Object value, FrameSlot slot) {
             if (stableValue != null && stableValue.getValue() != value) {
                 CompilerDirectives.transferToInterpreterAndInvalidate();
                 stableValue.getAssumption().invalidate();
-                int maxInvalidationCount = value instanceof RFunction ? MAX_FUNCTION_INVALIDATION_COUNT : MAX_INVALIDATION_COUNT;
-                if (invalidationCount++ < maxInvalidationCount) {
-                    out("setting singleton value %s = %s", identifier, value.getClass());
-                    stableValue = new StableValue<>(value, identifier.toString());
+                if (invalidationCount > 0) {
+                    invalidationCount--;
+                    out("setting singleton value %s = %s", slot.getIdentifier(), value == null ? "null" : value.getClass());
+                    stableValue = new StableValue<>(value, String.valueOf(slot.getIdentifier()));
                 } else {
-                    out("setting non-singleton value %s", identifier);
+                    out("setting non-singleton value %s", slot.getIdentifier());
                     stableValue = null;
                 }
             }
         }
 
-        public void invalidateValue() {
-            if (stableValue != null) {
-                stableValue.getAssumption().invalidate();
-                stableValue = null;
-            }
-        }
-
         public StableValue<Object> getStableValue() {
             return stableValue;
         }
     }
 
-    private static final WeakHashMap<FrameDescriptor, StableValue<MaterializedFrame>> descriptorEnclosingFrameAssumptions = new WeakHashMap<>();
-    private static final WeakHashMap<FrameDescriptor, Boolean> descriptorSingletonAssumptions = new WeakHashMap<>();
-    private static final WeakHashMap<FrameDescriptor, StableValue<FrameDescriptor>> descriptorEnclosingDescriptorAssumptions = new WeakHashMap<>();
-
     /**
      * Retrieves the not-changed-locally {@link Assumption} for the given frame slot.
      *
@@ -114,26 +459,23 @@ public final class FrameSlotChangeMonitor {
         Object info = slot.getInfo();
         if (!(info instanceof FrameSlotInfoImpl)) {
             CompilerDirectives.transferToInterpreter();
-            throw RInternalError.shouldNotReachHere("Each FrameSlot should hold a FrameSlotInfo in it's info field!");
+            throw RInternalError.shouldNotReachHere("Each FrameSlot should hold a FrameSlotInfo in its info field!");
         }
         return (FrameSlotInfoImpl) info;
     }
 
-    // method for creating new frame slots
-
-    public static FrameSlot addFrameSlot(FrameDescriptor fd, Object identifier, FrameSlotKind kind) {
-        boolean isSingletonFrame = descriptorSingletonAssumptions.containsKey(fd);
-        return fd.addFrameSlot(identifier, new FrameSlotInfoImpl(isSingletonFrame, identifier), kind);
-    }
-
-    public static FrameSlot findOrAddFrameSlot(FrameDescriptor fd, Object identifier) {
-        FrameSlot frameSlot = fd.findFrameSlot(identifier);
-        return frameSlot != null ? frameSlot : addFrameSlot(fd, identifier, FrameSlotKind.Illegal);
-    }
+    // methods for creating new frame slots
 
     public static FrameSlot findOrAddFrameSlot(FrameDescriptor fd, Object identifier, FrameSlotKind initialKind) {
+        CompilerAsserts.neverPartOfCompilation();
         FrameSlot frameSlot = fd.findFrameSlot(identifier);
-        return frameSlot != null ? frameSlot : addFrameSlot(fd, identifier, initialKind);
+        if (frameSlot != null) {
+            return frameSlot;
+        } else {
+            FrameDescriptorMetaData metaData = getMetaData(fd);
+            invalidateNames(metaData, Arrays.asList(identifier));
+            return fd.addFrameSlot(identifier, new FrameSlotInfoImpl(metaData.singletonFrame != null, "global".equals(metaData.name), identifier), initialKind);
+        }
     }
 
     // methods for changing frame slot contents
@@ -146,8 +488,12 @@ public final class FrameSlotChangeMonitor {
      *            {@link RInternalError} otherwise
      * @param invalidateProfile Used to guard the invalidation code.
      */
-    private static void checkAndInvalidate(Frame curFrame, FrameSlot slot, boolean isNonLocal, BranchProfile invalidateProfile) {
+    private static void checkAndInvalidate(Frame curFrame, FrameSlot slot, boolean isNonLocal, Object newValue, BranchProfile invalidateProfile) {
         assert curFrame.getFrameDescriptor() == slot.getFrameDescriptor();
+        FrameSlotInfoImpl info = getFrameSlotInfo(slot);
+        if (info.needsInvalidation()) {
+            info.setValue(newValue, slot);
+        }
 
         if (getNotChangedNonLocallyAssumption(slot).isValid()) {
             // Check whether current frame is used outside a regular stack
@@ -164,169 +510,57 @@ public final class FrameSlotChangeMonitor {
 
     public static void setByteAndInvalidate(Frame frame, FrameSlot frameSlot, byte newValue, boolean isNonLocal, BranchProfile invalidateProfile) {
         frame.setByte(frameSlot, newValue);
-        FrameSlotInfoImpl info = getFrameSlotInfo(frameSlot);
-        if (info.needsInvalidation()) {
-            info.setValue(newValue);
-        }
-        checkAndInvalidate(frame, frameSlot, isNonLocal, invalidateProfile);
+        checkAndInvalidate(frame, frameSlot, isNonLocal, newValue, invalidateProfile);
     }
 
     public static void setIntAndInvalidate(Frame frame, FrameSlot frameSlot, int newValue, boolean isNonLocal, BranchProfile invalidateProfile) {
         frame.setInt(frameSlot, newValue);
-        FrameSlotInfoImpl info = getFrameSlotInfo(frameSlot);
-        if (info.needsInvalidation()) {
-            info.setValue(newValue);
-        }
-        checkAndInvalidate(frame, frameSlot, isNonLocal, invalidateProfile);
+        checkAndInvalidate(frame, frameSlot, isNonLocal, newValue, invalidateProfile);
     }
 
     public static void setDoubleAndInvalidate(Frame frame, FrameSlot frameSlot, double newValue, boolean isNonLocal, BranchProfile invalidateProfile) {
         frame.setDouble(frameSlot, newValue);
-        FrameSlotInfoImpl info = getFrameSlotInfo(frameSlot);
-        if (info.needsInvalidation()) {
-            info.setValue(newValue);
-        }
-        checkAndInvalidate(frame, frameSlot, isNonLocal, invalidateProfile);
+        checkAndInvalidate(frame, frameSlot, isNonLocal, newValue, invalidateProfile);
     }
 
     public static void setObjectAndInvalidate(Frame frame, FrameSlot frameSlot, Object newValue, boolean isNonLocal, BranchProfile invalidateProfile) {
         frame.setObject(frameSlot, newValue);
-        FrameSlotInfoImpl info = getFrameSlotInfo(frameSlot);
-        if (info.needsInvalidation()) {
-            info.setValue(newValue);
-        }
-        checkAndInvalidate(frame, frameSlot, isNonLocal, invalidateProfile);
-    }
-
-    // update enclosing frames
-
-    public static void invalidateEnclosingFrame(Frame frame) {
-        CompilerAsserts.neverPartOfCompilation();
-        MaterializedFrame enclosingFrame = RArguments.getEnclosingFrame(frame);
-        getOrInitializeEnclosingFrameAssumption(frame, frame.getFrameDescriptor(), null, enclosingFrame);
-        getOrInitializeEnclosingFrameDescriptorAssumption(frame, frame.getFrameDescriptor(), enclosingFrame == null ? null : enclosingFrame.getFrameDescriptor());
+        checkAndInvalidate(frame, frameSlot, isNonLocal, newValue, invalidateProfile);
     }
 
     /**
      * Initializes the internal data structures for a newly created frame descriptor that is
      * intended to be used for a non-function frame (and thus will only ever be used for one frame).
-     *
-     * The namespace:base environment needs to be handled specially, because it shares a frame (and
-     * thus, also a frame descriptor) with the package:base environment.
      */
-    public static synchronized void initializeNonFunctionFrameDescriptor(FrameDescriptor frameDescriptor, boolean isNamespaceBase) {
-        descriptorEnclosingFrameAssumptions.put(frameDescriptor, StableValue.invalidated());
-        descriptorEnclosingDescriptorAssumptions.put(frameDescriptor, StableValue.invalidated());
-        if (!isNamespaceBase) {
-            descriptorSingletonAssumptions.put(frameDescriptor, Boolean.FALSE);
-        }
-    }
-
-    public static synchronized void initializeFunctionFrameDescriptor(FrameDescriptor frameDescriptor) {
-        descriptorEnclosingFrameAssumptions.put(frameDescriptor, StableValue.invalidated());
-        descriptorEnclosingDescriptorAssumptions.put(frameDescriptor, StableValue.invalidated());
+    public static synchronized void initializeNonFunctionFrameDescriptor(String name, MaterializedFrame frame) {
+        frameDescriptors.put(handleBaseNamespaceEnv(frame), new FrameDescriptorMetaData(name, frame));
     }
 
-    public static synchronized StableValue<MaterializedFrame> getEnclosingFrameAssumption(FrameDescriptor descriptor) {
-        return descriptorEnclosingFrameAssumptions.get(descriptor);
+    public static synchronized void initializeFunctionFrameDescriptor(String name, FrameDescriptor frameDescriptor) {
+        frameDescriptors.put(frameDescriptor, new FrameDescriptorMetaData(name, null));
     }
 
     public static synchronized StableValue<FrameDescriptor> getEnclosingFrameDescriptorAssumption(FrameDescriptor descriptor) {
-        return descriptorEnclosingDescriptorAssumptions.get(descriptor);
-    }
-
-    /**
-     * Special handling (return a marker frame) for the namespace:base environment.
-     */
-    private static FrameDescriptor handleBaseNamespaceEnv(Frame frame, FrameDescriptor originalFrameDescriptor) {
-        return frame instanceof NSBaseMaterializedFrame ? ((NSBaseMaterializedFrame) frame).getMarkerFrameDescriptor() : originalFrameDescriptor;
-    }
-
-    public static synchronized StableValue<FrameDescriptor> getOrInitializeEnclosingFrameDescriptorAssumption(Frame frame, FrameDescriptor originalFrameDescriptor, FrameDescriptor newValue) {
-        CompilerAsserts.neverPartOfCompilation();
-        FrameDescriptor frameDescriptor = handleBaseNamespaceEnv(frame, originalFrameDescriptor);
-        StableValue<FrameDescriptor> currentValue = descriptorEnclosingDescriptorAssumptions.get(frameDescriptor);
-        if (currentValue.getAssumption().isValid()) {
-            if (currentValue.getValue() == newValue) {
-                return currentValue;
-            } else {
-                currentValue.getAssumption().invalidate();
-            }
-        }
-        currentValue = new StableValue<>(newValue, "enclosing frame descriptor");
-        descriptorEnclosingDescriptorAssumptions.put(frameDescriptor, currentValue);
-        return currentValue;
-    }
-
-    public static synchronized StableValue<MaterializedFrame> getOrInitializeEnclosingFrameAssumption(Frame frame, FrameDescriptor originalFrameDescriptor, StableValue<MaterializedFrame> value,
-                    MaterializedFrame newValue) {
-        CompilerAsserts.neverPartOfCompilation();
-        FrameDescriptor frameDescriptor = handleBaseNamespaceEnv(frame, originalFrameDescriptor);
-        if (value != null) {
-            value.getAssumption().invalidate();
-        }
-        StableValue<MaterializedFrame> currentValue = descriptorEnclosingFrameAssumptions.get(frameDescriptor);
-        if (currentValue == null) {
-            return null;
-        }
-        if (currentValue.getAssumption().isValid()) {
-            if (currentValue.getValue() == newValue) {
-                return currentValue;
-            } else {
-                currentValue.getAssumption().invalidate();
-            }
-        }
-        if (currentValue == StableValue.<MaterializedFrame> invalidated()) {
-            currentValue = new StableValue<>(newValue, "enclosing frame");
-            descriptorEnclosingFrameAssumptions.put(frameDescriptor, currentValue);
-            return currentValue;
-        } else {
-            descriptorEnclosingFrameAssumptions.remove(frameDescriptor);
-            return null;
-        }
-    }
-
-    public static boolean checkSingletonFrame(VirtualFrame vf) {
-        CompilerDirectives.transferToInterpreterAndInvalidate();
-        return checkSingletonFrameInternal(vf);
-    }
-
-    private static synchronized boolean checkSingletonFrameInternal(VirtualFrame vf) {
-        Boolean value = descriptorSingletonAssumptions.get(vf.getFrameDescriptor());
-        if (value == null) {
-            return false;
-        } else if (value == Boolean.FALSE) {
-            out("marking frame descriptor %s as singleton", vf.getFrameDescriptor());
-            descriptorSingletonAssumptions.put(vf.getFrameDescriptor(), Boolean.TRUE);
-            return true;
-        } else {
-            out("marking frame descriptor %s as non-singleton", vf.getFrameDescriptor());
-            for (FrameSlot slot : vf.getFrameDescriptor().getSlots()) {
-                if (getFrameSlotInfo(slot).needsInvalidation()) {
-                    getFrameSlotInfo(slot).invalidateValue();
-                    out("  invalidating singleton slot %s", slot.getIdentifier());
-                }
-            }
-            descriptorSingletonAssumptions.remove(vf.getFrameDescriptor());
-            return false;
-        }
+        return frameDescriptors.get(descriptor).enclosingFrameDescriptor;
     }
 
     public static synchronized StableValue<Object> getStableValueAssumption(FrameDescriptor descriptor, FrameSlot frameSlot, Object value) {
         CompilerAsserts.neverPartOfCompilation();
         StableValue<Object> stableValue = getFrameSlotInfo(frameSlot).getStableValue();
         if (stableValue != null) {
-            assert descriptorSingletonAssumptions.containsKey(descriptor) : "single frame slot within non-singleton descriptor";
+            assert getMetaData(descriptor).singletonFrame != null : "single frame slot within non-singleton descriptor";
             assert stableValue.getValue() == value || (stableValue.getValue() != null && (stableValue.getValue().equals(value) || !stableValue.getAssumption().isValid())) : stableValue.getValue() +
                             " vs. " + value;
         }
         return stableValue;
     }
 
-    public static void updateValue(FrameSlot slot, Object value) {
-        FrameSlotInfoImpl info = getFrameSlotInfo(slot);
-        if (info.needsInvalidation()) {
-            info.setValue(value);
-        }
+    public static synchronized MaterializedFrame getSingletonFrame(FrameDescriptor descriptor) {
+        WeakReference<MaterializedFrame> singleton = getMetaData(descriptor).singletonFrame;
+        return singleton == null ? null : singleton.get();
+    }
+
+    public static boolean isValidFrameDescriptor(FrameDescriptor frameDesc) {
+        return getMetaData(frameDesc) != null;
     }
 }
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/env/frame/NSBaseMaterializedFrame.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/env/frame/NSBaseMaterializedFrame.java
index ef3392887ec3f89e3a78bdad9bd3f757288148f4..642a9da9ef22f883b1be193a4428bd90d7577fa1 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/env/frame/NSBaseMaterializedFrame.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/env/frame/NSBaseMaterializedFrame.java
@@ -45,13 +45,12 @@ public final class NSBaseMaterializedFrame implements MaterializedFrame {
         this.packageBaseFrame = packageBaseFrame;
         this.arguments = Arrays.copyOf(packageBaseFrame.getArguments(), packageBaseFrame.getArguments().length);
         this.markerFrameDescriptor = new FrameDescriptor();
-        FrameSlotChangeMonitor.initializeNonFunctionFrameDescriptor(markerFrameDescriptor, true);
-        RArguments.setEnclosingFrame(this, globalFrame);
+        FrameSlotChangeMonitor.initializeNonFunctionFrameDescriptor("namespace:base", this);
+        RArguments.initializeEnclosingFrame(this, globalFrame);
     }
 
     public void updateGlobalFrame(MaterializedFrame globalFrame) {
         RArguments.setEnclosingFrame(this, globalFrame);
-        FrameSlotChangeMonitor.invalidateEnclosingFrame(this);
     }
 
     public FrameDescriptor getMarkerFrameDescriptor() {
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/env/frame/REnvTruffleFrameAccess.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/env/frame/REnvTruffleFrameAccess.java
index 7eb94f4ddb8aa2286992545f043ff567f7b313e8..943bba2586594b0c95096f9e00ec2211a742cc2e 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/env/frame/REnvTruffleFrameAccess.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/env/frame/REnvTruffleFrameAccess.java
@@ -75,21 +75,16 @@ public final class REnvTruffleFrameAccess extends REnvFrameAccess {
         if (lockedBindings != null && lockedBindings.contains(key)) {
             throw new PutException(RError.Message.ENV_CHANGE_BINDING, key);
         }
-        FrameDescriptor fd = frame.getFrameDescriptor();
-        FrameSlot slot = fd.findFrameSlot(key);
-
         FrameSlotKind valueSlotKind = RRuntime.getSlotKind(value);
+        FrameDescriptor fd = frame.getFrameDescriptor();
+        FrameSlot slot = FrameSlotChangeMonitor.findOrAddFrameSlot(fd, key, valueSlotKind);
 
-        // Handle all other values
-        if (slot == null) {
-            slot = FrameSlotChangeMonitor.addFrameSlot(fd, key, valueSlotKind);
-        } else {
-            if (valueSlotKind != slot.getKind()) {
-                // we must not toggle between slot kinds, so go to Object
-                valueSlotKind = FrameSlotKind.Object;
-                slot.setKind(valueSlotKind);
-            }
+        if (valueSlotKind != slot.getKind()) {
+            // we must not toggle between slot kinds, so go to Object
+            valueSlotKind = FrameSlotKind.Object;
+            slot.setKind(valueSlotKind);
         }
+
         switch (valueSlotKind) {
             case Byte:
                 FrameSlotChangeMonitor.setByteAndInvalidate(frame, slot, (byte) value, false, null);
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test
index 84c1ffe516f66125b0648162f28629e3a4896794..f6880d4a82f9d94c583dd9311592551b1e298d19 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test
@@ -17460,6 +17460,10 @@ Error in environment(e1) <- 3 : replacement object is not an environment
 ##com.oracle.truffle.r.test.builtins.TestBuiltin_environmentassign.testenvironmentassign1
 #{ e1 <- new.env(); environment(e1) <- NULL }
 
+##com.oracle.truffle.r.test.builtins.TestBuiltin_environmentassign.testenvironmentassign1
+#{ f <- function() x; f2 <- f; e <- new.env(); assign('x', 2, envir=e); x <- 1; environment(f) <- e; c(f(), f2())}
+[1] 2 1
+
 ##com.oracle.truffle.r.test.builtins.TestBuiltin_environmentassign.testenvironmentassign1
 #{ f <- function() {}; e1 <- new.env(); environment(f) <- e1 }
 
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/TestS4.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/TestS4.java
index c63df2532b2b6d9af4ae81b7867e80801aaf4097..c67c5169556fe3b15f489216c410db35215c98e1 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/TestS4.java
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/TestS4.java
@@ -74,8 +74,7 @@ public class TestS4 extends TestBase {
     @Test
     public void testAllocation() {
         assertEval("{ new(\"numeric\") }");
-        // output slightly different from GNU R even though we use R's "show" method to print it
-        assertEval(Ignored.OutputFormatting, "{ setClass(\"foo\", representation(j=\"numeric\")); new(\"foo\", j=42) }");
+        assertEval(Ignored.Unknown, "{ setClass(\"foo\", representation(j=\"numeric\")); new(\"foo\", j=42) }");
     }
 
     @Test
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_attributes.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_attributes.java
index 7c14cc20416f6cb267d541c6585cdf18d428088d..de15156ae3ea2966f5c7c62472e61a25c99bc924 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_attributes.java
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_attributes.java
@@ -50,7 +50,7 @@ public class TestBuiltin_attributes extends TestBase {
 
     @Test
     public void testattributes7() {
-        assertEval(Ignored.Unknown, "argv <- list(NULL);attributes(argv[[1]]);");
+        assertEval("argv <- list(NULL);attributes(argv[[1]]);");
     }
 
     @Test
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_environmentassign.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_environmentassign.java
index 9d2f7b7c947aa142a8cd9b47d09ea1c74e4584da..c96618819e1bb81de79c103a91ddb84579ca8cfb 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_environmentassign.java
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_environmentassign.java
@@ -24,5 +24,6 @@ public class TestBuiltin_environmentassign extends TestBase {
         assertEval(Output.ContainsError, "{ e1 <- new.env(); environment(e1) <- 3 }");
 
         assertEval("{ f <- function() {}; e1 <- new.env(); environment(f) <- e1 }");
+        assertEval("{ f <- function() x; f2 <- f; e <- new.env(); assign('x', 2, envir=e); x <- 1; environment(f) <- e; c(f(), f2())}");
     }
 }
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_identical.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_identical.java
index 1addac815536b6a329ff06e3bdd6e95ae3fcf0fc..fd95b5d695f19e87b43daf2cf408c4ec8fcd6da6 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_identical.java
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_identical.java
@@ -39,8 +39,7 @@ public class TestBuiltin_identical extends TestBase {
 
     @Test
     public void testidentical5() {
-        assertEval(Ignored.Unknown,
-                        "argv <- list(structure(list(a = 1), .Dim = 1L, .Dimnames = list('a')), structure(list(a = 1), .Dim = 1L, .Dimnames = list('a')), TRUE, TRUE, TRUE, TRUE, FALSE); .Internal(identical(argv[[1]], argv[[2]], argv[[3]], argv[[4]], argv[[5]], argv[[6]], argv[[7]]))");
+        assertEval("argv <- list(structure(list(a = 1), .Dim = 1L, .Dimnames = list('a')), structure(list(a = 1), .Dim = 1L, .Dimnames = list('a')), TRUE, TRUE, TRUE, TRUE, FALSE); .Internal(identical(argv[[1]], argv[[2]], argv[[3]], argv[[4]], argv[[5]], argv[[6]], argv[[7]]))");
     }
 
     @Test
@@ -55,8 +54,7 @@ public class TestBuiltin_identical extends TestBase {
 
     @Test
     public void testidentical8() {
-        assertEval(Ignored.Unknown,
-                        "argv <- list(structure(list(a = 1), .Names = 'a', .Tsp = c(1, 1, 1), class = 'ts'), structure(list(a = 1), .Names = 'a', .Tsp = c(1, 1, 1), class = 'ts'), TRUE, TRUE, TRUE, TRUE, FALSE); .Internal(identical(argv[[1]], argv[[2]], argv[[3]], argv[[4]], argv[[5]], argv[[6]], argv[[7]]))");
+        assertEval("argv <- list(structure(list(a = 1), .Names = 'a', .Tsp = c(1, 1, 1), class = 'ts'), structure(list(a = 1), .Names = 'a', .Tsp = c(1, 1, 1), class = 'ts'), TRUE, TRUE, TRUE, TRUE, FALSE); .Internal(identical(argv[[1]], argv[[2]], argv[[3]], argv[[4]], argv[[5]], argv[[6]], argv[[7]]))");
     }
 
     @Test
@@ -137,8 +135,7 @@ public class TestBuiltin_identical extends TestBase {
 
     @Test
     public void testidentical24() {
-        assertEval(Ignored.Unknown,
-                        "argv <- list(list(c('r1', 'r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9', 'r10', 'r11', 'r12', 'r13', 'r14', 'r15', 'r16', 'r17', 'r18', 'r19', 'r20', 'r21', 'r22', 'r23', 'r24', 'r25', 'r26', 'r27', 'r28', 'r29', 'r30', 'r31', 'r32', 'r33', 'r34', 'r35', 'r36', 'r37', 'r38', 'r39', 'r40'), c('c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8', 'c9', 'c10', 'c11', 'c12', 'c13', 'c14', 'c15', 'c16', 'c17', 'c18', 'c19', 'c20')), list(character(0), character(0)), TRUE, TRUE, TRUE, TRUE, FALSE); .Internal(identical(argv[[1]], argv[[2]], argv[[3]], argv[[4]], argv[[5]], argv[[6]], argv[[7]]))");
+        assertEval("argv <- list(list(c('r1', 'r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9', 'r10', 'r11', 'r12', 'r13', 'r14', 'r15', 'r16', 'r17', 'r18', 'r19', 'r20', 'r21', 'r22', 'r23', 'r24', 'r25', 'r26', 'r27', 'r28', 'r29', 'r30', 'r31', 'r32', 'r33', 'r34', 'r35', 'r36', 'r37', 'r38', 'r39', 'r40'), c('c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8', 'c9', 'c10', 'c11', 'c12', 'c13', 'c14', 'c15', 'c16', 'c17', 'c18', 'c19', 'c20')), list(character(0), character(0)), TRUE, TRUE, TRUE, TRUE, FALSE); .Internal(identical(argv[[1]], argv[[2]], argv[[3]], argv[[4]], argv[[5]], argv[[6]], argv[[7]]))");
     }
 
     @Test
@@ -214,8 +211,8 @@ public class TestBuiltin_identical extends TestBase {
         assertEval("{ x <- 1 ; attr(x, \"hello\") <- 2 ; attr(x, \"my\") <- 10;  attr(x, \"hello\") <- NULL ; y <- 1 ; attr(y, \"my\") <- 10 ; identical(x,y) }");
         assertEval("{ identical(0/0,1[2]) }");
 
-        assertEval(Ignored.Unknown, "{ identical(list(1, list(2)), list(list(1), 1)) }");
-        assertEval(Ignored.Unknown, "{ identical(list(1, list(2)), list(1, list(2))) }");
+        assertEval("{ identical(list(1, list(2)), list(list(1), 1)) }");
+        assertEval("{ identical(list(1, list(2)), list(1, list(2))) }");
         assertEval(Ignored.Unknown, "{ x <- 1 ; attr(x, \"my\") <- 10; identical(x, 1) }");
         assertEval(Ignored.Unknown, "{ x <- 1 ; attr(x, \"my\") <- 10; y <- 1 ; attr(y, \"my\") <- 11 ; identical(x,y) }");
 
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_primUntrace.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_primUntrace.java
index 05bfe99ab50167d83724a76a1973942567d66df6..6bedb37ca92c8d737857dae3e1ef46f78a1285b2 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_primUntrace.java
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_primUntrace.java
@@ -19,6 +19,6 @@ public class TestBuiltin_primUntrace extends TestBase {
 
     @Test
     public void testprimUntrace1() {
-        assertEval(Ignored.Unknown, "argv <- list(.Primitive('sum'));.primUntrace(argv[[1]]);");
+        assertEval("argv <- list(.Primitive('sum'));.primUntrace(argv[[1]]);");
     }
 }
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_storagemodeassign_.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_storagemodeassign_.java
index 474ed87bd3338f13634bd9cd90fcf2edb7b4738e..c7ead89da5361fdc85beb84f0ba84547a862dfc5 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_storagemodeassign_.java
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_storagemodeassign_.java
@@ -20,7 +20,6 @@ public class TestBuiltin_storagemodeassign_ extends TestBase {
 
     @Test
     public void teststoragemodeassign_1() {
-        assertEval(Ignored.Unknown, "argv <- structure(list(structure(c(0, 1, 2), .Dim = c(3L, 1L)),     value = 'integer'), .Names = c('', 'value'));do.call('storage.mode<-', argv)");
+        assertEval("argv <- structure(list(structure(c(0, 1, 2), .Dim = c(3L, 1L)),     value = 'integer'), .Names = c('', 'value'));do.call('storage.mode<-', argv)");
     }
-
 }