diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Range.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Range.java
index 9395c6682ce00d2918381fb53a963329567b0bf6..f269495c60854adbcf3695aa66ae4d1c7fa398e0 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Range.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Range.java
@@ -65,8 +65,8 @@ public abstract class Range extends RBuiltinNode.Arg3 {
 
     @Specialization(guards = "args.getLength() == 1")
     protected RVector<?> rangeLengthOne(RArgsValuesAndNames args, boolean naRm, boolean finite) {
-        Object min = minReduce.executeReduce(args.getArgument(0), naRm, finite);
-        Object max = maxReduce.executeReduce(args.getArgument(0), naRm, finite);
+        Object min = minReduce.executeReduce(args.getArgument(0), naRm || finite, finite);
+        Object max = maxReduce.executeReduce(args.getArgument(0), naRm || finite, finite);
         return createResult(min, max);
     }
 
@@ -84,8 +84,8 @@ public abstract class Range extends RBuiltinNode.Arg3 {
     protected RVector<?> range(RArgsValuesAndNames args, boolean naRm, boolean finite,
                     @Cached("create()") Combine combine) {
         Object combined = combine.executeCombine(args, false);
-        Object min = minReduce.executeReduce(combined, naRm, finite);
-        Object max = maxReduce.executeReduce(combined, naRm, finite);
+        Object min = minReduce.executeReduce(combined, naRm || finite, finite);
+        Object max = maxReduce.executeReduce(combined, naRm || finite, finite);
         return createResult(min, max);
     }
 }
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/unary/UnaryArithmeticReduceNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/unary/UnaryArithmeticReduceNode.java
index 4f2f375192eb681092cf8f87eef33ad04c367e80..7230d7b17e60bd571680b3792d0dc235d8562e0c 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/unary/UnaryArithmeticReduceNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/unary/UnaryArithmeticReduceNode.java
@@ -22,17 +22,17 @@
  */
 package com.oracle.truffle.r.nodes.unary;
 
-import com.oracle.truffle.api.CompilerDirectives;
 import com.oracle.truffle.api.dsl.Cached;
 import com.oracle.truffle.api.dsl.Fallback;
 import com.oracle.truffle.api.dsl.ImportStatic;
 import com.oracle.truffle.api.dsl.Specialization;
 import com.oracle.truffle.api.dsl.TypeSystemReference;
 import com.oracle.truffle.api.interop.TruffleObject;
+import com.oracle.truffle.api.profiles.BranchProfile;
 import com.oracle.truffle.api.profiles.ConditionProfile;
 import com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef;
-import com.oracle.truffle.r.nodes.control.RLengthNode;
 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.data.RComplex;
 import com.oracle.truffle.r.runtime.data.RComplexVector;
@@ -42,8 +42,8 @@ import com.oracle.truffle.r.runtime.data.RTypes;
 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.nodes.EnableNACheckNode;
-import com.oracle.truffle.r.runtime.data.nodes.VectorIterator;
+import com.oracle.truffle.r.runtime.data.model.RAbstractVector;
+import com.oracle.truffle.r.runtime.data.nodes.VectorAccess;
 import com.oracle.truffle.r.runtime.interop.ForeignArray2R;
 import com.oracle.truffle.r.runtime.nodes.RBaseNode;
 import com.oracle.truffle.r.runtime.ops.BinaryArithmetic;
@@ -57,6 +57,8 @@ import com.oracle.truffle.r.runtime.ops.na.NACheck;
  * only applies to some types (e.g. double or integer), for other types 'finite' seems to be ignored
  * (e.g. logical). The only situation where semantics of finite is different to na.rm is double
  * values: na.rm removes NA and NaN, but not -/+Inf.
+ *
+ * FastR handles finite consistently (setting na.rm = TRUE if finite = TRUE) in the range builtin.
  */
 @ImportStatic({RRuntime.class})
 @TypeSystemReference(RTypes.class)
@@ -64,7 +66,6 @@ public abstract class UnaryArithmeticReduceNode extends RBaseNode {
 
     public abstract Object executeReduce(Object value, boolean naRm, boolean finite);
 
-    @Child private MultiElemStringHandlerNode stringHandler;
     @Child private BinaryArithmetic arithmetic;
 
     private final BinaryArithmeticFactory factory;
@@ -74,6 +75,8 @@ public abstract class UnaryArithmeticReduceNode extends RBaseNode {
 
     private final NACheck na = NACheck.create();
     private final ConditionProfile naRmProfile = ConditionProfile.createBinaryProfile();
+    private final BranchProfile emptyProfile = BranchProfile.create();
+    private final BranchProfile naResultProfile = BranchProfile.create();
 
     protected UnaryArithmeticReduceNode(ReduceSemantics semantics, BinaryArithmeticFactory factory) {
         this.factory = factory;
@@ -83,21 +86,15 @@ public abstract class UnaryArithmeticReduceNode extends RBaseNode {
         this.supportComplex = semantics.supportComplex;
     }
 
-    private String handleString(RStringVector operand, boolean naRm, boolean finite, int offset) {
-        if (stringHandler == null) {
-            CompilerDirectives.transferToInterpreterAndInvalidate();
-            stringHandler = insert(new MultiElemStringHandlerNode(semantics, factory, na));
-        }
-        return stringHandler.executeString(operand, naRm, finite, offset);
-    }
-
     private void emptyWarning() {
+        emptyProfile.enter();
         if (semantics.getEmptyWarning() != null) {
             warning(semantics.emptyWarning);
         }
     }
 
     private void naResultWarning() {
+        naResultProfile.enter();
         if (semantics.getNAResultWarning() != null) {
             warning(semantics.getNAResultWarning());
         }
@@ -136,20 +133,23 @@ public abstract class UnaryArithmeticReduceNode extends RBaseNode {
     protected double doDouble(double operand, boolean naRm, boolean finite,
                     @Cached("createBinaryProfile()") ConditionProfile finiteProfile,
                     @Cached("createBinaryProfile()") ConditionProfile isInfiniteProfile) {
+        if (finiteProfile.profile(finite) && isInfiniteProfile.profile(!RRuntime.isFinite(operand))) {
+            emptyWarning();
+            return semantics.getIntStart();
+        }
         na.enable(operand);
-        if (naRmProfile.profile(naRm || finite)) {
-            boolean profiledFinite = finiteProfile.profile(finite);
-            if (na.checkNAorNaN(operand) || (profiledFinite && isInfiniteProfile.profile(!RRuntime.isFinite(operand)))) {
+        if (naRmProfile.profile(naRm)) {
+            if (na.checkNAorNaN(operand)) {
                 // the only value we have should be removed...
                 emptyWarning();
                 return semantics.getIntStart();
             } else {
+                // known not to be NA or NaN
                 return operand;
             }
-        } else {
-            // since !naRm and !finite, NaN or +/-Inf can be valid results
-            return na.check(operand) ? RRuntime.DOUBLE_NA : operand;
         }
+        // since !naRm and !finite, NaN or +/-Inf can be valid results
+        return na.check(operand) ? RRuntime.DOUBLE_NA : operand;
     }
 
     @Specialization
@@ -199,35 +199,47 @@ public abstract class UnaryArithmeticReduceNode extends RBaseNode {
         }
     }
 
-    @Specialization
-    protected Object doIntVector(RAbstractIntVector operand, boolean naRm, boolean finite,
-                    @Cached("create()") EnableNACheckNode enableNACheckNode,
-                    @Cached("create()") RLengthNode lengthNode,
-                    @Cached("create()") VectorIterator.Int iterator) {
-        RBaseNode.reportWork(this, lengthNode.executeInteger(operand));
-        boolean profiledNaRm = naRmProfile.profile(naRm || finite);
+    private Object doInt(RAbstractVector vector, boolean naRm, VectorAccess access) {
+        boolean profiledNaRm = naRmProfile.profile(naRm);
         int result = semantics.getIntStart();
-        enableNACheckNode.execute(na, operand);
-        int opCount = 0;
-        Object it = iterator.init(operand);
-        while (iterator.hasNext(operand, it)) {
-            int d = iterator.next(operand, it);
-            if (na.check(d)) {
-                if (profiledNaRm) {
-                    continue;
-                } else {
-                    return RRuntime.INT_NA;
+        boolean empty = true;
+        try (VectorAccess.SequentialIterator iter = access.access(vector)) {
+            while (access.next(iter)) {
+                int d;
+                switch (access.getType()) {
+                    case Integer:
+                        d = access.getInt(iter);
+                        if (access.na.check(d)) {
+                            if (profiledNaRm) {
+                                continue;
+                            } else {
+                                return RRuntime.INT_NA;
+                            }
+                        }
+                        break;
+                    case Logical:
+                        byte logical = access.getLogical(iter);
+                        if (access.na.check(logical)) {
+                            if (profiledNaRm) {
+                                continue;
+                            } else {
+                                return RRuntime.INT_NA;
+                            }
+                        }
+                        d = logical; // 0 or 1
+                        break;
+                    default:
+                        throw RInternalError.shouldNotReachHere();
                 }
-            } else {
                 result = arithmetic.op(result, d);
                 if (RRuntime.isNA(result)) {
                     naResultWarning();
                     return RRuntime.INT_NA;
                 }
+                empty = false;
             }
-            opCount++;
         }
-        if (opCount == 0) {
+        if (empty) {
             emptyWarning();
             if (semantics.isUseDoubleStartForEmptyVector()) {
                 return semantics.getDoubleStart();
@@ -236,88 +248,79 @@ public abstract class UnaryArithmeticReduceNode extends RBaseNode {
         return result;
     }
 
-    @Specialization
-    protected double doDoubleVector(RAbstractDoubleVector operand, boolean naRm, boolean finite,
-                    @Cached("create()") EnableNACheckNode enableNACheckNode,
-                    @Cached("create()") RLengthNode lengthNode,
-                    @Cached("create()") VectorIterator.Double iterator,
-                    @Cached("createBinaryProfile()") ConditionProfile finiteProfile,
-                    @Cached("createBinaryProfile()") ConditionProfile isInfiniteProfile) {
-        RBaseNode.reportWork(this, lengthNode.executeInteger(operand));
-        boolean profiledNaRm = naRmProfile.profile(naRm || finite);
-        boolean profiledFinite = finiteProfile.profile(finite);
-        double result = semantics.getDoubleStart();
-        enableNACheckNode.execute(na, operand);
-        int opCount = 0;
+    @Specialization(guards = "access.supports(vector)")
+    protected Object doIntCached(RAbstractIntVector vector, boolean naRm, @SuppressWarnings("unused") boolean finite,
+                    @Cached("vector.access()") VectorAccess access) {
+        return doInt(vector, naRm, access);
+    }
 
-        Object it = iterator.init(operand);
-        while (iterator.hasNext(operand, it)) {
-            double d = iterator.next(operand, it);
-            if (na.checkNAorNaN(d)) {
-                if (profiledNaRm) {
-                    continue;   // ignore NA/NaN
-                } else if (na.check(d)) {
-                    // NA produces NA directly, but NaN should be handled by arithmetics.op to
-                    // produce NaN. We cannot directly return NaN because if we encounter NA later
-                    // on, we should return NA not NaN
-                    return RRuntime.DOUBLE_NA;
-                }
-            } else if (profiledFinite && isInfiniteProfile.profile(!RRuntime.isFinite(d))) {
-                // ignore -/+Inf if 'infinite == TRUE'
-                continue;
-            }
+    @Specialization(replaces = "doIntCached")
+    protected Object doIntGeneric(RAbstractIntVector vector, boolean naRm, @SuppressWarnings("unused") boolean finite) {
+        return doInt(vector, naRm, vector.slowPathAccess());
+    }
 
-            result = arithmetic.op(result, d);
-            opCount++;
-        }
-        if (opCount == 0) {
-            emptyWarning();
-        }
-        return result;
+    @Specialization(guards = "access.supports(vector)")
+    protected Object doLogicalCached(RAbstractLogicalVector vector, boolean naRm, @SuppressWarnings("unused") boolean finite,
+                    @Cached("vector.access()") VectorAccess access) {
+        return doInt(vector, naRm, access);
     }
 
-    @Specialization
-    protected Object doLogicalVector(RAbstractLogicalVector operand, boolean naRm, @SuppressWarnings("unused") boolean finite,
-                    @Cached("create()") EnableNACheckNode enableNACheckNode,
-                    @Cached("create()") RLengthNode lengthNode,
-                    @Cached("create()") VectorIterator.Logical iterator) {
-        RBaseNode.reportWork(this, lengthNode.executeInteger(operand));
-        boolean profiledNaRm = naRmProfile.profile(naRm);
-        int result = semantics.getIntStart();
-        enableNACheckNode.execute(na, operand);
-        int opCount = 0;
+    @Specialization(replaces = "doIntCached")
+    protected Object doLogicalGeneric(RAbstractLogicalVector vector, boolean naRm, @SuppressWarnings("unused") boolean finite) {
+        return doInt(vector, naRm, vector.slowPathAccess());
+    }
 
-        Object it = iterator.init(operand);
-        while (iterator.hasNext(operand, it)) {
-            byte d = iterator.next(operand, it);
-            if (na.check(d)) {
-                if (profiledNaRm) {
+    private double doDouble(RAbstractDoubleVector vector, boolean naRm, boolean finite, ConditionProfile finiteProfile, ConditionProfile isInfiniteProfile, VectorAccess access) {
+        boolean profiledNaRm = naRmProfile.profile(naRm);
+        boolean profiledFinite = finiteProfile.profile(finite);
+        double result = semantics.getDoubleStart();
+        boolean empty = true;
+        try (VectorAccess.SequentialIterator iter = access.access(vector)) {
+            while (access.next(iter)) {
+                double d = access.getDouble(iter);
+                if (access.na.checkNAorNaN(d)) {
+                    if (profiledNaRm) {
+                        continue;   // ignore NA/NaN
+                    } else if (access.na.check(d)) {
+                        // NA produces NA directly, but NaN should be handled by arithmetics.op to
+                        // produce NaN. We cannot directly return NaN because if we encounter NA
+                        // later
+                        // on, we should return NA not NaN
+                        return RRuntime.DOUBLE_NA;
+                    }
+                } else if (profiledFinite && isInfiniteProfile.profile(!RRuntime.isFinite(d))) {
+                    // ignore -/+Inf if 'infinite == TRUE'
                     continue;
-                } else {
-                    return RRuntime.INT_NA;
                 }
-            } else {
                 result = arithmetic.op(result, d);
-                if (RRuntime.isNA(result)) {
-                    naResultWarning();
-                    return RRuntime.INT_NA;
-                }
+                empty = false;
             }
-            opCount++;
         }
-        if (opCount == 0) {
+        if (empty) {
             emptyWarning();
-            if (semantics.isUseDoubleStartForEmptyVector()) {
-                return semantics.getDoubleStart();
-            }
         }
         return result;
     }
 
+    @Specialization(guards = "access.supports(vector)")
+    protected double doDoubleCached(RAbstractDoubleVector vector, boolean naRm, boolean finite,
+                    @Cached("createBinaryProfile()") ConditionProfile finiteProfile,
+                    @Cached("createBinaryProfile()") ConditionProfile isInfiniteProfile,
+                    @Cached("vector.access()") VectorAccess access) {
+        return doDouble(vector, naRm, finite, finiteProfile, isInfiniteProfile, access);
+    }
+
+    @Specialization(replaces = "doDoubleCached")
+    protected double doDoubleGeneric(RAbstractDoubleVector vector, boolean naRm, boolean finite,
+                    @Cached("createBinaryProfile()") ConditionProfile finiteProfile,
+                    @Cached("createBinaryProfile()") ConditionProfile isInfiniteProfile) {
+        return doDouble(vector, naRm, finite, finiteProfile, isInfiniteProfile, vector.slowPathAccess());
+    }
+
     @Specialization(guards = "supportComplex")
-    protected RComplex doComplexVector(RComplexVector operand, boolean naRm, boolean finite) {
+    protected RComplex doComplexVector(RComplexVector operand, boolean naRm, @SuppressWarnings("unused") boolean finite) {
         RBaseNode.reportWork(this, operand.getLength());
-        boolean profiledNaRm = naRmProfile.profile(naRm || finite);
+        boolean profiledNaRm = naRmProfile.profile(naRm);
         RComplex result = RRuntime.double2complex(semantics.getDoubleStart());
         int opCount = 0;
         na.enable(operand);
@@ -344,18 +347,14 @@ public abstract class UnaryArithmeticReduceNode extends RBaseNode {
     // does not work for String-s as, in particular, we cannot supply the (lexicographically)
     // "largest" String for the implementation of max function
 
-    private static String doStringVectorEmptyInternal(ReduceSemantics semantics, RBaseNode invokingNode) {
+    @Specialization(guards = {"supportString", "operand.getLength() == 0"})
+    protected String doStringVectorEmpty(@SuppressWarnings("unused") RStringVector operand, @SuppressWarnings("unused") boolean naRm, @SuppressWarnings("unused") boolean finite) {
         if (semantics.getEmptyWarning() != null) {
-            RError.warning(invokingNode, semantics.emptyWarningCharacter);
+            warning(semantics.emptyWarningCharacter);
         }
         return semantics.getStringStart();
     }
 
-    @Specialization(guards = {"supportString", "operand.getLength() == 0"})
-    protected String doStringVectorEmpty(@SuppressWarnings("unused") RStringVector operand, @SuppressWarnings("unused") boolean naRm, @SuppressWarnings("unused") boolean finite) {
-        return doStringVectorEmptyInternal(semantics, this);
-    }
-
     @Specialization(guards = {"supportString", "operand.getLength() == 1"})
     protected String doStringVectorOneElem(RStringVector operand, boolean naRm, boolean finite) {
         boolean profiledNaRm = naRmProfile.profile(naRm);
@@ -371,7 +370,40 @@ public abstract class UnaryArithmeticReduceNode extends RBaseNode {
 
     @Specialization(guards = {"supportString", "operand.getLength() > 1"})
     protected String doStringVector(RStringVector operand, boolean naRm, boolean finite) {
-        return handleString(operand, naRm, finite, 0);
+        boolean profiledNaRm = naRmProfile.profile(naRm);
+        na.enable(operand);
+        int offset = 0;
+        String result = operand.getDataAt(offset);
+        if (profiledNaRm) {
+            while (na.check(result)) {
+                // the following is meant to eliminate leading NA-s
+                if (offset == operand.getLength() - 1) {
+                    // last element - all other are NAs
+                    return doStringVectorEmpty(operand, naRm, finite);
+                }
+                result = operand.getDataAt(++offset);
+            }
+        } else {
+            if (na.check(result)) {
+                return result;
+            }
+        }
+        // when we reach here, it means that we have already seen one non-NA element
+        assert !RRuntime.isNA(result);
+        for (int i = offset + 1; i < operand.getLength(); i++) {
+            String current = operand.getDataAt(i);
+            if (na.check(current)) {
+                if (profiledNaRm) {
+                    // skip NA-s
+                    continue;
+                } else {
+                    return RRuntime.STRING_NA;
+                }
+            } else {
+                result = arithmetic.op(result, current);
+            }
+        }
+        return result;
     }
 
     @Specialization(guards = {"isForeignObject(obj)"})
@@ -452,67 +484,4 @@ public abstract class UnaryArithmeticReduceNode extends RBaseNode {
             return useDoubleStartForEmptyVector;
         }
     }
-
-    private static final class MultiElemStringHandlerNode extends RBaseNode {
-
-        @Child private MultiElemStringHandlerNode recursiveStringHandler;
-        @Child private BinaryArithmetic arithmetic;
-
-        private final ReduceSemantics semantics;
-        private final BinaryArithmeticFactory factory;
-        private final NACheck na;
-        private final ConditionProfile naRmProfile = ConditionProfile.createBinaryProfile();
-
-        MultiElemStringHandlerNode(ReduceSemantics semantics, BinaryArithmeticFactory factory, NACheck na) {
-            this.semantics = semantics;
-            this.factory = factory;
-            this.arithmetic = factory.createOperation();
-            this.na = na;
-        }
-
-        private String handleString(RStringVector operand, boolean naRm, boolean finite, int offset) {
-            if (recursiveStringHandler == null) {
-                CompilerDirectives.transferToInterpreterAndInvalidate();
-                recursiveStringHandler = insert(new MultiElemStringHandlerNode(semantics, factory, na));
-            }
-            return recursiveStringHandler.executeString(operand, naRm, finite, offset);
-        }
-
-        public String executeString(RStringVector operand, boolean naRm, boolean finite, int offset) {
-            boolean profiledNaRm = naRmProfile.profile(naRm);
-            na.enable(operand);
-            String result = operand.getDataAt(offset);
-            if (profiledNaRm) {
-                if (na.check(result)) {
-                    // the following is meant to eliminate leading NA-s
-                    if (offset == operand.getLength() - 1) {
-                        // last element - all other are NAs
-                        return doStringVectorEmptyInternal(semantics, this);
-                    } else {
-                        return handleString(operand, naRm, finite, offset + 1);
-                    }
-                }
-            } else {
-                if (na.check(result)) {
-                    return result;
-                }
-            }
-            // when we reach here, it means that we have already seen one non-NA element
-            assert !RRuntime.isNA(result);
-            for (int i = offset + 1; i < operand.getLength(); i++) {
-                String current = operand.getDataAt(i);
-                if (na.check(current)) {
-                    if (profiledNaRm) {
-                        // skip NA-s
-                        continue;
-                    } else {
-                        return RRuntime.STRING_NA;
-                    }
-                } else {
-                    result = arithmetic.op(result, current);
-                }
-            }
-            return result;
-        }
-    }
 }
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/unary/UnaryNotNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/unary/UnaryNotNode.java
index acd72feaf1484c0c8e4a36165d7538c523617628..1ee0499aa4ace76f7fa3e2b83eadb38ac6d32d38 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/unary/UnaryNotNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/unary/UnaryNotNode.java
@@ -32,34 +32,37 @@ import com.oracle.truffle.api.dsl.Fallback;
 import com.oracle.truffle.api.dsl.ImportStatic;
 import com.oracle.truffle.api.dsl.Specialization;
 import com.oracle.truffle.api.frame.VirtualFrame;
+import com.oracle.truffle.api.interop.Message;
 import com.oracle.truffle.api.interop.TruffleObject;
-import com.oracle.truffle.api.profiles.ConditionProfile;
+import com.oracle.truffle.r.nodes.attributes.SpecialAttributesFunctions.GetDimAttributeNode;
+import com.oracle.truffle.r.nodes.attributes.SpecialAttributesFunctions.GetDimNamesAttributeNode;
+import com.oracle.truffle.r.nodes.attributes.SpecialAttributesFunctions.GetNamesAttributeNode;
 import com.oracle.truffle.r.nodes.builtin.RBuiltinNode;
 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.builtins.RBuiltin;
-import com.oracle.truffle.r.runtime.data.RComplex;
 import com.oracle.truffle.r.runtime.data.RDataFactory;
-import com.oracle.truffle.r.runtime.data.RList;
-import com.oracle.truffle.r.runtime.data.RLogicalVector;
+import com.oracle.truffle.r.runtime.data.RDataFactory.VectorFactory;
 import com.oracle.truffle.r.runtime.data.RRaw;
-import com.oracle.truffle.r.runtime.data.RRawVector;
-import com.oracle.truffle.r.runtime.data.model.RAbstractComplexVector;
-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.RAbstractStringVector;
+import com.oracle.truffle.r.runtime.data.RVector;
+import com.oracle.truffle.r.runtime.data.model.RAbstractLogicalVector;
 import com.oracle.truffle.r.runtime.data.model.RAbstractVector;
+import com.oracle.truffle.r.runtime.data.nodes.VectorAccess;
+import com.oracle.truffle.r.runtime.data.nodes.VectorAccess.SequentialIterator;
+import com.oracle.truffle.r.runtime.data.nodes.VectorReuse;
 import com.oracle.truffle.r.runtime.interop.ForeignArray2R;
-import com.oracle.truffle.r.runtime.ops.na.NACheck;
 import com.oracle.truffle.r.runtime.ops.na.NAProfile;
 
-@ImportStatic({RRuntime.class})
+@ImportStatic({RRuntime.class, ForeignArray2R.class, Message.class, RType.class})
 @RBuiltin(name = "!", kind = PRIMITIVE, parameterNames = {""}, dispatch = OPS_GROUP_GENERIC, behavior = PURE_ARITHMETIC)
 public abstract class UnaryNotNode extends RBuiltinNode.Arg1 {
 
-    private final NACheck na = NACheck.create();
     private final NAProfile naProfile = NAProfile.create();
-    private final ConditionProfile zeroLengthProfile = ConditionProfile.createBinaryProfile();
+
+    @Child private GetDimAttributeNode getDims = GetDimAttributeNode.create();
+    @Child private GetNamesAttributeNode getNames = GetNamesAttributeNode.create();
+    @Child private GetDimNamesAttributeNode getDimNames = GetDimNamesAttributeNode.create();
 
     static {
         Casts.noCasts(UnaryNotNode.class);
@@ -77,10 +80,6 @@ public abstract class UnaryNotNode extends RBuiltinNode.Arg1 {
         return RRuntime.asLogical(operand == 0);
     }
 
-    private static byte not(RComplex operand) {
-        return RRuntime.asLogical(operand.getRealPart() == 0 && operand.getImaginaryPart() == 0);
-    }
-
     private static byte notRaw(RRaw operand) {
         return notRaw(operand.getValue());
     }
@@ -109,112 +108,83 @@ public abstract class UnaryNotNode extends RBuiltinNode.Arg1 {
         return RDataFactory.createRaw(notRaw(operand));
     }
 
-    @Specialization
-    protected RLogicalVector doLogicalVector(RLogicalVector vector) {
-        int length = vector.getLength();
-        byte[] result;
-        if (zeroLengthProfile.profile(length == 0)) {
-            result = new byte[0];
-        } else {
-            na.enable(vector);
-            result = new byte[length];
-            for (int i = 0; i < length; i++) {
-                byte value = vector.getDataAt(i);
-                result[i] = na.check(value) ? RRuntime.LOGICAL_NA : not(value);
+    @Specialization(guards = {"vectorAccess.supports(vector)", "reuse.supports(vector)"})
+    protected RAbstractVector doLogicalVectorCached(RAbstractLogicalVector vector,
+                    @Cached("vector.access()") VectorAccess vectorAccess,
+                    @Cached("createTemporary(vector)") VectorReuse reuse) {
+        RAbstractVector result = reuse.getResult(vector);
+        VectorAccess resultAccess = reuse.access(result);
+        try (SequentialIterator vectorIter = vectorAccess.access(vector); SequentialIterator resultIter = resultAccess.access(result)) {
+            while (vectorAccess.next(vectorIter) && resultAccess.next(resultIter)) {
+                byte value = vectorAccess.getLogical(vectorIter);
+                resultAccess.setLogical(resultIter, vectorAccess.na.check(value) ? RRuntime.LOGICAL_NA : not(value));
             }
         }
-        RLogicalVector resultVector = RDataFactory.createLogicalVector(result, na.neverSeenNA());
-        resultVector.copyAttributesFrom(vector);
-        return resultVector;
-    }
-
-    @Specialization
-    protected RLogicalVector doIntVector(RAbstractIntVector vector) {
-        int length = vector.getLength();
-        byte[] result;
-        if (zeroLengthProfile.profile(length == 0)) {
-            result = new byte[0];
-        } else {
-            na.enable(vector);
-            result = new byte[length];
-            for (int i = 0; i < length; i++) {
-                int value = vector.getDataAt(i);
-                result[i] = na.check(value) ? RRuntime.LOGICAL_NA : not(value);
-            }
-        }
-        RLogicalVector resultVector = RDataFactory.createLogicalVector(result, na.neverSeenNA());
-        copyNamesDimsDimNames(vector, resultVector);
-        return resultVector;
-    }
-
-    @Specialization
-    protected RLogicalVector doDoubleVector(RAbstractDoubleVector vector) {
-        int length = vector.getLength();
-        byte[] result;
-        if (zeroLengthProfile.profile(length == 0)) {
-            result = new byte[0];
-        } else {
-            na.enable(vector);
-            result = new byte[length];
-            for (int i = 0; i < length; i++) {
-                double value = vector.getDataAt(i);
-                result[i] = na.check(value) ? RRuntime.LOGICAL_NA : not(value);
-            }
-        }
-        RLogicalVector resultVector = RDataFactory.createLogicalVector(result, na.neverSeenNA());
-        copyNamesDimsDimNames(vector, resultVector);
-        return resultVector;
-    }
-
-    @Specialization
-    protected RLogicalVector doComplexVector(RAbstractComplexVector vector) {
-        int length = vector.getLength();
-        byte[] result;
-        if (zeroLengthProfile.profile(length == 0)) {
-            result = new byte[0];
-        } else {
-            na.enable(vector);
-            result = new byte[length];
-            for (int i = 0; i < length; i++) {
-                RComplex value = vector.getDataAt(i);
-                result[i] = na.check(value) ? RRuntime.LOGICAL_NA : not(value);
-            }
-        }
-        RLogicalVector resultVector = RDataFactory.createLogicalVector(result, na.neverSeenNA());
-        copyNamesDimsDimNames(vector, resultVector);
-        return resultVector;
+        result.setComplete(vectorAccess.na.neverSeenNA());
+        return result;
     }
 
+    @Specialization(replaces = "doLogicalVectorCached")
     @TruffleBoundary
-    private void copyNamesDimsDimNames(RAbstractVector vector, RLogicalVector resultVector) {
-        resultVector.copyNamesDimsDimNamesFrom(vector, this);
-    }
-
-    @Specialization
-    protected RRawVector doRawVector(RRawVector vector) {
-        int length = vector.getLength();
-        byte[] result;
-        if (zeroLengthProfile.profile(length == 0)) {
-            result = new byte[0];
-        } else {
-            result = new byte[length];
-            for (int i = 0; i < length; i++) {
-                result[i] = notRaw(vector.getRawDataAt(i));
+    protected RAbstractVector doLogicalGenericGeneric(RAbstractLogicalVector vector,
+                    @Cached("createTemporaryGeneric()") VectorReuse reuse) {
+        return doLogicalVectorCached(vector, vector.slowPathAccess(), reuse);
+    }
+
+    @Specialization(guards = {"vectorAccess.supports(vector)", "!isRAbstractLogicalVector(vector)"})
+    protected RAbstractVector doVectorCached(RAbstractVector vector,
+                    @Cached("vector.access()") VectorAccess vectorAccess,
+                    @Cached("createNew(Logical)") VectorAccess resultAccess,
+                    @Cached("createNew(Raw)") VectorAccess rawResultAccess,
+                    @Cached("create()") VectorFactory factory) {
+        try (SequentialIterator vectorIter = vectorAccess.access(vector)) {
+            int length = vectorAccess.getLength(vectorIter);
+            RAbstractVector result;
+            switch (vectorAccess.getType()) {
+                case Character:
+                case List:
+                case Expression:
+                    // special cases:
+                    if (length == 0) {
+                        return factory.createEmptyLogicalVector();
+                    } else {
+                        throw error(RError.Message.INVALID_ARG_TYPE);
+                    }
+                case Raw:
+                    result = factory.createRawVector(length);
+                    try (SequentialIterator resultIter = rawResultAccess.access(result)) {
+                        // raw does not produce a logical result, but (255 - value)
+                        while (vectorAccess.next(vectorIter) && rawResultAccess.next(resultIter)) {
+                            rawResultAccess.setRaw(resultIter, notRaw(vectorAccess.getRaw(vectorIter)));
+                        }
+                    }
+                    ((RVector<?>) result).copyAttributesFrom(vector);
+                    break;
+                default:
+                    result = factory.createLogicalVector(length, false);
+                    try (SequentialIterator resultIter = resultAccess.access(result)) {
+                        while (vectorAccess.next(vectorIter) && resultAccess.next(resultIter)) {
+                            byte value = vectorAccess.getLogical(vectorIter);
+                            resultAccess.setLogical(resultIter, vectorAccess.na.check(value) ? RRuntime.LOGICAL_NA : not(value));
+                        }
+                    }
+                    if (vectorAccess.getType() == RType.Logical) {
+                        ((RVector<?>) result).copyAttributesFrom(vector);
+                    } else {
+                        factory.reinitializeAttributes((RVector<?>) result, getDims.getDimensions(vector), getNames.getNames(vector), getDimNames.getDimNames(vector));
+                    }
+                    break;
             }
+            result.setComplete(vectorAccess.na.neverSeenNA());
+            return result;
         }
-        RRawVector resultVector = RDataFactory.createRawVector(result);
-        resultVector.copyAttributesFrom(vector);
-        return resultVector;
     }
 
-    @Specialization(guards = {"vector.getLength() == 0"})
-    protected RLogicalVector doStringVector(@SuppressWarnings("unused") RAbstractStringVector vector) {
-        return RDataFactory.createEmptyLogicalVector();
-    }
-
-    @Specialization(guards = {"list.getLength() == 0"})
-    protected RLogicalVector doList(@SuppressWarnings("unused") RList list) {
-        return RDataFactory.createEmptyLogicalVector();
+    @Specialization(replaces = "doVectorCached", guards = "!isRAbstractLogicalVector(vector)")
+    @TruffleBoundary
+    protected RAbstractVector doGenericGeneric(RAbstractVector vector,
+                    @Cached("create()") VectorFactory factory) {
+        return doVectorCached(vector, vector.slowPathAccess(), VectorAccess.createSlowPathNew(RType.Logical), VectorAccess.createSlowPathNew(RType.Raw), factory);
     }
 
     @Specialization(guards = {"isForeignObject(obj)"})