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 591ef9b77327ad4ed98de103c0195e5622545c0c..c312f099b5fb6b53510ff493030a52857dd8a5e0 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
@@ -4,7 +4,7 @@
  * http://www.gnu.org/licenses/gpl-2.0.html
  *
  * Copyright (c) 2014, Purdue University
- * Copyright (c) 2014, 2015, Oracle and/or its affiliates
+ * Copyright (c) 2014, 2016, Oracle and/or its affiliates
  *
  * All rights reserved.
  */
@@ -156,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 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 LocalReadVariableNode rvnGroup = LocalReadVariableNode.create(RRuntime.R_DOT_GROUP, false);
+        @Child private LocalReadVariableNode rvnClass = LocalReadVariableNode.create(RRuntime.R_DOT_CLASS, false);
+        @Child private LocalReadVariableNode rvnGeneric = LocalReadVariableNode.create(RRuntime.R_DOT_GENERIC, false);
+        @Child private LocalReadVariableNode rvnCall = LocalReadVariableNode.create(RRuntime.R_DOT_GENERIC_CALL_ENV, false);
+        @Child private LocalReadVariableNode rvnDef = LocalReadVariableNode.create(RRuntime.R_DOT_GENERIC_DEF_ENV, 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 097062b22e2c2456b3765376d19babb967b89459..c8bd90527c2b01be6702a18693e34694ee8ac9b5 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
@@ -6,7 +6,7 @@
  * Copyright (c) 1995, 1996, 1997  Robert Gentleman and Ross Ihaka
  * Copyright (c) 1995-2014, The R Core Team
  * Copyright (c) 2002-2008, The R Foundation
- * Copyright (c) 2015, Oracle and/or its affiliates
+ * Copyright (c) 2015, 2016, Oracle and/or its affiliates
  *
  * All rights reserved.
  */
@@ -118,7 +118,6 @@ public abstract class StandardGeneric extends RBuiltinNode {
             CompilerDirectives.transferToInterpreterAndInvalidate();
             collectArgumentsNode = insert(CollectGenericArgumentsNodeGen.create(sigArgs.getDataWithoutCopying(), sigLength));
         }
-
         String[] classes = collectArgumentsNode.execute(frame, sigArgs, sigLength);
         Object ret = dispatchGeneric.executeObject(frame, methodsEnv, mtable, RDataFactory.createStringVector(classes, RDataFactory.COMPLETE_VECTOR), fdef, fname);
         return ret;
@@ -132,6 +131,10 @@ public abstract class StandardGeneric extends RBuiltinNode {
             if (attributes == null) {
                 return null;
             }
+            if (genericAttrAccess == null) {
+                CompilerDirectives.transferToInterpreterAndInvalidate();
+                genericAttrAccess = insert(AttributeAccessNodeGen.create(RRuntime.GENERIC_ATTR_KEY));
+            }
             genObj = genericAttrAccess.execute(attributes);
             if (genObj == null) {
                 return null;
@@ -148,17 +151,16 @@ public abstract class StandardGeneric extends RBuiltinNode {
     protected Object stdGeneric(VirtualFrame frame, RAbstractStringVector fVec, @SuppressWarnings("unused") RMissing fdef) {
         String fname = fVec.getDataAt(0);
         int n = RArguments.getDepth(frame);
-        if (genericAttrAccess == null) {
-            CompilerDirectives.transferToInterpreterAndInvalidate();
-            assert sysFunction == null;
-            genericAttrAccess = insert(AttributeAccessNodeGen.create(RRuntime.GENERIC_ATTR_KEY));
-            sysFunction = insert(FrameFunctionsFactory.SysFunctionNodeGen.create(new RNode[1], null, null));
-        }
         Object fnObj = RArguments.getFunction(frame);
         fnObj = getFunction(frame, fVec, fname, fnObj);
         if (fnObj != null) {
             return fnObj;
         }
+        if (sysFunction == null) {
+            CompilerDirectives.transferToInterpreterAndInvalidate();
+            sysFunction = insert(FrameFunctionsFactory.SysFunctionNodeGen.create(new RNode[1], null, null));
+            RError.performanceWarning("sys.frame usage in standardGeneric");
+        }
         // TODO: GNU R counts to (i < n) - does their equivalent of getDepth return a different
         // value
         // TODO; shouldn't we count from n to 0?
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/CallMatcherNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/CallMatcherNode.java
index 3b8ebc385afb9afecaaf04969026b6de1fecfa48..469da5f98fc49ac7eafe1eb7b2cae09150018283 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/CallMatcherNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/CallMatcherNode.java
@@ -11,21 +11,35 @@
 
 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.CompilerDirectives.TruffleBoundary;
-import com.oracle.truffle.api.frame.*;
-import com.oracle.truffle.api.nodes.*;
-import com.oracle.truffle.api.profiles.*;
-import com.oracle.truffle.r.nodes.*;
-import com.oracle.truffle.r.nodes.builtin.*;
+import com.oracle.truffle.api.Truffle;
+import com.oracle.truffle.api.frame.VirtualFrame;
+import com.oracle.truffle.api.nodes.DirectCallNode;
+import com.oracle.truffle.api.nodes.ExplodeLoop;
+import com.oracle.truffle.api.nodes.IndirectCallNode;
+import com.oracle.truffle.api.nodes.NodeCost;
+import com.oracle.truffle.api.nodes.NodeInfo;
+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.RBuiltinRootNode;
 import com.oracle.truffle.r.nodes.function.ArgumentMatcher.MatchPermutation;
-import com.oracle.truffle.r.nodes.function.signature.*;
-import com.oracle.truffle.r.nodes.unary.*;
-import com.oracle.truffle.r.runtime.*;
-import com.oracle.truffle.r.runtime.RArguments.S3Args;
-import com.oracle.truffle.r.runtime.data.*;
-import com.oracle.truffle.r.runtime.nodes.*;
+import com.oracle.truffle.r.nodes.function.signature.RArgumentsNode;
+import com.oracle.truffle.r.nodes.unary.CastNode;
+import com.oracle.truffle.r.runtime.ArgumentsSignature;
+import com.oracle.truffle.r.runtime.RArguments;
+import com.oracle.truffle.r.runtime.RArguments.DispatchArgs;
+import com.oracle.truffle.r.runtime.RCaller;
+import com.oracle.truffle.r.runtime.RInternalError;
+import com.oracle.truffle.r.runtime.data.RArgsValuesAndNames;
+import com.oracle.truffle.r.runtime.data.RDataFactory;
+import com.oracle.truffle.r.runtime.data.REmpty;
+import com.oracle.truffle.r.runtime.data.RFunction;
+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 CallMatcherNode extends RBaseNode {
 
@@ -50,7 +64,7 @@ public abstract class CallMatcherNode extends RBaseNode {
         return new CallMatcherUninitializedNode(forNextMethod, argsAreEvaluated);
     }
 
-    public abstract Object execute(VirtualFrame frame, ArgumentsSignature suppliedSignature, Object[] suppliedArguments, RFunction function, S3Args s3Args);
+    public abstract Object execute(VirtualFrame frame, ArgumentsSignature suppliedSignature, Object[] suppliedArguments, RFunction function, DispatchArgs dispatchArgs);
 
     protected CallMatcherCachedNode specialize(ArgumentsSignature suppliedSignature, Object[] suppliedArguments, RFunction function, CallMatcherNode next) {
 
@@ -90,8 +104,8 @@ public abstract class CallMatcherNode extends RBaseNode {
         return new CallMatcherCachedNode(suppliedSignature, varArgSignatures, function, preparePermutation, permutation, forNextMethod, argsAreEvaluated, next);
     }
 
-    protected Object[] prepareArguments(VirtualFrame frame, Object[] reorderedArgs, ArgumentsSignature reorderedSignature, RFunction function, S3Args s3Args) {
-        return argsNode.execute(function, caller, null, RArguments.getDepth(frame) + 1, reorderedArgs, reorderedSignature, s3Args);
+    protected Object[] prepareArguments(VirtualFrame frame, Object[] reorderedArgs, ArgumentsSignature reorderedSignature, RFunction function, DispatchArgs dispatchArgs) {
+        return argsNode.execute(function, caller, null, RArguments.getDepth(frame) + 1, reorderedArgs, reorderedSignature, dispatchArgs);
     }
 
     protected final void evaluatePromises(VirtualFrame frame, RFunction function, Object[] args, int varArgIndex) {
@@ -139,17 +153,17 @@ public abstract class CallMatcherNode extends RBaseNode {
         private int depth;
 
         @Override
-        public Object execute(VirtualFrame frame, ArgumentsSignature suppliedSignature, Object[] suppliedArguments, RFunction function, S3Args s3Args) {
+        public Object execute(VirtualFrame frame, ArgumentsSignature suppliedSignature, Object[] suppliedArguments, RFunction function, DispatchArgs dispatchArgs) {
             CompilerDirectives.transferToInterpreterAndInvalidate();
             if (++depth > MAX_CACHE_DEPTH) {
-                return replace(new CallMatcherGenericNode(forNextMethod, argsAreEvaluated)).execute(frame, suppliedSignature, suppliedArguments, function, s3Args);
+                return replace(new CallMatcherGenericNode(forNextMethod, argsAreEvaluated)).execute(frame, suppliedSignature, suppliedArguments, function, dispatchArgs);
             } else {
                 CallMatcherCachedNode cachedNode = replace(specialize(suppliedSignature, suppliedArguments, function, this));
                 // for splitting if necessary
                 if (cachedNode.call != null && RCallNode.needsSplitting(function)) {
                     cachedNode.call.cloneCallTarget();
                 }
-                return cachedNode.execute(frame, suppliedSignature, suppliedArguments, function, s3Args);
+                return cachedNode.execute(frame, suppliedSignature, suppliedArguments, function, dispatchArgs);
             }
         }
 
@@ -196,7 +210,7 @@ public abstract class CallMatcherNode extends RBaseNode {
         }
 
         @Override
-        public Object execute(VirtualFrame frame, ArgumentsSignature suppliedSignature, Object[] suppliedArguments, RFunction function, S3Args s3Args) {
+        public Object execute(VirtualFrame frame, ArgumentsSignature suppliedSignature, Object[] suppliedArguments, RFunction function, DispatchArgs dispatchArgs) {
             if (suppliedSignature == cachedSuppliedSignature && function == cachedFunction && checkLastArgSignature(cachedSuppliedSignature, suppliedArguments)) {
 
                 Object[] preparedArguments = prepareSuppliedArgument(preparePermutation, suppliedArguments);
@@ -204,14 +218,14 @@ public abstract class CallMatcherNode extends RBaseNode {
                 Object[] reorderedArgs = ArgumentMatcher.matchArgumentsEvaluated(permutation, preparedArguments, formals);
                 evaluatePromises(frame, cachedFunction, reorderedArgs, formals.getSignature().getVarArgIndex());
                 if (call != null) {
-                    Object[] arguments = prepareArguments(frame, reorderedArgs, formals.getSignature(), cachedFunction, s3Args);
+                    Object[] arguments = prepareArguments(frame, reorderedArgs, formals.getSignature(), cachedFunction, dispatchArgs);
                     return call.call(frame, arguments);
                 } else {
                     applyCasts(reorderedArgs);
                     return builtin.execute(frame, reorderedArgs);
                 }
             } else {
-                return next.execute(frame, suppliedSignature, suppliedArguments, function, s3Args);
+                return next.execute(frame, suppliedSignature, suppliedArguments, function, dispatchArgs);
             }
         }
 
@@ -281,10 +295,10 @@ public abstract class CallMatcherNode extends RBaseNode {
         private final ConditionProfile hasVarArgsProfile = ConditionProfile.createBinaryProfile();
 
         @Override
-        public Object execute(VirtualFrame frame, ArgumentsSignature suppliedSignature, Object[] suppliedArguments, RFunction function, S3Args s3Args) {
+        public Object execute(VirtualFrame frame, ArgumentsSignature suppliedSignature, Object[] suppliedArguments, RFunction function, DispatchArgs dispatchArgs) {
             EvaluatedArguments reorderedArgs = reorderArguments(suppliedArguments, function, suppliedSignature);
             evaluatePromises(frame, function, reorderedArgs.getArguments(), reorderedArgs.getSignature().getVarArgIndex());
-            Object[] arguments = prepareArguments(frame, reorderedArgs.getArguments(), reorderedArgs.getSignature(), function, s3Args);
+            Object[] arguments = prepareArguments(frame, reorderedArgs.getArguments(), reorderedArgs.getSignature(), function, dispatchArgs);
             return call.call(frame, function.getTarget(), arguments);
         }
 
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 6b0189cb791bd587ee2d9f21f7356fa9f2595e9e..b156a78e614cb529e405d2ea38872080a6773e58 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
@@ -22,31 +22,57 @@
  */
 package com.oracle.truffle.r.nodes.function;
 
-import java.util.*;
+import java.util.ArrayList;
 
-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.frame.*;
-import com.oracle.truffle.api.instrument.*;
-import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.api.frame.FrameDescriptor;
+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.instrument.QuitException;
+import com.oracle.truffle.api.nodes.Node;
+import com.oracle.truffle.api.nodes.NodeUtil;
 import com.oracle.truffle.api.nodes.NodeUtil.NodeCountFilter;
-import com.oracle.truffle.api.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.control.*;
-import com.oracle.truffle.r.runtime.*;
+import com.oracle.truffle.api.profiles.BranchProfile;
+import com.oracle.truffle.api.profiles.ConditionProfile;
+import com.oracle.truffle.api.source.SourceSection;
+import com.oracle.truffle.r.nodes.InlineCacheNode;
+import com.oracle.truffle.r.nodes.RASTUtils;
+import com.oracle.truffle.r.nodes.RRootNode;
+import com.oracle.truffle.r.nodes.access.FrameSlotNode;
+import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode;
+import com.oracle.truffle.r.nodes.control.BreakException;
+import com.oracle.truffle.r.nodes.control.NextException;
+import com.oracle.truffle.r.runtime.ArgumentsSignature;
+import com.oracle.truffle.r.runtime.BrowserQuitException;
+import com.oracle.truffle.r.runtime.FunctionUID;
 import com.oracle.truffle.r.runtime.RAllNames.State;
+import com.oracle.truffle.r.runtime.RArguments;
+import com.oracle.truffle.r.runtime.RArguments.DispatchArgs;
 import com.oracle.truffle.r.runtime.RArguments.S3Args;
+import com.oracle.truffle.r.runtime.RArguments.S4Args;
+import com.oracle.truffle.r.runtime.RDeparse;
+import com.oracle.truffle.r.runtime.RError;
+import com.oracle.truffle.r.runtime.RErrorHandling;
+import com.oracle.truffle.r.runtime.RInternalError;
+import com.oracle.truffle.r.runtime.RRuntime;
+import com.oracle.truffle.r.runtime.RSerialize;
+import com.oracle.truffle.r.runtime.RType;
+import com.oracle.truffle.r.runtime.ReturnException;
 import com.oracle.truffle.r.runtime.Utils.DebugExitException;
-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.env.frame.*;
+import com.oracle.truffle.r.runtime.context.RContext;
+import com.oracle.truffle.r.runtime.data.RBuiltinDescriptor;
+import com.oracle.truffle.r.runtime.data.RNull;
+import com.oracle.truffle.r.runtime.data.RPromise;
+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.RFrameSlot;
 import com.oracle.truffle.r.runtime.instrument.FunctionUIDFactory;
-import com.oracle.truffle.r.runtime.nodes.*;
+import com.oracle.truffle.r.runtime.nodes.RNode;
+import com.oracle.truffle.r.runtime.nodes.RSyntaxNode;
 
 public final class FunctionDefinitionNode extends RRootNode implements RSyntaxNode {
 
@@ -77,12 +103,18 @@ public final class FunctionDefinitionNode extends RRootNode implements RSyntaxNo
     private final BranchProfile nextProfile = BranchProfile.create();
 
     @CompilationFinal private BranchProfile invalidateFrameSlotProfile;
+    // S3/S4 slots
     @Child private FrameSlotNode dotGenericSlot;
     @Child private FrameSlotNode dotMethodSlot;
+    // S3 slots
     @Child private FrameSlotNode dotClassSlot;
     @Child private FrameSlotNode dotGenericCallEnvSlot;
     @Child private FrameSlotNode dotGenericCallDefSlot;
     @Child private FrameSlotNode dotGroupSlot;
+    // S4 slots
+    @Child private FrameSlotNode dotDefinedSlot;
+    @Child private FrameSlotNode dotTargetSlot;
+    @Child private FrameSlotNode dotMethodsSlot;
 
     @Child private PostProcessArgumentsNode argPostProcess;
 
@@ -139,8 +171,11 @@ public final class FunctionDefinitionNode extends RRootNode implements RSyntaxNo
     private static boolean containsAnyDispatch(BodyNode body) {
         NodeCountFilter dispatchingMethodsFilter = node -> {
             if (node instanceof ReadVariableNode) {
-                String identifier = ((ReadVariableNode) node).getIdentifier();
-                return "UseMethod".equals(identifier) /* || "NextMethod".equals(identifier) */;
+                ReadVariableNode rvn = (ReadVariableNode) node;
+                String identifier = rvn.getIdentifier();
+                // TODO: can we also do this for S4 new?
+                return rvn.getMode() == RType.Function && ("UseMethod".equals(identifier) || "standardGeneric".equals(identifier));
+                /* || "NextMethod".equals(identifier) */
             }
             return false;
         };
@@ -170,28 +205,28 @@ public final class FunctionDefinitionNode extends RRootNode implements RSyntaxNo
                      * to do this. If a function uses lapply anywhere as name then it gets split.
                      * This could get exploited.
                      */
-                    RBuiltinDescriptor directBuiltin = RContext.lookupBuiltinDescriptor(readInternal.getIdentifier());
-                    if (directBuiltin != null && directBuiltin.isSplitCaller()) {
-                        return true;
-                    }
+                        RBuiltinDescriptor directBuiltin = RContext.lookupBuiltinDescriptor(readInternal.getIdentifier());
+                        if (directBuiltin != null && directBuiltin.isSplitCaller()) {
+                            return true;
+                        }
 
-                    if (readInternal.getIdentifier().equals(".Internal")) {
-                        Node internalFunctionArgument = RASTUtils.unwrap(internalCall.getArguments().getArguments()[0]);
-                        if (internalFunctionArgument instanceof RCallNode) {
-                            RCallNode innerCall = (RCallNode) internalFunctionArgument;
-                            if (innerCall.getFunctionNode() instanceof ReadVariableNode) {
-                                ReadVariableNode readInnerCall = (ReadVariableNode) innerCall.getFunctionNode();
-                                RBuiltinDescriptor builtin = RContext.lookupBuiltinDescriptor(readInnerCall.getIdentifier());
-                                if (builtin != null && builtin.isSplitCaller()) {
-                                    return true;
+                        if (readInternal.getIdentifier().equals(".Internal")) {
+                            Node internalFunctionArgument = RASTUtils.unwrap(internalCall.getArguments().getArguments()[0]);
+                            if (internalFunctionArgument instanceof RCallNode) {
+                                RCallNode innerCall = (RCallNode) internalFunctionArgument;
+                                if (innerCall.getFunctionNode() instanceof ReadVariableNode) {
+                                    ReadVariableNode readInnerCall = (ReadVariableNode) innerCall.getFunctionNode();
+                                    RBuiltinDescriptor builtin = RContext.lookupBuiltinDescriptor(readInnerCall.getIdentifier());
+                                    if (builtin != null && builtin.isSplitCaller()) {
+                                        return true;
+                                    }
                                 }
                             }
                         }
                     }
                 }
-            }
-            return false;
-        };
+                return false;
+            };
         return NodeUtil.countNodes(this, findAlwaysSplitInternal) > 0;
 
     }
@@ -232,7 +267,7 @@ public final class FunctionDefinitionNode extends RRootNode implements RSyntaxNo
         boolean runOnExitHandlers = true;
         try {
             verifyEnclosingAssumptions(vf);
-            setupS3Slots(vf);
+            setupDispatchSlots(vf);
             Object result = body.execute(vf);
             normalExit.enter();
             return result;
@@ -302,29 +337,56 @@ public final class FunctionDefinitionNode extends RRootNode implements RSyntaxNo
         }
     }
 
-    private void setupS3Slots(VirtualFrame frame) {
-        S3Args args = RArguments.getS3Args(frame);
-        if (args == null) {
+    private void setupDispatchSlots(VirtualFrame frame) {
+        DispatchArgs dispatchArgs = RArguments.getDispatchArgs(frame);
+        if (dispatchArgs == null) {
             return;
         }
         if (dotGenericSlot == null) {
             CompilerDirectives.transferToInterpreterAndInvalidate();
-            assert invalidateFrameSlotProfile == null && dotMethodSlot == null && dotClassSlot == null && dotGenericCallEnvSlot == null && dotGenericCallDefSlot == null && dotGroupSlot == null;
+            assert invalidateFrameSlotProfile == null && dotMethodSlot == null;
             invalidateFrameSlotProfile = BranchProfile.create();
             FrameDescriptor frameDescriptor = frame.getFrameDescriptor();
-            dotGenericSlot = insert(FrameSlotNode.createInitialized(frameDescriptor, RRuntime.RDotGeneric, true));
-            dotMethodSlot = insert(FrameSlotNode.createInitialized(frameDescriptor, RRuntime.RDotMethod, true));
-            dotClassSlot = insert(FrameSlotNode.createInitialized(frameDescriptor, RRuntime.RDotClass, true));
-            dotGenericCallEnvSlot = insert(FrameSlotNode.createInitialized(frameDescriptor, RRuntime.RDotGenericCallEnv, true));
-            dotGenericCallDefSlot = insert(FrameSlotNode.createInitialized(frameDescriptor, RRuntime.RDotGenericDefEnv, true));
-            dotGroupSlot = insert(FrameSlotNode.createInitialized(frameDescriptor, RRuntime.RDotGroup, true));
+
+            dotGenericSlot = insert(FrameSlotNode.createInitialized(frameDescriptor, RRuntime.R_DOT_GENERIC, true));
+            dotMethodSlot = insert(FrameSlotNode.createInitialized(frameDescriptor, RRuntime.R_DOT_METHOD, true));
+        }
+        FrameSlotChangeMonitor.setObjectAndInvalidate(frame, dotGenericSlot.executeFrameSlot(frame), dispatchArgs.generic, false, invalidateFrameSlotProfile);
+        FrameSlotChangeMonitor.setObjectAndInvalidate(frame, dotMethodSlot.executeFrameSlot(frame), dispatchArgs.method, false, invalidateFrameSlotProfile);
+
+        if (dispatchArgs instanceof S3Args) {
+            S3Args s3Args = (S3Args) dispatchArgs;
+            if (dotClassSlot == null) {
+                CompilerDirectives.transferToInterpreterAndInvalidate();
+                assert dotGenericCallEnvSlot == null && dotGenericCallDefSlot == null && dotGroupSlot == null;
+                invalidateFrameSlotProfile = BranchProfile.create();
+                FrameDescriptor frameDescriptor = frame.getFrameDescriptor();
+
+                dotClassSlot = insert(FrameSlotNode.createInitialized(frameDescriptor, RRuntime.R_DOT_CLASS, true));
+                dotGenericCallEnvSlot = insert(FrameSlotNode.createInitialized(frameDescriptor, RRuntime.R_DOT_GENERIC_CALL_ENV, true));
+                dotGenericCallDefSlot = insert(FrameSlotNode.createInitialized(frameDescriptor, RRuntime.R_DOT_GENERIC_DEF_ENV, true));
+                dotGroupSlot = insert(FrameSlotNode.createInitialized(frameDescriptor, RRuntime.R_DOT_GROUP, true));
+            }
+            FrameSlotChangeMonitor.setObjectAndInvalidate(frame, dotClassSlot.executeFrameSlot(frame), s3Args.clazz, false, invalidateFrameSlotProfile);
+            FrameSlotChangeMonitor.setObjectAndInvalidate(frame, dotGenericCallEnvSlot.executeFrameSlot(frame), s3Args.callEnv, false, invalidateFrameSlotProfile);
+            FrameSlotChangeMonitor.setObjectAndInvalidate(frame, dotGenericCallDefSlot.executeFrameSlot(frame), s3Args.defEnv, false, invalidateFrameSlotProfile);
+            FrameSlotChangeMonitor.setObjectAndInvalidate(frame, dotGroupSlot.executeFrameSlot(frame), s3Args.group, false, invalidateFrameSlotProfile);
+        } else {
+            S4Args s4Args = (S4Args) dispatchArgs;
+            if (dotDefinedSlot == null) {
+                CompilerDirectives.transferToInterpreterAndInvalidate();
+                assert dotTargetSlot == null && dotMethodsSlot == null;
+                invalidateFrameSlotProfile = BranchProfile.create();
+                FrameDescriptor frameDescriptor = frame.getFrameDescriptor();
+
+                dotDefinedSlot = insert(FrameSlotNode.createInitialized(frameDescriptor, RRuntime.R_DOT_DEFINED, true));
+                dotTargetSlot = insert(FrameSlotNode.createInitialized(frameDescriptor, RRuntime.R_DOT_TARGET, true));
+                dotMethodsSlot = insert(FrameSlotNode.createInitialized(frameDescriptor, RRuntime.R_DOT_METHODS, true));
+            }
+            FrameSlotChangeMonitor.setObjectAndInvalidate(frame, dotDefinedSlot.executeFrameSlot(frame), s4Args.defined, false, invalidateFrameSlotProfile);
+            FrameSlotChangeMonitor.setObjectAndInvalidate(frame, dotTargetSlot.executeFrameSlot(frame), s4Args.target, false, invalidateFrameSlotProfile);
+            FrameSlotChangeMonitor.setObjectAndInvalidate(frame, dotMethodsSlot.executeFrameSlot(frame), s4Args.methods, false, invalidateFrameSlotProfile);
         }
-        FrameSlotChangeMonitor.setObjectAndInvalidate(frame, dotGenericSlot.executeFrameSlot(frame), args.generic, false, invalidateFrameSlotProfile);
-        FrameSlotChangeMonitor.setObjectAndInvalidate(frame, dotMethodSlot.executeFrameSlot(frame), args.method, false, invalidateFrameSlotProfile);
-        FrameSlotChangeMonitor.setObjectAndInvalidate(frame, dotClassSlot.executeFrameSlot(frame), args.clazz, false, invalidateFrameSlotProfile);
-        FrameSlotChangeMonitor.setObjectAndInvalidate(frame, dotGenericCallEnvSlot.executeFrameSlot(frame), args.callEnv, false, invalidateFrameSlotProfile);
-        FrameSlotChangeMonitor.setObjectAndInvalidate(frame, dotGenericCallDefSlot.executeFrameSlot(frame), args.defEnv, false, invalidateFrameSlotProfile);
-        FrameSlotChangeMonitor.setObjectAndInvalidate(frame, dotGroupSlot.executeFrameSlot(frame), args.group, false, invalidateFrameSlotProfile);
     }
 
     @SuppressWarnings("unchecked")
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/signature/RArgumentsNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/signature/RArgumentsNode.java
index d3f7ae2e0933de45d10bb97eeabf5f35379d050f..e79bd6006b32992f2c0a1ad358435d5bab2d4caa 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/signature/RArgumentsNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/signature/RArgumentsNode.java
@@ -37,7 +37,7 @@ public abstract class RArgumentsNode extends RBaseNode {
      * certain parameters, on which Truffle DSL cannot specialize.
      */
 
-    public abstract Object[] execute(RFunction function, RCaller caller, MaterializedFrame callerFrame, int depth, Object[] evaluatedArgs, ArgumentsSignature signature, S3Args s3Args);
+    public abstract Object[] execute(RFunction function, RCaller caller, MaterializedFrame callerFrame, int depth, Object[] evaluatedArgs, ArgumentsSignature signature, DispatchArgs dispatchArgs);
 
     public static RArgumentsNode create() {
         return new RArgumentsUninitializedNode();
@@ -50,9 +50,9 @@ public abstract class RArgumentsNode extends RBaseNode {
 
     private static final class RArgumentsUninitializedNode extends RArgumentsNode {
         @Override
-        public Object[] execute(RFunction function, RCaller caller, MaterializedFrame callerFrame, int depth, Object[] evaluatedArgs, ArgumentsSignature signature, S3Args s3Args) {
+        public Object[] execute(RFunction function, RCaller caller, MaterializedFrame callerFrame, int depth, Object[] evaluatedArgs, ArgumentsSignature signature, DispatchArgs dispatchArgs) {
             CompilerDirectives.transferToInterpreterAndInvalidate();
-            return replace(new RArgumentsCachedNode(function)).execute(function, caller, callerFrame, depth, evaluatedArgs, signature, s3Args);
+            return replace(new RArgumentsCachedNode(function)).execute(function, caller, callerFrame, depth, evaluatedArgs, signature, dispatchArgs);
         }
     }
 
@@ -64,20 +64,20 @@ public abstract class RArgumentsNode extends RBaseNode {
         }
 
         @Override
-        public Object[] execute(RFunction function, RCaller caller, MaterializedFrame callerFrame, int depth, Object[] evaluatedArgs, ArgumentsSignature signature, S3Args s3Args) {
+        public Object[] execute(RFunction function, RCaller caller, MaterializedFrame callerFrame, int depth, Object[] evaluatedArgs, ArgumentsSignature signature, DispatchArgs dispatchArgs) {
             if (function == cachedFunction) {
-                return RArguments.create(cachedFunction, caller, callerFrame, depth, evaluatedArgs, signature, cachedFunction.getEnclosingFrame(), s3Args);
+                return RArguments.create(cachedFunction, caller, callerFrame, depth, evaluatedArgs, signature, cachedFunction.getEnclosingFrame(), dispatchArgs);
             } else {
                 CompilerDirectives.transferToInterpreterAndInvalidate();
-                return replace(new RArgumentsGenericNode()).execute(function, caller, callerFrame, depth, evaluatedArgs, signature, s3Args);
+                return replace(new RArgumentsGenericNode()).execute(function, caller, callerFrame, depth, evaluatedArgs, signature, dispatchArgs);
             }
         }
     }
 
     private static final class RArgumentsGenericNode extends RArgumentsNode {
         @Override
-        public Object[] execute(RFunction function, RCaller caller, MaterializedFrame callerFrame, int depth, Object[] evaluatedArgs, ArgumentsSignature signature, S3Args s3Args) {
-            return RArguments.create(function, caller, callerFrame, depth, evaluatedArgs, signature, function.getEnclosingFrame(), s3Args);
+        public Object[] execute(RFunction function, RCaller caller, MaterializedFrame callerFrame, int depth, Object[] evaluatedArgs, ArgumentsSignature signature, DispatchArgs dispatchArgs) {
+            return RArguments.create(function, caller, callerFrame, depth, evaluatedArgs, signature, function.getEnclosingFrame(), dispatchArgs);
         }
     }
 }
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 3320fd394af480e41c9cc24d12b710c0afffe9e2..68e0873ef73770a2cd225b42b11eb0a80b71ee58 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
@@ -6,7 +6,7 @@
  * Copyright (c) 1995, 1996, 1997  Robert Gentleman and Ross Ihaka
  * Copyright (c) 1995-2014, The R Core Team
  * Copyright (c) 2002-2008, The R Foundation
- * Copyright (c) 2015, Oracle and/or its affiliates
+ * Copyright (c) 2015, 2016, Oracle and/or its affiliates
  *
  * All rights reserved.
  */
@@ -93,7 +93,7 @@ public abstract class DispatchGeneric extends RBaseNode {
             }
         }
         method = loadMethod.executeRFunction(frame, methodsEnv, method, fname);
-        Object ret = executeMethod.executeMethod(frame, method);
+        Object ret = executeMethod.executeObject(frame, method);
         return ret;
     }
 
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 ebb74304609b7745f5cb407c77e109cf28e43f72..6dd3b86e41bd33bec87ba3aa1c0eee09cac239db 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
@@ -13,6 +13,7 @@
 package com.oracle.truffle.r.nodes.objects;
 
 import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import com.oracle.truffle.api.CompilerDirectives;
 import com.oracle.truffle.api.Truffle;
 import com.oracle.truffle.api.dsl.Specialization;
 import com.oracle.truffle.api.frame.FrameDescriptor;
@@ -22,11 +23,16 @@ 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.LocalReadVariableNode;
+import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode;
+import com.oracle.truffle.r.nodes.function.CallMatcherNode;
 import com.oracle.truffle.r.nodes.function.FormalArguments;
 import com.oracle.truffle.r.nodes.function.RMissingHelper;
+import com.oracle.truffle.r.nodes.function.signature.CollectArgumentsNode;
+import com.oracle.truffle.r.nodes.function.signature.CollectArgumentsNodeGen;
 import com.oracle.truffle.r.nodes.function.signature.RArgumentsNode;
 import com.oracle.truffle.r.runtime.ArgumentsSignature;
 import com.oracle.truffle.r.runtime.RArguments;
+import com.oracle.truffle.r.runtime.RArguments.S4Args;
 import com.oracle.truffle.r.runtime.RInternalError;
 import com.oracle.truffle.r.runtime.RRuntime;
 import com.oracle.truffle.r.runtime.context.RContext;
@@ -41,45 +47,30 @@ public abstract class ExecuteMethod extends RBaseNode {
     public abstract Object executeObject(VirtualFrame frame, RFunction fdef);
 
     @Child private LocalReadVariableNode readDefined = LocalReadVariableNode.create(RRuntime.R_DOT_DEFINED, true);
-    @Child private LocalReadVariableNode readMethod = LocalReadVariableNode.create(RRuntime.RDotMethod, true);
+    @Child private LocalReadVariableNode readMethod = LocalReadVariableNode.create(RRuntime.R_DOT_METHOD, 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 ReadVariableNode readGeneric = ReadVariableNode.create(RRuntime.R_DOT_GENERIC);
+    @Child private ReadVariableNode readMethods = ReadVariableNode.create(RRuntime.R_DOT_METHODS);
     @Child private RArgumentsNode argsNode = RArgumentsNode.create();
 
+    @Child private CollectArgumentsNode collectArgs;
+    @Child private CallMatcherNode callMatcher;
+
     @Specialization
     protected Object executeMethod(VirtualFrame frame, RFunction fdef) {
+        if (collectArgs == null) {
+            CompilerDirectives.transferToInterpreterAndInvalidate();
+            collectArgs = insert(CollectArgumentsNodeGen.create());
+            callMatcher = insert(CallMatcherNode.create(false, false));
+        }
 
-        Object[] args = argsNode.execute(RArguments.getFunction(frame), RArguments.getCall(frame), null, RArguments.getDepth(frame) + 1, RArguments.getArguments(frame),
-                        RArguments.getSignature(frame), null);
-        MaterializedFrame newFrame = Truffle.getRuntime().createMaterializedFrame(args);
-        FrameDescriptor desc = newFrame.getFrameDescriptor();
-        FrameSlotChangeMonitor.initializeFunctionFrameDescriptor("<executeMethod>", desc);
-        FrameSlotChangeMonitor.initializeEnclosingFrame(newFrame, RArguments.getFunction(frame).getEnclosingFrame());
         FormalArguments formals = ((RRootNode) fdef.getRootNode()).getFormalArguments();
-        if (formals != null) {
-            ArgumentsSignature signature = formals.getSignature();
-            MaterializedFrame currentFrame = frame.materialize();
-            FrameDescriptor currentFrameDesc = currentFrame.getFrameDescriptor();
-            for (int i = 0; i < signature.getLength(); i++) {
-                String argName = signature.getName(i);
-                boolean missing = RMissingHelper.isMissingArgument(frame, argName);
-                Object val = slotRead(currentFrame, currentFrameDesc, argName);
-                slotInit(newFrame, desc, argName, val);
-                if (missing && !(val instanceof RArgsValuesAndNames || val == RMissing.instance)) {
-                    throw RInternalError.unimplemented();
-                }
-            }
-        }
+        ArgumentsSignature signature = formals.getSignature();
+        Object[] oldArgs = collectArgs.execute(frame, signature);
 
-        slotInit(newFrame, desc, RRuntime.R_DOT_DEFINED, readDefined.execute(frame));
-        slotInit(newFrame, desc, RRuntime.RDotMethod, readMethod.execute(frame));
-        slotInit(newFrame, desc, RRuntime.R_DOT_TARGET, readTarget.execute(frame));
-        slotInit(newFrame, desc, RRuntime.RDotGeneric, readGeneric.execute(frame));
-        slotInit(newFrame, desc, RRuntime.R_DOT_METHODS, readMethods.execute(frame));
+        S4Args s4Args = new S4Args(readDefined.execute(frame), readMethod.execute(frame), readTarget.execute(frame), readGeneric.execute(frame), readMethods.execute(frame));
 
-        Object ret = callMethod(fdef, newFrame);
-        return ret;
+        return callMatcher.execute(frame, signature, oldArgs, fdef, s4Args);
     }
 
     @TruffleBoundary
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 6bc7bc3189f6162df8a66c3461ef8ef637dcc97b..803961b5176bc4d03ddbdc407c5e8e3a6db55a96 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
@@ -6,7 +6,7 @@
  * Copyright (c) 1995, 1996, 1997  Robert Gentleman and Ross Ihaka
  * Copyright (c) 1995-2014, The R Core Team
  * Copyright (c) 2002-2008, The R Foundation
- * Copyright (c) 2015, Oracle and/or its affiliates
+ * Copyright (c) 2015, 2016, Oracle and/or its affiliates
  *
  * All rights reserved.
  */
@@ -26,6 +26,7 @@ import com.oracle.truffle.r.nodes.function.signature.RArgumentsNode;
 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.RError;
 import com.oracle.truffle.r.runtime.RRuntime;
 import com.oracle.truffle.r.runtime.context.RContext;
 import com.oracle.truffle.r.runtime.data.RAttributable;
@@ -42,7 +43,7 @@ public abstract class LoadMethod extends RBaseNode {
     @Child private WriteLocalFrameVariableNode writeRTarget = WriteLocalFrameVariableNode.create(RRuntime.R_DOT_TARGET, null, WriteVariableNode.Mode.REGULAR);
     @Child private WriteLocalFrameVariableNode writeRDefined = WriteLocalFrameVariableNode.create(RRuntime.R_DOT_DEFINED, null, WriteVariableNode.Mode.REGULAR);
     @Child private WriteLocalFrameVariableNode writeRNextMethod = WriteLocalFrameVariableNode.create(RRuntime.R_DOT_NEXT_METHOD, null, WriteVariableNode.Mode.REGULAR);
-    @Child private WriteLocalFrameVariableNode writeRMethod = WriteLocalFrameVariableNode.create(RRuntime.RDotMethod, null, WriteVariableNode.Mode.REGULAR);
+    @Child private WriteLocalFrameVariableNode writeRMethod = WriteLocalFrameVariableNode.create(RRuntime.R_DOT_METHOD, null, WriteVariableNode.Mode.REGULAR);
     @Child private ReadVariableNode loadMethodFind;
     @Child private DirectCallNode loadMethodCall;
     @CompilationFinal private RFunction loadMethodFunction;
@@ -86,7 +87,7 @@ public abstract class LoadMethod extends RBaseNode {
                 loadMethodFind = insert(ReadVariableNode.createFunctionLookup(null, "loadMethod"));
                 loadMethodFunction = (RFunction) loadMethodFind.execute(null, methodsEnv.getFrame());
                 loadMethodCall = insert(Truffle.getRuntime().createDirectCallNode(loadMethodFunction.getTarget()));
-
+                RError.performanceWarning("loadMethod executing slow path");
             }
             RFunction currentFunction = (RFunction) loadMethodFind.execute(null, methodsEnv.getFrame());
             if (cached.profile(currentFunction == loadMethodFunction)) {
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 0e215243f0b60c8fd50d6f80911db3c73069c8fd..6ef072b227431e9a7b1ad418cfa5327086e2b3e5 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
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2016, 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
@@ -84,32 +84,54 @@ public final class RArguments {
     @CompilationFinal public static final String[] EMPTY_STRING_ARRAY = new String[0];
 
     @ValueType
-    public static final class S3Args {
-        public final String generic;
-        public final Object clazz;
+    public abstract static class DispatchArgs {
+        public final Object generic;
         public final Object method;
+
+        public DispatchArgs(Object generic, Object method) {
+            this.generic = generic;
+            this.method = method;
+        }
+    }
+
+    @ValueType
+    public static final class S3Args extends DispatchArgs {
+        public final Object clazz;
         public final MaterializedFrame callEnv;
         public final MaterializedFrame defEnv;
         public final String group;
 
         public S3Args(String generic, Object clazz, Object method, MaterializedFrame callEnv, MaterializedFrame defEnv, String group) {
+            super(generic, method);
             assert generic != null && callEnv != null : generic + " " + callEnv;
             assert generic.intern() == generic;
-            this.generic = generic;
             this.clazz = clazz;
-            this.method = method;
             this.callEnv = callEnv;
             this.defEnv = defEnv;
             this.group = group;
         }
     }
 
+    @ValueType
+    public static final class S4Args extends DispatchArgs {
+        public final Object defined;
+        public final Object target;
+        public final Object methods;
+
+        public S4Args(Object defined, Object method, Object target, Object generic, Object methods) {
+            super(generic, method);
+            this.defined = defined;
+            this.target = target;
+            this.methods = methods;
+        }
+    }
+
     static final int INDEX_ENVIRONMENT = 0;
     static final int INDEX_FUNCTION = 1;
     static final int INDEX_CALL = 2;
     static final int INDEX_CALLER_FRAME = 3;
     static final int INDEX_ENCLOSING_FRAME = 4;
-    static final int INDEX_S3_ARGS = 5;
+    static final int INDEX_DISPATCH_ARGS = 5;
     static final int INDEX_DEPTH = 6;
     static final int INDEX_IS_IRREGULAR = 7;
     static final int INDEX_SIGNATURE = 8;
@@ -132,13 +154,13 @@ public final class RArguments {
         return ((HasSignature) function.getRootNode()).getSignature();
     }
 
-    public static Object[] create(RFunction functionObj, RCaller call, MaterializedFrame callerFrame, int depth, Object[] evaluatedArgs, ArgumentsSignature signature, S3Args s3Args) {
+    public static Object[] create(RFunction functionObj, RCaller call, MaterializedFrame callerFrame, int depth, Object[] evaluatedArgs, ArgumentsSignature signature, DispatchArgs dispatchArgs) {
         CompilerAsserts.neverPartOfCompilation();
-        return create(functionObj, call, callerFrame, depth, evaluatedArgs, signature, functionObj.getEnclosingFrame(), s3Args);
+        return create(functionObj, call, callerFrame, depth, evaluatedArgs, signature, functionObj.getEnclosingFrame(), dispatchArgs);
     }
 
     public static Object[] create(RFunction functionObj, RCaller call, MaterializedFrame callerFrame, int depth, Object[] evaluatedArgs, ArgumentsSignature signature,
-                    MaterializedFrame enclosingFrame, S3Args s3Args) {
+                    MaterializedFrame enclosingFrame, DispatchArgs dispatchArgs) {
         assert evaluatedArgs != null && signature != null : evaluatedArgs + " " + signature;
         assert evaluatedArgs.length == signature.getLength() : Arrays.toString(evaluatedArgs) + " " + signature;
         assert signature == getSignature(functionObj) : signature + " vs. " + getSignature(functionObj);
@@ -151,7 +173,7 @@ public final class RArguments {
         a[INDEX_CALL] = call;
         a[INDEX_CALLER_FRAME] = callerFrame;
         a[INDEX_ENCLOSING_FRAME] = enclosingFrame;
-        a[INDEX_S3_ARGS] = s3Args;
+        a[INDEX_DISPATCH_ARGS] = dispatchArgs;
         a[INDEX_DEPTH] = depth;
         a[INDEX_IS_IRREGULAR] = false;
         a[INDEX_SIGNATURE] = signature;
@@ -183,8 +205,8 @@ public final class RArguments {
         return (MaterializedFrame) args[INDEX_CALLER_FRAME];
     }
 
-    public static S3Args getS3Args(Frame frame) {
-        return (S3Args) frame.getArguments()[INDEX_S3_ARGS];
+    public static DispatchArgs getDispatchArgs(Frame frame) {
+        return (DispatchArgs) frame.getArguments()[INDEX_DISPATCH_ARGS];
     }
 
     public static REnvironment getEnvironment(Frame 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 ca7ae5588d2a7532cfb924a10287a6905b7bd3bd..f7f6f51aa477f4929c0d6ab5db9190194f2c4a4c 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
@@ -117,17 +117,17 @@ public class RRuntime {
 
     public static final String RS3MethodsTable = ".__S3MethodsTable__.";
 
-    public static final String RDotGeneric = ".Generic";
+    public static final String R_DOT_GENERIC = ".Generic";
 
-    public static final String RDotMethod = ".Method";
+    public static final String R_DOT_METHOD = ".Method";
 
-    public static final String RDotClass = ".Class";
+    public static final String R_DOT_CLASS = ".Class";
 
-    public static final String RDotGenericCallEnv = ".GenericCallEnv";
+    public static final String R_DOT_GENERIC_CALL_ENV = ".GenericCallEnv";
 
-    public static final String RDotGenericDefEnv = ".GenericDefEnv";
+    public static final String R_DOT_GENERIC_DEF_ENV = ".GenericDefEnv";
 
-    public static final String RDotGroup = ".Group";
+    public static final String R_DOT_GROUP = ".Group";
 
     public static final String RDOT = ".";
 
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 f8a032a818947698cfa6b0c008ee91dba9d611aa..4a68fb8594521f67c966c8c9fa6dd09ebbbbd84f 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
@@ -1,3 +1,113 @@
+##com.oracle.truffle.r.test.S4.TestS4.runRSourceTests
+#{ source("com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/R/allocation1.R") }
+An object of class "Person"
+Slot "name":
+[1] "Hadley"
+
+Slot "age":
+[1] 31
+
+
+##com.oracle.truffle.r.test.S4.TestS4.runRSourceTests
+#{ source("com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/R/allocation10.R") }
+Error in validObject(hadley) :
+  invalid class “Person” object: Age is length 10.  Should be 1
+
+##com.oracle.truffle.r.test.S4.TestS4.runRSourceTests
+#{ source("com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/R/allocation2.R") }
+Error in validObject(.Object) :
+  invalid class “Person” object: invalid object for slot "age" in class "Person": got class "character", should be or extend class "numeric"
+
+##com.oracle.truffle.r.test.S4.TestS4.runRSourceTests
+#{ source("com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/R/allocation3.R") }
+Error in initialize(value, ...) :
+  invalid name for slot of class “Person”: sex
+
+##com.oracle.truffle.r.test.S4.TestS4.runRSourceTests
+#{ source("com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/R/allocation4.R") }
+numeric(0)
+
+##com.oracle.truffle.r.test.S4.TestS4.runRSourceTests
+#{ source("com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/R/allocation5.R") }
+numeric(0)
+
+##com.oracle.truffle.r.test.S4.TestS4.runRSourceTests
+#{ source("com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/R/allocation6.R") }
+[1] NA
+
+##com.oracle.truffle.r.test.S4.TestS4.runRSourceTests
+#{ source("com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/R/allocation7.R") }
+Error in validObject(.Object) :
+  invalid class “Person” object: Age is length 0.  Should be 1
+
+##com.oracle.truffle.r.test.S4.TestS4.runRSourceTests
+#{ source("com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/R/allocation8.R") }
+Error in validObject(.Object) :
+  invalid class “Person” object: Age is length 10.  Should be 1
+
+##com.oracle.truffle.r.test.S4.TestS4.runRSourceTests
+#{ source("com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/R/allocation9.R") }
+An object of class "Person"
+Slot "name":
+[1] "Hadley"
+
+Slot "age":
+[1] 31
+
+
+##com.oracle.truffle.r.test.S4.TestS4.runRSourceTests
+#{ source("com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/R/methods1.R") }
+Function: sides (package .GlobalEnv)
+object="Polygon"
+object="Square"
+object="Triangle"
+
+description       class        mode        text      opened    can read
+   "stdout"  "terminal"         "w"      "text"    "opened"        "no"
+  can write
+      "yes"
+description       class        mode        text      opened    can read
+   "stdout"  "terminal"         "w"      "text"    "opened"        "no"
+  can write
+      "yes"
+
+##com.oracle.truffle.r.test.S4.TestS4.runRSourceTests
+#{ source("com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/R/methods2.R") }
+Function: sides (package .GlobalEnv)
+object="Polygon"
+
+description       class        mode        text      opened    can read
+   "stdout"  "terminal"         "w"      "text"    "opened"        "no"
+  can write
+      "yes"
+description       class        mode        text      opened    can read
+   "stdout"  "terminal"         "w"      "text"    "opened"        "no"
+  can write
+      "yes"
+
+##com.oracle.truffle.r.test.S4.TestS4.runRSourceTests
+#{ source("com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/R/methods3.R") }
+Error in .valueClassTest({ :
+  invalid value from generic function ‘sides’, class “character”, expected “numeric”
+
+##com.oracle.truffle.r.test.S4.TestS4.runRSourceTests
+#{ source("com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/R/methods4.R") }
+Note: method with signature ‘A2#A1’ chosen for function ‘foo’,
+ target signature ‘A2#A2’.
+ "A1#A2" would also be valid
+[1] "2-1"
+[1] "2-1"
+
+##com.oracle.truffle.r.test.S4.TestS4.runRSourceTests
+#{ source("com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/R/methods5.R") }
+[1] "Looking for rust"
+[1] "Checking seat belts"
+
+##com.oracle.truffle.r.test.S4.TestS4.runRSourceTests
+#{ source("com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/R/slot_access1.R") }
+       name         age
+"character"   "numeric"
+
 ##com.oracle.truffle.r.test.S4.TestS4.testAllocation
 #{ new("numeric") }
 numeric(0)
@@ -43,6 +153,97 @@ Error: unexpected '}' in "{ asS4(NULL); isS4(NULL }"
 #{ x<-42; y<-asS4(x); isS4(y) }
 [1] TRUE
 
+##com.oracle.truffle.r.test.S4.TestS4.testMethods
+#{ gen<-function(object) 0; setGeneric("gen"); res<-print(gen); removeGeneric("gen"); res }
+function (object)
+standardGeneric("gen")
+<environment: 0x7fa971ee8c78>
+attr(,"generic")
+[1] "gen"
+attr(,"generic")attr(,"package")
+[1] ".GlobalEnv"
+attr(,"package")
+[1] ".GlobalEnv"
+attr(,"group")
+list()
+attr(,"valueClass")
+character(0)
+attr(,"signature")
+[1] "object"
+attr(,"default")
+Method Definition (Class "derivedDefaultMethod"):
+
+function (object)
+0
+
+Signatures:
+        object
+target  "ANY"
+defined "ANY"
+attr(,"skeleton")
+(function (object)
+0)(object)
+attr(,"class")
+[1] "standardGeneric"
+attr(,"class")attr(,"package")
+[1] "methods"
+standardGeneric for "gen" defined from package ".GlobalEnv"
+
+function (object)
+standardGeneric("gen")
+<environment: 0x7fa971ee8c78>
+Methods may be defined for arguments: object
+Use  showMethods("gen")  for currently available ones.
+
+##com.oracle.truffle.r.test.S4.TestS4.testMethods
+#{ gen<-function(object) 0; setGeneric("gen"); setClass("foo", representation(d="numeric")); setMethod("gen", signature(object="foo"), function(object) object@d); res<-print(gen(new("foo", d=42))); removeGeneric("gen"); res }
+[1] 42
+[1] 42
+
+##com.oracle.truffle.r.test.S4.TestS4.testMethods
+#{ setClass("foo", representation(d="numeric")); setClass("bar",  contains="foo"); setGeneric("gen", function(o) standardGeneric("gen")); setMethod("gen", signature(o="foo"), function(o) "FOO"); setMethod("gen", signature(o="bar"), function(o) "BAR"); res<-print(c(gen(new("foo", d=7)), gen(new("bar", d=42)))); removeGeneric("gen"); res }
+[1] "FOO" "BAR"
+[1] "FOO" "BAR"
+
+##com.oracle.truffle.r.test.S4.TestS4.testMethods
+#{ setGeneric("gen", function(o) standardGeneric("gen")); res<-print(setGeneric("gen", function(o) standardGeneric("gen"))); removeGeneric("gen"); res }
+[1] "gen"
+[1] "gen"
+
+##com.oracle.truffle.r.test.S4.TestS4.testMethods
+#{ setGeneric("gen", function(object) standardGeneric("gen")); res<-print(gen); removeGeneric("gen"); res }
+function(object) standardGeneric("gen")
+<environment: 0x7feb710c8bc8>
+attr(,"generic")
+[1] "gen"
+attr(,"generic")attr(,"package")
+[1] ".GlobalEnv"
+attr(,"package")
+[1] ".GlobalEnv"
+attr(,"group")
+list()
+attr(,"valueClass")
+character(0)
+attr(,"signature")
+[1] "object"
+attr(,"default")
+`\001NULL\001`
+attr(,"skeleton")
+(function (object)
+stop("invalid call in method dispatch to 'gen' (no default method)",
+    domain = NA))(object)
+attr(,"class")
+[1] "standardGeneric"
+attr(,"class")attr(,"package")
+[1] "methods"
+standardGeneric for "gen" defined from package ".GlobalEnv"
+
+function (object)
+standardGeneric("gen")
+<environment: 0x7feb710c8bc8>
+Methods may be defined for arguments: object
+Use  showMethods("gen")  for currently available ones.
+
 ##com.oracle.truffle.r.test.S4.TestS4.testSlotAccess
 #{ `@`(getClass("ClassUnionRepresentation"), "virtual") }
 [1] FALSE
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 1e275e9daf05635ec1718b8d9f16e8560cd08aef..3d4512bf1c69fcea01b67da2afdd95ff27c60cce 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
@@ -90,7 +90,6 @@ public class TestS4 extends TestRBase {
     }
 
     @Test
-    @Ignore
     public void testMethods() {
         // output slightly different from GNU R even though we use R's "show" method to print it
         assertEval(Ignored.OutputFormatting, "{ setGeneric(\"gen\", function(object) standardGeneric(\"gen\")); res<-print(gen); removeGeneric(\"gen\"); res }");
@@ -106,7 +105,7 @@ public class TestS4 extends TestRBase {
 
     @Override
     public String getTestDir() {
-        return null;// "S4";
+        return "S4";
     }
 
 }