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 00da0b6a7e35ea8447de6eb4e27945fc69115b06..3cdbd5354de3c4ffc170305dc7f55f59c02b33c2 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
@@ -86,14 +86,17 @@ import com.oracle.truffle.r.runtime.builtins.RBuiltinDescriptor;
 import com.oracle.truffle.r.runtime.conn.RConnection;
 import com.oracle.truffle.r.runtime.context.RContext;
 import com.oracle.truffle.r.runtime.data.RArgsValuesAndNames;
+import com.oracle.truffle.r.runtime.data.RAttributeStorage;
 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.RList;
 import com.oracle.truffle.r.runtime.data.RMissing;
 import com.oracle.truffle.r.runtime.data.RPromise;
 import com.oracle.truffle.r.runtime.data.RPromise.Closure;
 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;
 import com.oracle.truffle.r.runtime.nodes.RFastPathNode;
 import com.oracle.truffle.r.runtime.nodes.RNode;
@@ -279,8 +282,12 @@ public abstract class RCallNode extends RCallBaseNode implements RSyntaxNode, RS
                     @Cached("createWithError()") S3FunctionLookupNode dispatchLookup,
                     @Cached("createIdentityProfile()") ValueProfile builtinProfile,
                     @Cached("createBinaryProfile()") ConditionProfile implicitTypeProfile,
+                    @Cached("createBinaryProfile()") ConditionProfile isAttributableProfile,
                     @Cached("createBinaryProfile()") ConditionProfile resultIsBuiltinProfile,
-                    @Cached("create()") GetBaseEnvFrameNode getBaseEnvFrameNode) {
+                    @Cached("create()") GetBaseEnvFrameNode getBaseEnvFrameNode,
+                    @Cached("createBinaryProfile()") ConditionProfile isS4Profile, //
+                    @Cached("createEqualityProfile()") ValueProfile attrProfiles,
+                    @Cached("new()") PromiseCheckHelperNode promiseHelper) {
         RBuiltinDescriptor builtin = builtinProfile.profile(function.getRBuiltin());
         Object dispatchObject = dispatchArgument.execute(frame);
         // Cannot dispatch on REmpty
@@ -289,6 +296,23 @@ public abstract class RCallNode extends RCallBaseNode implements RSyntaxNode, RS
         }
         FrameSlot slot = dispatchTempSlot.initialize(frame, dispatchObject);
         try {
+            if (internalDispatchCall == null) {
+                CompilerDirectives.transferToInterpreterAndInvalidate();
+                internalDispatchCall = insert(FunctionDispatchNodeGen.create(this, false, slot));
+            }
+
+            if (isAttributableProfile.profile(dispatchObject instanceof RAttributeStorage) && isS4Profile.profile(((RAttributeStorage) dispatchObject).isS4())) {
+                RList list = (RList) promiseHelper.checkEvaluate(frame, REnvironment.getRegisteredNamespace("methods").get(".BasicFunsList"));
+// int index = list.getElementIndexByName(attrProfiles, builtin.getName());
+                int index = 0;
+                if (index != -1) {
+                    RFunction basicFun = (RFunction) list.getDataAt(index);
+                    Object result = internalDispatchCall.execute(frame, basicFun, lookupVarArgs(frame), null, null);
+                    if (result != RRuntime.DEFERRED_DEFAULT_MARKER) {
+                        return result;
+                    }
+                }
+            }
             RStringVector type = classHierarchyNode.execute(dispatchObject);
             S3Args s3Args;
             RFunction resultFunction;
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 364e2d84845ab4342aec11ec046c780fefad8c6f..0db9b9784f79af67b103dff695e126ce9123c909 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
@@ -164,6 +164,8 @@ public class RRuntime {
 
     public static final String DOT_SIG_ARGS = ".SigArgs";
 
+    public static final RSymbol DEFERRED_DEFAULT_MARKER = new RSymbol("__Deferred_Default_Marker__");
+
     public static final String R_TARGET = "target";
     public static final String R_DOT_TARGET = ".target";
     public static final String R_DEFINED = "defined";
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RVector.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RVector.java
index e0d3de69805cc1ae9818feff2efeba042e25f70e..87837333182d158bf2fd5e69233afc31be675da4 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RVector.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RVector.java
@@ -30,6 +30,7 @@ import com.oracle.truffle.api.CompilerAsserts;
 import com.oracle.truffle.api.CompilerDirectives;
 import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
 import com.oracle.truffle.api.object.DynamicObject;
+import com.oracle.truffle.api.profiles.ValueProfile;
 import com.oracle.truffle.r.runtime.RError;
 import com.oracle.truffle.r.runtime.RInternalError;
 import com.oracle.truffle.r.runtime.RRuntime;
@@ -203,6 +204,20 @@ public abstract class RVector<ArrayT> extends RSharingAttributeStorage implement
         return -1;
     }
 
+    @TruffleBoundary
+    public final int getElementIndexByName(ValueProfile profile, String name) {
+        if (getNames() == null) {
+            return -1;
+        }
+        RStringVector names = getNamesFromAttrs();
+        for (int i = 0; i < names.getLength(); i++) {
+            if (names.getDataAt(i).equals(name)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
     /**
      * Find the first element in the names list that {@code name} is a prefix of, and return its
      * index. If there are no names, or none is found, or there are multiple inexact matches, return
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 83b6bca2e5c7aa57fa66cfb50c50c58da9eb08a6..4f2f013d106679b9393f46f39220046e9cd180f4 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,296 @@
+##com.oracle.truffle.r.test.S4.TestS4.testAllocation#
+#{ new("numeric") }
+numeric(0)
+
+##com.oracle.truffle.r.test.S4.TestS4.testAllocation#
+#{ setClass("foo", representation(j="numeric")); new("foo", j=42) }
+An object of class "foo"
+Slot "j":
+[1] 42
+
+
+##com.oracle.truffle.r.test.S4.TestS4.testClassCreation#
+#{ setClass("foo"); setClass("bar", representation(j = "numeric"), contains = "foo"); is.null(getClass("foo")@prototype) }
+[1] FALSE
+
+##com.oracle.truffle.r.test.S4.TestS4.testClassCreation#Ignored.OutputFormatting#
+#{ setClass("foo", representation(j="numeric")); getClass("foo") }
+Class "foo" [in ".GlobalEnv"]
+
+Slots:
+
+Name:        j
+Class: numeric
+
+##com.oracle.truffle.r.test.S4.TestS4.testConversions#
+#{  asS4(7:42) }
+ [1]  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
+[26] 32 33 34 35 36 37 38 39 40 41 42
+
+##com.oracle.truffle.r.test.S4.TestS4.testConversions#
+#{ asS4(NULL); isS4(NULL) }
+[1] TRUE
+
+##com.oracle.truffle.r.test.S4.TestS4.testConversions#
+#{ isS4(NULL) }
+[1] FALSE
+
+##com.oracle.truffle.r.test.S4.TestS4.testConversions#
+#{ x<-42; isS4(x) }
+[1] FALSE
+
+##com.oracle.truffle.r.test.S4.TestS4.testConversions#
+#{ x<-42; y<-asS4(x); isS4(y) }
+[1] TRUE
+
+##com.oracle.truffle.r.test.S4.TestS4.testInternalDispatch#
+#setClass('foo', representation(d='numeric')); setMethod(`$`, signature('foo'), function(x, name) 'FOO'); obj <- new('foo'); obj$asdf
+[1] "$"
+[1] "FOO"
+
+##com.oracle.truffle.r.test.S4.TestS4.testMethods#Ignored.OutputFormatting#
+#{ gen<-function(object) 0; setGeneric("gen"); res<-print(gen); removeGeneric("gen"); res }
+function (object)
+standardGeneric("gen")
+<environment: 0x2983900>
+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: 0x2983900>
+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"); setMethod("diag<-", "foo", function(x, value) 42); removeMethod("diag<-", "foo"); removeGeneric("diag<-"); removeClass("foo") }
+Creating a generic function for ‘diag<-’ from package ‘base’ in the global environment
+[1] TRUE
+
+##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#Ignored.OutputFormatting#
+#{ setGeneric("gen", function(object) standardGeneric("gen")); res<-print(gen); removeGeneric("gen"); res }
+function(object) standardGeneric("gen")
+<environment: 0x11f3000>
+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: 0x11f3000>
+Methods may be defined for arguments: object
+Use  showMethods("gen")  for currently available ones.
+
+##com.oracle.truffle.r.test.S4.TestS4.testSlotAccess#Output.IgnoreErrorContext#
+# { x<-42; attr(x, "foo")<-7; x@foo }
+Error: trying to get slot "foo" from an object of a basic class ("numeric") with no slots
+
+##com.oracle.truffle.r.test.S4.TestS4.testSlotAccess#
+#{ `@`(getClass("ClassUnionRepresentation"), "virtual") }
+[1] FALSE
+
+##com.oracle.truffle.r.test.S4.TestS4.testSlotAccess#Output.IgnoreErrorContext#
+#{ `@`(getClass("ClassUnionRepresentation"), c("virtual", "foo")) }
+Error: invalid type or length for slot name
+
+##com.oracle.truffle.r.test.S4.TestS4.testSlotAccess#
+#{ `@`(getClass("ClassUnionRepresentation"), virtual) }
+[1] FALSE
+
+##com.oracle.truffle.r.test.S4.TestS4.testSlotAccess#
+#{ c(42)@.Data }
+[1] 42
+
+##com.oracle.truffle.r.test.S4.TestS4.testSlotAccess#Output.IgnoreErrorContext#
+#{ c(42)@foo }
+Error: trying to get slot "foo" from an object of a basic class ("numeric") with no slots
+
+##com.oracle.truffle.r.test.S4.TestS4.testSlotAccess#
+#{ getClass("ClassUnionRepresentation")@.S3Class }
+[1] "classRepresentation"
+attr(,"package")
+[1] "methods"
+
+##com.oracle.truffle.r.test.S4.TestS4.testSlotAccess#Output.IgnoreErrorContext#
+#{ getClass("ClassUnionRepresentation")@foo }
+Error: no slot of name "foo" for this object of class "classRepresentation"
+
+##com.oracle.truffle.r.test.S4.TestS4.testSlotAccess#
+#{ getClass("ClassUnionRepresentation")@virtual }
+[1] FALSE
+
+##com.oracle.truffle.r.test.S4.TestS4.testSlotAccess#
+#{ setClass("foo", contains="numeric"); x<-new("foo"); res<-slot(x, ".Data"); removeClass("foo"); res }
+numeric(0)
+
+##com.oracle.truffle.r.test.S4.TestS4.testSlotAccess#
+#{ setClass("foo", contains="numeric"); x<-new("foo"); res<-x@.Data; removeClass("foo"); res }
+numeric(0)
+
+##com.oracle.truffle.r.test.S4.TestS4.testSlotAccess#
+#{ x<-42; `@`(x, ".Data") }
+[1] 42
+
+##com.oracle.truffle.r.test.S4.TestS4.testSlotAccess#
+#{ x<-42; `@`(x, .Data) }
+[1] 42
+
+##com.oracle.truffle.r.test.S4.TestS4.testSlotAccess#
+#{ x<-42; attr(x, "foo")<-7; slot(x, "foo") }
+[1] 7
+
+##com.oracle.truffle.r.test.S4.TestS4.testSlotAccess#
+#{ x<-42; slot(x, ".Data") }
+[1] 42
+
+##com.oracle.truffle.r.test.S4.TestS4.testSlotAccess#Output.IgnoreErrorContext#
+#{ x<-NULL; `@`(x, foo) }
+Error: trying to get slot "foo" from an object of a basic class ("NULL") with no slots
+
+##com.oracle.truffle.r.test.S4.TestS4.testSlotAccess#Output.IgnoreErrorContext#
+#{ x<-NULL; x@foo }
+Error: trying to get slot "foo" from an object of a basic class ("NULL") with no slots
+
+##com.oracle.truffle.r.test.S4.TestS4.testSlotAccess#Output.IgnoreErrorContext#
+#{ x<-c(42); class(x)<-"bar"; x@foo }
+Error: trying to get slot "foo" from an object (class "bar") that is not an S4 object
+
+##com.oracle.truffle.r.test.S4.TestS4.testSlotAccess#
+#{ x<-function() 42; attr(x, "foo")<-7; y<-asS4(x); y@foo }
+[1] 7
+
+##com.oracle.truffle.r.test.S4.TestS4.testSlotAccess#
+#{ x<-getClass("ClassUnionRepresentation"); slot(x, "virtual") }
+[1] FALSE
+
+##com.oracle.truffle.r.test.S4.TestS4.testSlotAccess#Output.IgnoreErrorContext#
+#{ x<-getClass("ClassUnionRepresentation"); slot(x, virtual) }
+Error in slot(x, virtual) : object 'virtual' not found
+
+##com.oracle.truffle.r.test.S4.TestS4.testSlotAccess#
+#{ x<-paste0(".", "Data"); y<-42; slot(y, x) }
+[1] 42
+
+##com.oracle.truffle.r.test.S4.TestS4.testSlotUpdate#Output.IgnoreErrorContext#
+#{ x<-NULL; `@<-`(x, foo, "bar") }
+Error in (function (cl, name, valueClass)  :
+  ‘foo’ is not a slot in class “NULL”
+
+##com.oracle.truffle.r.test.S4.TestS4.testSlotUpdate#Output.IgnoreErrorContext#
+#{ x<-NULL; x@foo<-"bar" }
+Error in (function (cl, name, valueClass)  :
+  ‘foo’ is not a slot in class “NULL”
+
+##com.oracle.truffle.r.test.S4.TestS4.testSlotUpdate#Output.IgnoreErrorContext#
+#{ x<-function() 42; attr(x, "foo")<-7; slot(y, "foo")<-42 }
+Error in slot(y, "foo") <- 42 : object 'y' not found
+
+##com.oracle.truffle.r.test.S4.TestS4.testSlotUpdate#Output.IgnoreErrorContext#
+#{ x<-function() 42; attr(x, "foo")<-7; y<-asS4(x); y@foo<-42 }
+Error in (function (cl, name, valueClass)  :
+  ‘foo’ is not a slot in class “function”
+
+##com.oracle.truffle.r.test.S4.TestS4.testSlotUpdate#Output.IgnoreErrorContext#
+#{ x<-function() 42; attr(x, "foo")<-7; y@foo<-42 }
+Error in y@foo <- 42 : object 'y' not found
+
+##com.oracle.truffle.r.test.S4.TestS4.testSlotUpdate#
+#{ x<-getClass("ClassUnionRepresentation"); slot(x, "virtual", check=TRUE)<-TRUE; x@virtual }
+[1] TRUE
+
+##com.oracle.truffle.r.test.S4.TestS4.testSlotUpdate#
+#{ x<-getClass("ClassUnionRepresentation"); x@virtual<-TRUE; x@virtual }
+[1] TRUE
+
+##com.oracle.truffle.r.test.S4.TestS4.testSlotUpdate#
+#{ x<-initialize@valueClass; initialize@valueClass<-"foo"; initialize@valueClass<-x }
+
+##com.oracle.truffle.r.test.S4.TestS4.testStdGeneric#
+#{ standardGeneric("") }
+Error in standardGeneric("") :
+  argument to 'standardGeneric' must be a non-empty character string
+
+##com.oracle.truffle.r.test.S4.TestS4.testStdGeneric#
+#{ standardGeneric("foo", 42) }
+Error: expected a generic function or a primitive for dispatch, got an object of class "numeric"
+
+##com.oracle.truffle.r.test.S4.TestS4.testStdGeneric#
+#{ standardGeneric(42) }
+Error in standardGeneric(42) :
+  argument to 'standardGeneric' must be a non-empty character string
+
+##com.oracle.truffle.r.test.S4.TestS4.testStdGeneric#
+#{ standardGeneric(character()) }
+Error in standardGeneric(character()) :
+  argument to 'standardGeneric' must be a non-empty character string
+
+##com.oracle.truffle.r.test.S4.TestS4.testStdGeneric#Output.IgnoreErrorContext#
+#{ x<-42; class(x)<-character(); standardGeneric("foo", x) }
+Error: expected a generic function or a primitive for dispatch, got an object of class "numeric"
+
 ##com.oracle.truffle.r.test.builtins.TestBuiltin_Arg.testArg1#
 #argv <- list(1+2i);Arg(argv[[1]]);
 [1] 1.107149
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 4719890c6b743ade75373ea91fcada000a3342a8..821615e7d7643119a400274a1dc1f960e6ebf8b8 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
@@ -24,14 +24,16 @@ package com.oracle.truffle.r.test.S4;
 
 import org.junit.Test;
 
-import com.oracle.truffle.r.test.TestRBase;
+import com.oracle.truffle.r.test.TestBase;
+import com.oracle.truffle.r.test.TestBase.Ignored;
+import com.oracle.truffle.r.test.TestBase.Output;
 
 // Checkstyle: stop LineLength
 
 /**
  * Tests for the S4 object model implementation.
  */
-public class TestS4 extends TestRBase {
+public class TestS4 extends TestBase {
     @Test
     public void testSlotAccess() {
         assertEval("{ `@`(getClass(\"ClassUnionRepresentation\"), virtual) }");
@@ -123,11 +125,7 @@ public class TestS4 extends TestRBase {
         assertEval("{ standardGeneric(character()) }");
         assertEval("{ standardGeneric(\"\") }");
         assertEval("{ standardGeneric(\"foo\", 42) }");
-        assertEval("{ x<-42; class(x)<-character(); standardGeneric(\"foo\", x) }");
+        assertEval(Output.IgnoreErrorContext, "{ x<-42; class(x)<-character(); standardGeneric(\"foo\", x) }");
     }
 
-    @Override
-    public String getTestDir() {
-        return "S4";
-    }
 }
diff --git a/mx.fastr/mx_fastr.py b/mx.fastr/mx_fastr.py
index b61e314d5c722c2df25a2a138f773cef4e72ff30..782257b532a78329e952b39f4c02db0d82a52882 100644
--- a/mx.fastr/mx_fastr.py
+++ b/mx.fastr/mx_fastr.py
@@ -404,7 +404,7 @@ def _test_subpackage(name):
     return '.'.join((_test_package(), name))
 
 def _simple_generated_unit_tests():
-    return ','.join(map(_test_subpackage, ['engine.shell', 'library.base', 'library.grid', 'library.methods', 'library.stats', 'library.utils', 'library.fastr', 'builtins', 'functions', 'parser', 'rng', 'runtime.data']))
+    return ','.join(map(_test_subpackage, ['engine.shell', 'library.base', 'library.grid', 'library.methods', 'library.stats', 'library.utils', 'library.fastr', 'builtins', 'functions', 'parser', 'rng', 'runtime.data', 'S4']))
 
 def _simple_unit_tests():
     return ','.join([_simple_generated_unit_tests(), _test_subpackage('tck')])