diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/grid/GridFunctions.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/grid/GridFunctions.java
index 78338d6c6cc4335ed97664ba2bcbe401287e6105..71cf528a84ff3e9f6eb91a48a91ac1b8319701e2 100644
--- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/grid/GridFunctions.java
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/grid/GridFunctions.java
@@ -23,25 +23,31 @@ import com.oracle.truffle.r.runtime.data.RDataFactory;
 import com.oracle.truffle.r.runtime.data.RIntVector;
 import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector;
 import com.oracle.truffle.r.runtime.env.REnvironment;
+import com.oracle.truffle.r.runtime.ffi.GridRFFI;
 import com.oracle.truffle.r.runtime.ffi.RFFIFactory;
 
 /**
  * The .Call support for the grid package.
  */
 public class GridFunctions {
+
     public abstract static class InitGrid extends RExternalBuiltinNode.Arg1 {
+        @Child GridRFFI.GridRFFINode gridRFFINode = RFFIFactory.getRFFI().getGridRFFI().gridRFFINode();
+
         @Specialization
         @TruffleBoundary
         protected Object initGrid(REnvironment gridEvalEnv) {
-            return RFFIFactory.getRFFI().getGridRFFI().initGrid(gridEvalEnv);
+            return gridRFFINode.initGrid(gridEvalEnv);
         }
     }
 
     public static final class KillGrid extends RExternalBuiltinNode {
+        @Child GridRFFI.GridRFFINode gridRFFINode = RFFIFactory.getRFFI().getGridRFFI().gridRFFINode();
+
         @Override
         @TruffleBoundary
         public Object call(RArgsValuesAndNames args) {
-            return RFFIFactory.getRFFI().getGridRFFI().killGrid();
+            return gridRFFINode.killGrid();
         }
     }
 
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/tools/C_ParseRd.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/tools/C_ParseRd.java
index e793668a8a24f91d57eb57e120592cb04b223961..6e4b0c668a53fb90eb00a80bd21e0cb9716a9acd 100644
--- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/tools/C_ParseRd.java
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/tools/C_ParseRd.java
@@ -37,8 +37,10 @@ import com.oracle.truffle.r.runtime.data.RDataFactory;
 import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector;
 import com.oracle.truffle.r.runtime.env.REnvironment;
 import com.oracle.truffle.r.runtime.ffi.RFFIFactory;
+import com.oracle.truffle.r.runtime.ffi.ToolsRFFI;
 
 public abstract class C_ParseRd extends RExternalBuiltinNode.Arg9 {
+    @Child ToolsRFFI.ToolsRFFINode toolsRFFINode = RFFIFactory.getRFFI().getToolsRFFI().toolsRFFINode();
 
     @Override
     protected void createCasts(CastBuilder casts) {
@@ -64,7 +66,7 @@ public abstract class C_ParseRd extends RExternalBuiltinNode.Arg9 {
 
         try (RConnection openConn = RConnection.fromIndex(con).forceOpen("r")) {
             // @formatter:off
-            return RFFIFactory.getRFFI().getToolsRFFI().parseRd(openConn, srcfile,
+            return toolsRFFINode.parseRd(openConn, srcfile,
                             RDataFactory.createLogicalVectorFromScalar(verboseL),
                             RDataFactory.createLogicalVectorFromScalar(fragmentL),
                             RDataFactory.createStringVectorFromScalar(basename.getDataAt(0)),
diff --git a/com.oracle.truffle.r.native/fficall/src/jni/userrng_rffi.c b/com.oracle.truffle.r.native/fficall/src/jni/userrng_rffi.c
index a946f23f8b1f9fbcc72322e199a1103afe3649a8..fd510c9e56eaa4985c9067a574bf6a2b092415ed 100644
--- a/com.oracle.truffle.r.native/fficall/src/jni/userrng_rffi.c
+++ b/com.oracle.truffle.r.native/fficall/src/jni/userrng_rffi.c
@@ -29,27 +29,27 @@ typedef int* (*call_nSeed)(void);
 typedef int* (*call_seeds)(void);
 
 JNIEXPORT void JNICALL
-Java_com_oracle_truffle_r_runtime_ffi_jni_JNI_1UserRng_init(JNIEnv *env, jclass c, jlong address, jint seed) {
+Java_com_oracle_truffle_r_runtime_ffi_jni_JNI_1UserRng_nativeInit(JNIEnv *env, jclass c, jlong address, jint seed) {
 	call_init f = (call_init) address;
 	f(seed);
 }
 
 JNIEXPORT double JNICALL
-Java_com_oracle_truffle_r_runtime_ffi_jni_JNI_1UserRng_rand(JNIEnv *env, jclass c, jlong address) {
+Java_com_oracle_truffle_r_runtime_ffi_jni_JNI_1UserRng_nativeRand(JNIEnv *env, jclass c, jlong address) {
 	call_rand f = (call_rand) address;
 	double* dp = f();
 	return *dp;
 }
 
 JNIEXPORT jint JNICALL
-Java_com_oracle_truffle_r_runtime_ffi_jni_JNI_1UserRng_nSeed(JNIEnv *env, jclass c, jlong address) {
+Java_com_oracle_truffle_r_runtime_ffi_jni_JNI_1UserRng_nativeNSeed(JNIEnv *env, jclass c, jlong address) {
 	call_nSeed f = (call_nSeed) address;
 	int *pn = f();
 	return *pn;
 }
 
 JNIEXPORT void JNICALL
-Java_com_oracle_truffle_r_runtime_ffi_jni_JNI_1UserRng_seeds(JNIEnv *env, jclass c, jlong address, jintArray seedsArray) {
+Java_com_oracle_truffle_r_runtime_ffi_jni_JNI_1UserRng_nativeSeeds(JNIEnv *env, jclass c, jlong address, jintArray seedsArray) {
 	call_seeds f = (call_seeds) address;
 	int *pseeds = f();
 	int seedslen = (*env)->GetArrayLength(env, seedsArray);
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/BasePackage.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/BasePackage.java
index d3871d27be24f6105aa6adb51ee81c45046fd98b..d194d58219e4d6e254e1bcf4bd625683cae1121f 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/BasePackage.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/BasePackage.java
@@ -42,10 +42,10 @@ import com.oracle.truffle.r.nodes.builtin.base.fastpaths.MatrixFastPathNodeGen;
 import com.oracle.truffle.r.nodes.builtin.base.fastpaths.SetDiffFastPathNodeGen;
 import com.oracle.truffle.r.nodes.builtin.base.fastpaths.VectorFastPathsFactory.DoubleFastPathNodeGen;
 import com.oracle.truffle.r.nodes.builtin.base.fastpaths.VectorFastPathsFactory.IntegerFastPathNodeGen;
-import com.oracle.truffle.r.nodes.builtin.base.foreign.DotC;
-import com.oracle.truffle.r.nodes.builtin.base.foreign.DotCNodeGen;
-import com.oracle.truffle.r.nodes.builtin.base.foreign.ForeignFunctions;
-import com.oracle.truffle.r.nodes.builtin.base.foreign.ForeignFunctionsFactory;
+import com.oracle.truffle.r.nodes.builtin.base.foreign.CallAndExternalFunctions;
+import com.oracle.truffle.r.nodes.builtin.base.foreign.CallAndExternalFunctionsFactory;
+import com.oracle.truffle.r.nodes.builtin.base.foreign.FortranAndCFunctions;
+import com.oracle.truffle.r.nodes.builtin.base.foreign.FortranAndCFunctionsFactory;
 import com.oracle.truffle.r.nodes.builtin.base.infix.AccessField;
 import com.oracle.truffle.r.nodes.builtin.base.infix.AccessFieldNodeGen;
 import com.oracle.truffle.r.nodes.builtin.base.infix.AssignBuiltin;
@@ -217,6 +217,11 @@ public class BasePackage extends RBuiltinPackage {
         add(BrowserFunctions.BrowserSetDebug.class, BrowserFunctionsFactory.BrowserSetDebugNodeGen::create);
         add(BrowserFunctions.BrowserText.class, BrowserFunctionsFactory.BrowserTextNodeGen::create);
         add(Call.class, CallNodeGen::create);
+        add(CallAndExternalFunctions.DotCall.class, CallAndExternalFunctionsFactory.DotCallNodeGen::create);
+        add(CallAndExternalFunctions.DotCallGraphics.class, CallAndExternalFunctionsFactory.DotCallGraphicsNodeGen::create);
+        add(CallAndExternalFunctions.DotExternal.class, CallAndExternalFunctionsFactory.DotExternalNodeGen::create);
+        add(CallAndExternalFunctions.DotExternal2.class, CallAndExternalFunctionsFactory.DotExternal2NodeGen::create);
+        add(CallAndExternalFunctions.DotExternalGraphics.class, CallAndExternalFunctionsFactory.DotExternalGraphicsNodeGen::create);
         add(Capabilities.class, CapabilitiesNodeGen::create);
         add(Cat.class, CatNodeGen::create);
         add(Ceiling.class, CeilingNodeGen::create);
@@ -387,17 +392,12 @@ public class BasePackage extends RBuiltinPackage {
         add(FileFunctions.ListDirs.class, FileFunctionsFactory.ListDirsNodeGen::create);
         add(FileFunctions.Unlink.class, FileFunctionsFactory.UnlinkNodeGen::create);
         add(Floor.class, FloorNodeGen::create);
-        add(DotC.class, DotCNodeGen::create);
         add(ForceAndCall.class, ForceAndCallNodeGen::create);
-        add(ForeignFunctions.DotCall.class, ForeignFunctionsFactory.DotCallNodeGen::create);
-        add(ForeignFunctions.DotCallGraphics.class, ForeignFunctionsFactory.DotCallGraphicsNodeGen::create);
-        add(ForeignFunctions.DotExternal.class, ForeignFunctionsFactory.DotExternalNodeGen::create);
-        add(ForeignFunctions.DotExternal2.class, ForeignFunctionsFactory.DotExternal2NodeGen::create);
-        add(ForeignFunctions.DotExternalGraphics.class, ForeignFunctionsFactory.DotExternalGraphicsNodeGen::create);
-        add(ForeignFunctions.Fortran.class, ForeignFunctionsFactory.FortranNodeGen::create);
         add(Formals.class, FormalsNodeGen::create);
         add(Format.class, FormatNodeGen::create);
         add(FormatC.class, FormatCNodeGen::create);
+        add(FortranAndCFunctions.DotC.class, FortranAndCFunctionsFactory.DotCNodeGen::create);
+        add(FortranAndCFunctions.Fortran.class, FortranAndCFunctionsFactory.FortranNodeGen::create);
         add(FrameFunctions.MatchCall.class, FrameFunctionsFactory.MatchCallNodeGen::create);
         add(FrameFunctions.ParentFrame.class, FrameFunctionsFactory.ParentFrameNodeGen::create);
         add(FrameFunctions.SysCall.class, FrameFunctionsFactory.SysCallNodeGen::create);
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/GrepFunctions.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/GrepFunctions.java
index fe7ce64eb662db796aae3d05ae6981247b57ea65..06999f6a158957e5ea50770f8477daea0db39bec 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/GrepFunctions.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/GrepFunctions.java
@@ -63,6 +63,7 @@ import com.oracle.truffle.r.runtime.ops.na.NACheck;
  */
 public class GrepFunctions {
     public abstract static class CommonCodeAdapter extends RBuiltinNode {
+        @Child protected PCRERFFI.PCRERFFINode pcreRFFINode = RFFIFactory.getRFFI().getPCRERFFI().pcreRFFINode();
 
         /**
          * This profile is needed to satisfy API requirements.
@@ -205,8 +206,8 @@ public class GrepFunctions {
 
         protected PCRERFFI.Result compilePerlPattern(String pattern, boolean ignoreCase) {
             int cflags = ignoreCase ? PCRERFFI.CASELESS : 0;
-            long tables = RFFIFactory.getRFFI().getPCRERFFI().maketables();
-            PCRERFFI.Result pcre = RFFIFactory.getRFFI().getPCRERFFI().compile(pattern, cflags, tables);
+            long tables = pcreRFFINode.maketables();
+            PCRERFFI.Result pcre = pcreRFFINode.compile(pattern, cflags, tables);
             if (pcre.result == 0) {
                 // TODO output warning if pcre.errorMessage not NULL
                 throw RError.error(this, RError.Message.INVALID_REGEXP, pattern);
@@ -246,7 +247,7 @@ public class GrepFunctions {
                 for (int i = 0; i < len; i++) {
                     String text = vector.getDataAt(i);
                     if (!RRuntime.isNA(text)) {
-                        if (RFFIFactory.getRFFI().getPCRERFFI().exec(pcre.result, 0, text, 0, 0, ovector) >= 0) {
+                        if (pcreRFFINode.exec(pcre.result, 0, text, 0, 0, ovector) >= 0) {
                             matches[i] = true;
                         }
                     }
@@ -433,7 +434,7 @@ public class GrepFunctions {
                         int eflag = 0;
                         int lastEnd = -1;
                         StringBuffer sb = new StringBuffer();
-                        while (RFFIFactory.getRFFI().getPCRERFFI().exec(pcre.result, 0, input, offset, eflag, ovector) >= 0) {
+                        while (pcreRFFINode.exec(pcre.result, 0, input, offset, eflag, ovector) >= 0) {
                             nmatch++;
                             for (int j = offset; j < ovector[0]; j++) {
                                 sb.append(input.charAt(j));
@@ -789,13 +790,13 @@ public class GrepFunctions {
                 }
             } else if (perl) {
                 PCRERFFI.Result pcre = compilePerlPattern(pattern, ignoreCase);
-                int maxCaptureCount = RFFIFactory.getRFFI().getPCRERFFI().getCaptureCount(pcre.result, 0);
+                int maxCaptureCount = pcreRFFINode.getCaptureCount(pcre.result, 0);
                 int[] ovector = new int[(maxCaptureCount + 1) * 3];
                 int offset = 0;
                 while (true) {
-                    int captureCount = RFFIFactory.getRFFI().getPCRERFFI().exec(pcre.result, 0, text, offset, 0, ovector);
+                    int captureCount = pcreRFFINode.exec(pcre.result, 0, text, offset, 0, ovector);
                     if (captureCount >= 0) {
-                        String[] captureNames = RFFIFactory.getRFFI().getPCRERFFI().getCaptureNames(pcre.result, 0, maxCaptureCount);
+                        String[] captureNames = pcreRFFINode.getCaptureNames(pcre.result, 0, maxCaptureCount);
                         for (int i = 0; i < captureNames.length; i++) {
                             if (captureNames[i] == null) {
                                 captureNames[i] = "";
@@ -1152,7 +1153,7 @@ public class GrepFunctions {
             // treat split = NULL as split = ""
             RAbstractStringVector split = splitArg.getLength() == 0 ? RDataFactory.createStringVectorFromScalar("") : splitArg;
             String[] splits = new String[split.getLength()];
-            long pcreTables = perl ? RFFIFactory.getRFFI().getPCRERFFI().maketables() : 0;
+            long pcreTables = perl ? pcreRFFINode.maketables() : 0;
             PCRERFFI.Result[] pcreSplits = perl ? new PCRERFFI.Result[splits.length] : null;
 
             na.enable(x);
@@ -1161,7 +1162,7 @@ public class GrepFunctions {
                 splits[i] = fixed || perl ? split.getDataAt(i) : RegExp.checkPreDefinedClasses(split.getDataAt(i));
                 if (perl) {
                     if (!currentSplit.isEmpty()) {
-                        pcreSplits[i] = RFFIFactory.getRFFI().getPCRERFFI().compile(currentSplit, 0, pcreTables);
+                        pcreSplits[i] = pcreRFFINode.compile(currentSplit, 0, pcreTables);
                         if (pcreSplits[i].result == 0) {
                             // TODO output warning if pcre.errorMessage not NULL
                             throw RError.error(this, RError.Message.INVALID_REGEXP, currentSplit);
@@ -1231,14 +1232,14 @@ public class GrepFunctions {
             return RDataFactory.createStringVector(result, true);
         }
 
-        private static RStringVector splitPerl(String data, PCRERFFI.Result pcre) {
+        private RStringVector splitPerl(String data, PCRERFFI.Result pcre) {
             ArrayList<String> matches = new ArrayList<>();
             int lastEndOffset = 0;
             int lastEndIndex = 0;
             int[] ovector = new int[30];
             int[] fromByteMapping = getFromByteMapping(data); // non-null if it's necessary
 
-            while (RFFIFactory.getRFFI().getPCRERFFI().exec(pcre.result, 0, data, lastEndOffset, 0, ovector) >= 0) {
+            while (pcreRFFINode.exec(pcre.result, 0, data, lastEndOffset, 0, ovector) >= 0) {
                 // offset == byte position
                 // index == character position
                 int startOffset = ovector[0];
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/LaFunctions.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/LaFunctions.java
index 2e1b049a8896038a9cfd8cd5c5286209590fed69..bd82f21a35e201aa79282e19a78421de2cdd864c 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/LaFunctions.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/LaFunctions.java
@@ -49,6 +49,7 @@ import com.oracle.truffle.r.runtime.data.RStringVector;
 import com.oracle.truffle.r.runtime.data.RVector;
 import com.oracle.truffle.r.runtime.data.model.RAbstractDoubleVector;
 import com.oracle.truffle.r.runtime.data.model.RAbstractVector;
+import com.oracle.truffle.r.runtime.ffi.LapackRFFI;
 import com.oracle.truffle.r.runtime.ffi.RFFIFactory;
 import com.oracle.truffle.r.runtime.ops.na.NACheck;
 
@@ -61,18 +62,22 @@ import com.oracle.truffle.r.runtime.ops.na.NACheck;
  */
 public class LaFunctions {
 
+    protected abstract static class Adapter extends RBuiltinNode {
+        @Child protected LapackRFFI.LapackRFFINode lapackRFFINode = RFFIFactory.getRFFI().getLapackRFFI().getLapackRFFINode();
+    }
+
     @RBuiltin(name = "La_version", kind = INTERNAL, parameterNames = {}, behavior = READS_STATE)
-    public abstract static class Version extends RBuiltinNode {
+    public abstract static class Version extends Adapter {
         @Specialization
         @TruffleBoundary
         protected String doVersion() {
             int[] version = new int[3];
-            RFFIFactory.getRFFI().getLapackRFFI().ilaver(version);
+            lapackRFFINode.ilaver(version);
             return version[0] + "." + version[1] + "." + version[2];
         }
     }
 
-    private abstract static class RsgAdapter extends RBuiltinNode {
+    private abstract static class RsgAdapter extends Adapter {
         protected static final String[] NAMES = new String[]{"values", "vectors"};
         protected final BranchProfile errorProfile = BranchProfile.create();
 
@@ -108,7 +113,7 @@ public class LaFunctions {
             double[] wi = new double[n];
             double[] work = new double[1];
             // ask for optimal size of work array
-            int info = RFFIFactory.getRFFI().getLapackRFFI().dgeev(jobVL, jobVR, n, a, n, wr, wi, left, n, right, n, work, -1);
+            int info = lapackRFFINode.dgeev(jobVL, jobVR, n, a, n, wr, wi, left, n, right, n, work, -1);
             if (info != 0) {
                 errorProfile.enter();
                 throw RError.error(this, RError.Message.LAPACK_ERROR, info, "dgeev");
@@ -116,7 +121,7 @@ public class LaFunctions {
             // now allocate work array and make the actual call
             int lwork = (int) work[0];
             work = new double[lwork];
-            info = RFFIFactory.getRFFI().getLapackRFFI().dgeev(jobVL, jobVR, n, a, n, wr, wi, left, n, right, n, work, lwork);
+            info = lapackRFFINode.dgeev(jobVL, jobVR, n, a, n, wr, wi, left, n, right, n, work, lwork);
             if (info != 0) {
                 errorProfile.enter();
                 throw RError.error(this, RError.Message.LAPACK_ERROR, info, "dgeev");
@@ -209,7 +214,7 @@ public class LaFunctions {
             int[] isuppz = new int[2 * n];
             double[] work = new double[1];
             int[] iwork = new int[1];
-            int info = RFFIFactory.getRFFI().getLapackRFFI().dsyevr(jobv, range, uplo, n, x, n, vl, vu, il, iu, abstol, m, values, z, n, isuppz, work, lwork, iwork, liwork);
+            int info = lapackRFFINode.dsyevr(jobv, range, uplo, n, x, n, vl, vu, il, iu, abstol, m, values, z, n, isuppz, work, lwork, iwork, liwork);
             if (info != 0) {
                 errorProfile.enter();
                 throw RError.error(this, RError.Message.LAPACK_ERROR, info, "dysevr");
@@ -218,7 +223,7 @@ public class LaFunctions {
             liwork = iwork[0];
             work = new double[lwork];
             iwork = new int[liwork];
-            info = RFFIFactory.getRFFI().getLapackRFFI().dsyevr(jobv, range, uplo, n, x, n, vl, vu, il, iu, abstol, m, values, z, n, isuppz, work, lwork, iwork, liwork);
+            info = lapackRFFINode.dsyevr(jobv, range, uplo, n, x, n, vl, vu, il, iu, abstol, m, values, z, n, isuppz, work, lwork, iwork, liwork);
             if (info != 0) {
                 errorProfile.enter();
                 throw RError.error(this, RError.Message.LAPACK_ERROR, info, "dysevr");
@@ -239,7 +244,7 @@ public class LaFunctions {
     }
 
     @RBuiltin(name = "La_qr", kind = INTERNAL, parameterNames = {"in"}, behavior = PURE)
-    public abstract static class Qr extends RBuiltinNode {
+    public abstract static class Qr extends Adapter {
 
         @CompilationFinal private static final String[] NAMES = new String[]{"qr", "rank", "qraux", "pivot"};
 
@@ -266,14 +271,14 @@ public class LaFunctions {
             double[] tau = new double[m < n ? m : n];
             double[] work = new double[1];
             // ask for optimal size of work array
-            int info = RFFIFactory.getRFFI().getLapackRFFI().dgeqp3(m, n, a, m, jpvt, tau, work, -1);
+            int info = lapackRFFINode.dgeqp3(m, n, a, m, jpvt, tau, work, -1);
             if (info < 0) {
                 errorProfile.enter();
                 throw RError.error(this, RError.Message.LAPACK_ERROR, info, "dgeqp3");
             }
             int lwork = (int) work[0];
             work = new double[lwork];
-            info = RFFIFactory.getRFFI().getLapackRFFI().dgeqp3(m, n, a, m, jpvt, tau, work, lwork);
+            info = lapackRFFINode.dgeqp3(m, n, a, m, jpvt, tau, work, lwork);
             if (info < 0) {
                 errorProfile.enter();
                 throw RError.error(this, RError.Message.LAPACK_ERROR, info, "dgeqp3");
@@ -292,7 +297,7 @@ public class LaFunctions {
     }
 
     @RBuiltin(name = "qr_coef_real", kind = INTERNAL, parameterNames = {"q", "b"}, behavior = PURE)
-    public abstract static class QrCoefReal extends RBuiltinNode {
+    public abstract static class QrCoefReal extends Adapter {
 
         private final BranchProfile errorProfile = BranchProfile.create();
 
@@ -330,19 +335,19 @@ public class LaFunctions {
             // we work directly in the internal data of b
             double[] bData = b.getDataWithoutCopying();
             // ask for optimal size of work array
-            int info = RFFIFactory.getRFFI().getLapackRFFI().dormqr(SIDE, TRANS, n, nrhs, k, qrData, n, tauData, bData, n, work, -1);
+            int info = lapackRFFINode.dormqr(SIDE, TRANS, n, nrhs, k, qrData, n, tauData, bData, n, work, -1);
             if (info < 0) {
                 errorProfile.enter();
                 throw RError.error(this, RError.Message.LAPACK_ERROR, info, "dormqr");
             }
             int lwork = (int) work[0];
             work = new double[lwork];
-            info = RFFIFactory.getRFFI().getLapackRFFI().dormqr(SIDE, TRANS, n, nrhs, k, qrData, n, tauData, bData, n, work, lwork);
+            info = lapackRFFINode.dormqr(SIDE, TRANS, n, nrhs, k, qrData, n, tauData, bData, n, work, lwork);
             if (info < 0) {
                 errorProfile.enter();
                 throw RError.error(this, RError.Message.LAPACK_ERROR, info, "dormqr");
             }
-            info = RFFIFactory.getRFFI().getLapackRFFI().dtrtrs('U', 'N', 'N', k, nrhs, qrData, n, bData, n);
+            info = lapackRFFINode.dtrtrs('U', 'N', 'N', k, nrhs, qrData, n, bData, n);
             if (info < 0) {
                 errorProfile.enter();
                 throw RError.error(this, RError.Message.LAPACK_ERROR, info, "dtrtrs");
@@ -353,7 +358,7 @@ public class LaFunctions {
     }
 
     @RBuiltin(name = "det_ge_real", kind = INTERNAL, parameterNames = {"a", "uselog"}, behavior = PURE)
-    public abstract static class DetGeReal extends RBuiltinNode {
+    public abstract static class DetGeReal extends Adapter {
 
         private static final RStringVector NAMES_VECTOR = RDataFactory.createStringVector(new String[]{"modulus", "sign"}, RDataFactory.COMPLETE_VECTOR);
         private static final RStringVector DET_CLASS = RDataFactory.createStringVector(new String[]{"det"}, RDataFactory.COMPLETE_VECTOR);
@@ -386,7 +391,7 @@ public class LaFunctions {
             int[] ipiv = new int[n];
             double modulus = 0;
             double[] aData = a.getDataWithoutCopying();
-            int info = RFFIFactory.getRFFI().getLapackRFFI().dgetrf(n, n, aData, n, ipiv);
+            int info = lapackRFFINode.dgetrf(n, n, aData, n, ipiv);
             int sign = 1;
             if (info < 0) {
                 errorProfile.enter();
@@ -440,7 +445,7 @@ public class LaFunctions {
     }
 
     @RBuiltin(name = "La_chol", kind = INTERNAL, parameterNames = {"a", "pivot", "tol"}, behavior = PURE)
-    public abstract static class LaChol extends RBuiltinNode {
+    public abstract static class LaChol extends Adapter {
 
         private final BranchProfile errorProfile = BranchProfile.create();
         private final ConditionProfile noPivot = ConditionProfile.createBinaryProfile();
@@ -478,7 +483,7 @@ public class LaFunctions {
             }
             int info;
             if (noPivot.profile(!piv)) {
-                info = RFFIFactory.getRFFI().getLapackRFFI().dpotrf('U', m, aData, m);
+                info = lapackRFFINode.dpotrf('U', m, aData, m);
                 if (info != 0) {
                     errorProfile.enter();
                     // TODO informative error message (aka GnuR)
@@ -488,7 +493,7 @@ public class LaFunctions {
                 int[] ipiv = new int[m];
                 double[] work = new double[2 * m];
                 int[] rank = new int[1];
-                info = RFFIFactory.getRFFI().getLapackRFFI().dpstrf('U', n, aData, n, ipiv, rank, tol, work);
+                info = lapackRFFINode.dpstrf('U', n, aData, n, ipiv, rank, tol, work);
                 if (info != 0) {
                     errorProfile.enter();
                     // TODO informative error message (aka GnuR)
@@ -511,7 +516,7 @@ public class LaFunctions {
     }
 
     @RBuiltin(name = "La_solve", kind = INTERNAL, parameterNames = {"a", "bin", "tolin"}, behavior = PURE)
-    public abstract static class LaSolve extends RBuiltinNode {
+    public abstract static class LaSolve extends Adapter {
         protected final RAttributeProfiles attrProfiles = RAttributeProfiles.create();
         @Child private CastDoubleNode castDouble = CastDoubleNodeGen.create(false, false, false);
 
@@ -601,7 +606,7 @@ public class LaFunctions {
                 assert aDouble != a;
                 avals = aDouble.getInternalStore();
             }
-            int info = RFFIFactory.getRFFI().getLapackRFFI().dgesv(n, p, avals, n, ipiv, bData, n);
+            int info = lapackRFFINode.dgesv(n, p, avals, n, ipiv, bData, n);
             if (info < 0) {
                 RError.error(this, RError.Message.LAPACK_INVALID_VALUE, -info, "dgesv");
             }
@@ -609,10 +614,10 @@ public class LaFunctions {
                 RError.error(this, RError.Message.LAPACK_EXACTLY_SINGULAR, "dgesv", info, info);
             }
             if (tol > 0) {
-                double anorm = RFFIFactory.getRFFI().getLapackRFFI().dlange('1', n, n, avals, n, null);
+                double anorm = lapackRFFINode.dlange('1', n, n, avals, n, null);
                 double[] work = new double[4 * n];
                 double[] rcond = new double[1];
-                RFFIFactory.getRFFI().getLapackRFFI().dgecon('1', n, avals, n, anorm, rcond, work, ipiv);
+                lapackRFFINode.dgecon('1', n, avals, n, anorm, rcond, work, ipiv);
                 if (rcond[0] < tol) {
                     RError.error(this, RError.Message.SYSTEM_COMP_SINGULAR, rcond[0]);
                 }
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/ForeignFunctions.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/CallAndExternalFunctions.java
similarity index 80%
rename from com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/ForeignFunctions.java
rename to com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/CallAndExternalFunctions.java
index 652e1209f88aff67fca0de71f4c8e69e66e6581f..5f2c06c4cbdaec3ab2605da7fa6f4a560a6bd692 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/ForeignFunctions.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/CallAndExternalFunctions.java
@@ -15,7 +15,6 @@ import static com.oracle.truffle.r.runtime.RVisibility.CUSTOM;
 import static com.oracle.truffle.r.runtime.builtins.RBehavior.COMPLEX;
 import static com.oracle.truffle.r.runtime.builtins.RBuiltinKind.PRIMITIVE;
 
-import com.oracle.truffle.api.CompilerAsserts;
 import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
 import com.oracle.truffle.api.dsl.Cached;
 import com.oracle.truffle.api.dsl.Fallback;
@@ -91,7 +90,6 @@ import com.oracle.truffle.r.library.utils.ObjectSizeNodeGen;
 import com.oracle.truffle.r.library.utils.RprofNodeGen;
 import com.oracle.truffle.r.library.utils.RprofmemNodeGen;
 import com.oracle.truffle.r.library.utils.TypeConvertNodeGen;
-import com.oracle.truffle.r.nodes.builtin.RBuiltinNode;
 import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode;
 import com.oracle.truffle.r.nodes.builtin.RInternalCodeBuiltinNode;
 import com.oracle.truffle.r.nodes.objects.GetPrimNameNodeGen;
@@ -99,8 +97,6 @@ import com.oracle.truffle.r.nodes.objects.NewObjectNodeGen;
 import com.oracle.truffle.r.runtime.FastROptions;
 import com.oracle.truffle.r.runtime.RError;
 import com.oracle.truffle.r.runtime.RInternalCode;
-import com.oracle.truffle.r.runtime.RInternalError;
-import com.oracle.truffle.r.runtime.RRuntime;
 import com.oracle.truffle.r.runtime.builtins.RBuiltin;
 import com.oracle.truffle.r.runtime.context.RContext;
 import com.oracle.truffle.r.runtime.data.RArgsValuesAndNames;
@@ -108,22 +104,21 @@ import com.oracle.truffle.r.runtime.data.RDataFactory;
 import com.oracle.truffle.r.runtime.data.RList;
 import com.oracle.truffle.r.runtime.data.RMissing;
 import com.oracle.truffle.r.runtime.data.RNull;
-import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector;
 import com.oracle.truffle.r.runtime.ffi.CallRFFI;
 import com.oracle.truffle.r.runtime.ffi.DLL;
-import com.oracle.truffle.r.runtime.ffi.RFFIFactory;
 import com.oracle.truffle.r.runtime.ffi.NativeCallInfo;
+import com.oracle.truffle.r.runtime.ffi.RFFIFactory;
 
 /**
- * {@code .Call} {@code .Fortran}, {@code .External}, {@code .External2}, {@code External.graphics}
- * functions.
+ * {@code .Call}, {@code .Call.graphics}, {@code .External}, {@code .External2},
+ * {@code External.graphics} functions, which share a common signature.
  *
  * TODO Completeness (more types, more error checks), Performance (copying). Especially all the
  * subtleties around copying.
  *
  * See <a href="https://stat.ethz.ch/R-manual/R-devel/library/base/html/Foreign.html">here</a>.
  */
-public class ForeignFunctions {
+public class CallAndExternalFunctions {
 
     @TruffleBoundary
     protected static Object encodeArgumentPairList(RArgsValuesAndNames args, String symbolName) {
@@ -136,160 +131,8 @@ public class ForeignFunctions {
         return list;
     }
 
-    /**
-     * Locator for "builtin" package function implementations. The "builtin" packages contain many
-     * functions that are called from R code via the FFI, e.g. {@code .Call}, but implemented
-     * internally in GnuR, and not necessarily following the FFI API. The value passed to
-     * {@code .Call} etc., is a symbol, created when the package is loaded and stored in the
-     * namespace environment of the package, that is a list-valued object. Evidently these
-     * "builtins" are somewhat similar to the {@code .Primitive} and {@code .Internal} builtins and,
-     * similarly, most of these are re-implemented in Java in FastR. The
-     * {@link #lookupBuiltin(RList)} method checks the name in the list object and returns the
-     * {@link RExternalBuiltinNode} that implements the function, or {@code null}. A {@code null}
-     * result implies that the builtin is not implemented in Java, but called directly via the FFI
-     * interface, which is only possible for functions that use the FFI in a way that FastR can
-     * handle.
-     */
-    protected abstract static class LookupAdapter extends RBuiltinNode {
-        @Child private ExtractNativeCallInfoNode extractSymbolInfoNode = ExtractNativeCallInfoNodeGen.create();
+    protected abstract static class CallRFFIAdapter extends LookupAdapter {
         @Child protected CallRFFI.CallRFFINode callRFFINode = RFFIFactory.getRFFI().getCallRFFI().callRFFINode();
-
-        protected static class UnimplementedExternal extends RExternalBuiltinNode {
-            private final String name;
-
-            public UnimplementedExternal(String name) {
-                this.name = name;
-            }
-
-            @Override
-            public final Object call(RArgsValuesAndNames args) {
-                throw RInternalError.unimplemented("unimplemented external builtin: " + name);
-            }
-        }
-
-        protected abstract RExternalBuiltinNode lookupBuiltin(RList symbol);
-
-        private static final String UNKNOWN_EXTERNAL_BUILTIN = "UNKNOWN_EXTERNAL_BUILTIN";
-
-        protected static String lookupName(RList symbol) {
-            CompilerAsserts.neverPartOfCompilation();
-            if (symbol.getNames() != null) {
-                RAbstractStringVector names = symbol.getNames();
-                for (int i = 0; i < names.getLength(); i++) {
-                    if (names.getDataAt(i).equals("name")) {
-                        String name = RRuntime.asString(symbol.getDataAt(i));
-                        return name != null ? name : UNKNOWN_EXTERNAL_BUILTIN;
-                    }
-                }
-            }
-            return UNKNOWN_EXTERNAL_BUILTIN;
-        }
-
-        @TruffleBoundary
-        protected RuntimeException fallback(Object symbol) {
-            String name = null;
-            if (symbol instanceof RList) {
-                name = lookupName((RList) symbol);
-                name = name == UNKNOWN_EXTERNAL_BUILTIN ? null : name;
-                if (name != null && lookupBuiltin((RList) symbol) != null) {
-                    /*
-                     * if we reach this point, then the cache saw a different value for f. the lists
-                     * that contain the information about native calls are never expected to change.
-                     */
-                    throw RInternalError.shouldNotReachHere("fallback reached for " + getRBuiltin().name() + " " + name);
-                }
-            }
-            throw RError.nyi(this, getRBuiltin().name() + " specialization failure: " + (name == null ? "<unknown>" : name));
-        }
-
-        protected NativeCallInfo extractSymbolInfo(VirtualFrame frame, RList symbol) {
-            return (NativeCallInfo) extractSymbolInfoNode.execute(frame, symbol);
-        }
-
-        protected String checkPackageArg(Object rPackage, BranchProfile errorProfile) {
-            String libName = null;
-            if (!(rPackage instanceof RMissing)) {
-                libName = RRuntime.asString(rPackage);
-                if (libName == null) {
-                    errorProfile.enter();
-                    throw RError.error(this, RError.Message.ARGUMENT_MUST_BE_STRING, "PACKAGE");
-                }
-            }
-            return libName;
-        }
-
-        protected static RExternalBuiltinNode getExternalModelBuiltinNode(String name) {
-            return new RInternalCodeBuiltinNode(RContext.getInstance(), "stats", RInternalCode.loadSourceRelativeTo(StatsUtil.class, "model.R"), name);
-        }
-
-        protected static final int CallNST = DLL.NativeSymbolType.Call.ordinal();
-        protected static final int ExternalNST = DLL.NativeSymbolType.External.ordinal();
-
-        public static DLL.RegisteredNativeSymbol createRegisteredNativeSymbol(int nstOrd) {
-            // DSL cannot resolve DLL.DLL.NativeSymbolType
-            DLL.NativeSymbolType nst = DLL.NativeSymbolType.values()[nstOrd];
-            return new DLL.RegisteredNativeSymbol(nst, null, null);
-        }
-    }
-
-    /**
-     * Interface to .Fortran native functions. Some functions have explicit implementations in
-     * FastR, otherwise the .Fortran interface uses the machinery that implements the .C interface.
-     */
-    @RBuiltin(name = ".Fortran", kind = PRIMITIVE, parameterNames = {".NAME", "...", "NAOK", "DUP", "PACKAGE", "ENCODING"}, behavior = COMPLEX)
-    public abstract static class Fortran extends LookupAdapter {
-
-        @Override
-        public Object[] getDefaultParameterValues() {
-            return new Object[]{RMissing.instance, RArgsValuesAndNames.EMPTY, RRuntime.LOGICAL_FALSE, RRuntime.LOGICAL_FALSE, RMissing.instance, RMissing.instance};
-        }
-
-        @Override
-        @TruffleBoundary
-        protected RExternalBuiltinNode lookupBuiltin(RList symbol) {
-            switch (lookupName(symbol)) {
-                case "dqrdc2":
-                    return new Dqrdc2();
-                case "dqrcf":
-                    return new Dqrcf();
-                default:
-                    return null;
-            }
-        }
-
-        @SuppressWarnings("unused")
-        @Specialization(limit = "1", guards = {"cached == symbol", "builtin != null"})
-        protected Object doExternal(VirtualFrame frame, RList symbol, RArgsValuesAndNames args, byte naok, byte dup, Object rPackage, RMissing encoding, //
-                        @Cached("symbol") RList cached, //
-                        @Cached("lookupBuiltin(symbol)") RExternalBuiltinNode builtin) {
-            return builtin.call(frame, args);
-        }
-
-        @Specialization(guards = "lookupBuiltin(symbol) == null")
-        protected RList c(VirtualFrame frame, RList symbol, RArgsValuesAndNames args, byte naok, byte dup, @SuppressWarnings("unused") Object rPackage,
-                        @SuppressWarnings("unused") RMissing encoding) {
-            NativeCallInfo nativeCallInfo = extractSymbolInfo(frame, symbol);
-            return DotC.dispatch(this, nativeCallInfo, naok, dup, args);
-        }
-
-        @Specialization
-        protected RList c(RAbstractStringVector f, RArgsValuesAndNames args, byte naok, byte dup, Object rPackage, @SuppressWarnings("unused") RMissing encoding, //
-                        @Cached("create()") BranchProfile errorProfile) {
-            String libName = checkPackageArg(rPackage, errorProfile);
-            DLL.RegisteredNativeSymbol rns = new DLL.RegisteredNativeSymbol(DLL.NativeSymbolType.Fortran, null, null);
-            DLL.SymbolHandle func = DLL.findSymbol(f.getDataAt(0), libName, rns);
-            if (func == DLL.SYMBOL_NOT_FOUND) {
-                errorProfile.enter();
-                throw RError.error(this, RError.Message.C_SYMBOL_NOT_IN_TABLE, f);
-            }
-            return DotC.dispatch(this, new NativeCallInfo(f.getDataAt(0), func, rns.getDllInfo()), naok, dup, args);
-        }
-
-        @SuppressWarnings("unused")
-        @Fallback
-        protected Object fallback(Object symbol, Object args, Object naok, Object dup, Object rPackage, Object encoding) {
-            throw fallback(symbol);
-        }
     }
 
     /**
@@ -309,7 +152,7 @@ public class ForeignFunctions {
      * could be invoked by a string but experimentally that situation has never been encountered.
      */
     @RBuiltin(name = ".Call", kind = PRIMITIVE, parameterNames = {".NAME", "...", "PACKAGE"}, behavior = COMPLEX)
-    public abstract static class DotCall extends LookupAdapter {
+    public abstract static class DotCall extends CallRFFIAdapter {
 
         private final BranchProfile errorProfile = BranchProfile.create();
 
@@ -651,7 +494,7 @@ public class ForeignFunctions {
      * {@link DotCall}.
      */
     @RBuiltin(name = ".External", kind = PRIMITIVE, parameterNames = {".NAME", "...", "PACKAGE"}, behavior = COMPLEX)
-    public abstract static class DotExternal extends LookupAdapter {
+    public abstract static class DotExternal extends CallRFFIAdapter {
 
         private final BranchProfile errorProfile = BranchProfile.create();
 
@@ -742,7 +585,7 @@ public class ForeignFunctions {
     }
 
     @RBuiltin(name = ".External2", visibility = CUSTOM, kind = PRIMITIVE, parameterNames = {".NAME", "...", "PACKAGE"}, behavior = COMPLEX)
-    public abstract static class DotExternal2 extends LookupAdapter {
+    public abstract static class DotExternal2 extends CallRFFIAdapter {
         private static final Object CALL = "call";
         private static final Object OP = "op";
         private static final Object RHO = "rho";
@@ -819,7 +662,7 @@ public class ForeignFunctions {
     }
 
     @RBuiltin(name = ".External.graphics", kind = PRIMITIVE, parameterNames = {".NAME", "...", "PACKAGE"}, behavior = COMPLEX)
-    public abstract static class DotExternalGraphics extends LookupAdapter {
+    public abstract static class DotExternalGraphics extends CallRFFIAdapter {
 
         private final BranchProfile errorProfile = BranchProfile.create();
 
@@ -876,7 +719,7 @@ public class ForeignFunctions {
     }
 
     @RBuiltin(name = ".Call.graphics", kind = PRIMITIVE, parameterNames = {".NAME", "...", "PACKAGE"}, behavior = COMPLEX)
-    public abstract static class DotCallGraphics extends LookupAdapter {
+    public abstract static class DotCallGraphics extends CallRFFIAdapter {
 
         private final BranchProfile errorProfile = BranchProfile.create();
 
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/DotC.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/DotC.java
deleted file mode 100644
index 8009ea798359a524bc8b59bd25bdd0408fbe1d0e..0000000000000000000000000000000000000000
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/DotC.java
+++ /dev/null
@@ -1,215 +0,0 @@
-/*
- * This material is distributed under the GNU General Public License
- * Version 2. You may review the terms of this license at
- * http://www.gnu.org/licenses/gpl-2.0.html
- *
- * Copyright (c) 1995-2012, The R Core Team
- * Copyright (c) 2003, The R Foundation
- * Copyright (c) 2015, 2016, Oracle and/or its affiliates
- *
- * All rights reserved.
- */
-package com.oracle.truffle.r.nodes.builtin.base.foreign;
-
-import static com.oracle.truffle.r.runtime.builtins.RBehavior.COMPLEX;
-import static com.oracle.truffle.r.runtime.builtins.RBuiltinKind.PRIMITIVE;
-
-import com.oracle.truffle.api.CompilerAsserts;
-import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
-import com.oracle.truffle.api.dsl.Cached;
-import com.oracle.truffle.api.dsl.Specialization;
-import com.oracle.truffle.api.frame.VirtualFrame;
-import com.oracle.truffle.api.profiles.BranchProfile;
-import com.oracle.truffle.r.nodes.builtin.RBuiltinNode;
-import com.oracle.truffle.r.runtime.ArgumentsSignature;
-import com.oracle.truffle.r.runtime.RError;
-import com.oracle.truffle.r.runtime.RRuntime;
-import com.oracle.truffle.r.runtime.builtins.RBuiltin;
-import com.oracle.truffle.r.runtime.data.RArgsValuesAndNames;
-import com.oracle.truffle.r.runtime.data.RDataFactory;
-import com.oracle.truffle.r.runtime.data.RList;
-import com.oracle.truffle.r.runtime.data.RMissing;
-import com.oracle.truffle.r.runtime.data.RStringVector;
-import com.oracle.truffle.r.runtime.data.model.RAbstractDoubleVector;
-import com.oracle.truffle.r.runtime.data.model.RAbstractIntVector;
-import com.oracle.truffle.r.runtime.data.model.RAbstractLogicalVector;
-import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector;
-import com.oracle.truffle.r.runtime.ffi.DLL;
-import com.oracle.truffle.r.runtime.ffi.RFFIFactory;
-import com.oracle.truffle.r.runtime.ffi.NativeCallInfo;
-
-/**
- * {@code .C} functions.
- *
- * TODO Completeness (more types, more error checks), Performance (copying). Especially all the
- * subtleties around copying.
- *
- * See <a href="https://stat.ethz.ch/R-manual/R-devel/library/base/html/Foreign.html">here</a>.
- */
-@RBuiltin(name = ".C", kind = PRIMITIVE, parameterNames = {".NAME", "...", "NAOK", "DUP", "PACKAGE", "ENCODING"}, behavior = COMPLEX)
-public abstract class DotC extends RBuiltinNode {
-
-    private static final int SCALAR_DOUBLE = 0;
-    private static final int SCALAR_INT = 1;
-    private static final int SCALAR_LOGICAL = 2;
-    @SuppressWarnings("unused") private static final int SCALAR_STRING = 3;
-    private static final int VECTOR_DOUBLE = 10;
-    private static final int VECTOR_INT = 11;
-    private static final int VECTOR_LOGICAL = 12;
-    @SuppressWarnings("unused") private static final int VECTOR_STRING = 12;
-
-    @Child private ExtractNativeCallInfoNode extractSymbolInfoNode = ExtractNativeCallInfoNodeGen.create();
-
-    protected NativeCallInfo extractSymbolInfo(VirtualFrame frame, RList symbol) {
-        return (NativeCallInfo) extractSymbolInfoNode.execute(frame, symbol);
-    }
-
-    @Override
-    public Object[] getDefaultParameterValues() {
-        return new Object[]{RMissing.instance, RArgsValuesAndNames.EMPTY, RRuntime.LOGICAL_FALSE, RRuntime.LOGICAL_FALSE, RMissing.instance, RMissing.instance};
-    }
-
-    @SuppressWarnings("unused")
-    @Specialization
-    protected RList c(VirtualFrame frame, RList symbol, RArgsValuesAndNames args, byte naok, byte dup, Object rPackage, RMissing encoding) {
-        NativeCallInfo nativeCallInfo = extractSymbolInfo(frame, symbol);
-        return dispatch(this, nativeCallInfo, naok, dup, args);
-    }
-
-    @SuppressWarnings("unused")
-    @Specialization
-    protected RList c(RAbstractStringVector f, RArgsValuesAndNames args, byte naok, byte dup, Object rPackage, RMissing encoding, //
-                    @Cached("create()") BranchProfile errorProfile) {
-        String libName = null;
-        if (!(rPackage instanceof RMissing)) {
-            libName = RRuntime.asString(rPackage);
-            if (libName == null) {
-                errorProfile.enter();
-                throw RError.error(this, RError.Message.ARGUMENT_MUST_BE_STRING, "PACKAGE");
-            }
-        }
-        DLL.RegisteredNativeSymbol rns = new DLL.RegisteredNativeSymbol(DLL.NativeSymbolType.C, null, null);
-        DLL.SymbolHandle func = DLL.findSymbol(f.getDataAt(0), libName, rns);
-        if (func == DLL.SYMBOL_NOT_FOUND) {
-            errorProfile.enter();
-            throw RError.error(this, RError.Message.C_SYMBOL_NOT_IN_TABLE, f);
-        }
-        return dispatch(this, new NativeCallInfo(f.getDataAt(0), func, rns.getDllInfo()), naok, dup, args);
-    }
-
-    private static int[] checkNAs(RBuiltinNode node, int argIndex, int[] data) {
-        CompilerAsserts.neverPartOfCompilation();
-        for (int i = 0; i < data.length; i++) {
-            if (RRuntime.isNA(data[i])) {
-                throw RError.error(node, RError.Message.NA_IN_FOREIGN_FUNCTION_CALL, argIndex);
-            }
-        }
-        return data;
-    }
-
-    private static double[] checkNAs(RBuiltinNode node, int argIndex, double[] data) {
-        CompilerAsserts.neverPartOfCompilation();
-        for (int i = 0; i < data.length; i++) {
-            if (!RRuntime.isFinite(data[i])) {
-                throw RError.error(node, RError.Message.NA_NAN_INF_IN_FOREIGN_FUNCTION_CALL, argIndex);
-            }
-        }
-        return data;
-    }
-
-    private static RStringVector validateArgNames(int argsLength, ArgumentsSignature signature) {
-        String[] listArgNames = new String[argsLength];
-        for (int i = 0; i < argsLength; i++) {
-            String name = signature.getName(i);
-            if (name == null) {
-                name = RRuntime.NAMES_ATTR_EMPTY_VALUE;
-            }
-            listArgNames[i] = name;
-        }
-        return RDataFactory.createStringVector(listArgNames, RDataFactory.COMPLETE_VECTOR);
-    }
-
-    @TruffleBoundary
-    protected static RList dispatch(RBuiltinNode node, NativeCallInfo nativeCallInfo, byte naok, byte dup, RArgsValuesAndNames args) {
-        @SuppressWarnings("unused")
-        boolean dupArgs = RRuntime.fromLogical(dup);
-        @SuppressWarnings("unused")
-        boolean checkNA = RRuntime.fromLogical(naok);
-        // Analyze the args, making copies (ignoring dup for now)
-        Object[] array = args.getArguments();
-        int[] argTypes = new int[array.length];
-        Object[] nativeArgs = new Object[array.length];
-        for (int i = 0; i < array.length; i++) {
-            Object arg = array[i];
-            if (arg instanceof RAbstractDoubleVector) {
-                argTypes[i] = VECTOR_DOUBLE;
-                nativeArgs[i] = checkNAs(node, i + 1, ((RAbstractDoubleVector) arg).materialize().getDataCopy());
-            } else if (arg instanceof RAbstractIntVector) {
-                argTypes[i] = VECTOR_INT;
-                nativeArgs[i] = checkNAs(node, i + 1, ((RAbstractIntVector) arg).materialize().getDataCopy());
-            } else if (arg instanceof RAbstractLogicalVector) {
-                argTypes[i] = VECTOR_LOGICAL;
-                // passed as int[]
-                byte[] data = ((RAbstractLogicalVector) arg).materialize().getDataWithoutCopying();
-                int[] dataAsInt = new int[data.length];
-                for (int j = 0; j < data.length; j++) {
-                    // An NA is an error but the error handling happens in checkNAs
-                    dataAsInt[j] = RRuntime.isNA(data[j]) ? RRuntime.INT_NA : data[j];
-                }
-                nativeArgs[i] = checkNAs(node, i + 1, dataAsInt);
-            } else if (arg instanceof Double) {
-                argTypes[i] = SCALAR_DOUBLE;
-                nativeArgs[i] = checkNAs(node, i + 1, new double[]{(double) arg});
-            } else if (arg instanceof Integer) {
-                argTypes[i] = SCALAR_INT;
-                nativeArgs[i] = checkNAs(node, i + 1, new int[]{(int) arg});
-            } else if (arg instanceof Byte) {
-                argTypes[i] = SCALAR_LOGICAL;
-                nativeArgs[i] = checkNAs(node, i + 1, new int[]{RRuntime.isNA((byte) arg) ? RRuntime.INT_NA : (byte) arg});
-            } else {
-                throw RError.error(node, RError.Message.UNIMPLEMENTED_ARG_TYPE, i + 1);
-            }
-        }
-        RFFIFactory.getRFFI().getCRFFI().invoke(nativeCallInfo, nativeArgs);
-        // we have to assume that the native method updated everything
-        RStringVector listNames = validateArgNames(array.length, args.getSignature());
-        Object[] results = new Object[array.length];
-        for (int i = 0; i < array.length; i++) {
-            switch (argTypes[i]) {
-                case SCALAR_DOUBLE:
-                    results[i] = RDataFactory.createDoubleVector((double[]) nativeArgs[i], RDataFactory.COMPLETE_VECTOR);
-                    break;
-                case SCALAR_INT:
-                    results[i] = RDataFactory.createIntVector((int[]) nativeArgs[i], RDataFactory.COMPLETE_VECTOR);
-                    break;
-                case SCALAR_LOGICAL:
-                    // have to convert back from int[]
-                    int[] nativeIntArgs = (int[]) nativeArgs[i];
-                    byte[] nativeByteArgs = new byte[nativeIntArgs.length];
-                    for (int j = 0; j < nativeByteArgs.length; j++) {
-                        int nativeInt = nativeIntArgs[j];
-                        nativeByteArgs[j] = (byte) (nativeInt == RRuntime.INT_NA ? RRuntime.LOGICAL_NA : nativeInt & 0xFF);
-                    }
-                    results[i] = RDataFactory.createLogicalVector(nativeByteArgs, RDataFactory.COMPLETE_VECTOR);
-                    break;
-                case VECTOR_DOUBLE:
-                    results[i] = ((RAbstractDoubleVector) array[i]).materialize().copyResetData((double[]) nativeArgs[i]);
-                    break;
-                case VECTOR_INT:
-                    results[i] = ((RAbstractIntVector) array[i]).materialize().copyResetData((int[]) nativeArgs[i]);
-                    break;
-                case VECTOR_LOGICAL: {
-                    int[] intData = (int[]) nativeArgs[i];
-                    byte[] byteData = new byte[intData.length];
-                    for (int j = 0; j < intData.length; j++) {
-                        byteData[j] = RRuntime.isNA(intData[j]) ? RRuntime.LOGICAL_NA : RRuntime.asLogical(intData[j] != 0);
-                    }
-                    results[i] = ((RAbstractLogicalVector) array[i]).materialize().copyResetData(byteData);
-                    break;
-                }
-            }
-        }
-        return RDataFactory.createList(results, listNames);
-    }
-
-}
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/Dqrcf.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/Dqrcf.java
index b8dfa18b98a9656c6d2df0059a5589ebf7deda57..ebf0fb5462597a941b316806e45c1e71959e5bc5 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/Dqrcf.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/Dqrcf.java
@@ -21,9 +21,11 @@ import com.oracle.truffle.r.runtime.data.RList;
 import com.oracle.truffle.r.runtime.data.RStringVector;
 import com.oracle.truffle.r.runtime.data.model.RAbstractDoubleVector;
 import com.oracle.truffle.r.runtime.data.model.RAbstractIntVector;
+import com.oracle.truffle.r.runtime.ffi.RApplRFFI;
 import com.oracle.truffle.r.runtime.ffi.RFFIFactory;
 
 public final class Dqrcf extends RExternalBuiltinNode {
+    @Child private RApplRFFI.RApplRFFINode rApplRFFINode = RFFIFactory.getRFFI().getRApplRFFI().rApplRFFINode();
 
     private static final String E = RRuntime.NAMES_ATTR_EMPTY_VALUE;
     private static final RStringVector DQRCF_NAMES = RDataFactory.createStringVector(new String[]{E, E, E, E, E, E, "coef", "info"}, RDataFactory.COMPLETE_VECTOR);
@@ -45,7 +47,7 @@ public final class Dqrcf extends RExternalBuiltinNode {
             double[] y = yVec.materialize().getDataTemp();
             double[] b = bVec.materialize().getDataTemp();
             int[] info = infoVec.materialize().getDataTemp();
-            RFFIFactory.getRFFI().getRApplRFFI().dqrcf(x, n, k.getDataAt(0), qraux, y, ny, b, info);
+            rApplRFFINode.dqrcf(x, n, k.getDataAt(0), qraux, y, ny, b, info);
             RDoubleVector coef = RDataFactory.createDoubleVector(b, RDataFactory.COMPLETE_VECTOR);
             coef.copyAttributesFrom(attrProfiles, bVec);
             // @formatter:off
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/Dqrdc2.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/Dqrdc2.java
index fb842d465d81c176cdc1f5aeadaab8f3d07326c0..d1cac035699cd82ebef27232c8f901a9bdff7631 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/Dqrdc2.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/Dqrdc2.java
@@ -20,9 +20,11 @@ import com.oracle.truffle.r.runtime.data.RList;
 import com.oracle.truffle.r.runtime.data.RStringVector;
 import com.oracle.truffle.r.runtime.data.model.RAbstractDoubleVector;
 import com.oracle.truffle.r.runtime.data.model.RAbstractIntVector;
+import com.oracle.truffle.r.runtime.ffi.RApplRFFI;
 import com.oracle.truffle.r.runtime.ffi.RFFIFactory;
 
 public final class Dqrdc2 extends RExternalBuiltinNode {
+    @Child private RApplRFFI.RApplRFFINode rApplRFFINode = RFFIFactory.getRFFI().getRApplRFFI().rApplRFFINode();
 
     private static final String E = RRuntime.NAMES_ATTR_EMPTY_VALUE;
     private static final RStringVector DQRDC2_NAMES = RDataFactory.createStringVector(new String[]{"qr", E, E, E, E, "rank", "qraux", "pivot", E}, RDataFactory.COMPLETE_VECTOR);
@@ -44,7 +46,7 @@ public final class Dqrdc2 extends RExternalBuiltinNode {
             int[] rank = rankVec.materialize().getDataTemp();
             double[] qraux = qrauxVec.materialize().getDataTemp();
             int[] pivot = pivotVec.materialize().getDataTemp();
-            RFFIFactory.getRFFI().getRApplRFFI().dqrdc2(x, ldx, n, p, tol, rank, qraux, pivot, workVec.materialize().getDataCopy());
+            rApplRFFINode.dqrdc2(x, ldx, n, p, tol, rank, qraux, pivot, workVec.materialize().getDataCopy());
             // @formatter:off
             Object[] data = new Object[]{
                         RDataFactory.createDoubleVector(x, RDataFactory.COMPLETE_VECTOR, xVec.getDimensions()),
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/ExtractNativeCallInfoNode.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/ExtractNativeCallInfoNode.java
deleted file mode 100644
index 47d1f4a19c04070de6ce485cc2b780ee8f2bca86..0000000000000000000000000000000000000000
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/ExtractNativeCallInfoNode.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (c) 2016, 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
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package com.oracle.truffle.r.nodes.builtin.base.foreign;
-
-import com.oracle.truffle.api.CompilerDirectives;
-import com.oracle.truffle.api.dsl.Specialization;
-import com.oracle.truffle.api.frame.VirtualFrame;
-import com.oracle.truffle.api.nodes.Node;
-import com.oracle.truffle.r.nodes.access.vector.ElementAccessMode;
-import com.oracle.truffle.r.nodes.access.vector.ExtractVectorNode;
-import com.oracle.truffle.r.runtime.RRuntime;
-import com.oracle.truffle.r.runtime.data.RExternalPtr;
-import com.oracle.truffle.r.runtime.data.RList;
-import com.oracle.truffle.r.runtime.data.RLogical;
-import com.oracle.truffle.r.runtime.data.RMissing;
-import com.oracle.truffle.r.runtime.ffi.NativeCallInfo;
-import com.oracle.truffle.r.runtime.ffi.DLL.DLLInfo;
-import com.oracle.truffle.r.runtime.ffi.DLL.SymbolHandle;
-
-/**
- * Extracts the salient information needed for a native call from the {@link RList} value provided
- * from R.
- */
-public abstract class ExtractNativeCallInfoNode extends Node {
-    @Child private ExtractVectorNode nameExtract = ExtractVectorNode.create(ElementAccessMode.SUBSCRIPT, true);
-    @Child private ExtractVectorNode addressExtract = ExtractVectorNode.create(ElementAccessMode.SUBSCRIPT, true);
-    @Child private ExtractVectorNode packageExtract = ExtractVectorNode.create(ElementAccessMode.SUBSCRIPT, true);
-    @Child private ExtractVectorNode infoExtract = ExtractVectorNode.create(ElementAccessMode.SUBSCRIPT, true);
-
-    protected abstract Object execute(VirtualFrame frame, RList symbol);
-
-    @Specialization
-    protected Object extractNativeCallInfo(VirtualFrame frame, RList symbol) {
-        if (nameExtract == null) {
-            CompilerDirectives.transferToInterpreterAndInvalidate();
-            nameExtract = ExtractVectorNode.create(ElementAccessMode.SUBSCRIPT, true);
-        }
-        if (addressExtract == null) {
-            CompilerDirectives.transferToInterpreterAndInvalidate();
-            addressExtract = ExtractVectorNode.create(ElementAccessMode.SUBSCRIPT, true);
-        }
-        if (packageExtract == null) {
-            CompilerDirectives.transferToInterpreterAndInvalidate();
-            packageExtract = ExtractVectorNode.create(ElementAccessMode.SUBSCRIPT, true);
-        }
-        String name = RRuntime.asString(nameExtract.applyAccessField(frame, symbol, "name"));
-        SymbolHandle address = ((RExternalPtr) addressExtract.applyAccessField(frame, symbol, "address")).getAddr();
-        // field name may be "package" or "dll", but always at (R) index 3
-        RList packageList = (RList) packageExtract.apply(frame, symbol, new Object[]{3}, RLogical.valueOf(false), RMissing.instance);
-        DLLInfo dllInfo = (DLLInfo) ((RExternalPtr) addressExtract.applyAccessField(frame, packageList, "info")).getExternalObject();
-        return new NativeCallInfo(name, address, dllInfo);
-
-    }
-
-}
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/FortranAndCFunctions.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/FortranAndCFunctions.java
new file mode 100644
index 0000000000000000000000000000000000000000..af635ab2ccb0f6d77da60a71153212ff6c1d9f93
--- /dev/null
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/FortranAndCFunctions.java
@@ -0,0 +1,282 @@
+/*
+ * This material is distributed under the GNU General Public License
+ * Version 2. You may review the terms of this license at
+ * http://www.gnu.org/licenses/gpl-2.0.html
+ *
+ * Copyright (c) 1995-2012, The R Core Team
+ * Copyright (c) 2003, The R Foundation
+ * Copyright (c) 2015, 2016, Oracle and/or its affiliates
+ *
+ * All rights reserved.
+ */
+package com.oracle.truffle.r.nodes.builtin.base.foreign;
+
+import static com.oracle.truffle.r.runtime.builtins.RBehavior.COMPLEX;
+import static com.oracle.truffle.r.runtime.builtins.RBuiltinKind.PRIMITIVE;
+
+import com.oracle.truffle.api.CompilerAsserts;
+import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import com.oracle.truffle.api.dsl.Cached;
+import com.oracle.truffle.api.dsl.Fallback;
+import com.oracle.truffle.api.dsl.Specialization;
+import com.oracle.truffle.api.frame.VirtualFrame;
+import com.oracle.truffle.api.profiles.BranchProfile;
+import com.oracle.truffle.r.nodes.builtin.RBuiltinNode;
+import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode;
+import com.oracle.truffle.r.runtime.ArgumentsSignature;
+import com.oracle.truffle.r.runtime.RError;
+import com.oracle.truffle.r.runtime.RInternalError;
+import com.oracle.truffle.r.runtime.RRuntime;
+import com.oracle.truffle.r.runtime.builtins.RBuiltin;
+import com.oracle.truffle.r.runtime.data.RArgsValuesAndNames;
+import com.oracle.truffle.r.runtime.data.RDataFactory;
+import com.oracle.truffle.r.runtime.data.RList;
+import com.oracle.truffle.r.runtime.data.RMissing;
+import com.oracle.truffle.r.runtime.data.RStringVector;
+import com.oracle.truffle.r.runtime.data.model.RAbstractDoubleVector;
+import com.oracle.truffle.r.runtime.data.model.RAbstractIntVector;
+import com.oracle.truffle.r.runtime.data.model.RAbstractLogicalVector;
+import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector;
+import com.oracle.truffle.r.runtime.ffi.CRFFI;
+import com.oracle.truffle.r.runtime.ffi.DLL;
+import com.oracle.truffle.r.runtime.ffi.RFFIFactory;
+import com.oracle.truffle.r.runtime.ffi.NativeCallInfo;
+
+/**
+ * {@code .C} and {@code .Fortran} functions, which share a common signature.
+ *
+ * TODO Completeness (more types, more error checks), Performance (copying). Especially all the
+ * subtleties around copying.
+ *
+ * See <a href="https://stat.ethz.ch/R-manual/R-devel/library/base/html/Foreign.html">here</a>.
+ */
+public class FortranAndCFunctions {
+
+    protected abstract static class CRFFIAdapter extends LookupAdapter {
+        private static final int SCALAR_DOUBLE = 0;
+        private static final int SCALAR_INT = 1;
+        private static final int SCALAR_LOGICAL = 2;
+        @SuppressWarnings("unused") private static final int SCALAR_STRING = 3;
+        private static final int VECTOR_DOUBLE = 10;
+        private static final int VECTOR_INT = 11;
+        private static final int VECTOR_LOGICAL = 12;
+        @SuppressWarnings("unused") private static final int VECTOR_STRING = 12;
+
+        @Child private CRFFI.CRFFINode cRFFINode = RFFIFactory.getRFFI().getCRFFI().getCRFFINode();
+
+        @Override
+        public Object[] getDefaultParameterValues() {
+            return new Object[]{RMissing.instance, RArgsValuesAndNames.EMPTY, RRuntime.LOGICAL_FALSE, RRuntime.LOGICAL_FALSE, RMissing.instance, RMissing.instance};
+        }
+
+        @TruffleBoundary
+        protected RList dispatch(RBuiltinNode node, NativeCallInfo nativeCallInfo, byte naok, byte dup, RArgsValuesAndNames args) {
+            @SuppressWarnings("unused")
+            boolean dupArgs = RRuntime.fromLogical(dup);
+            @SuppressWarnings("unused")
+            boolean checkNA = RRuntime.fromLogical(naok);
+            // Analyze the args, making copies (ignoring dup for now)
+            Object[] array = args.getArguments();
+            int[] argTypes = new int[array.length];
+            Object[] nativeArgs = new Object[array.length];
+            for (int i = 0; i < array.length; i++) {
+                Object arg = array[i];
+                if (arg instanceof RAbstractDoubleVector) {
+                    argTypes[i] = VECTOR_DOUBLE;
+                    nativeArgs[i] = checkNAs(node, i + 1, ((RAbstractDoubleVector) arg).materialize().getDataCopy());
+                } else if (arg instanceof RAbstractIntVector) {
+                    argTypes[i] = VECTOR_INT;
+                    nativeArgs[i] = checkNAs(node, i + 1, ((RAbstractIntVector) arg).materialize().getDataCopy());
+                } else if (arg instanceof RAbstractLogicalVector) {
+                    argTypes[i] = VECTOR_LOGICAL;
+                    // passed as int[]
+                    byte[] data = ((RAbstractLogicalVector) arg).materialize().getDataWithoutCopying();
+                    int[] dataAsInt = new int[data.length];
+                    for (int j = 0; j < data.length; j++) {
+                        // An NA is an error but the error handling happens in checkNAs
+                        dataAsInt[j] = RRuntime.isNA(data[j]) ? RRuntime.INT_NA : data[j];
+                    }
+                    nativeArgs[i] = checkNAs(node, i + 1, dataAsInt);
+                } else if (arg instanceof Double) {
+                    argTypes[i] = SCALAR_DOUBLE;
+                    nativeArgs[i] = checkNAs(node, i + 1, new double[]{(double) arg});
+                } else if (arg instanceof Integer) {
+                    argTypes[i] = SCALAR_INT;
+                    nativeArgs[i] = checkNAs(node, i + 1, new int[]{(int) arg});
+                } else if (arg instanceof Byte) {
+                    argTypes[i] = SCALAR_LOGICAL;
+                    nativeArgs[i] = checkNAs(node, i + 1, new int[]{RRuntime.isNA((byte) arg) ? RRuntime.INT_NA : (byte) arg});
+                } else {
+                    throw RError.error(node, RError.Message.UNIMPLEMENTED_ARG_TYPE, i + 1);
+                }
+            }
+            cRFFINode.invoke(nativeCallInfo, nativeArgs);
+            // we have to assume that the native method updated everything
+            RStringVector listNames = validateArgNames(array.length, args.getSignature());
+            Object[] results = new Object[array.length];
+            for (int i = 0; i < array.length; i++) {
+                switch (argTypes[i]) {
+                    case SCALAR_DOUBLE:
+                        results[i] = RDataFactory.createDoubleVector((double[]) nativeArgs[i], RDataFactory.COMPLETE_VECTOR);
+                        break;
+                    case SCALAR_INT:
+                        results[i] = RDataFactory.createIntVector((int[]) nativeArgs[i], RDataFactory.COMPLETE_VECTOR);
+                        break;
+                    case SCALAR_LOGICAL:
+                        // have to convert back from int[]
+                        int[] nativeIntArgs = (int[]) nativeArgs[i];
+                        byte[] nativeByteArgs = new byte[nativeIntArgs.length];
+                        for (int j = 0; j < nativeByteArgs.length; j++) {
+                            int nativeInt = nativeIntArgs[j];
+                            nativeByteArgs[j] = (byte) (nativeInt == RRuntime.INT_NA ? RRuntime.LOGICAL_NA : nativeInt & 0xFF);
+                        }
+                        results[i] = RDataFactory.createLogicalVector(nativeByteArgs, RDataFactory.COMPLETE_VECTOR);
+                        break;
+                    case VECTOR_DOUBLE:
+                        results[i] = ((RAbstractDoubleVector) array[i]).materialize().copyResetData((double[]) nativeArgs[i]);
+                        break;
+                    case VECTOR_INT:
+                        results[i] = ((RAbstractIntVector) array[i]).materialize().copyResetData((int[]) nativeArgs[i]);
+                        break;
+                    case VECTOR_LOGICAL: {
+                        int[] intData = (int[]) nativeArgs[i];
+                        byte[] byteData = new byte[intData.length];
+                        for (int j = 0; j < intData.length; j++) {
+                            byteData[j] = RRuntime.isNA(intData[j]) ? RRuntime.LOGICAL_NA : RRuntime.asLogical(intData[j] != 0);
+                        }
+                        results[i] = ((RAbstractLogicalVector) array[i]).materialize().copyResetData(byteData);
+                        break;
+                    }
+                }
+            }
+            return RDataFactory.createList(results, listNames);
+        }
+
+        private static int[] checkNAs(RBuiltinNode node, int argIndex, int[] data) {
+            CompilerAsserts.neverPartOfCompilation();
+            for (int i = 0; i < data.length; i++) {
+                if (RRuntime.isNA(data[i])) {
+                    throw RError.error(node, RError.Message.NA_IN_FOREIGN_FUNCTION_CALL, argIndex);
+                }
+            }
+            return data;
+        }
+
+        private static double[] checkNAs(RBuiltinNode node, int argIndex, double[] data) {
+            CompilerAsserts.neverPartOfCompilation();
+            for (int i = 0; i < data.length; i++) {
+                if (!RRuntime.isFinite(data[i])) {
+                    throw RError.error(node, RError.Message.NA_NAN_INF_IN_FOREIGN_FUNCTION_CALL, argIndex);
+                }
+            }
+            return data;
+        }
+
+        private static RStringVector validateArgNames(int argsLength, ArgumentsSignature signature) {
+            String[] listArgNames = new String[argsLength];
+            for (int i = 0; i < argsLength; i++) {
+                String name = signature.getName(i);
+                if (name == null) {
+                    name = RRuntime.NAMES_ATTR_EMPTY_VALUE;
+                }
+                listArgNames[i] = name;
+            }
+            return RDataFactory.createStringVector(listArgNames, RDataFactory.COMPLETE_VECTOR);
+        }
+
+    }
+
+    /**
+     * Interface to .Fortran native functions. Some functions have explicit implementations in
+     * FastR, otherwise the .Fortran interface uses the machinery that implements the .C interface.
+     */
+    @RBuiltin(name = ".Fortran", kind = PRIMITIVE, parameterNames = {".NAME", "...", "NAOK", "DUP", "PACKAGE", "ENCODING"}, behavior = COMPLEX)
+    public abstract static class Fortran extends CRFFIAdapter {
+
+        @Override
+        @TruffleBoundary
+        protected RExternalBuiltinNode lookupBuiltin(RList symbol) {
+            switch (lookupName(symbol)) {
+                case "dqrdc2":
+                    return new Dqrdc2();
+                case "dqrcf":
+                    return new Dqrcf();
+                default:
+                    return null;
+            }
+        }
+
+        @SuppressWarnings("unused")
+        @Specialization(limit = "1", guards = {"cached == symbol", "builtin != null"})
+        protected Object doExternal(VirtualFrame frame, RList symbol, RArgsValuesAndNames args, byte naok, byte dup, Object rPackage, RMissing encoding, //
+                        @Cached("symbol") RList cached, //
+                        @Cached("lookupBuiltin(symbol)") RExternalBuiltinNode builtin) {
+            return builtin.call(frame, args);
+        }
+
+        @Specialization(guards = "lookupBuiltin(symbol) == null")
+        protected RList c(VirtualFrame frame, RList symbol, RArgsValuesAndNames args, byte naok, byte dup, @SuppressWarnings("unused") Object rPackage,
+                        @SuppressWarnings("unused") RMissing encoding) {
+            NativeCallInfo nativeCallInfo = extractSymbolInfo(frame, symbol);
+            return dispatch(this, nativeCallInfo, naok, dup, args);
+        }
+
+        @Specialization
+        protected RList c(RAbstractStringVector symbol, RArgsValuesAndNames args, byte naok, byte dup, Object rPackage, @SuppressWarnings("unused") RMissing encoding, //
+                        @Cached("create()") BranchProfile errorProfile) {
+            String libName = checkPackageArg(rPackage, errorProfile);
+            DLL.RegisteredNativeSymbol rns = new DLL.RegisteredNativeSymbol(DLL.NativeSymbolType.Fortran, null, null);
+            DLL.SymbolHandle func = DLL.findSymbol(symbol.getDataAt(0), libName, rns);
+            if (func == DLL.SYMBOL_NOT_FOUND) {
+                errorProfile.enter();
+                throw RError.error(this, RError.Message.C_SYMBOL_NOT_IN_TABLE, symbol);
+            }
+            return dispatch(this, new NativeCallInfo(symbol.getDataAt(0), func, rns.getDllInfo()), naok, dup, args);
+        }
+
+        @SuppressWarnings("unused")
+        @Fallback
+        protected Object fallback(Object symbol, Object args, Object naok, Object dup, Object rPackage, Object encoding) {
+            throw fallback(symbol);
+        }
+    }
+
+    @RBuiltin(name = ".C", kind = PRIMITIVE, parameterNames = {".NAME", "...", "NAOK", "DUP", "PACKAGE", "ENCODING"}, behavior = COMPLEX)
+    public abstract static class DotC extends CRFFIAdapter {
+
+        @Override
+        @TruffleBoundary
+        protected RExternalBuiltinNode lookupBuiltin(RList symbol) {
+            throw RInternalError.shouldNotReachHere();
+        }
+
+        @SuppressWarnings("unused")
+        @Specialization
+        protected RList c(VirtualFrame frame, RList symbol, RArgsValuesAndNames args, byte naok, byte dup, Object rPackage, RMissing encoding) {
+            NativeCallInfo nativeCallInfo = extractSymbolInfo(frame, symbol);
+            return dispatch(this, nativeCallInfo, naok, dup, args);
+        }
+
+        @SuppressWarnings("unused")
+        @Specialization
+        protected RList c(RAbstractStringVector symbol, RArgsValuesAndNames args, byte naok, byte dup, Object rPackage, RMissing encoding, //
+                        @Cached("create()") BranchProfile errorProfile) {
+            String libName = null;
+            if (!(rPackage instanceof RMissing)) {
+                libName = RRuntime.asString(rPackage);
+                if (libName == null) {
+                    errorProfile.enter();
+                    throw RError.error(this, RError.Message.ARGUMENT_MUST_BE_STRING, "PACKAGE");
+                }
+            }
+            DLL.RegisteredNativeSymbol rns = new DLL.RegisteredNativeSymbol(DLL.NativeSymbolType.C, null, null);
+            DLL.SymbolHandle func = DLL.findSymbol(symbol.getDataAt(0), libName, rns);
+            if (func == DLL.SYMBOL_NOT_FOUND) {
+                errorProfile.enter();
+                throw RError.error(this, RError.Message.C_SYMBOL_NOT_IN_TABLE, symbol);
+            }
+            return dispatch(this, new NativeCallInfo(symbol.getDataAt(0), func, rns.getDllInfo()), naok, dup, args);
+        }
+
+    }
+}
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/LookupAdapter.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/LookupAdapter.java
new file mode 100644
index 0000000000000000000000000000000000000000..9569ccc557fd49addc15c425f2d5e320b3f2ad54
--- /dev/null
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/LookupAdapter.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2015, 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
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.oracle.truffle.r.nodes.builtin.base.foreign;
+
+import com.oracle.truffle.api.CompilerAsserts;
+import com.oracle.truffle.api.CompilerDirectives;
+import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import com.oracle.truffle.api.dsl.Specialization;
+import com.oracle.truffle.api.frame.VirtualFrame;
+import com.oracle.truffle.api.nodes.Node;
+import com.oracle.truffle.api.profiles.BranchProfile;
+import com.oracle.truffle.r.library.stats.StatsUtil;
+import com.oracle.truffle.r.nodes.access.vector.ElementAccessMode;
+import com.oracle.truffle.r.nodes.access.vector.ExtractVectorNode;
+import com.oracle.truffle.r.nodes.builtin.RBuiltinNode;
+import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode;
+import com.oracle.truffle.r.nodes.builtin.RInternalCodeBuiltinNode;
+import com.oracle.truffle.r.nodes.builtin.base.foreign.LookupAdapterFactory.ExtractNativeCallInfoNodeGen;
+import com.oracle.truffle.r.runtime.RError;
+import com.oracle.truffle.r.runtime.RInternalCode;
+import com.oracle.truffle.r.runtime.RInternalError;
+import com.oracle.truffle.r.runtime.RRuntime;
+import com.oracle.truffle.r.runtime.context.RContext;
+import com.oracle.truffle.r.runtime.data.RArgsValuesAndNames;
+import com.oracle.truffle.r.runtime.data.RExternalPtr;
+import com.oracle.truffle.r.runtime.data.RList;
+import com.oracle.truffle.r.runtime.data.RLogical;
+import com.oracle.truffle.r.runtime.data.RMissing;
+import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector;
+import com.oracle.truffle.r.runtime.ffi.DLL;
+import com.oracle.truffle.r.runtime.ffi.NativeCallInfo;
+import com.oracle.truffle.r.runtime.ffi.DLL.DLLInfo;
+import com.oracle.truffle.r.runtime.ffi.DLL.SymbolHandle;
+
+/**
+ * Locator for "builtin" package function implementations. The "builtin" packages contain many
+ * functions that are called from R code via the FFI, e.g. {@code .Call}, but implemented internally
+ * in GnuR, and not necessarily following the FFI API. The value passed to {@code .Call} etc., is a
+ * symbol, created when the package is loaded and stored in the namespace environment of the
+ * package, that is a list-valued object. Evidently these "builtins" are somewhat similar to the
+ * {@code .Primitive} and {@code .Internal} builtins and, similarly, most of these are
+ * re-implemented in Java in FastR. The {@link #lookupBuiltin(RList)} method checks the name in the
+ * list object and returns the {@link RExternalBuiltinNode} that implements the function, or
+ * {@code null}. A {@code null} result implies that the builtin is not implemented in Java, but
+ * called directly via the FFI interface, which is only possible for functions that use the FFI in a
+ * way that FastR can handle.
+ *
+ * This class also handles the "lookup" of the {@link NativeCallInfo} data for builtins that are
+ * still implemented by native code.
+ */
+abstract class LookupAdapter extends RBuiltinNode {
+    @Child private ExtractNativeCallInfoNode extractSymbolInfoNode = ExtractNativeCallInfoNodeGen.create();
+
+    protected static class UnimplementedExternal extends RExternalBuiltinNode {
+        private final String name;
+
+        public UnimplementedExternal(String name) {
+            this.name = name;
+        }
+
+        @Override
+        public final Object call(RArgsValuesAndNames args) {
+            throw RInternalError.unimplemented("unimplemented external builtin: " + name);
+        }
+    }
+
+    protected abstract RExternalBuiltinNode lookupBuiltin(RList symbol);
+
+    private static final String UNKNOWN_EXTERNAL_BUILTIN = "UNKNOWN_EXTERNAL_BUILTIN";
+
+    protected static String lookupName(RList symbol) {
+        CompilerAsserts.neverPartOfCompilation();
+        if (symbol.getNames() != null) {
+            RAbstractStringVector names = symbol.getNames();
+            for (int i = 0; i < names.getLength(); i++) {
+                if (names.getDataAt(i).equals("name")) {
+                    String name = RRuntime.asString(symbol.getDataAt(i));
+                    return name != null ? name : UNKNOWN_EXTERNAL_BUILTIN;
+                }
+            }
+        }
+        return UNKNOWN_EXTERNAL_BUILTIN;
+    }
+
+    @TruffleBoundary
+    protected RuntimeException fallback(Object symbol) {
+        String name = null;
+        if (symbol instanceof RList) {
+            name = lookupName((RList) symbol);
+            name = name == UNKNOWN_EXTERNAL_BUILTIN ? null : name;
+            if (name != null && lookupBuiltin((RList) symbol) != null) {
+                /*
+                 * if we reach this point, then the cache saw a different value for f. the lists
+                 * that contain the information about native calls are never expected to change.
+                 */
+                throw RInternalError.shouldNotReachHere("fallback reached for " + getRBuiltin().name() + " " + name);
+            }
+        }
+        throw RError.nyi(this, getRBuiltin().name() + " specialization failure: " + (name == null ? "<unknown>" : name));
+    }
+
+    protected NativeCallInfo extractSymbolInfo(VirtualFrame frame, RList symbol) {
+        return (NativeCallInfo) extractSymbolInfoNode.execute(frame, symbol);
+    }
+
+    protected String checkPackageArg(Object rPackage, BranchProfile errorProfile) {
+        String libName = null;
+        if (!(rPackage instanceof RMissing)) {
+            libName = RRuntime.asString(rPackage);
+            if (libName == null) {
+                errorProfile.enter();
+                throw RError.error(this, RError.Message.ARGUMENT_MUST_BE_STRING, "PACKAGE");
+            }
+        }
+        return libName;
+    }
+
+    /**
+     * Extracts the salient information needed for a native call from the {@link RList} value
+     * provided from R.
+     */
+    public abstract static class ExtractNativeCallInfoNode extends Node {
+        @Child private ExtractVectorNode nameExtract = ExtractVectorNode.create(ElementAccessMode.SUBSCRIPT, true);
+        @Child private ExtractVectorNode addressExtract = ExtractVectorNode.create(ElementAccessMode.SUBSCRIPT, true);
+        @Child private ExtractVectorNode packageExtract = ExtractVectorNode.create(ElementAccessMode.SUBSCRIPT, true);
+        @Child private ExtractVectorNode infoExtract = ExtractVectorNode.create(ElementAccessMode.SUBSCRIPT, true);
+
+        protected abstract Object execute(VirtualFrame frame, RList symbol);
+
+        @Specialization
+        protected Object extractNativeCallInfo(VirtualFrame frame, RList symbol) {
+            if (nameExtract == null) {
+                CompilerDirectives.transferToInterpreterAndInvalidate();
+                nameExtract = ExtractVectorNode.create(ElementAccessMode.SUBSCRIPT, true);
+            }
+            if (addressExtract == null) {
+                CompilerDirectives.transferToInterpreterAndInvalidate();
+                addressExtract = ExtractVectorNode.create(ElementAccessMode.SUBSCRIPT, true);
+            }
+            if (packageExtract == null) {
+                CompilerDirectives.transferToInterpreterAndInvalidate();
+                packageExtract = ExtractVectorNode.create(ElementAccessMode.SUBSCRIPT, true);
+            }
+            String name = RRuntime.asString(nameExtract.applyAccessField(frame, symbol, "name"));
+            SymbolHandle address = ((RExternalPtr) addressExtract.applyAccessField(frame, symbol, "address")).getAddr();
+            // field name may be "package" or "dll", but always at (R) index 3
+            RList packageList = (RList) packageExtract.apply(frame, symbol, new Object[]{3}, RLogical.valueOf(false), RMissing.instance);
+            DLLInfo dllInfo = (DLLInfo) ((RExternalPtr) addressExtract.applyAccessField(frame, packageList, "info")).getExternalObject();
+            return new NativeCallInfo(name, address, dllInfo);
+
+        }
+    }
+
+    protected static RExternalBuiltinNode getExternalModelBuiltinNode(String name) {
+        return new RInternalCodeBuiltinNode(RContext.getInstance(), "stats", RInternalCode.loadSourceRelativeTo(StatsUtil.class, "model.R"), name);
+    }
+
+    protected static final int CallNST = DLL.NativeSymbolType.Call.ordinal();
+    protected static final int ExternalNST = DLL.NativeSymbolType.External.ordinal();
+
+    public static DLL.RegisteredNativeSymbol createRegisteredNativeSymbol(int nstOrd) {
+        // DSL cannot resolve DLL.DLL.NativeSymbolType
+        DLL.NativeSymbolType nst = DLL.NativeSymbolType.values()[nstOrd];
+        return new DLL.RegisteredNativeSymbol(nst, null, null);
+    }
+}
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/FastrDqrls.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/FastrDqrls.java
index 07719a305b4ca2937b7dbd2d4a5cb64eddebc9b0..81767da8e0d65ae564317aac007716c30fede1f5 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/FastrDqrls.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/FastrDqrls.java
@@ -30,6 +30,7 @@ import com.oracle.truffle.r.runtime.data.RList;
 import com.oracle.truffle.r.runtime.data.RStringVector;
 import com.oracle.truffle.r.runtime.data.model.RAbstractDoubleVector;
 import com.oracle.truffle.r.runtime.data.model.RAbstractVector;
+import com.oracle.truffle.r.runtime.ffi.RApplRFFI;
 import com.oracle.truffle.r.runtime.ffi.RFFIFactory;
 
 /**
@@ -38,6 +39,7 @@ import com.oracle.truffle.r.runtime.ffi.RFFIFactory;
  */
 @RBuiltin(name = ".fastr.dqrls", visibility = OFF, kind = PRIMITIVE, parameterNames = {"x", "n", "p", "y", "ny", "tol", "coeff"}, behavior = PURE)
 public abstract class FastrDqrls extends RBuiltinNode {
+    @Child private RApplRFFI.RApplRFFINode rApplRFFINode = RFFIFactory.getRFFI().getRApplRFFI().rApplRFFINode();
 
     private static final String[] NAMES = new String[]{"qr", "coefficients", "residuals", "effects", "rank", "pivot", "qraux", "tol", "pivoted"};
     private static RStringVector namesVector = null;
@@ -82,7 +84,7 @@ public abstract class FastrDqrls extends RBuiltinNode {
             pivot[i] = i + 1;
         }
 
-        RFFIFactory.getRFFI().getRApplRFFI().dqrls(x, n, p, y, ny, tol, coeff, residuals, effects, rank, pivot, qraux, work);
+        rApplRFFINode.dqrls(x, n, p, y, ny, tol, coeff, residuals, effects, rank, pivot, qraux, work);
 
         RDoubleVector resultCoeffVect = RDataFactory.createDoubleVector(coeff, RDataFactory.COMPLETE_VECTOR);
         resultCoeffVect.copyAttributesFrom(coeffAttributeProfiles, coeffVec);
diff --git a/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/generic/Generic_Grid.java b/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/generic/Generic_Grid.java
index 8e1df26de4542db1e598d98cda69db08fc4ffd12..f44263215c268e63a6344ef7b65906778e93d5a8 100644
--- a/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/generic/Generic_Grid.java
+++ b/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/generic/Generic_Grid.java
@@ -24,6 +24,7 @@ package com.oracle.truffle.r.runtime.ffi.generic;
 
 import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
 import com.oracle.truffle.r.runtime.env.REnvironment;
+import com.oracle.truffle.r.runtime.ffi.CallRFFI;
 import com.oracle.truffle.r.runtime.ffi.DLL;
 import com.oracle.truffle.r.runtime.ffi.DLL.SymbolHandle;
 import com.oracle.truffle.r.runtime.ffi.GridRFFI;
@@ -32,46 +33,55 @@ import com.oracle.truffle.r.runtime.ffi.NativeCallInfo;
 import com.oracle.truffle.r.runtime.ffi.RFFIFactory;
 
 public class Generic_Grid implements GridRFFI {
-    private static final class GridProvider {
-        private static GridProvider grid;
-        private static DLL.SymbolHandle initGrid;
-        private static DLL.SymbolHandle killGrid;
+    private static class Generic_GridRFFINode extends GridRFFINode {
+        private CallRFFI.CallRFFINode callRFFINode = RFFIFactory.getRFFI().getCallRFFI().callRFFINode();
 
-        @TruffleBoundary
-        private GridProvider() {
-            System.load(LibPaths.getPackageLibPath("grid"));
-            initGrid = DLL.findSymbol("L_initGrid", "grid", DLL.RegisteredNativeSymbol.any());
-            killGrid = DLL.findSymbol("L_killGrid", "grid", DLL.RegisteredNativeSymbol.any());
-            assert initGrid != DLL.SYMBOL_NOT_FOUND && killGrid != DLL.SYMBOL_NOT_FOUND;
-        }
+        private static final class GridProvider {
+            private static GridProvider grid;
+            private static DLL.SymbolHandle initGrid;
+            private static DLL.SymbolHandle killGrid;
 
-        static GridProvider gridProvider() {
-            if (grid == null) {
-                grid = new GridProvider();
+            @TruffleBoundary
+            private GridProvider() {
+                System.load(LibPaths.getPackageLibPath("grid"));
+                initGrid = DLL.findSymbol("L_initGrid", "grid", DLL.RegisteredNativeSymbol.any());
+                killGrid = DLL.findSymbol("L_killGrid", "grid", DLL.RegisteredNativeSymbol.any());
+                assert initGrid != DLL.SYMBOL_NOT_FOUND && killGrid != DLL.SYMBOL_NOT_FOUND;
+            }
+
+            static GridProvider gridProvider() {
+                if (grid == null) {
+                    grid = new GridProvider();
+                }
+                return grid;
+            }
+
+            @SuppressWarnings("static-method")
+            long getInitGrid() {
+                return initGrid.asAddress();
             }
-            return grid;
-        }
 
-        @SuppressWarnings("static-method")
-        long getInitGrid() {
-            return initGrid.asAddress();
+            @SuppressWarnings("static-method")
+            long getKillGrid() {
+                return killGrid.asAddress();
+            }
         }
 
-        @SuppressWarnings("static-method")
-        long getKillGrid() {
-            return killGrid.asAddress();
+        @Override
+        public Object initGrid(REnvironment gridEvalEnv) {
+            long initGrid = GridProvider.gridProvider().getInitGrid();
+            return callRFFINode.invokeCall(new NativeCallInfo("L_initGrid", new SymbolHandle(initGrid), DLL.findLibrary("grid")), new Object[]{gridEvalEnv});
         }
-    }
 
-    @Override
-    public Object initGrid(REnvironment gridEvalEnv) {
-        long initGrid = GridProvider.gridProvider().getInitGrid();
-        return RFFIFactory.getRFFI().getCallRFFI().callRFFINode().invokeCall(new NativeCallInfo("L_initGrid", new SymbolHandle(initGrid), DLL.findLibrary("grid")), new Object[]{gridEvalEnv});
+        @Override
+        public Object killGrid() {
+            long killGrid = GridProvider.gridProvider().getKillGrid();
+            return callRFFINode.invokeCall(new NativeCallInfo("L_killGrid", new SymbolHandle(killGrid), DLL.findLibrary("grid")), new Object[0]);
+        }
     }
 
     @Override
-    public Object killGrid() {
-        long killGrid = GridProvider.gridProvider().getKillGrid();
-        return RFFIFactory.getRFFI().getCallRFFI().callRFFINode().invokeCall(new NativeCallInfo("L_killGrid", new SymbolHandle(killGrid), DLL.findLibrary("grid")), new Object[0]);
+    public GridRFFINode gridRFFINode() {
+        return new Generic_GridRFFINode();
     }
 }
diff --git a/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/generic/Generic_Tools.java b/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/generic/Generic_Tools.java
index bd5724e4bb19ed8cfde6c34cb4804872ebd91cb0..a7d4a797d516586fcb2b64369b4487be785948d0 100644
--- a/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/generic/Generic_Tools.java
+++ b/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/generic/Generic_Tools.java
@@ -30,6 +30,7 @@ import com.oracle.truffle.r.runtime.conn.RConnection;
 import com.oracle.truffle.r.runtime.data.RLogicalVector;
 import com.oracle.truffle.r.runtime.data.RStringVector;
 import com.oracle.truffle.r.runtime.env.REnvironment;
+import com.oracle.truffle.r.runtime.ffi.CallRFFI;
 import com.oracle.truffle.r.runtime.ffi.DLL;
 import com.oracle.truffle.r.runtime.ffi.DLL.SymbolHandle;
 import com.oracle.truffle.r.runtime.ffi.LibPaths;
@@ -38,46 +39,59 @@ import com.oracle.truffle.r.runtime.ffi.RFFIFactory;
 import com.oracle.truffle.r.runtime.ffi.ToolsRFFI;
 
 public class Generic_Tools implements ToolsRFFI {
-    private static final class ToolsProvider {
-        private static final String C_PARSE_RD = "C_parseRd";
-        private static ToolsProvider tools;
-        private static DLL.SymbolHandle parseRd;
+    private static class Generic_ToolsRFFINode extends ToolsRFFINode {
+        private CallRFFI.CallRFFINode callRFFINode = RFFIFactory.getRFFI().getCallRFFI().callRFFINode();
 
-        @TruffleBoundary
-        private ToolsProvider() {
-            System.load(LibPaths.getPackageLibPath("tools"));
-            parseRd = DLL.findSymbol(C_PARSE_RD, "tools", DLL.RegisteredNativeSymbol.any());
-            assert parseRd != DLL.SYMBOL_NOT_FOUND;
-        }
+        private static final class ToolsProvider {
+            private static final String C_PARSE_RD = "C_parseRd";
+            private static ToolsProvider tools;
+            private static DLL.SymbolHandle parseRd;
+
+            @TruffleBoundary
+            private ToolsProvider() {
+                System.load(LibPaths.getPackageLibPath("tools"));
+                parseRd = DLL.findSymbol(C_PARSE_RD, "tools", DLL.RegisteredNativeSymbol.any());
+                assert parseRd != DLL.SYMBOL_NOT_FOUND;
+            }
+
+            static ToolsProvider toolsProvider() {
+                if (tools == null) {
+                    tools = new ToolsProvider();
+                }
+                return tools;
+            }
 
-        static ToolsProvider toolsProvider() {
-            if (tools == null) {
-                tools = new ToolsProvider();
+            @SuppressWarnings("static-method")
+            long getParseRd() {
+                return parseRd.asAddress();
             }
-            return tools;
         }
 
-        @SuppressWarnings("static-method")
-        long getParseRd() {
-            return parseRd.asAddress();
+        private static final Semaphore parseRdCritical = new Semaphore(1, false);
+        private NativeCallInfo nativeCallInfo;
+
+        @Override
+        public Object parseRd(RConnection con, REnvironment srcfile, RLogicalVector verbose, RLogicalVector fragment, RStringVector basename, RLogicalVector warningCalls, Object macros,
+                        RLogicalVector warndups) {
+            // The C code is not thread safe.
+            try {
+                parseRdCritical.acquire();
+                long parseRd = ToolsProvider.toolsProvider().getParseRd();
+                if (nativeCallInfo == null) {
+                    nativeCallInfo = new NativeCallInfo("parseRd", new SymbolHandle(parseRd), DLL.findLibrary("tools"));
+                }
+                return callRFFINode.invokeCall(nativeCallInfo,
+                                new Object[]{con, srcfile, verbose, fragment, basename, warningCalls, macros, warndups});
+            } catch (Throwable ex) {
+                throw RInternalError.shouldNotReachHere(ex, "error during Rd parsing");
+            } finally {
+                parseRdCritical.release();
+            }
         }
     }
 
-    private static final Semaphore parseRdCritical = new Semaphore(1, false);
-
     @Override
-    public Object parseRd(RConnection con, REnvironment srcfile, RLogicalVector verbose, RLogicalVector fragment, RStringVector basename, RLogicalVector warningCalls, Object macros,
-                    RLogicalVector warndups) {
-        // The C code is not thread safe.
-        try {
-            parseRdCritical.acquire();
-            long parseRd = ToolsProvider.toolsProvider().getParseRd();
-            return RFFIFactory.getRFFI().getCallRFFI().callRFFINode().invokeCall(new NativeCallInfo("parseRd", new SymbolHandle(parseRd), DLL.findLibrary("tools")),
-                            new Object[]{con, srcfile, verbose, fragment, basename, warningCalls, macros, warndups});
-        } catch (Throwable ex) {
-            throw RInternalError.shouldNotReachHere(ex, "error during Rd parsing");
-        } finally {
-            parseRdCritical.release();
-        }
+    public ToolsRFFINode toolsRFFINode() {
+        return new Generic_ToolsRFFINode();
     }
 }
diff --git a/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jni/JNI_C.java b/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jni/JNI_C.java
index 1928c09f73d4053f518cb777a1c8257b78451ae6..13092011133a88a6f06d7c522d8694b2cc92518f 100644
--- a/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jni/JNI_C.java
+++ b/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jni/JNI_C.java
@@ -30,21 +30,27 @@ import com.oracle.truffle.r.runtime.ffi.CRFFI;
 import com.oracle.truffle.r.runtime.ffi.NativeCallInfo;
 
 public class JNI_C implements CRFFI {
-
-    /**
-     * This is rather similar to {@link JNI_Call}, except the objects are guaranteed to be native
-     * array types, no upcalls are possible, and no result is returned. However, the receiving
-     * function expects actual native arrays (not SEXPs), so these have to be handled on the JNI
-     * side.
-     */
-    @Override
-    @TruffleBoundary
-    public synchronized void invoke(NativeCallInfo nativeCallInfo, Object[] args) {
-        if (traceEnabled()) {
-            traceDownCall(nativeCallInfo.name, args);
+    private static class JNI_CRFFINode extends CRFFINode {
+        /**
+         * This is rather similar to {@link JNI_Call}, except the objects are guaranteed to be
+         * native array types, no upcalls are possible, and no result is returned. However, the
+         * receiving function expects actual native arrays (not SEXPs), so these have to be handled
+         * on the JNI side.
+         */
+        @Override
+        @TruffleBoundary
+        public synchronized void invoke(NativeCallInfo nativeCallInfo, Object[] args) {
+            if (traceEnabled()) {
+                traceDownCall(nativeCallInfo.name, args);
+            }
+            c(nativeCallInfo.address.asAddress(), args);
         }
-        c(nativeCallInfo.address.asAddress(), args);
     }
 
     private static native void c(long address, Object[] args);
+
+    @Override
+    public CRFFINode getCRFFINode() {
+        return new JNI_CRFFINode();
+    }
 }
diff --git a/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jni/JNI_Call.java b/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jni/JNI_Call.java
index 8a36622c131f5246cb1e0183991db17dbd4863f4..f39d93925ece061d526918cda5dd6933dec4b809 100644
--- a/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jni/JNI_Call.java
+++ b/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jni/JNI_Call.java
@@ -49,7 +49,7 @@ import com.oracle.truffle.r.runtime.ffi.UpCallsRFFIFactory;
  */
 public class JNI_Call implements CallRFFI {
 
-    public static class JNI_CallRFFINode extends CallRFFINode {
+    private static class JNI_CallRFFINode extends CallRFFINode {
 
         @Override
         @TruffleBoundary
diff --git a/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jni/JNI_Lapack.java b/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jni/JNI_Lapack.java
index e6d37de0f1f956b90a964530f09c98c501ab724e..6550897121e2872e3904231456e8e42fc25a22b2 100644
--- a/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jni/JNI_Lapack.java
+++ b/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jni/JNI_Lapack.java
@@ -26,77 +26,85 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
 import com.oracle.truffle.r.runtime.ffi.LapackRFFI;
 
 public class JNI_Lapack implements LapackRFFI {
-    @Override
-    @TruffleBoundary
-    public void ilaver(int[] version) {
-        native_ilaver(version);
-    }
-
-    @Override
-    @TruffleBoundary
-    public int dgeev(char jobVL, char jobVR, int n, double[] a, int lda, double[] wr, double[] wi, double[] vl, int ldvl, double[] vr, int ldvr, double[] work, int lwork) {
-        return native_dgeev(jobVL, jobVR, n, a, lda, wr, wi, vl, ldvl, vr, ldvr, work, lwork);
-    }
-
-    @Override
-    @TruffleBoundary
-    public int dgeqp3(int m, int n, double[] a, int lda, int[] jpvt, double[] tau, double[] work, int lwork) {
-        return native_dgeqp3(m, n, a, lda, jpvt, tau, work, lwork);
-    }
-
-    @Override
-    @TruffleBoundary
-    public int dormqr(char side, char trans, int m, int n, int k, double[] a, int lda, double[] tau, double[] c, int ldc, double[] work, int lwork) {
-        return native_dormqr(side, trans, m, n, k, a, lda, tau, c, ldc, work, lwork);
-    }
-
-    @Override
-    @TruffleBoundary
-    public int dtrtrs(char uplo, char trans, char diag, int n, int nrhs, double[] a, int lda, double[] b, int ldb) {
-        return native_dtrtrs(uplo, trans, diag, n, nrhs, a, lda, b, ldb);
-    }
-
-    @Override
-    @TruffleBoundary
-    public int dgetrf(int m, int n, double[] a, int lda, int[] ipiv) {
-        return native_dgetrf(m, n, a, lda, ipiv);
-    }
-
-    @Override
-    @TruffleBoundary
-    public int dpotrf(char uplo, int n, double[] a, int lda) {
-        return native_dpotrf(uplo, n, a, lda);
+    private static class JNI_LapackRFFINode extends LapackRFFINode {
+        @Override
+        @TruffleBoundary
+        public void ilaver(int[] version) {
+            native_ilaver(version);
+        }
+
+        @Override
+        @TruffleBoundary
+        public int dgeev(char jobVL, char jobVR, int n, double[] a, int lda, double[] wr, double[] wi, double[] vl, int ldvl, double[] vr, int ldvr, double[] work, int lwork) {
+            return native_dgeev(jobVL, jobVR, n, a, lda, wr, wi, vl, ldvl, vr, ldvr, work, lwork);
+        }
+
+        @Override
+        @TruffleBoundary
+        public int dgeqp3(int m, int n, double[] a, int lda, int[] jpvt, double[] tau, double[] work, int lwork) {
+            return native_dgeqp3(m, n, a, lda, jpvt, tau, work, lwork);
+        }
+
+        @Override
+        @TruffleBoundary
+        public int dormqr(char side, char trans, int m, int n, int k, double[] a, int lda, double[] tau, double[] c, int ldc, double[] work, int lwork) {
+            return native_dormqr(side, trans, m, n, k, a, lda, tau, c, ldc, work, lwork);
+        }
+
+        @Override
+        @TruffleBoundary
+        public int dtrtrs(char uplo, char trans, char diag, int n, int nrhs, double[] a, int lda, double[] b, int ldb) {
+            return native_dtrtrs(uplo, trans, diag, n, nrhs, a, lda, b, ldb);
+        }
+
+        @Override
+        @TruffleBoundary
+        public int dgetrf(int m, int n, double[] a, int lda, int[] ipiv) {
+            return native_dgetrf(m, n, a, lda, ipiv);
+        }
+
+        @Override
+        @TruffleBoundary
+        public int dpotrf(char uplo, int n, double[] a, int lda) {
+            return native_dpotrf(uplo, n, a, lda);
+        }
+
+        @Override
+        @TruffleBoundary
+        public int dpstrf(char uplo, int n, double[] a, int lda, int[] piv, int[] rank, double tol, double[] work) {
+            return native_dpstrf(uplo, n, a, lda, piv, rank, tol, work);
+        }
+
+        @Override
+        @TruffleBoundary
+        public int dgesv(int n, int nrhs, double[] a, int lda, int[] ipiv, double[] b, int ldb) {
+            return native_dgesv(n, nrhs, a, lda, ipiv, b, ldb);
+        }
+
+        @Override
+        @TruffleBoundary
+        public double dlange(char norm, int m, int n, double[] a, int lda, double[] work) {
+            return native_dlange(norm, m, n, a, lda, work);
+        }
+
+        @Override
+        @TruffleBoundary
+        public int dgecon(char norm, int n, double[] a, int lda, double anorm, double[] rcond, double[] work, int[] iwork) {
+            return native_dgecon(norm, n, a, lda, anorm, rcond, work, iwork);
+        }
+
+        @Override
+        public int dsyevr(char jobz, char range, char uplo, int n, double[] a, int lda, double vl, double vu, int il, int iu, double abstol, int[] m,
+                        double[] w, double[] z, int ldz, int[] isuppz, double[] work, int lwork, int[] iwork, int liwork) {
+            return native_dsyevr(jobz, range, uplo, n, a, lda, vl, vu, il, iu, abstol, m, w, z, ldz, isuppz, work, lwork, iwork, liwork);
+        }
     }
 
     @Override
-    @TruffleBoundary
-    public int dpstrf(char uplo, int n, double[] a, int lda, int[] piv, int[] rank, double tol, double[] work) {
-        return native_dpstrf(uplo, n, a, lda, piv, rank, tol, work);
+    public LapackRFFINode getLapackRFFINode() {
+        return new JNI_LapackRFFINode();
     }
 
-    @Override
-    @TruffleBoundary
-    public int dgesv(int n, int nrhs, double[] a, int lda, int[] ipiv, double[] b, int ldb) {
-        return native_dgesv(n, nrhs, a, lda, ipiv, b, ldb);
-    }
-
-    @Override
-    @TruffleBoundary
-    public double dlange(char norm, int m, int n, double[] a, int lda, double[] work) {
-        return native_dlange(norm, m, n, a, lda, work);
-    }
-
-    @Override
-    @TruffleBoundary
-    public int dgecon(char norm, int n, double[] a, int lda, double anorm, double[] rcond, double[] work, int[] iwork) {
-        return native_dgecon(norm, n, a, lda, anorm, rcond, work, iwork);
-    }
-
-    @Override
-    public int dsyevr(char jobz, char range, char uplo, int n, double[] a, int lda, double vl, double vu, int il, int iu, double abstol, int[] m,
-                    double[] w, double[] z, int ldz, int[] isuppz, double[] work, int lwork, int[] iwork, int liwork) {
-        return native_dsyevr(jobz, range, uplo, n, a, lda, vl, vu, il, iu, abstol, m, w, z, ldz, isuppz, work, lwork, iwork, liwork);
-    }
     // Checkstyle: stop method name
 
     private static native void native_ilaver(int[] version);
diff --git a/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jni/JNI_PCRE.java b/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jni/JNI_PCRE.java
index dda34a3c1896e369eb8799ffdd981cb4129d8688..cfad8dc4d53dd71361b00604d684f8cd5b28dde9 100644
--- a/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jni/JNI_PCRE.java
+++ b/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jni/JNI_PCRE.java
@@ -28,45 +28,47 @@ import com.oracle.truffle.r.runtime.RInternalError;
 import com.oracle.truffle.r.runtime.ffi.PCRERFFI;
 
 public class JNI_PCRE implements PCRERFFI {
-    @Override
-    public long maketables() {
-        return nativeMaketables();
-    }
+    private static class JNI_PCRERFFINode extends PCRERFFINode {
+        @Override
+        public long maketables() {
+            return nativeMaketables();
+        }
 
-    @Override
-    public Result compile(String pattern, int options, long tables) {
-        return nativeCompile(pattern, options, tables);
-    }
+        @Override
+        public Result compile(String pattern, int options, long tables) {
+            return nativeCompile(pattern, options, tables);
+        }
 
-    @Override
-    public int getCaptureCount(long code, long extra) {
-        int res = nativeGetCaptureCount(code, extra);
-        if (res < 0) {
-            CompilerDirectives.transferToInterpreter();
-            throw RError.error(RError.NO_CALLER, RError.Message.WRONG_PCRE_INFO, res);
+        @Override
+        public int getCaptureCount(long code, long extra) {
+            int res = nativeGetCaptureCount(code, extra);
+            if (res < 0) {
+                CompilerDirectives.transferToInterpreter();
+                throw RError.error(RError.NO_CALLER, RError.Message.WRONG_PCRE_INFO, res);
+            }
+            return res;
         }
-        return res;
-    }
 
-    @Override
-    public String[] getCaptureNames(long code, long extra, int captureCount) {
-        String[] ret = new String[captureCount];
-        int res = nativeGetCaptureNames(code, extra, ret);
-        if (res < 0) {
-            CompilerDirectives.transferToInterpreter();
-            throw RError.error(RError.NO_CALLER, RError.Message.WRONG_PCRE_INFO, res);
+        @Override
+        public String[] getCaptureNames(long code, long extra, int captureCount) {
+            String[] ret = new String[captureCount];
+            int res = nativeGetCaptureNames(code, extra, ret);
+            if (res < 0) {
+                CompilerDirectives.transferToInterpreter();
+                throw RError.error(RError.NO_CALLER, RError.Message.WRONG_PCRE_INFO, res);
+            }
+            return ret;
         }
-        return ret;
-    }
 
-    @Override
-    public Result study(long code, int options) {
-        throw RInternalError.unimplemented("pcre_study");
-    }
+        @Override
+        public Result study(long code, int options) {
+            throw RInternalError.unimplemented("pcre_study");
+        }
 
-    @Override
-    public int exec(long code, long extra, String subject, int offset, int options, int[] ovector) {
-        return nativeExec(code, extra, subject, offset, options, ovector, ovector.length);
+        @Override
+        public int exec(long code, long extra, String subject, int offset, int options, int[] ovector) {
+            return nativeExec(code, extra, subject, offset, options, ovector, ovector.length);
+        }
     }
 
     private static native long nativeMaketables();
@@ -80,4 +82,9 @@ public class JNI_PCRE implements PCRERFFI {
     private static native int nativeExec(long code, long extra, String subject, int offset,
                     int options, int[] ovector, int ovectorLen);
 
+    @Override
+    public PCRERFFINode pcreRFFINode() {
+        return new JNI_PCRERFFINode();
+    }
+
 }
diff --git a/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jni/JNI_RAppl.java b/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jni/JNI_RAppl.java
index b48a2fa9c113c12f68a2632ee147f1b3c55c4287..84e9f41389ea11c075246182b01961df103c41c7 100644
--- a/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jni/JNI_RAppl.java
+++ b/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jni/JNI_RAppl.java
@@ -26,24 +26,30 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
 import com.oracle.truffle.r.runtime.ffi.RApplRFFI;
 
 public class JNI_RAppl implements RApplRFFI {
-    @Override
-    @TruffleBoundary
-    public void dqrdc2(double[] x, int ldx, int n, int p, double tol, int[] rank, double[] qraux, int[] pivot, double[] work) {
-        native_dqrdc2(x, ldx, n, p, tol, rank, qraux, pivot, work);
+    private static class JNI_RApplRFFINode extends RApplRFFINode {
+        @Override
+        @TruffleBoundary
+        public void dqrdc2(double[] x, int ldx, int n, int p, double tol, int[] rank, double[] qraux, int[] pivot, double[] work) {
+            native_dqrdc2(x, ldx, n, p, tol, rank, qraux, pivot, work);
+        }
+
+        @Override
+        @TruffleBoundary
+        public void dqrcf(double[] x, int n, int k, double[] qraux, double[] y, int ny, double[] b, int[] info) {
+            native_dqrcf(x, n, k, qraux, y, ny, b, info);
+        }
+
+        @Override
+        @TruffleBoundary
+        public void dqrls(double[] x, int n, int p, double[] y, int ny, double tol, double[] b, double[] rsd, double[] qty, int[] k, int[] jpvt, double[] qraux, double[] work) {
+            native_dqrls(x, n, p, y, ny, tol, b, rsd, qty, k, jpvt, qraux, work);
+        }
     }
 
     @Override
-    @TruffleBoundary
-    public void dqrcf(double[] x, int n, int k, double[] qraux, double[] y, int ny, double[] b, int[] info) {
-        native_dqrcf(x, n, k, qraux, y, ny, b, info);
+    public RApplRFFINode rApplRFFINode() {
+        return new JNI_RApplRFFINode();
     }
-
-    @Override
-    @TruffleBoundary
-    public void dqrls(double[] x, int n, int p, double[] y, int ny, double tol, double[] b, double[] rsd, double[] qty, int[] k, int[] jpvt, double[] qraux, double[] work) {
-        native_dqrls(x, n, p, y, ny, tol, b, rsd, qty, k, jpvt, qraux, work);
-    }
-
     // Checkstyle: stop method name
 
     private static native void native_dqrdc2(double[] x, int ldx, int n, int p, double tol, int[] rank, double[] qraux, int[] pivot, double[] work);
diff --git a/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jni/JNI_UserRng.java b/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jni/JNI_UserRng.java
index 1387cb869aaaa5c1a8f649187c2aa27cf7d4a24d..d3df6fab2cec55f4abe60efa82b11b30dcdd99fc 100644
--- a/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jni/JNI_UserRng.java
+++ b/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jni/JNI_UserRng.java
@@ -27,36 +27,44 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
 import com.oracle.truffle.r.runtime.ffi.UserRngRFFI;
 
 public class JNI_UserRng implements UserRngRFFI {
-    @Override
-    @TruffleBoundary
-    public void init(int seed) {
-        init(Function.Init.getSymbolHandle().asAddress(), seed);
+    private static class JNI_UserRngRFFINode extends UserRngRFFINode {
+        @Override
+        @TruffleBoundary
+        public void init(int seed) {
+            nativeInit(Function.Init.getSymbolHandle().asAddress(), seed);
 
-    }
+        }
 
-    @Override
-    @TruffleBoundary
-    public double rand() {
-        return rand(Function.Rand.getSymbolHandle().asAddress());
-    }
+        @Override
+        @TruffleBoundary
+        public double rand() {
+            return nativeRand(Function.Rand.getSymbolHandle().asAddress());
+        }
 
-    @Override
-    @TruffleBoundary
-    public int nSeed() {
-        return nSeed(Function.NSeed.getSymbolHandle().asAddress());
+        @Override
+        @TruffleBoundary
+        public int nSeed() {
+            return nativeNSeed(Function.NSeed.getSymbolHandle().asAddress());
+        }
+
+        @Override
+        @TruffleBoundary
+        public void seeds(int[] n) {
+            nativeSeeds(Function.Seedloc.getSymbolHandle().asAddress(), n);
+        }
     }
 
     @Override
-    @TruffleBoundary
-    public void seeds(int[] n) {
-        seeds(Function.Seedloc.getSymbolHandle().asAddress(), n);
+    public UserRngRFFINode userRngRFFINode() {
+        return new JNI_UserRngRFFINode();
     }
 
-    private static native void init(long address, int seed);
+    private static native void nativeInit(long address, int seed);
+
+    private static native double nativeRand(long address);
 
-    private static native double rand(long address);
+    private static native int nativeNSeed(long address);
 
-    private static native int nSeed(long address);
+    private static native void nativeSeeds(long address, int[] n);
 
-    private static native void seeds(long address, int[] n);
 }
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/CRFFI.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/CRFFI.java
index 24cdcd702ad4a17447951e0f3aff6fbf9f5267fc..25fa782b9c8939f4717495b7b6a312bdcc6416e1 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/CRFFI.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/CRFFI.java
@@ -22,14 +22,20 @@
  */
 package com.oracle.truffle.r.runtime.ffi;
 
+import com.oracle.truffle.api.nodes.Node;
+
 /**
  * Support for the {.C} and {.Fortran} calls.
  */
 public interface CRFFI {
-    /**
-     * Invoke the native method identified by {@code symbolInfo} passing it the arguments in
-     * {@code args}. The values in {@code args} should be native types,e.g., {@code double[]} not
-     * {@code RDoubleVector}.
-     */
-    void invoke(NativeCallInfo nativeCallInfo, Object[] args);
+    abstract class CRFFINode extends Node {
+        /**
+         * Invoke the native method identified by {@code symbolInfo} passing it the arguments in
+         * {@code args}. The values in {@code args} should be native types,e.g., {@code double[]}
+         * not {@code RDoubleVector}.
+         */
+        public abstract void invoke(NativeCallInfo nativeCallInfo, Object[] args);
+    }
+
+    CRFFINode getCRFFINode();
 }
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/CallRFFI.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/CallRFFI.java
index 5930f8d17e6230d6fcb82bb00047a2a5e72bf881..a773e43a2fc7d8d6af75ded36a1c99cb23c42971 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/CallRFFI.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/CallRFFI.java
@@ -28,7 +28,7 @@ import com.oracle.truffle.api.nodes.Node;
  * Support for the {.Call} and {.External} calls.
  */
 public interface CallRFFI {
-    public abstract static class CallRFFINode extends Node {
+    abstract class CallRFFINode extends Node {
         /**
          * Invoke the native function identified by {@code symbolInfo} passing it the arguments in
          * {@code args}. The values in {@code args} can be any of the types used to represent
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/GridRFFI.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/GridRFFI.java
index 561ba671024c66baf1db51eacd3776bc0b218778..f413af683385fc1a4e029e7c9df982189261cd0c 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/GridRFFI.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/GridRFFI.java
@@ -22,10 +22,15 @@
  */
 package com.oracle.truffle.r.runtime.ffi;
 
+import com.oracle.truffle.api.nodes.Node;
 import com.oracle.truffle.r.runtime.env.REnvironment;
 
 public interface GridRFFI {
-    Object initGrid(REnvironment gridEvalEnv);
+    abstract class GridRFFINode extends Node {
+        public abstract Object initGrid(REnvironment gridEvalEnv);
 
-    Object killGrid();
+        public abstract Object killGrid();
+    }
+
+    GridRFFINode gridRFFINode();
 }
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/LapackRFFI.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/LapackRFFI.java
index 9d537ec5006221e9796736eaf19bc85e645631c0..306186a614732b107b6f5bd119ede7fde980fe78 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/LapackRFFI.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/LapackRFFI.java
@@ -22,67 +22,73 @@
  */
 package com.oracle.truffle.r.runtime.ffi;
 
+import com.oracle.truffle.api.nodes.Node;
+
 /**
  * Collection of statically typed Lapack methods that are used in the {@code base} package. The
  * signatures match the Fortran definition with the exception that the "info" value is returned as
  * the result of the call.
  */
 public interface LapackRFFI {
-    /**
-     * Return version info, mjor, minor, patch, in {@code version}.
-     */
-    void ilaver(int[] version);
+    abstract class LapackRFFINode extends Node {
+        /**
+         * Return version info, mjor, minor, patch, in {@code version}.
+         */
+        public abstract void ilaver(int[] version);
+
+        /**
+         * See <a href="http://www.netlib.org/lapack/explore-html/d9/d28/dgeev_8f.html">spec</a>.
+         */
+        public abstract int dgeev(char jobVL, char jobVR, int n, double[] a, int lda, double[] wr, double[] wi, double[] vl, int ldvl, double[] vr, int ldvr, double[] work, int lwork);
 
-    /**
-     * See <a href="http://www.netlib.org/lapack/explore-html/d9/d28/dgeev_8f.html">spec</a>.
-     */
-    int dgeev(char jobVL, char jobVR, int n, double[] a, int lda, double[] wr, double[] wi, double[] vl, int ldvl, double[] vr, int ldvr, double[] work, int lwork);
+        /**
+         * See <a href="http://www.netlib.org/lapack/explore-html/db/de5/dgeqp3_8f.html">spec</a>.
+         */
+        public abstract int dgeqp3(int m, int n, double[] a, int lda, int[] jpvt, double[] tau, double[] work, int lwork);
 
-    /**
-     * See <a href="http://www.netlib.org/lapack/explore-html/db/de5/dgeqp3_8f.html">spec</a>.
-     */
-    int dgeqp3(int m, int n, double[] a, int lda, int[] jpvt, double[] tau, double[] work, int lwork);
+        /**
+         * See <a href="http://www.netlib.org/lapack/explore-html/da/d82/dormqr_8f.html">spec</a>.
+         */
+        public abstract int dormqr(char side, char trans, int m, int n, int k, double[] a, int lda, double[] tau, double[] c, int ldc, double[] work, int lwork);
 
-    /**
-     * See <a href="http://www.netlib.org/lapack/explore-html/da/d82/dormqr_8f.html">spec</a>.
-     */
-    int dormqr(char side, char trans, int m, int n, int k, double[] a, int lda, double[] tau, double[] c, int ldc, double[] work, int lwork);
+        /**
+         * See <a href="http://www.netlib.org/lapack/explore-html/d6/d6f/dtrtrs_8f.html">spec</a>.
+         */
+        public abstract int dtrtrs(char uplo, char trans, char diag, int n, int nrhs, double[] a, int lda, double[] b, int ldb);
 
-    /**
-     * See <a href="http://www.netlib.org/lapack/explore-html/d6/d6f/dtrtrs_8f.html">spec</a>.
-     */
-    int dtrtrs(char uplo, char trans, char diag, int n, int nrhs, double[] a, int lda, double[] b, int ldb);
+        /**
+         * See <a href="http://www.netlib.org/lapack/explore-html/d3/d6a/dgetrf_8f.html">spec</a>.
+         */
+        public abstract int dgetrf(int m, int n, double[] a, int lda, int[] ipiv);
 
-    /**
-     * See <a href="http://www.netlib.org/lapack/explore-html/d3/d6a/dgetrf_8f.html">spec</a>.
-     */
-    int dgetrf(int m, int n, double[] a, int lda, int[] ipiv);
+        /**
+         * See <a href="http://www.netlib.org/lapack/explore-html/d0/d8a/dpotrf_8f.html">spec</a>.
+         */
+        public abstract int dpotrf(char uplo, int n, double[] a, int lda);
 
-    /**
-     * See <a href="http://www.netlib.org/lapack/explore-html/d0/d8a/dpotrf_8f.html">spec</a>.
-     */
-    int dpotrf(char uplo, int n, double[] a, int lda);
+        /**
+         * See <a href="http://www.netlib.org/lapack/explore-html/dd/dad/dpstrf_8f.html">spec</a>.
+         */
+        public abstract int dpstrf(char uplo, int n, double[] a, int lda, int[] piv, int[] rank, double tol, double[] work);
 
-    /**
-     * See <a href="http://www.netlib.org/lapack/explore-html/dd/dad/dpstrf_8f.html">spec</a>.
-     */
-    int dpstrf(char uplo, int n, double[] a, int lda, int[] piv, int[] rank, double tol, double[] work);
+        /**
+         * See <a href="http://www.netlib.org/lapack/explore-html/d8/d72/dgesv_8f.html">spec</a>.
+         */
+        public abstract int dgesv(int n, int nrhs, double[] a, int lda, int[] ipiv, double[] b, int ldb);
 
-    /**
-     * See <a href="http://www.netlib.org/lapack/explore-html/d8/d72/dgesv_8f.html">spec</a>.
-     */
-    int dgesv(int n, int nrhs, double[] a, int lda, int[] ipiv, double[] b, int ldb);
+        /**
+         * See <a href="http://www.netlib.org/lapack/explore-html/dc/d09/dlange_8f.html">spec</a>.
+         */
+        public abstract double dlange(char norm, int m, int n, double[] a, int lda, double[] work);
 
-    /**
-     * See <a href="http://www.netlib.org/lapack/explore-html/dc/d09/dlange_8f.html">spec</a>.
-     */
-    double dlange(char norm, int m, int n, double[] a, int lda, double[] work);
+        /**
+         * See <a href="http://www.netlib.org/lapack/explore-html/db/de4/dgecon_8f.html">spec</a>.
+         */
+        public abstract int dgecon(char norm, int n, double[] a, int lda, double anorm, double[] rcond, double[] work, int[] iwork);
 
-    /**
-     * See <a href="http://www.netlib.org/lapack/explore-html/db/de4/dgecon_8f.html">spec</a>.
-     */
-    int dgecon(char norm, int n, double[] a, int lda, double anorm, double[] rcond, double[] work, int[] iwork);
+        public abstract int dsyevr(char jobz, char range, char uplo, int n, double[] a, int lda, double vl, double vu, int il, int iu, double abstol, int[] m, double[] w,
+                        double[] z, int ldz, int[] isuppz, double[] work, int lwork, int[] iwork, int liwork);
+    }
 
-    int dsyevr(char jobz, char range, char uplo, int n, double[] a, int lda, double vl, double vu, int il, int iu, double abstol, int[] m, double[] w,
-                    double[] z, int ldz, int[] isuppz, double[] work, int lwork, int[] iwork, int liwork);
+    LapackRFFINode getLapackRFFINode();
 }
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/PCRERFFI.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/PCRERFFI.java
index 1825446c61a5d71aa069c44c0460febb2b902922..81a91ecb1dd6fc3fb695490d70743683550d32a6 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/PCRERFFI.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/PCRERFFI.java
@@ -22,6 +22,8 @@
  */
 package com.oracle.truffle.r.runtime.ffi;
 
+import com.oracle.truffle.api.nodes.Node;
+
 /**
  * An interface to the <a href="http://www.pcre.org/original/doc/html/index.html">PCRE</a> library
  * for Perl regular expressions.
@@ -47,16 +49,21 @@ public interface PCRERFFI {
         }
     }
 
-    long maketables();
+    abstract class PCRERFFINode extends Node {
+
+        public abstract long maketables();
 
-    Result compile(String pattern, int options, long tables);
+        public abstract Result compile(String pattern, int options, long tables);
 
-    int getCaptureCount(long code, long extra);
+        public abstract int getCaptureCount(long code, long extra);
 
-    String[] getCaptureNames(long code, long extra, int captureCount);
+        public abstract String[] getCaptureNames(long code, long extra, int captureCount);
 
-    Result study(long code, int options);
+        public abstract Result study(long code, int options);
+
+        public abstract int exec(long code, long extra, String subject, int offset, int options, int[] ovector);
+    }
 
-    int exec(long code, long extra, String subject, int offset, int options, int[] ovector);
+    PCRERFFINode pcreRFFINode();
 
 }
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/RApplRFFI.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/RApplRFFI.java
index 4f20c85d9b914cd51b4ae9ef42aa7da45a300317..bc40f240118696438a50e3bb626e0b2501351e3a 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/RApplRFFI.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/RApplRFFI.java
@@ -22,17 +22,23 @@
  */
 package com.oracle.truffle.r.runtime.ffi;
 
+import com.oracle.truffle.api.nodes.Node;
+
 /**
  * Collection of statically typed methods (from Linpack and elsewhere) that are built in to a GnuR
  * implementation and factored out into a separate library in FastR. This corresponds to the
  * {@code libappl} library in GnuR.
  */
 public interface RApplRFFI {
-    // Linpack
-    void dqrdc2(double[] x, int ldx, int n, int p, double tol, int[] rank, double[] qraux, int[] pivot, double[] work);
+    abstract class RApplRFFINode extends Node {
+        // Linpack
+        public abstract void dqrdc2(double[] x, int ldx, int n, int p, double tol, int[] rank, double[] qraux, int[] pivot, double[] work);
+
+        public abstract void dqrcf(double[] x, int n, int k, double[] qraux, double[] y, int ny, double[] b, int[] info);
 
-    void dqrcf(double[] x, int n, int k, double[] qraux, double[] y, int ny, double[] b, int[] info);
+        public abstract void dqrls(double[] x, int n, int p, double[] y, int ny, double tol, double[] b, double[] rsd, double[] qty, int[] k, int[] jpvt, double[] qraux, double[] work);
+    }
 
-    void dqrls(double[] x, int n, int p, double[] y, int ny, double tol, double[] b, double[] rsd, double[] qty, int[] k, int[] jpvt, double[] qraux, double[] work);
+    RApplRFFINode rApplRFFINode();
 
 }
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/StatsRFFI.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/StatsRFFI.java
index a6940fbd86b4969722001e7e4257765d43252746..6e4d58f145b96e6a330566e47c53d54e9842dfeb 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/StatsRFFI.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/StatsRFFI.java
@@ -30,7 +30,7 @@ import com.oracle.truffle.api.nodes.Node;
  * {@code fft_factor} and {@code fft_work}. functions from the GNU R C code.
  */
 public interface StatsRFFI {
-    public abstract static class FFTNode extends Node {
+    abstract class FFTNode extends Node {
         public abstract void executeFactor(int n, int[] pmaxf, int[] pmaxp);
 
         public abstract int executeWork(double[] a, int nseg, int n, int nspn, int isn, double[] work, int[] iwork);
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/ToolsRFFI.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/ToolsRFFI.java
index 04bc7e21a310117776602427c0dee389e3154b6b..85538d2347d6d48776c8adab362c2db9ff192377 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/ToolsRFFI.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/ToolsRFFI.java
@@ -22,6 +22,7 @@
  */
 package com.oracle.truffle.r.runtime.ffi;
 
+import com.oracle.truffle.api.nodes.Node;
 import com.oracle.truffle.r.runtime.conn.RConnection;
 import com.oracle.truffle.r.runtime.data.RLogicalVector;
 import com.oracle.truffle.r.runtime.data.RStringVector;
@@ -31,14 +32,18 @@ import com.oracle.truffle.r.runtime.env.REnvironment;
  * Interface to native (C) methods provided by the {@code tools} package.
  */
 public interface ToolsRFFI {
-    /**
-     * This invokes the Rd parser, written in C, and part of GnuR, that does its work using the R
-     * FFI interface. The R code initially invokes this via {@code .External2(C_parseRd, ...)},
-     * which has a custom specialization in the implementation of the {@code .External2} builtin.
-     * That does some work in Java, and then calls this method to invoke the actual C code. We can't
-     * go straight to the GnuR C entry point as that makes GnuR-specific assumptions about, for
-     * example, how connections are implemented.
-     */
-    Object parseRd(RConnection con, REnvironment srcfile, RLogicalVector verbose, RLogicalVector fragment, RStringVector basename, RLogicalVector warningCalls, Object macros,
-                    RLogicalVector warndups);
+    abstract class ToolsRFFINode extends Node {
+        /**
+         * This invokes the Rd parser, written in C, and part of GnuR, that does its work using the
+         * R FFI interface. The R code initially invokes this via {@code .External2(C_parseRd, ...)}
+         * , which has a custom specialization in the implementation of the {@code .External2}
+         * builtin. That does some work in Java, and then calls this method to invoke the actual C
+         * code. We can't go straight to the GnuR C entry point as that makes GnuR-specific
+         * assumptions about, for example, how connections are implemented.
+         */
+        public abstract Object parseRd(RConnection con, REnvironment srcfile, RLogicalVector verbose, RLogicalVector fragment, RStringVector basename, RLogicalVector warningCalls, Object macros,
+                        RLogicalVector warndups);
+    }
+
+    ToolsRFFINode toolsRFFINode();
 }
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/UserRngRFFI.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/UserRngRFFI.java
index dc692727fcfe6b46100e854a859d63ec0abaa700..569540ed503d52b74af5861cfab8276f66a275a7 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/UserRngRFFI.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/UserRngRFFI.java
@@ -22,17 +22,23 @@
  */
 package com.oracle.truffle.r.runtime.ffi;
 
+import com.oracle.truffle.api.nodes.Node;
+
 /**
  * Explicit statically typed interface to user-supplied random number generators.
  */
 public interface UserRngRFFI {
+    abstract class UserRngRFFINode extends Node {
+
+        public abstract void init(int seed);
 
-    void init(int seed);
+        public abstract double rand();
 
-    double rand();
+        public abstract int nSeed();
 
-    int nSeed();
+        public abstract void seeds(int[] n);
+    }
 
-    void seeds(int[] n);
+    UserRngRFFINode userRngRFFINode();
 
 }
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/rng/user/UserRNG.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/rng/user/UserRNG.java
index 84187071f92296f69c96bec987616bcefd1d0270..b209867c61113a26a4bd9f902f9a66675a84a2ae 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/rng/user/UserRNG.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/rng/user/UserRNG.java
@@ -66,8 +66,15 @@ public final class UserRNG implements RandomNumberGenerator {
 
     }
 
-    private UserRngRFFI userRngRFFI;
     private int nSeeds = 0;
+    private UserRngRFFI.UserRngRFFINode userRngRFFINode;
+
+    private UserRngRFFI.UserRngRFFINode getUserRngRFFINode() {
+        if (userRngRFFINode == null) {
+            userRngRFFINode = RFFIFactory.getRFFI().getUserRngRFFI().userRngRFFINode();
+        }
+        return userRngRFFINode;
+    }
 
     @Override
     @TruffleBoundary
@@ -79,15 +86,14 @@ public final class UserRNG implements RandomNumberGenerator {
         for (Function f : Function.values()) {
             f.setSymbolHandle(dllInfo);
         }
-        userRngRFFI = RFFIFactory.getRFFI().getUserRngRFFI();
         if (Function.Init.isDefined()) {
-            userRngRFFI.init(seed);
+            getUserRngRFFINode().init(seed);
         }
         if (Function.Seedloc.isDefined() && !Function.NSeed.isDefined()) {
             RError.warning(RError.NO_CALLER, RError.Message.RNG_READ_SEEDS);
         }
         if (Function.NSeed.isDefined()) {
-            int ns = userRngRFFI.nSeed();
+            int ns = getUserRngRFFINode().nSeed();
             if (ns < 0 || ns > 625) {
                 RError.warning(RError.NO_CALLER, RError.Message.GENERIC, "seed length must be in 0...625; ignored");
             } else {
@@ -125,7 +131,7 @@ public final class UserRNG implements RandomNumberGenerator {
             return null;
         }
         int[] seeds = new int[nSeeds];
-        userRngRFFI.seeds(seeds);
+        getUserRngRFFINode().seeds(seeds);
         int[] result = new int[nSeeds + 1];
         System.arraycopy(seeds, 0, result, 1, seeds.length);
         return result;
@@ -133,7 +139,7 @@ public final class UserRNG implements RandomNumberGenerator {
 
     @Override
     public double genrandDouble() {
-        return userRngRFFI.rand();
+        return getUserRngRFFINode().rand();
     }
 
     @Override
diff --git a/mx.fastr/copyrights/overrides b/mx.fastr/copyrights/overrides
index 34b4bca629f2ceea3857c3c258899c620e4673e9..7dabf754391dcb6c7cc52860c0a2089c3779e868 100644
--- a/mx.fastr/copyrights/overrides
+++ b/mx.fastr/copyrights/overrides
@@ -154,7 +154,8 @@ com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/f
 com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/Dqrdc2.java,gnu_r.copyright
 com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/Fft.java,gnu_r.copyright
 com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/Flushconsole.java,gnu_r.copyright
-com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/ForeignFunctions.java,gnu_r.copyright
+com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/FortranAndCFunctions.java,gnu_r.copyright
+com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/CallAndExternalFunctions.java,gnu_r.copyright
 com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/MakeQuartzDefault.java,gnu_r.copyright
 com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/ReadTableHead.java,gnu_r.copyright
 com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/Dnorm4.java,gnu_r.copyright