Revert "[vm/ffi] FFI callbacks on X64."

This reverts commit be209f7846e8db8ad6512f3a0d7681b2d14bced1.

Reason for revert: failures on dartkb and windows bots

Original change's description:
> [vm/ffi] FFI callbacks on X64.
> 
> For context on the design, see go/dart-ffi-callbacks
> 
> Change-Id: I2482e3c932e73f9a4c00fa7e218ff85f9328fc51
> Cq-Include-Trybots: luci.dart.try:vm-kernel-linux-debug-simdbc64-try, vm-kernel-linux-release-simdbc64-try, vm-kernel-mac-debug-simdbc64-try, vm-kernel-mac-release-simdbc64-try, vm-kernel-reload-mac-debug-simdbc64-try, vm-kernel-reload-mac-release-simdbc64-try, vm-kernel-linux-debug-ia32-try, vm-dartkb-linux-debug-simarm64-try
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/100240
> Commit-Queue: Samir Jindel <sjindel@google.com>
> Reviewed-by: Daco Harkes <dacoharkes@google.com>

TBR=sjindel@google.com,ajcbik@google.com,dacoharkes@google.com

Change-Id: I4cb3d93675d68a51ac9e125ad1d572c47e8b5903
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Cq-Include-Trybots: luci.dart.try:vm-kernel-linux-debug-simdbc64-try, vm-kernel-linux-release-simdbc64-try, vm-kernel-mac-debug-simdbc64-try, vm-kernel-mac-release-simdbc64-try, vm-kernel-reload-mac-debug-simdbc64-try, vm-kernel-reload-mac-release-simdbc64-try, vm-kernel-linux-debug-ia32-try, vm-dartkb-linux-debug-simarm64-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/102983
Reviewed-by: Samir Jindel <sjindel@google.com>
Commit-Queue: Samir Jindel <sjindel@google.com>
diff --git a/pkg/vm/lib/transformations/ffi_use_sites.dart b/pkg/vm/lib/transformations/ffi_use_sites.dart
index d61ca95..4b40caf 100644
--- a/pkg/vm/lib/transformations/ffi_use_sites.dart
+++ b/pkg/vm/lib/transformations/ffi_use_sites.dart
@@ -275,10 +275,9 @@
   }
 
   bool _isStatic(Expression node) {
-    if (node is StaticGet) {
-      return node.target is Procedure;
-    }
-    return node is ConstantExpression;
+    if (node is! StaticGet) return false;
+
+    return (node as StaticGet).target is Procedure;
   }
 }
 
diff --git a/runtime/bin/ffi_test_functions.cc b/runtime/bin/ffi_test_functions.cc
index 8e937f0..f1c418c 100644
--- a/runtime/bin/ffi_test_functions.cc
+++ b/runtime/bin/ffi_test_functions.cc
@@ -7,19 +7,15 @@
 #include <stddef.h>
 #include <stdlib.h>
 #include <sys/types.h>
-#include <csignal>
 
 #include "platform/assert.h"
 #include "platform/globals.h"
-#include "vm/os_thread.h"
 #if defined(HOST_OS_WINDOWS)
 #include <psapi.h>
 #else
 #include <unistd.h>
 #endif
 
-#include <setjmp.h>
-#include <signal.h>
 #include <iostream>
 #include <limits>
 
@@ -27,9 +23,6 @@
 
 namespace dart {
 
-////////////////////////////////////////////////////////////////////////////////
-// Tests for Dart -> native calls.
-
 // Sums two ints and adds 42.
 // Simple function to test trampolines.
 // Also used for testing argument exception on passing null instead of a Dart
@@ -456,8 +449,7 @@
   return retval;
 }
 
-////////////////////////////////////////////////////////////////////////////////
-// Functions for stress-testing.
+// Functions for stress-testing GC by returning values that require boxing.
 
 DART_EXPORT int64_t MinInt64() {
   return 0x8000000000000000;
@@ -519,213 +511,4 @@
 }
 #endif
 
-////////////////////////////////////////////////////////////////////////////////
-// Tests for callbacks.
-
-#define CHECK(X)                                                               \
-  if (!(X)) {                                                                  \
-    fprintf(stderr, "%s\n", "Check failed: " #X);                              \
-    return 1;                                                                  \
-  }
-
-#define CHECK_EQ(X, Y) CHECK((X) == (Y))
-
-// Sanity test.
-DART_EXPORT int TestSimpleAddition(int (*add)(int, int)) {
-  CHECK_EQ(add(10, 20), 30);
-  return 0;
-}
-
-//// Following tests are copied from above, with the role of Dart and C++ code
-//// reversed.
-
-DART_EXPORT int TestIntComputation(
-    int64_t (*fn)(int8_t, int16_t, int32_t, int64_t)) {
-  CHECK_EQ(fn(125, 250, 500, 1000), 625);
-  CHECK_EQ(0x7FFFFFFFFFFFFFFFLL, fn(0, 0, 0, 0x7FFFFFFFFFFFFFFFLL));
-  CHECK_EQ(((int64_t)-0x8000000000000000LL),
-           fn(0, 0, 0, -0x8000000000000000LL));
-  return 0;
-}
-
-DART_EXPORT int TestUintComputation(
-    uint64_t (*fn)(uint8_t, uint16_t, uint32_t, uint64_t)) {
-  CHECK_EQ(0x7FFFFFFFFFFFFFFFLL, fn(0, 0, 0, 0x7FFFFFFFFFFFFFFFLL));
-  CHECK_EQ(-0x8000000000000000LL, fn(0, 0, 0, -0x8000000000000000LL));
-  CHECK_EQ(-1, (int64_t)fn(0, 0, 0, -1));
-  return 0;
-}
-
-DART_EXPORT int TestSimpleMultiply(double (*fn)(double)) {
-  CHECK_EQ(fn(2.0), 2.0 * 1.337);
-  return 0;
-}
-
-DART_EXPORT int TestSimpleMultiplyFloat(float (*fn)(float)) {
-  CHECK(std::abs(fn(2.0) - 2.0 * 1.337) < 0.001);
-  return 0;
-}
-
-DART_EXPORT int TestManyInts(intptr_t (*fn)(intptr_t,
-                                            intptr_t,
-                                            intptr_t,
-                                            intptr_t,
-                                            intptr_t,
-                                            intptr_t,
-                                            intptr_t,
-                                            intptr_t,
-                                            intptr_t,
-                                            intptr_t)) {
-  CHECK_EQ(55, fn(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
-  return 0;
-}
-
-DART_EXPORT int TestManyDoubles(double (*fn)(double,
-                                             double,
-                                             double,
-                                             double,
-                                             double,
-                                             double,
-                                             double,
-                                             double,
-                                             double,
-                                             double)) {
-  CHECK_EQ(55, fn(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
-  return 0;
-}
-
-DART_EXPORT int TestManyArgs(double (*fn)(intptr_t a,
-                                          float b,
-                                          intptr_t c,
-                                          double d,
-                                          intptr_t e,
-                                          float f,
-                                          intptr_t g,
-                                          double h,
-                                          intptr_t i,
-                                          float j,
-                                          intptr_t k,
-                                          double l,
-                                          intptr_t m,
-                                          float n,
-                                          intptr_t o,
-                                          double p,
-                                          intptr_t q,
-                                          float r,
-                                          intptr_t s,
-                                          double t)) {
-  CHECK(210.0 == fn(1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9, 10.0, 11, 12.0, 13, 14.0,
-                    15, 16.0, 17, 18.0, 19, 20.0));
-  return 0;
-}
-
-DART_EXPORT int TestStore(int64_t* (*fn)(int64_t* a)) {
-  int64_t p[2] = {42, 1000};
-  int64_t* result = fn(p);
-  CHECK_EQ(*result, 1337);
-  CHECK_EQ(p[1], 1337);
-  CHECK_EQ(result, p + 1);
-  return 0;
-}
-
-DART_EXPORT int TestReturnNull(int32_t fn()) {
-  CHECK_EQ(fn(), 0);
-  return 0;
-}
-
-DART_EXPORT int TestNullPointers(int64_t* (*fn)(int64_t* ptr)) {
-  CHECK_EQ(fn(nullptr), nullptr);
-  int64_t p[2] = {0};
-  CHECK_EQ(fn(p), p + 1);
-  return 0;
-}
-
-struct CallbackTestData {
-  int success;
-  void (*callback)();
-};
-
-#if defined(TARGET_OS_LINUX) && !defined(PRODUCT)
-
-thread_local sigjmp_buf buf;
-void CallbackTestSignalHandler(int) {
-  siglongjmp(buf, 1);
-}
-
-int ExpectAbort(void (*fn)()) {
-  fprintf(stderr, "**** EXPECT STACKTRACE TO FOLLOW. THIS IS OK. ****\n");
-
-  struct sigaction old_action;
-  int result = __sigsetjmp(buf, /*savesigs=*/1);
-  if (result == 0) {
-    // Install signal handler.
-    struct sigaction handler;
-    handler.sa_handler = CallbackTestSignalHandler;
-    sigemptyset(&handler.sa_mask);
-    handler.sa_flags = 0;
-
-    sigaction(SIGABRT, &handler, &old_action);
-
-    fn();
-  } else {
-    // Caught the setjmp.
-    sigaction(SIGABRT, &old_action, NULL);
-    exit(0);
-  }
-  fprintf(stderr, "Expected abort!!!\n");
-  exit(1);
-}
-
-void* TestCallbackOnThreadOutsideIsolate(void* parameter) {
-  CallbackTestData* data = reinterpret_cast<CallbackTestData*>(parameter);
-  data->success = ExpectAbort(data->callback);
-  return NULL;
-}
-
-int TestCallbackOtherThreadHelper(void* (*tester)(void*), void (*fn)()) {
-  CallbackTestData data = {1, fn};
-
-  pthread_attr_t attr;
-  int result = pthread_attr_init(&attr);
-  CHECK_EQ(result, 0);
-
-  pthread_t tid;
-  result = pthread_create(&tid, &attr, tester, &data);
-  CHECK_EQ(result, 0);
-
-  result = pthread_attr_destroy(&attr);
-  CHECK_EQ(result, 0);
-
-  void* retval;
-  result = pthread_join(tid, &retval);
-
-  // Doesn't actually return because the other thread will exit when the test is
-  // finished.
-  UNREACHABLE();
-}
-
-// Run a callback on another thread and verify that it triggers SIGABRT.
-DART_EXPORT int TestCallbackWrongThread(void (*fn)()) {
-  return TestCallbackOtherThreadHelper(&TestCallbackOnThreadOutsideIsolate, fn);
-}
-
-// Verify that we get SIGABRT when invoking a native callback outside an
-// isolate.
-DART_EXPORT int TestCallbackOutsideIsolate(void (*fn)()) {
-  Dart_Isolate current = Dart_CurrentIsolate();
-
-  Dart_ExitIsolate();
-  CallbackTestData data = {1, fn};
-  TestCallbackOnThreadOutsideIsolate(&data);
-  Dart_EnterIsolate(current);
-
-  return data.success;
-}
-
-DART_EXPORT int TestCallbackWrongIsolate(void (*fn)()) {
-  return ExpectAbort(fn);
-}
-
-#endif  // defined(TARGET_OS_LINUX) && !defined(PRODUCT)
-
 }  // namespace dart
diff --git a/runtime/lib/ffi.cc b/runtime/lib/ffi.cc
index 12620f4..34a5530 100644
--- a/runtime/lib/ffi.cc
+++ b/runtime/lib/ffi.cc
@@ -9,7 +9,6 @@
 #include "vm/class_finalizer.h"
 #include "vm/compiler/assembler/assembler.h"
 #include "vm/compiler/ffi.h"
-#include "vm/compiler/jit/compiler.h"
 #include "vm/exceptions.h"
 #include "vm/log.h"
 #include "vm/native_arguments.h"
@@ -547,68 +546,53 @@
   return raw_closure;
 }
 
-// Generates assembly to trampoline from native code into Dart.
-static uword CompileNativeCallback(const Function& c_signature,
-                                   const Function& dart_target) {
+// Generates assembly to trampoline from C++ back into Dart.
+static void* GenerateFfiInverseTrampoline(const Function& signature,
+                                          void* dart_entry_point) {
 #if defined(DART_PRECOMPILED_RUNTIME) || defined(DART_PRECOMPILER)
   UNREACHABLE();
 #elif !defined(TARGET_ARCH_X64)
   // https://github.com/dart-lang/sdk/issues/35774
-  // FFI is supported, but callbacks are not.
-  Exceptions::ThrowUnsupportedError(
-      "FFI callbacks are currently supported on 64-bit Intel only.");
+  UNREACHABLE();
+#elif !defined(TARGET_OS_LINUX) && !defined(TARGET_OS_MACOS) &&                \
+    !defined(TARGET_OS_WINDOWS)
+  // https://github.com/dart-lang/sdk/issues/35760 Arm32 && Android
+  // https://github.com/dart-lang/sdk/issues/35772 Arm64
+  // https://github.com/dart-lang/sdk/issues/35773 DBC
+  UNREACHABLE();
 #else
-  Thread* const thread = Thread::Current();
-  const int32_t callback_id = thread->AllocateFfiCallbackId();
 
-  // Create a new Function named 'FfiCallback' and stick it in the 'dart:ffi'
-  // library. Note that these functions will never be invoked by Dart, so it
-  // doesn't matter that they all have the same name.
-  Zone* const Z = thread->zone();
-  const String& name =
-      String::ZoneHandle(Symbols::New(Thread::Current(), "FfiCallback"));
-  const Library& lib = Library::Handle(Library::FfiLibrary());
-  const Class& owner_class = Class::Handle(lib.toplevel_class());
-  const Function& function =
-      Function::Handle(Z, Function::New(name, RawFunction::kFfiTrampoline,
-                                        /*is_static=*/true,
-                                        /*is_const=*/false,
-                                        /*is_abstract=*/false,
-                                        /*is_external=*/false,
-                                        /*is_native=*/false, owner_class,
-                                        TokenPosition::kMinSource));
-  function.set_is_debuggable(false);
-
-  // Set callback-specific fields which the flow-graph builder needs to generate
-  // the body.
-  function.SetFfiCSignature(c_signature);
-  function.SetFfiCallbackId(callback_id);
-  function.SetFfiCallbackTarget(dart_target);
-
-  // We compile the callback immediately because we need to return a pointer to
-  // the entry-point. Native calls do not use patching like Dart calls, so we
-  // cannot compile it lazily.
-  const Object& result =
-      Object::Handle(Z, Compiler::CompileOptimizedFunction(thread, function));
-  if (result.IsError()) {
-    Exceptions::PropagateError(Error::Cast(result));
-  }
-  ASSERT(result.IsCode());
-  const Code& code = Code::Cast(result);
-
-  thread->SetFfiCallbackCode(callback_id, code);
-
-  return code.EntryPoint();
+  // TODO(dacoharkes): Implement this.
+  // https://github.com/dart-lang/sdk/issues/35761
+  // Look at StubCode::GenerateInvokeDartCodeStub.
+  UNREACHABLE();
 #endif
 }
 
+// TODO(dacoharkes): Implement this feature.
+// https://github.com/dart-lang/sdk/issues/35761
+// For now, it always returns Pointer with address 0.
 DEFINE_NATIVE_ENTRY(Ffi_fromFunction, 1, 1) {
   GET_NATIVE_TYPE_ARGUMENT(type_arg, arguments->NativeTypeArgAt(0));
   GET_NON_NULL_NATIVE_ARGUMENT(Closure, closure, arguments->NativeArgAt(0));
 
-  const Function& native_signature =
-      Function::Handle(((Type&)type_arg).signature());
+  Function& c_signature = Function::Handle(((Type&)type_arg).signature());
+
   Function& func = Function::Handle(closure.function());
+  Code& code = Code::Handle(func.EnsureHasCode());
+  void* entryPoint = reinterpret_cast<void*>(code.EntryPoint());
+
+  THR_Print("Ffi_fromFunction: %s\n", type_arg.ToCString());
+  THR_Print("Ffi_fromFunction: %s\n", c_signature.ToCString());
+  THR_Print("Ffi_fromFunction: %s\n", closure.ToCString());
+  THR_Print("Ffi_fromFunction: %s\n", func.ToCString());
+  THR_Print("Ffi_fromFunction: %s\n", code.ToCString());
+  THR_Print("Ffi_fromFunction: %p\n", entryPoint);
+  THR_Print("Ffi_fromFunction: %" Pd "\n", code.Size());
+
+  intptr_t address = reinterpret_cast<intptr_t>(
+      GenerateFfiInverseTrampoline(c_signature, entryPoint));
+
   TypeArguments& type_args = TypeArguments::Handle(zone);
   type_args = TypeArguments::New(1);
   type_args.SetTypeAt(Pointer::kNativeTypeArgPos, type_arg);
@@ -624,19 +608,9 @@
       ClassFinalizer::FinalizeType(Class::Handle(), native_function_type);
   native_function_type ^= native_function_type.Canonicalize();
 
-  // The FE verifies that the target of a 'fromFunction' is a static method, so
-  // the value we see here must be a static tearoff. See ffi_use_sites.dart for
-  // details.
-  //
-  // TODO(36748): Define hot-reload semantics of native callbacks. We may need
-  // to look up the target by name.
-  ASSERT(func.IsImplicitClosureFunction());
-  func = func.parent_function();
-  ASSERT(func.is_static());
+  address = 0;  // https://github.com/dart-lang/sdk/issues/35761
 
-  const uword address = CompileNativeCallback(native_signature, func);
-
-  const Pointer& result = Pointer::Handle(Pointer::New(
+  Pointer& result = Pointer::Handle(Pointer::New(
       native_function_type, Integer::Handle(zone, Integer::New(address))));
 
   return result.raw();
@@ -708,7 +682,7 @@
     } else if (loc.IsFpuRegister()) {
       descr.SetFpuRegister(loc.fpu_reg(), arg_value);
     } else {
-      ASSERT(loc.IsStackSlot() || loc.IsDoubleStackSlot());
+      ASSERT(loc.IsStackSlot());
       ASSERT(loc.stack_index() < num_stack_slots);
       descr.SetStackSlotValue(loc.stack_index(), arg_value);
     }
diff --git a/runtime/lib/ffi_patch.dart b/runtime/lib/ffi_patch.dart
index 6915d51..c8fde6c 100644
--- a/runtime/lib/ffi_patch.dart
+++ b/runtime/lib/ffi_patch.dart
@@ -53,21 +53,3 @@
   @patch
   void free() native "Ffi_free";
 }
-
-// This method gets called when an exception bubbles up to the native -> Dart
-// boundary from an FFI native callback. Since native code does not have any
-// concept of exceptions, the exception cannot be propagated any further.
-// Instead, print a warning with the exception and return 0/0.0 from the
-// callback.
-//
-// TODO(36856): Iron out the story behind exceptions.
-@pragma("vm:entry-point")
-void _handleExposedException(dynamic exception, dynamic stackTrace) {
-  print(
-      "==================== UNHANDLED EXCEPTION FROM FFI CALLBACK ====================");
-  print(
-      """ ** Native callbacks should not throw exceptions because they cannot be
-    propagated into native code. **""");
-  print("EXCEPTION: $exception");
-  print(stackTrace);
-}
diff --git a/runtime/vm/compiler/assembler/assembler_ia32.cc b/runtime/vm/compiler/assembler/assembler_ia32.cc
index dc08528..32656b9 100644
--- a/runtime/vm/compiler/assembler/assembler_ia32.cc
+++ b/runtime/vm/compiler/assembler/assembler_ia32.cc
@@ -2085,10 +2085,9 @@
 }
 
 void Assembler::TransitionGeneratedToNative(Register destination_address,
-                                            Register new_exit_frame,
                                             Register scratch) {
   // Save exit frame information to enable stack walking.
-  movl(Address(THR, Thread::top_exit_frame_info_offset()), new_exit_frame);
+  movl(Address(THR, Thread::top_exit_frame_info_offset()), FPREG);
 
   // Mark that the thread is executing native code.
   movl(VMTagAddress(), destination_address);
diff --git a/runtime/vm/compiler/assembler/assembler_ia32.h b/runtime/vm/compiler/assembler/assembler_ia32.h
index c09a0e5..f9e01e5 100644
--- a/runtime/vm/compiler/assembler/assembler_ia32.h
+++ b/runtime/vm/compiler/assembler/assembler_ia32.h
@@ -649,7 +649,6 @@
   // Require a temporary register 'tmp'.
   // Clobber all non-CPU registers (e.g. XMM registers and the "FPU stack").
   void TransitionGeneratedToNative(Register destination_address,
-                                   Register new_exit_frame,
                                    Register scratch);
   void TransitionNativeToGenerated(Register scratch);
 
diff --git a/runtime/vm/compiler/assembler/assembler_x64.cc b/runtime/vm/compiler/assembler/assembler_x64.cc
index f9e725b..073a179 100644
--- a/runtime/vm/compiler/assembler/assembler_x64.cc
+++ b/runtime/vm/compiler/assembler/assembler_x64.cc
@@ -164,10 +164,9 @@
   EmitUint8(0xC0 + (dst & 0x07));
 }
 
-void Assembler::TransitionGeneratedToNative(Register destination_address,
-                                            Register new_exit_frame) {
+void Assembler::TransitionGeneratedToNative(Register destination_address) {
   // Save exit frame information to enable stack walking.
-  movq(Address(THR, Thread::top_exit_frame_info_offset()), new_exit_frame);
+  movq(Address(THR, Thread::top_exit_frame_info_offset()), FPREG);
 
   movq(Assembler::VMTagAddress(), destination_address);
   movq(Address(THR, compiler::target::Thread::execution_state_offset()),
@@ -1518,18 +1517,6 @@
   }
 }
 
-void Assembler::EmitEntryFrameVerification() {
-#if defined(DEBUG)
-  Label ok;
-  leaq(RAX, Address(RBP, target::frame_layout.exit_link_slot_from_entry_fp *
-                             target::kWordSize));
-  cmpq(RAX, RSP);
-  j(EQUAL, &ok);
-  Stop("target::frame_layout.exit_link_slot_from_entry_fp mismatch");
-  Bind(&ok);
-#endif
-}
-
 void Assembler::PushRegisters(intptr_t cpu_register_set,
                               intptr_t xmm_register_set) {
   const intptr_t xmm_regs_count = RegisterSet::RegisterCount(xmm_register_set);
diff --git a/runtime/vm/compiler/assembler/assembler_x64.h b/runtime/vm/compiler/assembler/assembler_x64.h
index 4df9250..1fe5400 100644
--- a/runtime/vm/compiler/assembler/assembler_x64.h
+++ b/runtime/vm/compiler/assembler/assembler_x64.h
@@ -306,8 +306,7 @@
 
   void setcc(Condition condition, ByteRegister dst);
 
-  void TransitionGeneratedToNative(Register destination_address,
-                                   Register new_exit_frame);
+  void TransitionGeneratedToNative(Register destination_address);
   void TransitionNativeToGenerated();
 
 // Register-register, register-address and address-register instructions.
@@ -778,13 +777,6 @@
   void LeaveFrame();
   void ReserveAlignedFrameSpace(intptr_t frame_space);
 
-  // In debug mode, generates code to verify that:
-  //   FP + kExitLinkSlotFromFp == SP
-  //
-  // Triggers breakpoint otherwise.
-  // Clobbers RAX.
-  void EmitEntryFrameVerification();
-
   // Create a frame for calling into runtime that preserves all volatile
   // registers.  Frame's RSP is guaranteed to be correctly aligned and
   // frame_space bytes are reserved under it.
diff --git a/runtime/vm/compiler/backend/constant_propagator.cc b/runtime/vm/compiler/backend/constant_propagator.cc
index 44a3055..931ffa3 100644
--- a/runtime/vm/compiler/backend/constant_propagator.cc
+++ b/runtime/vm/compiler/backend/constant_propagator.cc
@@ -142,10 +142,6 @@
   }
 }
 
-void ConstantPropagator::VisitNativeEntry(NativeEntryInstr* block) {
-  VisitFunctionEntry(block);
-}
-
 void ConstantPropagator::VisitOsrEntry(OsrEntryInstr* block) {
   for (auto def : *block->initial_definitions()) {
     def->Accept(this);
@@ -196,10 +192,6 @@
   // Nothing to do.
 }
 
-void ConstantPropagator::VisitNativeReturn(NativeReturnInstr* instr) {
-  // Nothing to do.
-}
-
 void ConstantPropagator::VisitThrow(ThrowInstr* instr) {
   // Nothing to do.
 }
@@ -373,10 +365,6 @@
   SetValue(instr, non_constant_);
 }
 
-void ConstantPropagator::VisitNativeParameter(NativeParameterInstr* instr) {
-  SetValue(instr, non_constant_);
-}
-
 void ConstantPropagator::VisitPushArgument(PushArgumentInstr* instr) {
   if (SetValue(instr, instr->value()->definition()->constant_value())) {
     // The worklist implementation breaks down around push arguments,
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler.cc b/runtime/vm/compiler/backend/flow_graph_compiler.cc
index 1a8e9bb..00f1bdf 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler.cc
@@ -1365,7 +1365,7 @@
                                : ic_data.arguments_descriptor());
   ASSERT(ArgumentsDescriptor(arguments_descriptor).TypeArgsLen() ==
          args_info.type_args_len);
-  if (is_optimizing() && !ForcedOptimization()) {
+  if (is_optimizing()) {
     EmitOptimizedStaticCall(function, arguments_descriptor,
                             args_info.count_with_type_args, deopt_id, token_pos,
                             locs, entry_kind);
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler.h b/runtime/vm/compiler/backend/flow_graph_compiler.h
index cd43179..8795377 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler.h
+++ b/runtime/vm/compiler/backend/flow_graph_compiler.h
@@ -409,11 +409,6 @@
     return block_order_;
   }
 
-  // If 'ForcedOptimization()' returns 'true', we are compiling in optimized
-  // mode for a function which cannot deoptimize. Certain optimizations, e.g.
-  // speculative optimizations and call patching are disabled.
-  bool ForcedOptimization() const { return function().ForceOptimize(); }
-
   const FlowGraph& flow_graph() const { return flow_graph_; }
 
   BlockEntryInstr* current_block() const { return current_block_; }
diff --git a/runtime/vm/compiler/backend/il.cc b/runtime/vm/compiler/backend/il.cc
index 86bf257..5c840ec 100644
--- a/runtime/vm/compiler/backend/il.cc
+++ b/runtime/vm/compiler/backend/il.cc
@@ -27,7 +27,6 @@
 #include "vm/regexp_assembler_ir.h"
 #include "vm/resolver.h"
 #include "vm/scopes.h"
-#include "vm/stack_frame.h"
 #include "vm/stub_code.h"
 #include "vm/symbols.h"
 #include "vm/type_testing_stubs.h"
@@ -3826,9 +3825,7 @@
     __ nop();
   }
 #endif
-  if (tag() == Instruction::kFunctionEntry) {
-    __ Bind(compiler->GetJumpLabel(this));
-  }
+  __ Bind(compiler->GetJumpLabel(this));
 
 // In the AOT compiler we want to reduce code size, so generate no
 // fall-through code in [FlowGraphCompiler::CompileGraph()].
@@ -3884,21 +3881,6 @@
   }
 }
 
-LocationSummary* NativeEntryInstr::MakeLocationSummary(Zone* zone,
-                                                       bool optimizing) const {
-  UNREACHABLE();
-}
-
-#if !defined(TARGET_ARCH_X64)
-void NativeEntryInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
-  UNREACHABLE();
-}
-
-void NativeReturnInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
-  UNREACHABLE();
-}
-#endif
-
 LocationSummary* OsrEntryInstr::MakeLocationSummary(Zone* zone,
                                                     bool optimizing) const {
   UNREACHABLE();
@@ -4004,43 +3986,6 @@
   UNREACHABLE();
 }
 
-void NativeParameterInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
-#if !defined(TARGET_ARCH_DBC)
-  // The native entry frame has size -kExitLinkSlotFromFp. In order to access
-  // the top of stack from above the entry frame, we add a constant to account
-  // for the the two frame pointers and return address of the entry frame.
-  constexpr intptr_t kEntryFramePadding = 3;
-  FrameRebase rebase(/*old_base=*/SPREG, /*new_base=*/FPREG,
-                     -kExitLinkSlotFromEntryFp + kEntryFramePadding);
-  const Location dst = locs()->out(0);
-  const Location src = rebase.Rebase(loc_);
-  NoTemporaryAllocator no_temp;
-  compiler->EmitMove(dst, src, &no_temp);
-#else
-  UNREACHABLE();
-#endif
-}
-
-LocationSummary* NativeParameterInstr::MakeLocationSummary(Zone* zone,
-                                                           bool opt) const {
-#if !defined(TARGET_ARCH_DBC)
-  ASSERT(opt);
-  Location input = Location::Any();
-  if (representation() == kUnboxedInt64 && compiler::target::kWordSize < 8) {
-    input = Location::Pair(Location::RequiresRegister(),
-                           Location::RequiresFpuRegister());
-  } else {
-    input = RegisterKindForResult() == Location::kRegister
-                ? Location::RequiresRegister()
-                : Location::RequiresFpuRegister();
-  }
-  return LocationSummary::Make(zone, /*num_inputs=*/0, input,
-                               LocationSummary::kNoCall);
-#else
-  UNREACHABLE();
-#endif
-}
-
 bool ParallelMoveInstr::IsRedundant() const {
   for (intptr_t i = 0; i < moves_.length(); i++) {
     if (!moves_[i]->IsRedundant()) {
@@ -5429,16 +5374,6 @@
   }
 }
 
-LocationSummary* NativeReturnInstr::MakeLocationSummary(Zone* zone,
-                                                        bool opt) const {
-  const intptr_t kNumInputs = 1;
-  const intptr_t kNumTemps = 0;
-  LocationSummary* locs = new (zone)
-      LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kNoCall);
-  locs->set_in(0, result_location_);
-  return locs;
-}
-
 #undef Z
 
 #else
@@ -5461,11 +5396,6 @@
   return summary;
 }
 
-LocationSummary* NativeReturnInstr::MakeLocationSummary(Zone* zone,
-                                                        bool opt) const {
-  UNREACHABLE();
-}
-
 #endif  // !defined(TARGET_ARCH_DBC)
 
 Representation FfiCallInstr::representation() const {
diff --git a/runtime/vm/compiler/backend/il.h b/runtime/vm/compiler/backend/il.h
index 3d1f7f4..3e91b50 100644
--- a/runtime/vm/compiler/backend/il.h
+++ b/runtime/vm/compiler/backend/il.h
@@ -336,21 +336,18 @@
   M(JoinEntry, kNoGC)                                                          \
   M(TargetEntry, kNoGC)                                                        \
   M(FunctionEntry, kNoGC)                                                      \
-  M(NativeEntry, kNoGC)                                                        \
   M(OsrEntry, kNoGC)                                                           \
   M(IndirectEntry, kNoGC)                                                      \
   M(CatchBlockEntry, kNoGC)                                                    \
   M(Phi, kNoGC)                                                                \
   M(Redefinition, kNoGC)                                                       \
   M(Parameter, kNoGC)                                                          \
-  M(NativeParameter, kNoGC)                                                    \
   M(LoadIndexedUnsafe, kNoGC)                                                  \
   M(StoreIndexedUnsafe, kNoGC)                                                 \
   M(TailCall, kNoGC)                                                           \
   M(ParallelMove, kNoGC)                                                       \
   M(PushArgument, kNoGC)                                                       \
   M(Return, kNoGC)                                                             \
-  M(NativeReturn, kNoGC)                                                       \
   M(Throw, kNoGC)                                                              \
   M(ReThrow, kNoGC)                                                            \
   M(Stop, _)                                                                   \
@@ -919,28 +916,6 @@
 
   virtual bool UseSharedSlowPathStub(bool is_optimizing) const { return false; }
 
-  // 'RegisterKindForResult()' returns the register kind necessary to hold the
-  // result.
-  //
-  // This is not virtual because instructions should override representation()
-  // instead.
-  Location::Kind RegisterKindForResult() const {
-    const Representation rep = representation();
-#if !defined(TARGET_ARCH_DBC)
-    if ((rep == kUnboxedFloat) || (rep == kUnboxedDouble) ||
-        (rep == kUnboxedFloat32x4) || (rep == kUnboxedInt32x4) ||
-        (rep == kUnboxedFloat64x2)) {
-      return Location::kFpuRegister;
-    }
-#else
-    // DBC supports only unboxed doubles and does not have distinguished FPU
-    // registers.
-    ASSERT((rep != kUnboxedFloat32x4) && (rep != kUnboxedInt32x4) &&
-           (rep != kUnboxedFloat64x2));
-#endif
-    return Location::kRegister;
-  }
-
  protected:
   // GetDeoptId and/or CopyDeoptIdFrom.
   friend class CallSiteInliner;
@@ -1639,33 +1614,6 @@
   DISALLOW_COPY_AND_ASSIGN(FunctionEntryInstr);
 };
 
-// Represents entry into a function from native code.
-//
-// Native entries are not allowed to have regular parameters. They should use
-// NativeParameter instead (which doesn't count as an initial definition).
-class NativeEntryInstr : public FunctionEntryInstr {
- public:
-  NativeEntryInstr(const ZoneGrowableArray<Location>* argument_locations,
-                   GraphEntryInstr* graph_entry,
-                   intptr_t block_id,
-                   intptr_t try_index,
-                   intptr_t deopt_id,
-                   intptr_t callback_id)
-      : FunctionEntryInstr(graph_entry, block_id, try_index, deopt_id),
-        callback_id_(callback_id),
-        argument_locations_(argument_locations) {}
-
-  DECLARE_INSTRUCTION(NativeEntry)
-
-  PRINT_TO_SUPPORT
-
- private:
-  void SaveArgument(FlowGraphCompiler* compiler, Location loc) const;
-
-  const intptr_t callback_id_;
-  const ZoneGrowableArray<Location>* const argument_locations_;
-};
-
 // Represents an OSR entrypoint to a function.
 //
 // The OSR entry has it's own initial definitions.
@@ -2245,57 +2193,6 @@
   DISALLOW_COPY_AND_ASSIGN(ParameterInstr);
 };
 
-// Native parameters are not treated as initial definitions because they cannot
-// be inlined and are only usable in optimized code. The location must be a
-// stack location relative to the position of the stack (SPREG) after
-// register-based arguments have been saved on entry to a native call. See
-// NativeEntryInstr::EmitNativeCode for more details.
-//
-// TOOD(33549): Unify with ParameterInstr.
-class NativeParameterInstr : public Definition {
- public:
-  NativeParameterInstr(Location loc, Representation representation)
-      : loc_(loc), representation_(representation) {
-    if (loc.IsPairLocation()) {
-      for (intptr_t i : {0, 1}) {
-        ASSERT(loc_.Component(i).HasStackIndex() &&
-               loc_.Component(i).base_reg() == SPREG);
-      }
-    } else {
-      ASSERT(loc_.HasStackIndex() && loc_.base_reg() == SPREG);
-    }
-  }
-
-  DECLARE_INSTRUCTION(NativeParameter)
-
-  virtual Representation representation() const { return representation_; }
-
-  intptr_t InputCount() const { return 0; }
-  Value* InputAt(intptr_t i) const {
-    UNREACHABLE();
-    return NULL;
-  }
-
-  virtual bool ComputeCanDeoptimize() const { return false; }
-
-  virtual bool HasUnknownSideEffects() const { return false; }
-
-  // TODO(sjindel): We can make this more precise.
-  virtual CompileType ComputeType() const { return CompileType::Dynamic(); }
-
-  virtual bool MayThrow() const { return false; }
-
-  PRINT_OPERANDS_TO_SUPPORT
-
- private:
-  virtual void RawSetInputAt(intptr_t i, Value* value) { UNREACHABLE(); }
-
-  const Location loc_;
-  const Representation representation_;
-
-  DISALLOW_COPY_AND_ASSIGN(NativeParameterInstr);
-};
-
 // Stores a tagged pointer to a slot accessible from a fixed register.  It has
 // the form:
 //
@@ -2359,11 +2256,8 @@
 // the frame.  This is asserted via `inliner.cc::CalleeGraphValidator`.
 class LoadIndexedUnsafeInstr : public TemplateDefinition<1, NoThrow> {
  public:
-  LoadIndexedUnsafeInstr(Value* index,
-                         intptr_t offset,
-                         CompileType result_type,
-                         Representation representation = kTagged)
-      : offset_(offset), representation_(representation) {
+  LoadIndexedUnsafeInstr(Value* index, intptr_t offset, CompileType result_type)
+      : offset_(offset) {
     UpdateType(result_type);
     SetInputAt(0, index);
   }
@@ -2374,6 +2268,7 @@
     ASSERT(index == 0);
     return kTagged;
   }
+  virtual Representation representation() const { return kTagged; }
   virtual bool ComputeCanDeoptimize() const { return false; }
   virtual bool HasUnknownSideEffects() const { return false; }
 
@@ -2389,7 +2284,6 @@
 
  private:
   const intptr_t offset_;
-  const Representation representation_;
 
   DISALLOW_COPY_AND_ASSIGN(LoadIndexedUnsafeInstr);
 };
@@ -2499,40 +2393,6 @@
   DISALLOW_COPY_AND_ASSIGN(ReturnInstr);
 };
 
-// Represents a return from a Dart function into native code.
-class NativeReturnInstr : public ReturnInstr {
- public:
-  NativeReturnInstr(TokenPosition token_pos,
-                    Value* value,
-                    Representation rep,
-                    Location result_location,
-                    intptr_t deopt_id)
-      : ReturnInstr(token_pos, value, deopt_id),
-        result_representation_(rep),
-        result_location_(result_location) {}
-
-  DECLARE_INSTRUCTION(NativeReturn)
-
-  PRINT_OPERANDS_TO_SUPPORT
-
-  virtual Representation RequiredInputRepresentation(intptr_t idx) const {
-    ASSERT(idx == 0);
-    return result_representation_;
-  }
-
-  virtual bool CanBecomeDeoptimizationTarget() const {
-    // Unlike ReturnInstr, NativeReturnInstr cannot be inlined (because it's
-    // returning into native code).
-    return false;
-  }
-
- private:
-  const Representation result_representation_;
-  const Location result_location_;
-
-  DISALLOW_COPY_AND_ASSIGN(NativeReturnInstr);
-};
-
 class ThrowInstr : public TemplateInstruction<0, Throws> {
  public:
   explicit ThrowInstr(TokenPosition token_pos, intptr_t deopt_id)
diff --git a/runtime/vm/compiler/backend/il_ia32.cc b/runtime/vm/compiler/backend/il_ia32.cc
index 8e0e512..349a09c 100644
--- a/runtime/vm/compiler/backend/il_ia32.cc
+++ b/runtime/vm/compiler/backend/il_ia32.cc
@@ -886,7 +886,7 @@
   __ popl(tmp);
   __ movl(Address(FPREG, kSavedCallerPcSlotFromFp * kWordSize), tmp);
 
-  __ TransitionGeneratedToNative(branch, FPREG, tmp);
+  __ TransitionGeneratedToNative(branch, tmp);
   __ call(branch);
 
   // The x86 calling convention requires floating point values to be returned on
diff --git a/runtime/vm/compiler/backend/il_printer.cc b/runtime/vm/compiler/backend/il_printer.cc
index a2f841d..fe7508f 100644
--- a/runtime/vm/compiler/backend/il_printer.cc
+++ b/runtime/vm/compiler/backend/il_printer.cc
@@ -524,10 +524,12 @@
 void FfiCallInstr::PrintOperandsTo(BufferFormatter* f) const {
   f->Print(" pointer=");
   InputAt(TargetAddressIndex())->PrintTo(f);
+  f->Print(" signature=%s",
+           Type::Handle(signature_.SignatureType()).ToCString());
   for (intptr_t i = 0, n = InputCount(); i < n - 1; ++i) {
     f->Print(", ");
     InputAt(i)->PrintTo(f);
-    f->Print(" (@%s)", arg_locations_[i].ToCString());
+    f->Print(" (at %s) ", arg_locations_[i].ToCString());
   }
 }
 
@@ -1061,24 +1063,6 @@
   BlockEntryWithInitialDefs::PrintInitialDefinitionsTo(f);
 }
 
-void NativeEntryInstr::PrintTo(BufferFormatter* f) const {
-  f->Print("B%" Pd "[native function entry]:%" Pd, block_id(), GetDeoptId());
-  if (HasParallelMove()) {
-    f->Print("\n");
-    parallel_move()->PrintTo(f);
-  }
-  BlockEntryWithInitialDefs::PrintInitialDefinitionsTo(f);
-}
-
-void NativeReturnInstr::PrintOperandsTo(BufferFormatter* f) const {
-  value()->PrintTo(f);
-}
-
-void NativeParameterInstr::PrintOperandsTo(BufferFormatter* f) const {
-  f->Print("%s as %s", loc_.ToCString(),
-           RepresentationToCString(representation_));
-}
-
 void CatchBlockEntryInstr::PrintTo(BufferFormatter* f) const {
   f->Print("B%" Pd "[target catch try_idx %" Pd " catch_try_idx %" Pd "]",
            block_id(), try_index(), catch_try_index());
diff --git a/runtime/vm/compiler/backend/il_x64.cc b/runtime/vm/compiler/backend/il_x64.cc
index 70073b1..ce0b196 100644
--- a/runtime/vm/compiler/backend/il_x64.cc
+++ b/runtime/vm/compiler/backend/il_x64.cc
@@ -139,43 +139,6 @@
   __ set_constant_pool_allowed(true);
 }
 
-void NativeReturnInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
-  __ LeaveDartFrame();
-
-  // Pop dummy return address.
-  __ popq(TMP);
-
-  // Anything besides the return register.
-  const Register vm_tag_reg = RBX, old_exit_frame_reg = RCX;
-
-  __ popq(old_exit_frame_reg);
-
-  // Restore top_resource.
-  __ popq(TMP);
-  __ movq(Address(THR, compiler::target::Thread::top_resource_offset()), TMP);
-
-  __ popq(vm_tag_reg);
-
-  // TransitionGeneratedToNative will reset the exit frame info to
-  // old_exit_frame_reg *before* entering the safepoint.
-  __ TransitionGeneratedToNative(vm_tag_reg, old_exit_frame_reg);
-
-  // Restore C++ ABI callee-saved registers.
-  __ PopRegisters(CallingConventions::kCalleeSaveCpuRegisters,
-                  CallingConventions::kCalleeSaveXmmRegisters);
-
-  // Leave the entry frame.
-  __ LeaveFrame();
-
-  // Leave the dummy frame holding the pushed arguments.
-  __ LeaveFrame();
-
-  __ ret();
-
-  // For following blocks.
-  __ set_constant_pool_allowed(true);
-}
-
 static Condition NegateCondition(Condition condition) {
   switch (condition) {
     case EQUAL:
@@ -958,7 +921,7 @@
   __ movq(Address(FPREG, kSavedCallerPcSlotFromFp * kWordSize), TMP);
 
   // Update information in the thread object and enter a safepoint.
-  __ TransitionGeneratedToNative(target_address, FPREG);
+  __ TransitionGeneratedToNative(target_address);
 
   __ CallCFunction(target_address);
 
@@ -980,130 +943,6 @@
   __ popq(TMP);
 }
 
-void NativeEntryInstr::SaveArgument(FlowGraphCompiler* compiler,
-                                    Location loc) const {
-  ASSERT(!loc.IsPairLocation());
-
-  if (loc.HasStackIndex()) return;
-
-  if (loc.IsRegister()) {
-    __ pushq(loc.reg());
-  } else if (loc.IsFpuRegister()) {
-    __ movq(TMP, loc.fpu_reg());
-    __ pushq(TMP);
-  } else {
-    UNREACHABLE();
-  }
-}
-
-void NativeEntryInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
-  if (FLAG_precompiled_mode) {
-    UNREACHABLE();
-  }
-
-  __ Bind(compiler->GetJumpLabel(this));
-
-  // Create a dummy frame holding the pushed arguments. This simplifies
-  // NativeReturnInstr::EmitNativeCode.
-  __ EnterFrame(0);
-
-  // Save the argument registers, in reverse order.
-  for (intptr_t i = argument_locations_->length(); i-- > 0;) {
-    SaveArgument(compiler, argument_locations_->At(i));
-  }
-
-  // Enter the entry frame.
-  __ EnterFrame(0);
-
-  // Save a space for the code object.
-  __ PushImmediate(Immediate(0));
-
-  // InvokoeDartCodeStub saves the arguments descriptor here. We don't have one,
-  // but we need to follow the same frame layout for the stack walker.
-  __ PushImmediate(Immediate(0));
-
-  // Save ABI callee-saved registers.
-  __ PushRegisters(CallingConventions::kCalleeSaveCpuRegisters,
-                   CallingConventions::kCalleeSaveXmmRegisters);
-
-  // Load the thread object.
-  // TODO(35765): Fix linking issue on AOT.
-  // TOOD(35934): Exclude native callbacks from snapshots.
-  //
-  // Create another frame to align the frame before continuing in "native" code.
-  {
-    __ EnterFrame(0);
-    __ ReserveAlignedFrameSpace(0);
-
-    __ movq(
-        RAX,
-        Immediate(reinterpret_cast<int64_t>(DLRT_GetThreadForNativeCallback)));
-    __ call(RAX);
-    __ movq(THR, RAX);
-
-    __ LeaveFrame();
-  }
-
-  // Save the current VMTag on the stack.
-  __ movq(RAX, Assembler::VMTagAddress());
-  __ pushq(RAX);
-
-  // Save top resource.
-  __ pushq(Address(THR, compiler::target::Thread::top_resource_offset()));
-  __ movq(Address(THR, compiler::target::Thread::top_resource_offset()),
-          Immediate(0));
-
-  // Save top exit frame info. Stack walker expects it to be here.
-  __ pushq(
-      Address(THR, compiler::target::Thread::top_exit_frame_info_offset()));
-
-  // In debug mode, verify that we've pushed the top exit frame info at the
-  // correct offset from FP.
-  __ EmitEntryFrameVerification();
-
-  // TransitionNativeToGenerated will reset top exit frame info to 0 *after*
-  // leaving the safepoint.
-  __ TransitionNativeToGenerated();
-
-  // Now that the safepoint has ended, we can touch Dart objects without
-  // handles.
-  // Otherwise we'll clobber the argument sent from the caller.
-  COMPILE_ASSERT(RAX != CallingConventions::kArg1Reg);
-  __ movq(CallingConventions::kArg1Reg, Immediate(callback_id_));
-  __ movq(RAX, Address(THR, compiler::target::Thread::
-                                verify_callback_isolate_entry_point_offset()));
-  __ call(RAX);
-
-  // Load the code object.
-  __ movq(RAX, Address(THR, compiler::target::Thread::callback_code_offset()));
-  __ movq(RAX, FieldAddress(
-                   RAX, compiler::target::GrowableObjectArray::data_offset()));
-  __ movq(CODE_REG,
-          FieldAddress(RAX, compiler::target::Array::data_offset() +
-                                callback_id_ * compiler::target::kWordSize));
-
-  // Put the code object in the reserved slot.
-  __ movq(Address(FPREG, kPcMarkerSlotFromFp * compiler::target::kWordSize),
-          CODE_REG);
-
-  if (FLAG_precompiled_mode && FLAG_use_bare_instructions) {
-    __ movq(PP, Address(THR,
-                        compiler::target::Thread::global_object_pool_offset()));
-  } else {
-    __ xorq(PP, PP);  // GC-safe value into PP.
-  }
-
-  // Push a dummy return address which suggests that we are inside of
-  // InvokeDartCodeStub. This is how the stack walker detects an entry frame.
-  __ movq(
-      RAX,
-      Address(THR, compiler::target::Thread::invoke_dart_code_stub_offset()));
-  __ pushq(FieldAddress(RAX, compiler::target::Code::entry_point_offset()));
-
-  // Continue with Dart frame setup.
-  FunctionEntryInstr::EmitNativeCode(compiler);
-}
-
 static bool CanBeImmediateIndex(Value* index, intptr_t cid) {
   if (!index->definition()->IsConstant()) return false;
   const Object& constant = index->definition()->AsConstant()->value();
@@ -1826,10 +1665,10 @@
 
   Label ok, fail_label;
 
-  Label* deopt = NULL;
-  if (compiler->is_optimizing()) {
-    deopt = compiler->AddDeoptStub(deopt_id(), ICData::kDeoptGuardField);
-  }
+  Label* deopt =
+      compiler->is_optimizing()
+          ? compiler->AddDeoptStub(deopt_id(), ICData::kDeoptGuardField)
+          : NULL;
 
   Label* fail = (deopt != NULL) ? deopt : &fail_label;
 
diff --git a/runtime/vm/compiler/backend/linearscan.cc b/runtime/vm/compiler/backend/linearscan.cc
index b5fea9c..4f5473c 100644
--- a/runtime/vm/compiler/backend/linearscan.cc
+++ b/runtime/vm/compiler/backend/linearscan.cc
@@ -824,6 +824,25 @@
   }
 }
 
+static Location::Kind RegisterKindForResult(Instruction* instr) {
+  const Representation rep = instr->representation();
+#if !defined(TARGET_ARCH_DBC)
+  if ((rep == kUnboxedFloat) || (rep == kUnboxedDouble) ||
+      (rep == kUnboxedFloat32x4) || (rep == kUnboxedInt32x4) ||
+      (rep == kUnboxedFloat64x2)) {
+    return Location::kFpuRegister;
+  } else {
+    return Location::kRegister;
+  }
+#else
+  // DBC supports only unboxed doubles and does not have distinguished FPU
+  // registers.
+  ASSERT((rep != kUnboxedFloat32x4) && (rep != kUnboxedInt32x4) &&
+         (rep != kUnboxedFloat64x2));
+  return Location::kRegister;
+#endif
+}
+
 //
 // When describing shape of live ranges in comments below we are going to use
 // the following notation:
@@ -972,11 +991,11 @@
     // All phi resolution moves are connected. Phi's live range is
     // complete.
     AssignSafepoints(phi, range);
-    CompleteRange(range, phi->RegisterKindForResult());
+    CompleteRange(range, RegisterKindForResult(phi));
     if (is_pair_phi) {
       LiveRange* second_range = GetLiveRange(ToSecondPairVreg(vreg));
       AssignSafepoints(phi, second_range);
-      CompleteRange(second_range, phi->RegisterKindForResult());
+      CompleteRange(second_range, RegisterKindForResult(phi));
     }
 
     move_idx += is_pair_phi ? 2 : 1;
@@ -1284,7 +1303,7 @@
   }
 
   AssignSafepoints(def, range);
-  CompleteRange(range, def->RegisterKindForResult());
+  CompleteRange(range, RegisterKindForResult(def));
 }
 
 // Create and update live ranges corresponding to instruction's inputs,
diff --git a/runtime/vm/compiler/ffi.cc b/runtime/vm/compiler/ffi.cc
index 665382a..823e8ef 100644
--- a/runtime/vm/compiler/ffi.cc
+++ b/runtime/vm/compiler/ffi.cc
@@ -9,8 +9,6 @@
 #include "platform/globals.h"
 #include "vm/compiler/backend/locations.h"
 #include "vm/compiler/runtime_api.h"
-#include "vm/growable_array.h"
-#include "vm/stack_frame.h"
 
 namespace dart {
 
@@ -184,7 +182,7 @@
           class Location,
           class Register,
           class FpuRegister>
-class ArgumentAllocator : public ValueObject {
+class ArgumentFrameState : public ValueObject {
  public:
   Location AllocateArgument(Representation rep) {
     switch (rep) {
@@ -209,13 +207,9 @@
     }
 
     // Argument must be spilled.
-    if (rep == kUnboxedInt64 && compiler::target::kWordSize == 4) {
+    if ((rep == kUnboxedInt64 || rep == kUnboxedDouble) &&
+        compiler::target::kWordSize == 4) {
       return AllocateAlignedStackSlots(rep);
-    } else if (rep == kUnboxedDouble) {
-      // By convention, we always use DoubleStackSlot for doubles, even on
-      // 64-bit systems.
-      ASSERT(!CallingConventions::kAlignArguments);
-      return AllocateDoubleStackSlot();
     } else {
       return AllocateStackSlot();
     }
@@ -227,13 +221,6 @@
                                CallingConventions::kStackPointerRegister);
   }
 
-  Location AllocateDoubleStackSlot() {
-    const Location result = Location::DoubleStackSlot(
-        stack_height_in_slots, CallingConventions::kStackPointerRegister);
-    stack_height_in_slots += 8 / compiler::target::kWordSize;
-    return result;
-  }
-
   // Allocates a pair of stack slots where the first stack slot is aligned to an
   // 8-byte boundary, if necessary.
   Location AllocateAlignedStackSlots(Representation rep) {
@@ -300,68 +287,6 @@
   intptr_t stack_height_in_slots = 0;
 };
 
-ZoneGrowableArray<Location>*
-CallbackArgumentTranslator::TranslateArgumentLocations(
-    const ZoneGrowableArray<Location>& arg_locs) {
-  auto& pushed_locs = *(new ZoneGrowableArray<Location>(arg_locs.length()));
-
-  CallbackArgumentTranslator translator;
-  for (intptr_t i = 0, n = arg_locs.length(); i < n; i++) {
-    translator.AllocateArgument(arg_locs[i]);
-  }
-  for (intptr_t i = 0, n = arg_locs.length(); i < n; ++i) {
-    pushed_locs.Add(translator.TranslateArgument(arg_locs[i]));
-  }
-
-  return &pushed_locs;
-}
-
-void CallbackArgumentTranslator::AllocateArgument(Location arg) {
-  if (arg.IsPairLocation()) {
-    AllocateArgument(arg.Component(0));
-    AllocateArgument(arg.Component(1));
-    return;
-  }
-  if (arg.HasStackIndex()) return;
-  ASSERT(arg.IsRegister() || arg.IsFpuRegister());
-  if (arg.IsRegister()) {
-    argument_slots_required_++;
-  } else {
-    argument_slots_required_ += 8 / compiler::target::kWordSize;
-  }
-}
-
-Location CallbackArgumentTranslator::TranslateArgument(Location arg) {
-  if (arg.IsPairLocation()) {
-    const Location low = TranslateArgument(arg.Component(0));
-    const Location high = TranslateArgument(arg.Component(1));
-    return Location::Pair(low, high);
-  }
-
-  if (arg.HasStackIndex()) {
-    // Add extra slots after the saved arguments for the return address and
-    // frame pointer of the dummy arguments frame, which will be between the
-    // saved argument registers and stack arguments. Also add slots for the
-    // shadow space if present (factored into
-    // kCallbackSlotsBeforeSavedArguments).
-    FrameRebase rebase(
-        /*old_base=*/SPREG, /*new_base=*/SPREG,
-        /*stack_delta=*/argument_slots_required_ +
-            kCallbackSlotsBeforeSavedArguments);
-    return rebase.Rebase(arg);
-  }
-
-  if (arg.IsRegister()) {
-    return Location::StackSlot(argument_slots_used_++, SPREG);
-  }
-
-  ASSERT(arg.IsFpuRegister());
-  const Location result =
-      Location::DoubleStackSlot(argument_slots_used_, SPREG);
-  argument_slots_used_ += 8 / compiler::target::kWordSize;
-  return result;
-}
-
 // Takes a list of argument representations, and converts it to a list of
 // argument locations based on calling convention.
 template <class CallingConventions,
@@ -374,7 +299,7 @@
   auto result = new ZoneGrowableArray<Location>(num_arguments);
 
   // Loop through all arguments and assign a register or a stack location.
-  ArgumentAllocator<CallingConventions, Location, Register, FpuRegister>
+  ArgumentFrameState<CallingConventions, Location, Register, FpuRegister>
       frame_state;
   for (intptr_t i = 0; i < num_arguments; i++) {
     Representation rep = arg_reps[i];
diff --git a/runtime/vm/compiler/ffi.h b/runtime/vm/compiler/ffi.h
index 846775a..cd8e96f 100644
--- a/runtime/vm/compiler/ffi.h
+++ b/runtime/vm/compiler/ffi.h
@@ -108,31 +108,6 @@
 
 #endif  // defined(TARGET_ARCH_DBC)
 
-// This classes translates the ABI location of arguments into the locations they
-// will inhabit after entry-frame setup in the invocation of a native callback.
-//
-// Native -> Dart callbacks must push all the arguments before executing any
-// Dart code because the reading the Thread from TLS requires calling a native
-// stub, and the argument registers are volatile on all ABIs we support.
-//
-// To avoid complicating initial definitions, all callback arguments are read
-// off the stack from their pushed locations, so this class updates the argument
-// positions to account for this.
-//
-// See 'NativeEntryInstr::EmitNativeCode' for details.
-class CallbackArgumentTranslator : public ValueObject {
- public:
-  static ZoneGrowableArray<Location>* TranslateArgumentLocations(
-      const ZoneGrowableArray<Location>& arg_locs);
-
- private:
-  void AllocateArgument(Location arg);
-  Location TranslateArgument(Location arg);
-
-  intptr_t argument_slots_used_ = 0;
-  intptr_t argument_slots_required_ = 0;
-};
-
 }  // namespace ffi
 
 }  // namespace compiler
diff --git a/runtime/vm/compiler/frontend/kernel_to_il.cc b/runtime/vm/compiler/frontend/kernel_to_il.cc
index 8e9a84b..6049959 100644
--- a/runtime/vm/compiler/frontend/kernel_to_il.cc
+++ b/runtime/vm/compiler/frontend/kernel_to_il.cc
@@ -16,7 +16,6 @@
 #include "vm/compiler/jit/compiler.h"
 #include "vm/kernel_loader.h"
 #include "vm/longjump.h"
-#include "vm/native_entry.h"
 #include "vm/object_store.h"
 #include "vm/report.h"
 #include "vm/resolver.h"
@@ -2441,31 +2440,6 @@
   return Fragment(extend);
 }
 
-Fragment FlowGraphBuilder::FfiExceptionalReturnValue(
-    const AbstractType& result_type,
-    Representation representation) {
-  ASSERT(optimizing_);
-  Object& result = Object::ZoneHandle(Z, Object::null());
-  if (representation == kUnboxedFloat || representation == kUnboxedDouble) {
-    result = Double::New(0.0, Heap::kOld);
-  } else {
-    result = Integer::New(0, Heap::kOld);
-  }
-  Fragment code;
-  code += Constant(result);
-  code += UnboxTruncate(representation);
-  return code;
-}
-
-#if !defined(TARGET_ARCH_DBC)
-Fragment FlowGraphBuilder::NativeReturn(Representation result) {
-  auto* instr = new (Z)
-      NativeReturnInstr(TokenPosition::kNoSource, Pop(), result,
-                        compiler::ffi::ResultLocation(result), DeoptId::kNone);
-  return Fragment(instr);
-}
-#endif
-
 Fragment FlowGraphBuilder::FfiPointerFromAddress(const Type& result_type) {
   Fragment test;
   TargetEntryInstr* null_entry;
@@ -2524,69 +2498,8 @@
   return Fragment(instr);
 }
 
-Fragment FlowGraphBuilder::FfiConvertArgumentToDart(
-    const AbstractType& ffi_type,
-    const Representation native_representation) {
-  Fragment body;
-  if (compiler::ffi::NativeTypeIsPointer(ffi_type)) {
-    body += Box(kUnboxedFfiIntPtr);
-    body += FfiPointerFromAddress(Type::Cast(ffi_type));
-  } else if (compiler::ffi::NativeTypeIsVoid(ffi_type)) {
-    body += Drop();
-    body += NullConstant();
-  } else {
-    const Representation from_rep = native_representation;
-    const Representation to_rep = compiler::ffi::TypeRepresentation(ffi_type);
-    if (from_rep != to_rep) {
-      body += BitCast(from_rep, to_rep);
-    } else {
-      body += FfiUnboxedExtend(from_rep, ffi_type);
-    }
-    body += Box(to_rep);
-  }
-  return body;
-}
-
-Fragment FlowGraphBuilder::FfiConvertArgumentToNative(
-    const Function& function,
-    const AbstractType& ffi_type,
-    const Representation native_representation) {
-  Fragment body;
-  // Check for 'null'. Only ffi.Pointers are allowed to be null.
-  if (!compiler::ffi::NativeTypeIsPointer(ffi_type)) {
-    body += LoadLocal(MakeTemporary());
-    body <<=
-        new (Z) CheckNullInstr(Pop(), String::ZoneHandle(Z, function.name()),
-                               GetNextDeoptId(), TokenPosition::kNoSource);
-  }
-
-  if (compiler::ffi::NativeTypeIsPointer(ffi_type)) {
-    body += LoadAddressFromFfiPointer();
-    body += UnboxTruncate(kUnboxedFfiIntPtr);
-  } else {
-    Representation from_rep = compiler::ffi::TypeRepresentation(ffi_type);
-    body += UnboxTruncate(from_rep);
-
-    Representation to_rep = native_representation;
-    if (from_rep != to_rep) {
-      body += BitCast(from_rep, to_rep);
-    } else {
-      body += FfiUnboxedExtend(from_rep, ffi_type);
-    }
-  }
-  return body;
-}
-
 FlowGraph* FlowGraphBuilder::BuildGraphOfFfiTrampoline(
     const Function& function) {
-  if (function.FfiCallbackTarget() != Function::null()) {
-    return BuildGraphOfFfiCallback(function);
-  } else {
-    return BuildGraphOfFfiNative(function);
-  }
-}
-
-FlowGraph* FlowGraphBuilder::BuildGraphOfFfiNative(const Function& function) {
   graph_entry_ =
       new (Z) GraphEntryInstr(*parsed_function_, Compiler::kNoOSRDeoptId);
 
@@ -2619,7 +2532,29 @@
   for (intptr_t pos = 1; pos < function.num_fixed_parameters(); pos++) {
     body += LoadLocal(parsed_function_->ParameterVariable(pos));
     ffi_type = signature.ParameterTypeAt(pos);
-    body += FfiConvertArgumentToNative(function, ffi_type, arg_reps[pos - 1]);
+
+    // Check for 'null'. Only ffi.Pointers are allowed to be null.
+    if (!compiler::ffi::NativeTypeIsPointer(ffi_type)) {
+      body += LoadLocal(parsed_function_->ParameterVariable(pos));
+      body <<=
+          new (Z) CheckNullInstr(Pop(), String::ZoneHandle(Z, function.name()),
+                                 GetNextDeoptId(), TokenPosition::kNoSource);
+    }
+
+    if (compiler::ffi::NativeTypeIsPointer(ffi_type)) {
+      body += LoadAddressFromFfiPointer();
+      body += UnboxTruncate(kUnboxedFfiIntPtr);
+    } else {
+      Representation from_rep = compiler::ffi::TypeRepresentation(ffi_type);
+      body += UnboxTruncate(from_rep);
+
+      Representation to_rep = arg_reps[pos - 1];
+      if (from_rep != to_rep) {
+        body += BitCast(from_rep, to_rep);
+      } else {
+        body += FfiUnboxedExtend(from_rep, ffi_type);
+      }
+    }
   }
 
   // Push the function pointer, which is stored (boxed) in the first slot of the
@@ -2634,109 +2569,34 @@
   body += FfiCall(signature, arg_reps, arg_locs, arg_host_locs);
 
   ffi_type = signature.result_type();
+  if (compiler::ffi::NativeTypeIsPointer(ffi_type)) {
+    body += Box(kUnboxedFfiIntPtr);
+    body += FfiPointerFromAddress(Type::Cast(ffi_type));
+  } else if (compiler::ffi::NativeTypeIsVoid(ffi_type)) {
+    body += Drop();
+    body += NullConstant();
+  } else {
 #if !defined(TARGET_ARCH_DBC)
-  const Representation from_rep =
-      compiler::ffi::ResultRepresentation(signature);
+    Representation from_rep = compiler::ffi::ResultRepresentation(signature);
 #else
-  const Representation from_rep =
-      compiler::ffi::ResultHostRepresentation(signature);
+    Representation from_rep =
+        compiler::ffi::ResultHostRepresentation(signature);
 #endif  // !defined(TARGET_ARCH_DBC)
-  body += FfiConvertArgumentToDart(ffi_type, from_rep);
+    Representation to_rep = compiler::ffi::TypeRepresentation(ffi_type);
+    if (from_rep != to_rep) {
+      body += BitCast(from_rep, to_rep);
+    } else {
+      body += FfiUnboxedExtend(from_rep, ffi_type);
+    }
+    body += Box(to_rep);
+  }
+
   body += Return(TokenPosition::kNoSource);
 
   return new (Z) FlowGraph(*parsed_function_, graph_entry_, last_used_block_id_,
                            prologue_info);
 }
 
-FlowGraph* FlowGraphBuilder::BuildGraphOfFfiCallback(const Function& function) {
-#if !defined(TARGET_ARCH_DBC)
-  const Function& signature = Function::ZoneHandle(Z, function.FfiCSignature());
-  const auto& arg_reps = *compiler::ffi::ArgumentRepresentations(signature);
-  const auto& arg_locs = *compiler::ffi::ArgumentLocations(arg_reps);
-  const auto& callback_locs =
-      *compiler::ffi::CallbackArgumentTranslator::TranslateArgumentLocations(
-          arg_locs);
-
-  graph_entry_ =
-      new (Z) GraphEntryInstr(*parsed_function_, Compiler::kNoOSRDeoptId);
-
-  auto* const native_entry = new (Z) NativeEntryInstr(
-      &arg_locs, graph_entry_, AllocateBlockId(), CurrentTryIndex(),
-      GetNextDeoptId(), function.FfiCallbackId());
-
-  graph_entry_->set_normal_entry(native_entry);
-
-  Fragment function_body(native_entry);
-  function_body += CheckStackOverflowInPrologue(function.token_pos());
-
-  // Wrap the entire method in a big try/catch. This is important to ensure that
-  // the VM does not crash if the callback throws an exception.
-  const intptr_t try_handler_index = AllocateTryIndex();
-  Fragment body = TryCatch(try_handler_index);
-  ++try_depth_;
-
-  // Box and push the arguments.
-  AbstractType& ffi_type = AbstractType::Handle(Z);
-  for (intptr_t i = 0, n = callback_locs.length(); i < n; ++i) {
-    ffi_type = signature.ParameterTypeAt(i + 1);
-    auto* parameter =
-        new (Z) NativeParameterInstr(callback_locs[i], arg_reps[i]);
-    Push(parameter);
-    body <<= parameter;
-    body += FfiConvertArgumentToDart(ffi_type, arg_reps[i]);
-    body += PushArgument();
-  }
-
-  // Call the target.
-  //
-  // TODO(36748): Determine the hot-reload semantics of callbacks and update the
-  // rebind-rule accordingly.
-  body += StaticCall(TokenPosition::kNoSource,
-                     Function::ZoneHandle(Z, function.FfiCallbackTarget()),
-                     callback_locs.length(), Array::empty_array(),
-                     ICData::kNoRebind);
-
-  ffi_type = signature.result_type();
-  const Representation result_rep =
-      compiler::ffi::ResultRepresentation(signature);
-  body += FfiConvertArgumentToNative(function, ffi_type, result_rep);
-  body += NativeReturn(result_rep);
-
-  --try_depth_;
-  function_body += body;
-
-  ++catch_depth_;
-  Fragment catch_body =
-      CatchBlockEntry(Array::empty_array(), try_handler_index,
-                      /*needs_stacktrace=*/true, /*is_synthesized=*/true);
-
-  catch_body += LoadLocal(CurrentException());
-  catch_body += PushArgument();
-  catch_body += LoadLocal(CurrentStackTrace());
-  catch_body += PushArgument();
-
-  // Find '_handleExposedException(e, st)' from ffi_patch.dart and call it.
-  const Library& ffi_lib =
-      Library::Handle(Z, Library::LookupLibrary(thread_, Symbols::DartFfi()));
-  const Function& handler = Function::ZoneHandle(
-      Z, ffi_lib.LookupFunctionAllowPrivate(Symbols::HandleExposedException()));
-  ASSERT(!handler.IsNull());
-  catch_body += StaticCall(TokenPosition::kNoSource, handler, /*num_args=*/2,
-                           /*arg_names=*/Array::empty_array(), ICData::kStatic);
-  catch_body += Drop();
-
-  catch_body += FfiExceptionalReturnValue(ffi_type, result_rep);
-  catch_body += NativeReturn(result_rep);
-  --catch_depth_;
-
-  PrologueInfo prologue_info(-1, -1);
-  return new (Z) FlowGraph(*parsed_function_, graph_entry_, last_used_block_id_,
-                           prologue_info);
-#else
-  UNREACHABLE();
-#endif
-}
-
 void FlowGraphBuilder::SetCurrentTryCatchBlock(TryCatchBlock* try_catch_block) {
   try_catch_block_ = try_catch_block;
   SetCurrentTryIndex(try_catch_block == nullptr ? kInvalidTryIndex
diff --git a/runtime/vm/compiler/frontend/kernel_to_il.h b/runtime/vm/compiler/frontend/kernel_to_il.h
index 9ccc1a94..1e34e13 100644
--- a/runtime/vm/compiler/frontend/kernel_to_il.h
+++ b/runtime/vm/compiler/frontend/kernel_to_il.h
@@ -101,8 +101,6 @@
   FlowGraph* BuildGraphOfNoSuchMethodDispatcher(const Function& function);
   FlowGraph* BuildGraphOfInvokeFieldDispatcher(const Function& function);
   FlowGraph* BuildGraphOfFfiTrampoline(const Function& function);
-  FlowGraph* BuildGraphOfFfiCallback(const Function& function);
-  FlowGraph* BuildGraphOfFfiNative(const Function& function);
 
   Fragment NativeFunctionBody(const Function& function,
                               LocalVariable* first_parameter);
@@ -238,26 +236,6 @@
   // the pointer.
   Fragment FfiPointerFromAddress(const Type& result_type);
 
-  // Pushes an (unboxed) bogus value returned when a native -> Dart callback
-  // throws an exception.
-  Fragment FfiExceptionalReturnValue(const AbstractType& result_type,
-                                     const Representation target);
-
-  // Pops a Dart object and push the unboxed native version, according to the
-  // semantics of FFI argument translation.
-  Fragment FfiConvertArgumentToNative(
-      const Function& function,
-      const AbstractType& ffi_type,
-      const Representation native_representation);
-
-  // Reverse of 'FfiConvertArgumentToNative'.
-  Fragment FfiConvertArgumentToDart(const AbstractType& ffi_type,
-                                    const Representation native_representation);
-
-  // Return from a native -> Dart callback. Can only be used in conjunction with
-  // NativeEntry and NativeParameter are used.
-  Fragment NativeReturn(Representation result);
-
   // Bit-wise cast between representations.
   // Pops the input and pushes the converted result.
   // Currently only works with equal sizes and floating point <-> integer.
diff --git a/runtime/vm/compiler/frontend/scope_builder.cc b/runtime/vm/compiler/frontend/scope_builder.cc
index a0f984d..dde3667 100644
--- a/runtime/vm/compiler/frontend/scope_builder.cc
+++ b/runtime/vm/compiler/frontend/scope_builder.cc
@@ -390,17 +390,6 @@
                                             : Object::dynamic_type().raw()));
         scope_->InsertParameterAt(i, variable);
       }
-      // Callbacks need try/catch variables.
-      if (function.IsFfiTrampoline() &&
-          function.FfiCallbackTarget() != Function::null()) {
-        ++depth_.try_;
-        AddTryVariables();
-        --depth_.try_;
-        ++depth_.catch_;
-        AddCatchVariables();
-        FinalizeCatchVariables();
-        --depth_.catch_;
-      }
       break;
     case RawFunction::kSignatureFunction:
     case RawFunction::kIrregexpFunction:
diff --git a/runtime/vm/compiler/runtime_api.cc b/runtime/vm/compiler/runtime_api.cc
index 5637049..8f927b8 100644
--- a/runtime/vm/compiler/runtime_api.cc
+++ b/runtime/vm/compiler/runtime_api.cc
@@ -500,7 +500,6 @@
   V(Thread, top_resource_offset)                                               \
   V(Thread, vm_tag_offset)                                                     \
   V(Thread, safepoint_state_offset)                                            \
-  V(Thread, callback_code_offset)                                              \
   V(TimelineStream, enabled_offset)                                            \
   V(TwoByteString, data_offset)                                                \
   V(Type, arguments_offset)                                                    \
@@ -583,10 +582,6 @@
 word Thread::array_write_barrier_entry_point_offset() {
   return dart::Thread::array_write_barrier_entry_point_offset();
 }
-
-word Thread::verify_callback_isolate_entry_point_offset() {
-  return dart::Thread::verify_callback_entry_offset();
-}
 #endif  // !defined(TARGET_ARCH_DBC)
 
 #if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64) ||                  \
diff --git a/runtime/vm/compiler/runtime_api.h b/runtime/vm/compiler/runtime_api.h
index a873fbd..d910575 100644
--- a/runtime/vm/compiler/runtime_api.h
+++ b/runtime/vm/compiler/runtime_api.h
@@ -572,7 +572,6 @@
   static word write_barrier_wrappers_thread_offset(intptr_t regno);
   static word array_write_barrier_entry_point_offset();
   static word write_barrier_entry_point_offset();
-  static word verify_callback_isolate_entry_point_offset();
   static word vm_tag_offset();
   static uword vm_tag_compiled_id();
 
@@ -584,8 +583,6 @@
   static uword native_execution_state();
   static uword generated_execution_state();
 
-  static word callback_code_offset();
-
 #if !defined(TARGET_ARCH_DBC)
   static word write_barrier_code_offset();
   static word array_write_barrier_code_offset();
diff --git a/runtime/vm/compiler/stub_code_compiler_arm.cc b/runtime/vm/compiler/stub_code_compiler_arm.cc
index 0988331..e82fa9f 100644
--- a/runtime/vm/compiler/stub_code_compiler_arm.cc
+++ b/runtime/vm/compiler/stub_code_compiler_arm.cc
@@ -290,10 +290,6 @@
   __ Ret();
 }
 
-void StubCodeCompiler::GenerateVerifyCallbackStub(Assembler* assembler) {
-  __ Breakpoint();
-}
-
 void StubCodeCompiler::GenerateNullErrorSharedWithoutFPURegsStub(
     Assembler* assembler) {
   GenerateSharedStub(
diff --git a/runtime/vm/compiler/stub_code_compiler_arm64.cc b/runtime/vm/compiler/stub_code_compiler_arm64.cc
index 1ef512c..9bee589 100644
--- a/runtime/vm/compiler/stub_code_compiler_arm64.cc
+++ b/runtime/vm/compiler/stub_code_compiler_arm64.cc
@@ -235,10 +235,6 @@
   __ Ret();
 }
 
-void StubCodeCompiler::GenerateVerifyCallbackStub(Assembler* assembler) {
-  __ Breakpoint();
-}
-
 // R1: The extracted method.
 // R4: The type_arguments_field_offset (or 0)
 void StubCodeCompiler::GenerateBuildMethodExtractorStub(
diff --git a/runtime/vm/compiler/stub_code_compiler_ia32.cc b/runtime/vm/compiler/stub_code_compiler_ia32.cc
index 68dbdcd..806c130 100644
--- a/runtime/vm/compiler/stub_code_compiler_ia32.cc
+++ b/runtime/vm/compiler/stub_code_compiler_ia32.cc
@@ -154,10 +154,6 @@
   __ ret();
 }
 
-void StubCodeCompiler::GenerateVerifyCallbackStub(Assembler* assembler) {
-  __ Breakpoint();
-}
-
 void StubCodeCompiler::GenerateNullErrorSharedWithoutFPURegsStub(
     Assembler* assembler) {
   __ Breakpoint();
diff --git a/runtime/vm/compiler/stub_code_compiler_x64.cc b/runtime/vm/compiler/stub_code_compiler_x64.cc
index 32fb433..4313cd4 100644
--- a/runtime/vm/compiler/stub_code_compiler_x64.cc
+++ b/runtime/vm/compiler/stub_code_compiler_x64.cc
@@ -204,13 +204,8 @@
   all_registers.AddAllGeneralRegisters();
   __ PushRegisters(all_registers.cpu_registers(),
                    all_registers.fpu_registers());
-
-  __ EnterFrame(0);
-  __ ReserveAlignedFrameSpace(0);
   __ movq(RAX, Address(THR, kEnterSafepointRuntimeEntry.OffsetFromThread()));
   __ CallCFunction(RAX);
-  __ LeaveFrame();
-
   __ PopRegisters(all_registers.cpu_registers(), all_registers.fpu_registers());
   __ ret();
 }
@@ -220,31 +215,12 @@
   all_registers.AddAllGeneralRegisters();
   __ PushRegisters(all_registers.cpu_registers(),
                    all_registers.fpu_registers());
-
-  __ EnterFrame(0);
-  __ ReserveAlignedFrameSpace(0);
   __ movq(RAX, Address(THR, kExitSafepointRuntimeEntry.OffsetFromThread()));
   __ CallCFunction(RAX);
-  __ LeaveFrame();
-
   __ PopRegisters(all_registers.cpu_registers(), all_registers.fpu_registers());
   __ ret();
 }
 
-void StubCodeCompiler::GenerateVerifyCallbackStub(Assembler* assembler) {
-  // SP points to return address, which needs to be the second argument to
-  // VerifyCallbackIsolate.
-  __ movq(CallingConventions::kArg2Reg, Address(SPREG, 0));
-
-  __ EnterFrame(0);
-  __ ReserveAlignedFrameSpace(0);
-  __ movq(RAX,
-          Address(THR, kVerifyCallbackIsolateRuntimeEntry.OffsetFromThread()));
-  __ CallCFunction(RAX);
-  __ LeaveFrame();
-  __ ret();
-}
-
 // RBX: The extracted method.
 // RDX: The type_arguments_field_offset (or 0)
 void StubCodeCompiler::GenerateBuildMethodExtractorStub(
@@ -1110,8 +1086,19 @@
   __ pushq(RAX);
 
   // The constant target::frame_layout.exit_link_slot_from_entry_fp must be kept
-  // in sync with the code above.
-  __ EmitEntryFrameVerification();
+  // in sync with the code below.
+#if defined(DEBUG)
+  {
+    Label ok;
+    __ leaq(RAX,
+            Address(RBP, target::frame_layout.exit_link_slot_from_entry_fp *
+                             target::kWordSize));
+    __ cmpq(RAX, RSP);
+    __ j(EQUAL, &ok);
+    __ Stop("target::frame_layout.exit_link_slot_from_entry_fp mismatch");
+    __ Bind(&ok);
+  }
+#endif
 
   __ movq(Address(THR, target::Thread::top_exit_frame_info_offset()),
           Immediate(0));
diff --git a/runtime/vm/exceptions.cc b/runtime/vm/exceptions.cc
index 79361ba..73a41f0 100644
--- a/runtime/vm/exceptions.cc
+++ b/runtime/vm/exceptions.cc
@@ -995,12 +995,6 @@
   Exceptions::ThrowByType(Exceptions::kRange, args);
 }
 
-void Exceptions::ThrowUnsupportedError(const char* msg) {
-  const Array& args = Array::Handle(Array::New(1));
-  args.SetAt(0, String::Handle(String::New(msg)));
-  Exceptions::ThrowByType(Exceptions::kUnsupported, args);
-}
-
 void Exceptions::ThrowRangeErrorMsg(const char* msg) {
   const Array& args = Array::Handle(Array::New(1));
   args.SetAt(0, String::Handle(String::New(msg)));
diff --git a/runtime/vm/exceptions.h b/runtime/vm/exceptions.h
index 46b4d68..894f374 100644
--- a/runtime/vm/exceptions.h
+++ b/runtime/vm/exceptions.h
@@ -84,7 +84,6 @@
                                             intptr_t expected_from,
                                             intptr_t expected_to);
   DART_NORETURN static void ThrowRangeErrorMsg(const char* msg);
-  DART_NORETURN static void ThrowUnsupportedError(const char* msg);
   DART_NORETURN static void ThrowCompileTimeError(const LanguageError& error);
 
   // Returns a RawInstance if the exception is successfully created,
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index 4f490d3..c427228 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -6065,34 +6065,6 @@
   return FfiTrampolineData::Cast(obj).c_signature();
 }
 
-int32_t Function::FfiCallbackId() const {
-  ASSERT(IsFfiTrampoline());
-  const Object& obj = Object::Handle(raw_ptr()->data_);
-  ASSERT(!obj.IsNull());
-  return FfiTrampolineData::Cast(obj).callback_id();
-}
-
-void Function::SetFfiCallbackId(int32_t value) const {
-  ASSERT(IsFfiTrampoline());
-  const Object& obj = Object::Handle(raw_ptr()->data_);
-  ASSERT(!obj.IsNull());
-  FfiTrampolineData::Cast(obj).set_callback_id(value);
-}
-
-RawFunction* Function::FfiCallbackTarget() const {
-  ASSERT(IsFfiTrampoline());
-  const Object& obj = Object::Handle(raw_ptr()->data_);
-  ASSERT(!obj.IsNull());
-  return FfiTrampolineData::Cast(obj).callback_target();
-}
-
-void Function::SetFfiCallbackTarget(const Function& target) const {
-  ASSERT(IsFfiTrampoline());
-  const Object& obj = Object::Handle(raw_ptr()->data_);
-  ASSERT(!obj.IsNull());
-  FfiTrampolineData::Cast(obj).set_callback_target(target);
-}
-
 RawType* Function::SignatureType() const {
   Type& type = Type::Handle(ExistingSignatureType());
   if (type.IsNull()) {
@@ -8246,22 +8218,12 @@
   StorePointer(&raw_ptr()->c_signature_, value.raw());
 }
 
-void FfiTrampolineData::set_callback_target(const Function& value) const {
-  StorePointer(&raw_ptr()->callback_target_, value.raw());
-}
-
-void FfiTrampolineData::set_callback_id(int32_t callback_id) const {
-  StoreNonPointer(&raw_ptr()->callback_id_, callback_id);
-}
-
 RawFfiTrampolineData* FfiTrampolineData::New() {
   ASSERT(Object::ffi_trampoline_data_class() != Class::null());
   RawObject* raw =
       Object::Allocate(FfiTrampolineData::kClassId,
                        FfiTrampolineData::InstanceSize(), Heap::kOld);
-  RawFfiTrampolineData* data = reinterpret_cast<RawFfiTrampolineData*>(raw);
-  data->ptr()->callback_id_ = -1;
-  return data;
+  return reinterpret_cast<RawFfiTrampolineData*>(raw);
 }
 
 const char* FfiTrampolineData::ToCString() const {
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index dfc1e9b..540a7ac 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -2005,20 +2005,6 @@
   // Can only be used on FFI trampolines.
   RawFunction* FfiCSignature() const;
 
-  // Can only be called on FFI trampolines.
-  // -1 for Dart -> native calls.
-  int32_t FfiCallbackId() const;
-
-  // Can only be called on FFI trampolines.
-  void SetFfiCallbackId(int32_t value) const;
-
-  // Can only be called on FFI trampolines.
-  // Null for Dart -> native calls.
-  RawFunction* FfiCallbackTarget() const;
-
-  // Can only be called on FFI trampolines.
-  void SetFfiCallbackTarget(const Function& target) const;
-
   // Return a new function with instantiated result and parameter types.
   RawFunction* InstantiateSignatureFrom(
       const TypeArguments& instantiator_type_arguments,
@@ -3149,12 +3135,6 @@
   RawFunction* c_signature() const { return raw_ptr()->c_signature_; }
   void set_c_signature(const Function& value) const;
 
-  RawFunction* callback_target() const { return raw_ptr()->callback_target_; }
-  void set_callback_target(const Function& value) const;
-
-  int32_t callback_id() const { return raw_ptr()->callback_id_; }
-  void set_callback_id(int32_t value) const;
-
   static RawFfiTrampolineData* New();
 
   FINAL_HEAP_OBJECT_IMPLEMENTATION(FfiTrampolineData, Object);
@@ -5025,7 +5005,7 @@
     return ContainsInstructionAt(raw(), addr);
   }
 
-  static bool ContainsInstructionAt(const RawCode* code, uword addr) {
+  static bool ContainsInstructionAt(RawCode* code, uword addr) {
     return Instructions::ContainsPc(code->ptr()->instructions_, addr);
   }
 
@@ -8292,14 +8272,6 @@
   static RawGrowableObjectArray* New(const Array& array,
                                      Heap::Space space = Heap::kNew);
 
-  static RawSmi* NoSafepointLength(const RawGrowableObjectArray* array) {
-    return array->ptr()->length_;
-  }
-
-  static RawArray* NoSafepointData(const RawGrowableObjectArray* array) {
-    return array->ptr()->data_;
-  }
-
  private:
   RawArray* DataArray() const { return data()->ptr(); }
   RawObject** ObjectAddr(intptr_t index) const {
diff --git a/runtime/vm/raw_object.h b/runtime/vm/raw_object.h
index 3494b45..75e7a92 100644
--- a/runtime/vm/raw_object.h
+++ b/runtime/vm/raw_object.h
@@ -1013,19 +1013,7 @@
   VISIT_FROM(RawObject*, signature_type_);
   RawType* signature_type_;
   RawFunction* c_signature_;
-
-  // Target Dart method for callbacks, otherwise null.
-  RawFunction* callback_target_;
-
-  VISIT_TO(RawObject*, callback_target_);
-
-  // Callback id for callbacks, otherwise 0.
-  //
-  // The callbacks ids are used so that native callbacks can lookup their own
-  // code objects, since native code doesn't pass code objects into function
-  // calls. The callback id is also used to for verifying that callbacks are
-  // called on the correct isolate. See DLRT_VerifyCallbackIsolate for details.
-  uint32_t callback_id_;
+  VISIT_TO(RawObject*, c_signature_);
 };
 
 class RawField : public RawObject {
diff --git a/runtime/vm/runtime_entry.cc b/runtime/vm/runtime_entry.cc
index b4b4563..1a7ba2e 100644
--- a/runtime/vm/runtime_entry.cc
+++ b/runtime/vm/runtime_entry.cc
@@ -26,7 +26,6 @@
 #include "vm/service_isolate.h"
 #include "vm/stack_frame.h"
 #include "vm/symbols.h"
-#include "vm/thread.h"
 #include "vm/thread_registry.h"
 #include "vm/type_testing_stubs.h"
 
@@ -2766,29 +2765,4 @@
 }
 DEFINE_RAW_LEAF_RUNTIME_ENTRY(ExitSafepoint, 0, false, &DFLRT_ExitSafepoint);
 
-// Not registered as a runtime entry because we can't use Thread to look it up.
-extern "C" Thread* DLRT_GetThreadForNativeCallback() {
-  Thread* const thread = Thread::Current();
-  if (thread == nullptr) {
-    FATAL("Cannot invoke native callback outside an isolate.");
-  }
-  if (thread->no_callback_scope_depth() != 0) {
-    FATAL("Cannot invoke native callback when API callbacks are prohibited.");
-  }
-  if (!thread->IsMutatorThread()) {
-    FATAL("Native callbacks must be invoked on the mutator thread.");
-  }
-  return thread;
-}
-
-extern "C" void DLRT_VerifyCallbackIsolate(int32_t callback_id,
-                                           uword return_address) {
-  Thread::Current()->VerifyCallbackIsolate(callback_id, return_address);
-}
-DEFINE_RAW_LEAF_RUNTIME_ENTRY(
-    VerifyCallbackIsolate,
-    1,
-    false /* is_float */,
-    reinterpret_cast<RuntimeFunction>(&DLRT_VerifyCallbackIsolate));
-
 }  // namespace dart
diff --git a/runtime/vm/runtime_entry.h b/runtime/vm/runtime_entry.h
index f072c14..85e9d7f2 100644
--- a/runtime/vm/runtime_entry.h
+++ b/runtime/vm/runtime_entry.h
@@ -145,9 +145,6 @@
 RUNTIME_ENTRY_LIST(DECLARE_RUNTIME_ENTRY)
 LEAF_RUNTIME_ENTRY_LIST(DECLARE_LEAF_RUNTIME_ENTRY)
 
-// Expected to be called inside a safepoint.
-extern "C" Thread* DLRT_GetThreadForNativeCallback();
-
 const char* DeoptReasonToCString(ICData::DeoptReasonId deopt_reason);
 
 void DeoptimizeAt(const Code& optimized_code, StackFrame* frame);
diff --git a/runtime/vm/runtime_entry_list.h b/runtime/vm/runtime_entry_list.h
index b0c241e..dd4159d 100644
--- a/runtime/vm/runtime_entry_list.h
+++ b/runtime/vm/runtime_entry_list.h
@@ -84,8 +84,7 @@
   V(RawBool*, CaseInsensitiveCompareUTF16, RawString*, RawSmi*, RawSmi*,       \
     RawSmi*)                                                                   \
   V(void, EnterSafepoint)                                                      \
-  V(void, ExitSafepoint)                                                       \
-  V(void, VerifyCallbackIsolate, int32_t, uword)
+  V(void, ExitSafepoint)
 
 }  // namespace dart
 
diff --git a/runtime/vm/stack_frame.h b/runtime/vm/stack_frame.h
index 3b59005..bf67520 100644
--- a/runtime/vm/stack_frame.h
+++ b/runtime/vm/stack_frame.h
@@ -471,13 +471,6 @@
   return fp + LocalVarIndex(0, index) * kWordSize;
 }
 
-#if !defined(TARGET_ARCH_X64)
-// For FFI native -> Dart callbacks, the number of stack slots between arguments
-// passed on stack and arguments saved in callback prologue. This placeholder
-// here is for unsupported architectures.
-constexpr intptr_t kCallbackSlotsBeforeSavedArguments = -1;
-#endif
-
 }  // namespace dart
 
 #endif  // RUNTIME_VM_STACK_FRAME_H_
diff --git a/runtime/vm/stack_frame_x64.h b/runtime/vm/stack_frame_x64.h
index 7659b51..00d0059 100644
--- a/runtime/vm/stack_frame_x64.h
+++ b/runtime/vm/stack_frame_x64.h
@@ -5,8 +5,6 @@
 #ifndef RUNTIME_VM_STACK_FRAME_X64_H_
 #define RUNTIME_VM_STACK_FRAME_X64_H_
 
-#include "vm/constants_x64.h"
-
 namespace dart {
 
 /* X64 Dart Frame Layout
@@ -54,13 +52,6 @@
 static const int kExitLinkSlotFromEntryFp = -10;
 #endif  // defined(_WIN64)
 
-// For FFI native -> Dart callbacks, the number of stack slots between arguments
-// passed on stack and arguments saved in callback prologue. 2 = return adddress
-// (1) + saved frame pointer (1). Also add slots for the shadow space, if
-// present.
-constexpr intptr_t kCallbackSlotsBeforeSavedArguments =
-    2 + CallingConventions::kShadowSpaceBytes;
-
 }  // namespace dart
 
 #endif  // RUNTIME_VM_STACK_FRAME_X64_H_
diff --git a/runtime/vm/stub_code_list.h b/runtime/vm/stub_code_list.h
index 248c455..7f93f5b8 100644
--- a/runtime/vm/stub_code_list.h
+++ b/runtime/vm/stub_code_list.h
@@ -77,8 +77,7 @@
   V(OneArgCheckInlineCacheWithExactnessCheck)                                  \
   V(OneArgOptimizedCheckInlineCacheWithExactnessCheck)                         \
   V(EnterSafepoint)                                                            \
-  V(ExitSafepoint)                                                             \
-  V(VerifyCallback)
+  V(ExitSafepoint)
 
 #else
 #define VM_STUB_CODE_LIST(V)                                                   \
diff --git a/runtime/vm/symbols.h b/runtime/vm/symbols.h
index f265e3e..845d780 100644
--- a/runtime/vm/symbols.h
+++ b/runtime/vm/symbols.h
@@ -179,7 +179,6 @@
   V(GetterPrefix, "get:")                                                      \
   V(GreaterEqualOperator, ">=")                                                \
   V(GrowRegExpStack, "_growRegExpStack")                                       \
-  V(HandleExposedException, "_handleExposedException")                         \
   V(HaveSameRuntimeType, "_haveSameRuntimeType")                               \
   V(Hide, "hide")                                                              \
   V(ICData, "ICData")                                                          \
diff --git a/runtime/vm/thread.cc b/runtime/vm/thread.cc
index 71a9b2d..6b48726 100644
--- a/runtime/vm/thread.cc
+++ b/runtime/vm/thread.cc
@@ -6,7 +6,6 @@
 
 #include "vm/dart_api_state.h"
 #include "vm/growable_array.h"
-#include "vm/heap/safepoint.h"
 #include "vm/isolate.h"
 #include "vm/json_stream.h"
 #include "vm/lockers.h"
@@ -77,7 +76,6 @@
       resume_pc_(0),
       execution_state_(kThreadInNative),
       safepoint_state_(0),
-      ffi_callback_code_(GrowableObjectArray::null()),
       task_kind_(kUnknownTask),
       dart_stream_(NULL),
       thread_lock_(),
@@ -670,7 +668,6 @@
   visitor->VisitPointer(reinterpret_cast<RawObject**>(&active_stacktrace_));
   visitor->VisitPointer(reinterpret_cast<RawObject**>(&sticky_error_));
   visitor->VisitPointer(reinterpret_cast<RawObject**>(&async_stack_trace_));
-  visitor->VisitPointer(reinterpret_cast<RawObject**>(&ffi_callback_code_));
 
 #if !defined(DART_PRECOMPILED_RUNTIME)
   if (interpreter() != NULL) {
@@ -939,45 +936,4 @@
   }
 }
 
-const intptr_t kInitialCallbackIdsReserved = 1024;
-int32_t Thread::AllocateFfiCallbackId() {
-  Zone* Z = isolate()->current_zone();
-  if (ffi_callback_code_ == GrowableObjectArray::null()) {
-    ffi_callback_code_ = GrowableObjectArray::New(kInitialCallbackIdsReserved);
-  }
-  const auto& array = GrowableObjectArray::Handle(Z, ffi_callback_code_);
-  array.Add(Code::Handle(Z, Code::null()));
-  return array.Length() - 1;
-}
-
-void Thread::SetFfiCallbackCode(int32_t callback_id, const Code& code) {
-  Zone* Z = isolate()->current_zone();
-  const auto& array = GrowableObjectArray::Handle(Z, ffi_callback_code_);
-  array.SetAt(callback_id, code);
-}
-
-void Thread::VerifyCallbackIsolate(int32_t callback_id, uword entry) {
-  NoSafepointScope _;
-
-  const RawGrowableObjectArray* const array = ffi_callback_code_;
-  if (array == GrowableObjectArray::null()) {
-    FATAL("Cannot invoke callback on incorrect isolate.");
-  }
-
-  const RawSmi* const length_smi =
-      GrowableObjectArray::NoSafepointLength(array);
-  const intptr_t length = Smi::Value(length_smi);
-
-  if (callback_id < 0 || callback_id >= length) {
-    FATAL("Cannot invoke callback on incorrect isolate.");
-  }
-
-  RawObject** const code_array =
-      Array::DataOf(GrowableObjectArray::NoSafepointData(array));
-  const RawCode* const code = Code::RawCast(code_array[callback_id]);
-  if (!Code::ContainsInstructionAt(code, entry)) {
-    FATAL("Cannot invoke callback on incorrect isolate.");
-  }
-}
-
 }  // namespace dart
diff --git a/runtime/vm/thread.h b/runtime/vm/thread.h
index 44bb9e6..c9d81bb 100644
--- a/runtime/vm/thread.h
+++ b/runtime/vm/thread.h
@@ -168,8 +168,8 @@
   V(uword, monomorphic_miss_entry_, StubCode::MonomorphicMiss().EntryPoint(),  \
     0)                                                                         \
   V(uword, optimize_entry_, StubCode::OptimizeFunction().EntryPoint(), 0)      \
-  V(uword, deoptimize_entry_, StubCode::Deoptimize().EntryPoint(), 0)          \
-  V(uword, verify_callback_entry_, StubCode::VerifyCallback().EntryPoint(), 0)
+  V(uword, deoptimize_entry_, StubCode::Deoptimize().EntryPoint(), 0)
+
 #endif
 
 #define CACHED_ADDRESSES_LIST(V)                                               \
@@ -315,10 +315,6 @@
     return OFFSET_OF(Thread, safepoint_state_);
   }
 
-  static intptr_t callback_code_offset() {
-    return OFFSET_OF(Thread, ffi_callback_code_);
-  }
-
   TaskKind task_kind() const { return task_kind_; }
 
   // Retrieves and clears the stack overflow flags.  These are set by
@@ -772,13 +768,6 @@
     }
   }
 
-  int32_t AllocateFfiCallbackId();
-  void SetFfiCallbackCode(int32_t callback_id, const Code& code);
-
-  // Ensure that 'entry' points within the code of the callback identified by
-  // 'callback_id'. Aborts otherwise.
-  void VerifyCallbackIsolate(int32_t callback_id, uword entry);
-
   Thread* next() const { return next_; }
 
   // Visit all object pointers.
@@ -872,7 +861,6 @@
   uword resume_pc_;
   uword execution_state_;
   uword safepoint_state_;
-  RawGrowableObjectArray* ffi_callback_code_;
 
   // ---- End accessed from generated code. ----
 
diff --git a/tests/ffi/ffi.status b/tests/ffi/ffi.status
index 024505c..2b170cf 100644
--- a/tests/ffi/ffi.status
+++ b/tests/ffi/ffi.status
@@ -25,9 +25,6 @@
 [ $arch == x64 || $arch == arm64 || $arch == simdbc64 ]
 enable_structs_test: SkipByDesign  # Tests that structs don't work on 32-bit systems.
 
-[ $arch != x64 ]
-function_callbacks_test: Skip # Issue 35761
-
 [ $runtime == dart_precompiled ]
 *: Skip # AOT is not yet supported: dartbug.com/35765
 
diff --git a/tests/ffi/function_callbacks_test.dart b/tests/ffi/function_callbacks_test.dart
index bef1599..0ad50a9 100644
--- a/tests/ffi/function_callbacks_test.dart
+++ b/tests/ffi/function_callbacks_test.dart
@@ -8,192 +8,71 @@
 
 library FfiTest;
 
-import 'dart:io';
-import 'dart:ffi';
-import 'dart:isolate';
+import 'dart:ffi' as ffi;
+
 import 'dylib_utils.dart';
 
 import "package:expect/expect.dart";
 
-typedef NativeCallbackTest = Int32 Function(Pointer);
-typedef NativeCallbackTestFn = int Function(Pointer);
+import 'coordinate.dart';
 
-final DynamicLibrary testLibrary = dlopenPlatformSpecific("ffi_test_functions");
+typedef NativeCoordinateOp = Coordinate Function(Coordinate);
 
-class Test {
-  final String name;
-  final Pointer callback;
-  final bool skip;
+typedef CoordinateTrice = Coordinate Function(
+    ffi.Pointer<ffi.NativeFunction<NativeCoordinateOp>>, Coordinate);
 
-  Test(this.name, this.callback, {bool skipIf: false}) : skip = skipIf {}
-
-  void run() {
-    if (skip) return;
-
-    final NativeCallbackTestFn tester = testLibrary
-        .lookupFunction<NativeCallbackTest, NativeCallbackTestFn>("Test$name");
-    final int testCode = tester(callback);
-    if (testCode != 0) {
-      Expect.fail("Test $name failed.");
-    }
-  }
+void main() {
+  testFunctionWithFunctionPointer();
+  testNativeFunctionWithFunctionPointer();
 }
 
-typedef SimpleAdditionType = Int32 Function(Int32, Int32);
-int simpleAddition(int x, int y) => x + y;
+ffi.DynamicLibrary ffiTestFunctions =
+    dlopenPlatformSpecific("ffi_test_functions");
 
-typedef IntComputationType = Int64 Function(Int8, Int16, Int32, Int64);
-int intComputation(int a, int b, int c, int d) => d - c + b - a;
+/// pass a pointer to a c function as an argument to a c function
+void testFunctionWithFunctionPointer() {
+  ffi.Pointer<ffi.NativeFunction<NativeCoordinateOp>>
+      transposeCoordinatePointer =
+      ffiTestFunctions.lookup("TransposeCoordinate");
 
-typedef UintComputationType = Uint64 Function(Uint8, Uint16, Uint32, Uint64);
-int uintComputation(int a, int b, int c, int d) => d - c + b - a;
+  ffi.Pointer<ffi.NativeFunction<CoordinateTrice>> p2 =
+      ffiTestFunctions.lookup("CoordinateUnOpTrice");
+  CoordinateTrice coordinateUnOpTrice = p2.asFunction();
 
-typedef SimpleMultiplyType = Double Function(Double);
-double simpleMultiply(double x) => x * 1.337;
+  Coordinate c1 = Coordinate(10.0, 20.0, null);
+  c1.next = c1;
 
-typedef SimpleMultiplyFloatType = Float Function(Float);
-double simpleMultiplyFloat(double x) => x * 1.337;
+  Coordinate result = coordinateUnOpTrice(transposeCoordinatePointer, c1);
 
-typedef ManyIntsType = IntPtr Function(IntPtr, IntPtr, IntPtr, IntPtr, IntPtr,
-    IntPtr, IntPtr, IntPtr, IntPtr, IntPtr);
-int manyInts(
-    int a, int b, int c, int d, int e, int f, int g, int h, int i, int j) {
-  return a + b + c + d + e + f + g + h + i + j;
+  print(result.runtimeType);
+  print(result.x);
+  print(result.y);
+
+  c1.free();
 }
 
-typedef ManyDoublesType = Double Function(Double, Double, Double, Double,
-    Double, Double, Double, Double, Double, Double);
-double manyDoubles(double a, double b, double c, double d, double e, double f,
-    double g, double h, double i, double j) {
-  return a + b + c + d + e + f + g + h + i + j;
+typedef BinaryOp = int Function(int, int);
+
+typedef NativeIntptrBinOp = ffi.IntPtr Function(ffi.IntPtr, ffi.IntPtr);
+
+typedef NativeIntptrBinOpLookup
+    = ffi.Pointer<ffi.NativeFunction<NativeIntptrBinOp>> Function();
+
+void testNativeFunctionWithFunctionPointer() {
+  ffi.Pointer<ffi.NativeFunction<NativeIntptrBinOpLookup>> p1 =
+      ffiTestFunctions.lookup("IntptrAdditionClosure");
+  NativeIntptrBinOpLookup intptrAdditionClosure = p1.asFunction();
+
+  ffi.Pointer<ffi.NativeFunction<NativeIntptrBinOp>> intptrAdditionPointer =
+      intptrAdditionClosure();
+  BinaryOp intptrAddition = intptrAdditionPointer.asFunction();
+  Expect.equals(37, intptrAddition(10, 27));
 }
 
-typedef ManyArgsType = Double Function(
-    IntPtr,
-    Float,
-    IntPtr,
-    Double,
-    IntPtr,
-    Float,
-    IntPtr,
-    Double,
-    IntPtr,
-    Float,
-    IntPtr,
-    Double,
-    IntPtr,
-    Float,
-    IntPtr,
-    Double,
-    IntPtr,
-    Float,
-    IntPtr,
-    Double);
-double manyArgs(
-    int _1,
-    double _2,
-    int _3,
-    double _4,
-    int _5,
-    double _6,
-    int _7,
-    double _8,
-    int _9,
-    double _10,
-    int _11,
-    double _12,
-    int _13,
-    double _14,
-    int _15,
-    double _16,
-    int _17,
-    double _18,
-    int _19,
-    double _20) {
-  return _1 +
-      _2 +
-      _3 +
-      _4 +
-      _5 +
-      _6 +
-      _7 +
-      _8 +
-      _9 +
-      _10 +
-      _11 +
-      _12 +
-      _13 +
-      _14 +
-      _15 +
-      _16 +
-      _17 +
-      _18 +
-      _19 +
-      _20;
-}
+int myPlus(int a, int b) => a + b;
 
-typedef StoreType = Pointer<Int64> Function(Pointer<Int64>);
-Pointer<Int64> store(Pointer<Int64> ptr) => ptr.elementAt(1)..store(1337);
+typedef NativeApplyTo42And74Type = ffi.IntPtr Function(
+    ffi.Pointer<ffi.NativeFunction<NativeIntptrBinOp>>);
 
-typedef NullPointersType = Pointer<Int64> Function(Pointer<Int64>);
-Pointer<Int64> nullPointers(Pointer<Int64> ptr) => ptr?.elementAt(1);
-
-typedef ReturnNullType = Int32 Function();
-int returnNull() {
-  print('Expect "unhandled exception" error message to follow.');
-  return null;
-}
-
-typedef ReturnVoid = Void Function();
-void returnVoid() {}
-
-final List<Test> testcases = [
-  Test("SimpleAddition", fromFunction<SimpleAdditionType>(simpleAddition)),
-  Test("IntComputation", fromFunction<IntComputationType>(intComputation)),
-  Test("UintComputation", fromFunction<UintComputationType>(uintComputation)),
-  Test("SimpleMultiply", fromFunction<SimpleMultiplyType>(simpleMultiply)),
-  Test("SimpleMultiplyFloat",
-      fromFunction<SimpleMultiplyFloatType>(simpleMultiplyFloat)),
-  Test("ManyInts", fromFunction<ManyIntsType>(manyInts)),
-  Test("ManyDoubles", fromFunction<ManyDoublesType>(manyDoubles)),
-  Test("ManyArgs", fromFunction<ManyArgsType>(manyArgs)),
-  Test("Store", fromFunction<StoreType>(store)),
-  Test("NullPointers", fromFunction<NullPointersType>(nullPointers)),
-  Test("ReturnNull", fromFunction<ReturnNullType>(returnNull)),
-];
-
-testCallbackWrongThread() =>
-    Test("CallbackWrongThread", fromFunction<ReturnVoid>(returnVoid)).run();
-
-testCallbackOutsideIsolate() =>
-    Test("CallbackOutsideIsolate", fromFunction<ReturnVoid>(returnVoid)).run();
-
-isolateHelper(int callbackPointer) {
-  final Pointer<Void> ptr = fromAddress(callbackPointer);
-  final NativeCallbackTestFn tester =
-      testLibrary.lookupFunction<NativeCallbackTest, NativeCallbackTestFn>(
-          "TestCallbackWrongIsolate");
-  Expect.equals(0, tester(ptr));
-}
-
-testCallbackWrongIsolate() async {
-  final int callbackPointer = fromFunction<ReturnVoid>(returnVoid).address;
-  final ReceivePort exitPort = ReceivePort();
-  await Isolate.spawn(isolateHelper, callbackPointer,
-      errorsAreFatal: true, onExit: exitPort.sendPort);
-  await exitPort.first;
-}
-
-void main() async {
-  testcases.forEach((t) => t.run());
-
-  // These tests terminate the process after successful completion, so we have
-  // to run them separately.
-  //
-  // Since they use signal handlers they only run on Linux.
-  if (Platform.isLinux && !const bool.fromEnvironment("dart.vm.product")) {
-    testCallbackWrongThread(); //# 01: ok
-    testCallbackOutsideIsolate(); //# 02: ok
-    await testCallbackWrongIsolate(); //# 03: ok
-  }
-}
+typedef ApplyTo42And74Type = int Function(
+    ffi.Pointer<ffi.NativeFunction<NativeIntptrBinOp>>);