diff --git a/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/shell/EmbeddedConsoleHandler.java b/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/shell/EmbeddedConsoleHandler.java
index d0249e7a18639dd18ef3faa1a91d1b44073a93b1..e551bd71487c301541e4e1a2c9a3e800f11d7453 100644
--- a/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/shell/EmbeddedConsoleHandler.java
+++ b/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/shell/EmbeddedConsoleHandler.java
@@ -39,8 +39,11 @@ import com.oracle.truffle.api.nodes.RootNode;
 import com.oracle.truffle.r.launcher.ConsoleHandler;
 import com.oracle.truffle.r.launcher.DelegatingConsoleHandler;
 import com.oracle.truffle.r.runtime.RInterfaceCallbacks;
+import com.oracle.truffle.r.runtime.ffi.REmbedRFFI;
 import com.oracle.truffle.r.runtime.ffi.REmbedRFFI.ReadConsoleNode;
 import com.oracle.truffle.r.runtime.ffi.REmbedRFFI.WriteConsoleBaseNode;
+import com.oracle.truffle.r.runtime.ffi.REmbedRFFI.WriteConsoleNode;
+import com.oracle.truffle.r.runtime.ffi.REmbedRFFI.WriteErrConsoleNode;
 import com.oracle.truffle.r.runtime.ffi.RFFIFactory;
 
 /**
@@ -195,7 +198,7 @@ public final class EmbeddedConsoleHandler extends DelegatingConsoleHandler {
     private CallTarget getReadLineCallTarget() {
         if (readLineCallTarget == null) {
             readLineCallTarget = Truffle.getRuntime().createCallTarget(new RootNode(null) {
-                @Child private ReadConsoleNode readConsoleNode = RFFIFactory.getREmbedRFFI().createReadConsoleNode();
+                @Child private ReadConsoleNode readConsoleNode = ReadConsoleNode.create();
 
                 @Override
                 public Object execute(VirtualFrame frame) {
@@ -208,14 +211,14 @@ public final class EmbeddedConsoleHandler extends DelegatingConsoleHandler {
 
     private CallTarget getWriteCallTarget() {
         if (writeCallTarget == null) {
-            writeCallTarget = createWriteCallTarget(RFFIFactory.getREmbedRFFI().createWriteConsoleNode());
+            writeCallTarget = createWriteCallTarget(WriteConsoleNode.create());
         }
         return writeCallTarget;
     }
 
     private CallTarget getWriteErrCallTarget() {
         if (writeErrCallTarget == null) {
-            writeErrCallTarget = createWriteCallTarget(RFFIFactory.getREmbedRFFI().createWriteErrConsoleNode());
+            writeErrCallTarget = createWriteCallTarget(WriteErrConsoleNode.create());
         }
         return writeErrCallTarget;
     }
diff --git a/com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/llvm/TruffleLLVM_Context.java b/com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/llvm/TruffleLLVM_Context.java
index 34d2a00ddf87b52bb24badd850aa5de3d5976906..65330af2da6f961259a4a971e22658e46c856f6c 100644
--- a/com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/llvm/TruffleLLVM_Context.java
+++ b/com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/llvm/TruffleLLVM_Context.java
@@ -32,6 +32,7 @@ import com.oracle.truffle.r.runtime.ffi.LapackRFFI;
 import com.oracle.truffle.r.runtime.ffi.MiscRFFI;
 import com.oracle.truffle.r.runtime.ffi.NativeFunction;
 import com.oracle.truffle.r.runtime.ffi.PCRERFFI;
+import com.oracle.truffle.r.runtime.ffi.REmbedRFFI;
 import com.oracle.truffle.r.runtime.ffi.RFFIContext;
 import com.oracle.truffle.r.runtime.ffi.StatsRFFI;
 import com.oracle.truffle.r.runtime.ffi.ToolsRFFI;
@@ -50,7 +51,7 @@ final class TruffleLLVM_Context extends RFFIContext {
         super(new TruffleLLVM_C(), new BaseRFFI(TruffleLLVM_DownCallNodeFactory.INSTANCE), new TruffleLLVM_Call(), new TruffleLLVM_DLL(), new TruffleLLVM_UserRng(),
                         new ZipRFFI(TruffleLLVM_DownCallNodeFactory.INSTANCE), new PCRERFFI(TruffleLLVM_DownCallNodeFactory.INSTANCE),
                         new LapackRFFI(TruffleLLVM_DownCallNodeFactory.INSTANCE), new StatsRFFI(TruffleLLVM_DownCallNodeFactory.INSTANCE),
-                        new ToolsRFFI(), new TruffleLLVM_REmbed(), new MiscRFFI(TruffleLLVM_DownCallNodeFactory.INSTANCE));
+                        new ToolsRFFI(), new REmbedRFFI(TruffleLLVM_DownCallNodeFactory.INSTANCE), new MiscRFFI(TruffleLLVM_DownCallNodeFactory.INSTANCE));
     }
 
     static TruffleLLVM_Context getContextState() {
diff --git a/com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/llvm/TruffleLLVM_REmbed.java b/com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/llvm/TruffleLLVM_REmbed.java
deleted file mode 100644
index 7fa4235085bc75b739e7300b48932b08d3adb697..0000000000000000000000000000000000000000
--- a/com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/llvm/TruffleLLVM_REmbed.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (c) 2014, 2018, 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.ffi.impl.llvm;
-
-import com.oracle.truffle.r.runtime.RInternalError;
-import com.oracle.truffle.r.runtime.ffi.REmbedRFFI;
-
-public class TruffleLLVM_REmbed implements REmbedRFFI {
-    @Override
-    public ReadConsoleNode createReadConsoleNode() {
-        throw RInternalError.unimplemented("TODO");
-    }
-
-    @Override
-    public WriteConsoleNode createWriteConsoleNode() {
-        throw RInternalError.unimplemented("TODO");
-    }
-
-    @Override
-    public WriteErrConsoleNode createWriteErrConsoleNode() {
-        throw RInternalError.unimplemented("TODO");
-    }
-}
diff --git a/com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/managed/Managed_REmbedRFFI.java b/com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/managed/Managed_REmbedRFFI.java
deleted file mode 100644
index 7a99eaba80463fa098dee79c767c50859c3268be..0000000000000000000000000000000000000000
--- a/com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/managed/Managed_REmbedRFFI.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (c) 2017, 2018, 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.ffi.impl.managed;
-
-import static com.oracle.truffle.r.ffi.impl.managed.Managed_RFFIFactory.unsupported;
-
-import com.oracle.truffle.r.runtime.RInternalError;
-import com.oracle.truffle.r.runtime.ffi.REmbedRFFI;
-
-public class Managed_REmbedRFFI implements REmbedRFFI {
-    @Override
-    public ReadConsoleNode createReadConsoleNode() {
-        throw unsupported("REmbed");
-    }
-
-    @Override
-    public WriteConsoleNode createWriteConsoleNode() {
-        throw unsupported("REmbed");
-    }
-
-    @Override
-    public WriteErrConsoleNode createWriteErrConsoleNode() {
-        throw unsupported("REmbed");
-    }
-}
diff --git a/com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/managed/Managed_RFFIFactory.java b/com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/managed/Managed_RFFIFactory.java
index 728b244fa3b67f40b4a1b7cccbf702a0bc0dea6e..3d32fdf2838e08d6b5e1c1c9bf0587a980b3d2c0 100644
--- a/com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/managed/Managed_RFFIFactory.java
+++ b/com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/managed/Managed_RFFIFactory.java
@@ -37,6 +37,7 @@ import com.oracle.truffle.r.runtime.ffi.LapackRFFI;
 import com.oracle.truffle.r.runtime.ffi.MiscRFFI;
 import com.oracle.truffle.r.runtime.ffi.NativeFunction;
 import com.oracle.truffle.r.runtime.ffi.PCRERFFI;
+import com.oracle.truffle.r.runtime.ffi.REmbedRFFI;
 import com.oracle.truffle.r.runtime.ffi.RFFIContext;
 import com.oracle.truffle.r.runtime.ffi.RFFIFactory;
 import com.oracle.truffle.r.runtime.ffi.StatsRFFI;
@@ -108,7 +109,8 @@ public final class Managed_RFFIFactory extends RFFIFactory {
                     throw unsupported("user defined RNG");
                 }
             }, new ZipRFFI(Managed_DownCallNodeFactory.INSTANCE), new PCRERFFI(Managed_DownCallNodeFactory.INSTANCE), new LapackRFFI(Managed_DownCallNodeFactory.INSTANCE),
-                            new StatsRFFI(Managed_DownCallNodeFactory.INSTANCE), new ToolsRFFI(), new Managed_REmbedRFFI(), new MiscRFFI(Managed_DownCallNodeFactory.INSTANCE));
+                            new StatsRFFI(Managed_DownCallNodeFactory.INSTANCE), new ToolsRFFI(), new REmbedRFFI(Managed_DownCallNodeFactory.INSTANCE),
+                            new MiscRFFI(Managed_DownCallNodeFactory.INSTANCE));
         }
 
         private static class IgnoreUpCallExceptionNode extends Node implements HandleUpCallExceptionNode {
diff --git a/com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/nfi/TruffleNFI_Call.java b/com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/nfi/TruffleNFI_Call.java
index 98bea5a094ff0a446211952b5e7dd6295edb56ba..9489dea36533d1a22dcdc453ec77aa1c149709dc 100644
--- a/com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/nfi/TruffleNFI_Call.java
+++ b/com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/nfi/TruffleNFI_Call.java
@@ -185,13 +185,6 @@ public class TruffleNFI_Call implements CallRFFI {
         if (traceEnabled()) {
             traceDownCallReturn(name, result);
         }
-        TruffleNFI_Context nfiCtx = (TruffleNFI_Context) RContext.getInstance().getStateRFFI();
-        RuntimeException lastUpCallEx = nfiCtx.getLastUpCallException();
-        if (lastUpCallEx != null) {
-            CompilerDirectives.transferToInterpreter();
-            nfiCtx.setLastUpCallException(null);
-            throw lastUpCallEx;
-        }
     }
 
     @Override
diff --git a/com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/nfi/TruffleNFI_Context.java b/com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/nfi/TruffleNFI_Context.java
index 31635cc690c340ab23191edd67aee486b20b13c7..7b4c1ecec2669cd03be24e4aa23852b22db14b1b 100644
--- a/com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/nfi/TruffleNFI_Context.java
+++ b/com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/nfi/TruffleNFI_Context.java
@@ -59,6 +59,7 @@ import com.oracle.truffle.r.runtime.ffi.LapackRFFI;
 import com.oracle.truffle.r.runtime.ffi.MiscRFFI;
 import com.oracle.truffle.r.runtime.ffi.NativeFunction;
 import com.oracle.truffle.r.runtime.ffi.PCRERFFI;
+import com.oracle.truffle.r.runtime.ffi.REmbedRFFI;
 import com.oracle.truffle.r.runtime.ffi.RFFIContext;
 import com.oracle.truffle.r.runtime.ffi.RFFIVariables;
 import com.oracle.truffle.r.runtime.ffi.StatsRFFI;
@@ -93,7 +94,8 @@ final class TruffleNFI_Context extends RFFIContext {
     TruffleNFI_Context() {
         super(new TruffleNFI_C(), new BaseRFFI(TruffleNFI_DownCallNodeFactory.INSTANCE), new TruffleNFI_Call(), new TruffleNFI_DLL(), new TruffleNFI_UserRng(),
                         new ZipRFFI(TruffleNFI_DownCallNodeFactory.INSTANCE), new PCRERFFI(TruffleNFI_DownCallNodeFactory.INSTANCE), new LapackRFFI(TruffleNFI_DownCallNodeFactory.INSTANCE),
-                        new StatsRFFI(TruffleNFI_DownCallNodeFactory.INSTANCE), new ToolsRFFI(), new TruffleNFI_REmbed(), new MiscRFFI(TruffleNFI_DownCallNodeFactory.INSTANCE));
+                        new StatsRFFI(TruffleNFI_DownCallNodeFactory.INSTANCE), new ToolsRFFI(), new REmbedRFFI(TruffleNFI_DownCallNodeFactory.INSTANCE),
+                        new MiscRFFI(TruffleNFI_DownCallNodeFactory.INSTANCE));
         // forward constructor
     }
 
@@ -345,6 +347,7 @@ final class TruffleNFI_Context extends RFFIContext {
 
     @Override
     public long beforeDowncall() {
+        assert transientAllocations.size() == 0 : "transientAllocations should have been cleared in afterDowncall";
         super.beforeDowncall();
         if (hasAccessLock) {
             acquireLock();
@@ -354,6 +357,7 @@ final class TruffleNFI_Context extends RFFIContext {
 
     @Override
     public void afterDowncall(long beforeValue) {
+        super.afterDowncall(beforeValue);
         popCallbacks(beforeValue);
         for (Long ptr : transientAllocations) {
             UnsafeAdapter.UNSAFE.freeMemory(ptr);
@@ -362,7 +366,12 @@ final class TruffleNFI_Context extends RFFIContext {
         if (hasAccessLock) {
             releaseLock();
         }
-        super.afterDowncall(beforeValue);
+        RuntimeException lastUpCallEx = getLastUpCallException();
+        if (lastUpCallEx != null) {
+            CompilerDirectives.transferToInterpreter();
+            setLastUpCallException(null);
+            throw lastUpCallEx;
+        }
     }
 
     public static TruffleNFI_Context getInstance() {
diff --git a/com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/nfi/TruffleNFI_DownCallNodeFactory.java b/com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/nfi/TruffleNFI_DownCallNodeFactory.java
index 60eea1dfb8dcce3dd65a2cc61e1ad3ee1b658f19..c54cca485f6a5db8e33ef0cbc90c815430c5c7c5 100644
--- a/com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/nfi/TruffleNFI_DownCallNodeFactory.java
+++ b/com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/nfi/TruffleNFI_DownCallNodeFactory.java
@@ -91,7 +91,7 @@ public final class TruffleNFI_DownCallNodeFactory extends DownCallNodeFactory {
             @ExplodeLoop
             protected void afterCall(long before, NativeFunction function, TruffleObject target, Object[] args) {
                 if (function.hasComplexInteraction()) {
-                    RContext.getInstance().getRFFI().afterDowncall(before);
+                    ((TruffleNFI_Context) RContext.getInstance().getRFFI()).afterDowncall(before);
                 }
 
                 for (Object obj : args) {
diff --git a/com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/nfi/TruffleNFI_REmbed.java b/com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/nfi/TruffleNFI_REmbed.java
deleted file mode 100644
index 64c70971c20435a6d83a151bbfe696cf21cc670b..0000000000000000000000000000000000000000
--- a/com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/nfi/TruffleNFI_REmbed.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (c) 2017, 2018, 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.ffi.impl.nfi;
-
-import com.oracle.truffle.api.CompilerDirectives;
-import com.oracle.truffle.api.interop.ForeignAccess;
-import com.oracle.truffle.api.interop.Message;
-import com.oracle.truffle.api.interop.TruffleObject;
-import com.oracle.truffle.api.interop.UnsupportedMessageException;
-import com.oracle.truffle.api.nodes.Node;
-import com.oracle.truffle.r.runtime.RInternalError;
-import com.oracle.truffle.r.runtime.ffi.NativeFunction;
-import com.oracle.truffle.r.runtime.ffi.REmbedRFFI;
-
-public class TruffleNFI_REmbed implements REmbedRFFI {
-
-    private static class TruffleNFI_ReadConsoleNode extends TruffleNFI_DownCallNode implements ReadConsoleNode {
-        @Child private Node unboxNode;
-
-        @Override
-        protected NativeFunction getFunction() {
-            return NativeFunction.rembedded_read_console;
-        }
-
-        @Override
-        public String execute(String prompt) {
-            Object result = call(prompt);
-            if (result instanceof String) {
-                return (String) result;
-            }
-            assert result instanceof TruffleObject : "NFI is expected to send us TruffleObject or String";
-            if (unboxNode == null) {
-                CompilerDirectives.transferToInterpreterAndInvalidate();
-                unboxNode = insert(Message.UNBOX.createNode());
-            }
-            try {
-                return (String) ForeignAccess.sendUnbox(unboxNode, (TruffleObject) result);
-            } catch (ClassCastException | UnsupportedMessageException e) {
-                CompilerDirectives.transferToInterpreter();
-                throw RInternalError.shouldNotReachHere("Unboxing TruffleObject from NFI, which should be String wrapper, failed. " + e.getMessage());
-            }
-        }
-    }
-
-    private static class TruffleNFI_WriteConsoleNode extends TruffleNFI_DownCallNode implements WriteConsoleNode {
-        @Override
-        protected NativeFunction getFunction() {
-            return NativeFunction.rembedded_write_console;
-        }
-
-        @Override
-        public void execute(String x) {
-            call(x, x.length());
-        }
-    }
-
-    private static class TruffleNFI_WriteErrConsoleNode extends TruffleNFI_DownCallNode implements WriteErrConsoleNode {
-        @Override
-        protected NativeFunction getFunction() {
-            return NativeFunction.rembedded_write_err_console;
-        }
-
-        @Override
-        public void execute(String x) {
-            call(x, x.length());
-        }
-    }
-
-    @Override
-    public ReadConsoleNode createReadConsoleNode() {
-        return new TruffleNFI_ReadConsoleNode();
-    }
-
-    @Override
-    public WriteConsoleNode createWriteConsoleNode() {
-        return new TruffleNFI_WriteConsoleNode();
-    }
-
-    @Override
-    public WriteErrConsoleNode createWriteErrConsoleNode() {
-        return new TruffleNFI_WriteErrConsoleNode();
-    }
-}
diff --git a/com.oracle.truffle.r.native/fficall/src/truffle_nfi/Rembedded.c b/com.oracle.truffle.r.native/fficall/src/truffle_nfi/Rembedded.c
index 21214ae229252fa78034d2e6714096e7ec3acb20..2ee67837291d22f7397d77cd232ddf33f0f8de98 100644
--- a/com.oracle.truffle.r.native/fficall/src/truffle_nfi/Rembedded.c
+++ b/com.oracle.truffle.r.native/fficall/src/truffle_nfi/Rembedded.c
@@ -420,7 +420,7 @@ void uR_ProcessEvents(void) {
 }
 
 void uR_CleanUp(SA_TYPE x, int y, int z) {
-    return ((call_R_CleanUp) callbacks[R_CleanUp_x])(x, y, z);
+    ((call_R_CleanUp) callbacks[R_CleanUp_x])(x, y, z);
 }
 
 void (*ptr_R_Suicide)(const char *) = uR_Suicide;
@@ -505,11 +505,11 @@ void R_runHandlers(InputHandler *handlers, fd_set *mask) {
 // -----------------------------------------------------------------------------------------------
 // Downcalls from Java. We invoke these functions via REmbedRFFI
 
-void invokeCleanUp(int x, int y, int z) {
+void rembedded_cleanup(int x, int y, int z) {
     ptr_R_CleanUp(x, y, z);
 }
 
-void invokeSuicide(char* msg) {
+void rembedded_suicide(char* msg) {
     ptr_R_Suicide(msg);
 }
 
@@ -522,7 +522,7 @@ void rembedded_write_err_console(char *cbuf, int len) {
 }
 
 char* rembedded_read_console(const char* prompt) {
-    char* cbuf = malloc(sizeof(char) * 1024);
+    unsigned char* cbuf = malloc(sizeof(char) * 1024);
     int n = (*ptr_R_ReadConsole)(prompt, cbuf, 1024, 0);
     return cbuf;
 }
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RCleanUp.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RCleanUp.java
index 4d5f97ecf48f8962ac593aec7fc5e76284e0ef2a..61ddc58a29f36f818353ceb8ad4264d102d68ce8 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RCleanUp.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RCleanUp.java
@@ -27,6 +27,8 @@ import com.oracle.truffle.r.runtime.ffi.CallRFFI.InvokeCallNode;
 import com.oracle.truffle.r.runtime.ffi.DLL.RFindSymbolNode;
 import com.oracle.truffle.r.runtime.ffi.DLL.SymbolHandle;
 import com.oracle.truffle.r.runtime.ffi.NativeCallInfo;
+import com.oracle.truffle.r.runtime.ffi.REmbedRFFI;
+import com.oracle.truffle.r.runtime.ffi.REmbedRFFI.EmbeddedCleanUpNode;
 import com.oracle.truffle.r.runtime.gnur.SA_TYPE;
 import com.oracle.truffle.r.runtime.instrument.InstrumentationState;
 
@@ -44,8 +46,7 @@ public abstract class RCleanUp {
     public static void cleanUp(RContext ctx, SA_TYPE saveType, int status, boolean runLast) {
         if (RInterfaceCallbacks.R_CleanUp.isOverridden()) {
             RootCallTarget invokeUserCleanup = ctx.getOrCreateCachedCallTarget(UserDefinedCleanUpRootNode.class, () -> new UserDefinedCleanUpRootNode(ctx).getCallTarget());
-            Object[] args = new Object[]{asVector(saveType.ordinal()), asVector(status), asVector(runLast ? 1 : 0)};
-            invokeUserCleanup.call(args);
+            invokeUserCleanup.call(saveType.ordinal(), status, runLast ? 1 : 0);
         } else {
             stdCleanUp(saveType, status, runLast);
         }
@@ -134,29 +135,18 @@ public abstract class RCleanUp {
         RContext.getEngine().checkAndRunStartupShutdownFunction(".Last.sys");
     }
 
-    private static RIntVector asVector(int value) {
-        return RDataFactory.createIntVectorFromScalar(value);
-    }
-
     private static final class UserDefinedCleanUpRootNode extends RootNode {
         protected UserDefinedCleanUpRootNode(RContext ctx) {
             super(null);
-            invokeCallNode = ctx.getRFFI().callRFFI.createInvokeCallNode();
+            cleanUpNode = ctx.getRFFI().embedRFFI.createEmbeddedCleanUpNode();
             Truffle.getRuntime().createCallTarget(this);
         }
 
-        private static final String SYMBOL_NAME = "invokeCleanUp";
-        @Child InvokeCallNode invokeCallNode;
-        @Child RFindSymbolNode findSymbolNode = RFindSymbolNode.create();
+        @Child private EmbeddedCleanUpNode cleanUpNode;
 
         @Override
         public Object execute(VirtualFrame frame) {
-            SymbolHandle invokeCleanUp = findSymbolNode.execute(SYMBOL_NAME, null, null);
-            if (invokeCleanUp == null) {
-                CompilerDirectives.transferToInterpreter();
-                throw RInternalError.shouldNotReachHere("Cannot find " + SYMBOL_NAME + " symbol which should be declared in Rembedded.c");
-            }
-            invokeCallNode.dispatch(new NativeCallInfo(SYMBOL_NAME, invokeCleanUp, null), frame.getArguments());
+            cleanUpNode.execute((int) frame.getArguments()[0], (int) frame.getArguments()[1], (int) frame.getArguments()[2]);
             return null;
         }
     }
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RSuicide.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RSuicide.java
index 632e7b2594bccddec8fb27cb642881f09b3823b1..18e74dc486654fc7ec677b7bf7906ea82c453c5e 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RSuicide.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RSuicide.java
@@ -34,6 +34,7 @@ import com.oracle.truffle.r.runtime.ffi.CallRFFI.InvokeCallNode;
 import com.oracle.truffle.r.runtime.ffi.DLL.RFindSymbolNode;
 import com.oracle.truffle.r.runtime.ffi.DLL.SymbolHandle;
 import com.oracle.truffle.r.runtime.ffi.NativeCallInfo;
+import com.oracle.truffle.r.runtime.ffi.REmbedRFFI.EmbeddedSuicideNode;
 import com.oracle.truffle.r.runtime.ffi.RFFIFactory;
 
 public abstract class RSuicide {
@@ -73,36 +74,25 @@ public abstract class RSuicide {
         throw new ExitException(2, false);
     }
 
-    private static RStringVector asVector(String value) {
-        return RDataFactory.createStringVector(value);
-    }
-
     private static void invokeUserDefinedSuicide(RContext ctx, String msg) {
         if (ctx != null && RInterfaceCallbacks.R_Suicide.isOverridden()) {
             RootCallTarget invokeUserCleanup = ctx.getOrCreateCachedCallTarget(UserDefinedSuicideRootNode.class, () -> new UserDefinedSuicideRootNode(ctx).getCallTarget());
-            invokeUserCleanup.call(new Object[]{asVector(msg)});
+            invokeUserCleanup.call(msg);
         }
     }
 
     private static final class UserDefinedSuicideRootNode extends RootNode {
         protected UserDefinedSuicideRootNode(RContext ctx) {
             super(null);
-            invokeCallNode = ctx.getRFFI().callRFFI.createInvokeCallNode();
+            suicideNode = ctx.getRFFI().embedRFFI.createEmbeddedSuicideNode();
             Truffle.getRuntime().createCallTarget(this);
         }
 
-        private static final String SYMBOL_NAME = "invokeSuicide";
-        @Child InvokeCallNode invokeCallNode;
-        @Child RFindSymbolNode findSymbolNode = RFindSymbolNode.create();
+        @Child private EmbeddedSuicideNode suicideNode;
 
         @Override
         public Object execute(VirtualFrame frame) {
-            SymbolHandle invokeSuicide = findSymbolNode.execute(SYMBOL_NAME, null, null);
-            if (invokeSuicide == null) {
-                CompilerDirectives.transferToInterpreter();
-                throw RInternalError.shouldNotReachHere("Cannot find " + SYMBOL_NAME + " symbol which should be declared in Rembedded.c");
-            }
-            invokeCallNode.dispatch(new NativeCallInfo(SYMBOL_NAME, invokeSuicide, null), frame.getArguments());
+            suicideNode.execute((String) frame.getArguments()[0]);
             return null;
         }
     }
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/NativeFunction.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/NativeFunction.java
index de1d47a0f6518e4c141c5b6a3a70459e7cae8ba5..091ab6b5132bf935b2cc5041ddb5c0735652db3d 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/NativeFunction.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/NativeFunction.java
@@ -82,11 +82,13 @@ public enum NativeFunction {
     // FastR helpers
     set_exception_flag("(): void"),
     // FastR internal helper for R embedded mode
-    rembedded_write_console("(string, sint32):void", "", anyLibrary(), true),
-    rembedded_write_err_console("(string, sint32):void", "", anyLibrary(), true),
-    rembedded_read_console("(string):string", "", anyLibrary(), true),
-    rembedded_native_clean_up("(sint32, sint32, sint32):void", "", anyLibrary(), true),
-    rembedded_native_suicide("(string):void", "", anyLibrary(), true),
+    rembedded_cleanup("(sint32, sint32, sint32):void", "", baseLibrary(), true),
+    rembedded_suicide("(string):void", "", baseLibrary(), true),
+    rembedded_write_console("(string, sint32):void", "", baseLibrary(), true),
+    rembedded_write_err_console("(string, sint32):void", "", baseLibrary(), true),
+    rembedded_read_console("(string):string", "", baseLibrary(), true),
+    rembedded_native_clean_up("(sint32, sint32, sint32):void", "", baseLibrary(), true),
+    rembedded_native_suicide("(string):void", "", baseLibrary(), true),
     // user-defined RNG
     unif_init("(sint32): void", "user_", anyLibrary()),
     norm_rand("(): pointer", "user_", anyLibrary()),
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/REmbedRFFI.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/REmbedRFFI.java
index 929f2558b46f0d5de02413c1f7c6c30f4e75ef66..52a059a1558a5553aeeb015339b7a1bbc4a8d673 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/REmbedRFFI.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/REmbedRFFI.java
@@ -22,40 +22,131 @@
  */
 package com.oracle.truffle.r.runtime.ffi;
 
-import com.oracle.truffle.api.nodes.NodeInterface;
+import com.oracle.truffle.api.CompilerDirectives;
+import com.oracle.truffle.api.interop.ForeignAccess;
+import com.oracle.truffle.api.interop.Message;
+import com.oracle.truffle.api.interop.TruffleObject;
+import com.oracle.truffle.api.interop.UnsupportedMessageException;
+import com.oracle.truffle.api.nodes.Node;
+import com.oracle.truffle.r.runtime.RInternalError;
+import com.oracle.truffle.r.runtime.ffi.DownCallNodeFactory.DownCallNode;
 
 /**
  * Function down-calls related to the embedded API. TODO: these all should be invoked as proper
  * down-calls because the user code may want to use R API.
  */
-public interface REmbedRFFI {
-    interface ReadConsoleNode extends NodeInterface {
-        String execute(String prompt);
+public final class REmbedRFFI {
+    private final DownCallNodeFactory downCallNodeFactory;
 
-        static REmbedRFFI.ReadConsoleNode create() {
+    public REmbedRFFI(DownCallNodeFactory downCallNodeFactory) {
+        this.downCallNodeFactory = downCallNodeFactory;
+    }
+
+    public static final class ReadConsoleNode extends NativeCallNode {
+        @Child private Node unboxNode;
+
+        private ReadConsoleNode(DownCallNodeFactory factory) {
+            super(factory.createDownCallNode(NativeFunction.rembedded_read_console));
+        }
+
+        public String execute(String prompt) {
+            Object result = call(prompt);
+            if (result instanceof String) {
+                return (String) result;
+            }
+            assert result instanceof TruffleObject : "NFI is expected to send us TruffleObject or String";
+            if (unboxNode == null) {
+                CompilerDirectives.transferToInterpreterAndInvalidate();
+                unboxNode = insert(Message.UNBOX.createNode());
+            }
+            try {
+                return (String) ForeignAccess.sendUnbox(unboxNode, (TruffleObject) result);
+            } catch (ClassCastException | UnsupportedMessageException e) {
+                CompilerDirectives.transferToInterpreter();
+                throw RInternalError.shouldNotReachHere("Unboxing TruffleObject from NFI, which should be String wrapper, failed. " + e.getMessage());
+            }
+        }
+
+        public static REmbedRFFI.ReadConsoleNode create() {
             return RFFIFactory.getREmbedRFFI().createReadConsoleNode();
         }
     }
 
-    interface WriteConsoleBaseNode extends NodeInterface {
-        void execute(String x);
+    public abstract static class WriteConsoleBaseNode extends NativeCallNode {
+        private WriteConsoleBaseNode(DownCallNode downCallNode) {
+            super(downCallNode);
+        }
+
+        public final void execute(String x) {
+            call(x, x.length());
+        }
     }
 
-    interface WriteConsoleNode extends WriteConsoleBaseNode {
-        static REmbedRFFI.WriteConsoleNode create() {
+    public static final class WriteConsoleNode extends WriteConsoleBaseNode {
+        public static REmbedRFFI.WriteConsoleNode create() {
             return RFFIFactory.getREmbedRFFI().createWriteConsoleNode();
         }
+
+        public WriteConsoleNode(DownCallNodeFactory factory) {
+            super(factory.createDownCallNode(NativeFunction.rembedded_write_console));
+        }
     }
 
-    interface WriteErrConsoleNode extends WriteConsoleBaseNode {
-        static REmbedRFFI.WriteErrConsoleNode create() {
+    public static final class WriteErrConsoleNode extends WriteConsoleBaseNode {
+        public static REmbedRFFI.WriteErrConsoleNode create() {
             return RFFIFactory.getREmbedRFFI().createWriteErrConsoleNode();
         }
+
+        public WriteErrConsoleNode(DownCallNodeFactory factory) {
+            super(factory.createDownCallNode(NativeFunction.rembedded_write_err_console));
+        }
     }
 
-    ReadConsoleNode createReadConsoleNode();
+    public static final class EmbeddedSuicideNode extends NativeCallNode {
+        private EmbeddedSuicideNode(DownCallNodeFactory factory) {
+            super(factory.createDownCallNode(NativeFunction.rembedded_suicide));
+        }
 
-    WriteConsoleNode createWriteConsoleNode();
+        public void execute(String message) {
+            call(message);
+        }
 
-    WriteErrConsoleNode createWriteErrConsoleNode();
+        public static EmbeddedSuicideNode create() {
+            return RFFIFactory.getREmbedRFFI().createEmbeddedSuicideNode();
+        }
+    }
+
+    public static final class EmbeddedCleanUpNode extends NativeCallNode {
+        private EmbeddedCleanUpNode(DownCallNodeFactory factory) {
+            super(factory.createDownCallNode(NativeFunction.rembedded_cleanup));
+        }
+
+        public void execute(int x, int y, int z) {
+            call(x, y, z);
+        }
+
+        public static EmbeddedCleanUpNode create() {
+            return RFFIFactory.getREmbedRFFI().createEmbeddedCleanUpNode();
+        }
+    }
+
+    ReadConsoleNode createReadConsoleNode() {
+        return new ReadConsoleNode(downCallNodeFactory);
+    }
+
+    WriteConsoleNode createWriteConsoleNode() {
+        return new WriteConsoleNode(downCallNodeFactory);
+    }
+
+    WriteErrConsoleNode createWriteErrConsoleNode() {
+        return new WriteErrConsoleNode(downCallNodeFactory);
+    }
+
+    public EmbeddedSuicideNode createEmbeddedSuicideNode() {
+        return new EmbeddedSuicideNode(downCallNodeFactory);
+    }
+
+    public EmbeddedCleanUpNode createEmbeddedCleanUpNode() {
+        return new EmbeddedCleanUpNode(downCallNodeFactory);
+    }
 }