diff --git a/make/autoconf/configure.ac b/make/autoconf/configure.ac
index 05f2a51a7ed..31cda63f033 100644
--- a/make/autoconf/configure.ac
+++ b/make/autoconf/configure.ac
@@ -218,6 +218,10 @@ JDKOPT_SETUP_ADDRESS_SANITIZER
 # UndefinedBehaviorSanitizer
 JDKOPT_SETUP_UNDEFINED_BEHAVIOR_SANITIZER
 
+# Fallback linker
+# This needs to go before 'LIB_DETERMINE_DEPENDENCIES'
+JDKOPT_SETUP_FALLBACK_LINKER
+
 ###############################################################################
 #
 # Check dependencies for external and internal libraries.
diff --git a/make/autoconf/jdk-options.m4 b/make/autoconf/jdk-options.m4
index 06749ebe761..0a7242b6e25 100644
--- a/make/autoconf/jdk-options.m4
+++ b/make/autoconf/jdk-options.m4
@@ -864,3 +864,22 @@ AC_DEFUN([JDKOPT_SETUP_MACOSX_SIGNING],
     AC_SUBST(MACOSX_CODESIGN_MODE)
   fi
 ])
+
+################################################################################
+#
+# fallback linker
+#
+AC_DEFUN_ONCE([JDKOPT_SETUP_FALLBACK_LINKER],
+[
+  FALLBACK_LINKER_DEFAULT=false
+
+  if HOTSPOT_CHECK_JVM_VARIANT(zero); then
+    FALLBACK_LINKER_DEFAULT=true
+  fi
+
+  UTIL_ARG_ENABLE(NAME: fallback-linker, DEFAULT: $FALLBACK_LINKER_DEFAULT,
+      RESULT: ENABLE_FALLBACK_LINKER,
+      DESC: [enable libffi-based fallback implementation of java.lang.foreign.Linker],
+      CHECKING_MSG: [if fallback linker enabled])
+  AC_SUBST(ENABLE_FALLBACK_LINKER)
+])
diff --git a/make/autoconf/libraries.m4 b/make/autoconf/libraries.m4
index 7a1d8d80bb2..25bf79463df 100644
--- a/make/autoconf/libraries.m4
+++ b/make/autoconf/libraries.m4
@@ -82,7 +82,7 @@ AC_DEFUN_ONCE([LIB_DETERMINE_DEPENDENCIES],
   fi
 
   # Check if ffi is needed
-  if HOTSPOT_CHECK_JVM_VARIANT(zero); then
+  if HOTSPOT_CHECK_JVM_VARIANT(zero) || test "x$ENABLE_FALLBACK_LINKER" = "xtrue"; then
     NEEDS_LIB_FFI=true
   else
     NEEDS_LIB_FFI=false
diff --git a/make/autoconf/spec.gmk.in b/make/autoconf/spec.gmk.in
index 84ff5955eb1..64d44713406 100644
--- a/make/autoconf/spec.gmk.in
+++ b/make/autoconf/spec.gmk.in
@@ -407,6 +407,9 @@ TEST_JOBS?=@TEST_JOBS@
 DEFAULT_MAKE_TARGET:=@DEFAULT_MAKE_TARGET@
 DEFAULT_LOG:=@DEFAULT_LOG@
 
+# Fallback linker
+ENABLE_FALLBACK_LINKER:=@ENABLE_FALLBACK_LINKER@
+
 FREETYPE_TO_USE:=@FREETYPE_TO_USE@
 FREETYPE_LIBS:=@FREETYPE_LIBS@
 FREETYPE_CFLAGS:=@FREETYPE_CFLAGS@
diff --git a/make/conf/jib-profiles.js b/make/conf/jib-profiles.js
index 064dd7bbd32..f789e71fbdf 100644
--- a/make/conf/jib-profiles.js
+++ b/make/conf/jib-profiles.js
@@ -575,11 +575,12 @@ var getJibProfilesProfiles = function (input, common, data) {
         "linux-x64-zero": {
             target_os: "linux",
             target_cpu: "x64",
-            dependencies: ["devkit", "gtest"],
+            dependencies: ["devkit", "gtest", "libffi"],
             configure_args: concat(common.configure_args_64bit, [
                 "--with-zlib=system",
                 "--with-jvm-variants=zero",
-                "--enable-libffi-bundling"
+                "--with-libffi=" + input.get("libffi", "home_path"),
+                "--enable-libffi-bundling",
             ])
         },
 
@@ -729,6 +730,40 @@ var getJibProfilesProfiles = function (input, common, data) {
             common.debug_profile_artifacts(artifactData[name]));
     });
 
+    // Define artifact just for linux-x64-zero, which is the only one we test on
+    ["linux-x64"].forEach(function (name) {
+        var o = artifactData[name]
+        var pf = o.platform
+        var jdk_subdir = (o.jdk_subdir != null ? o.jdk_subdir : "jdk-" + data.version);
+        var jdk_suffix = (o.jdk_suffix != null ? o.jdk_suffix : "tar.gz");
+        var zeroName = name + "-zero";
+        profiles[zeroName].artifacts = {
+            jdk: {
+                local: "bundles/\\(jdk.*bin." + jdk_suffix + "\\)",
+                remote: [
+                    "bundles/" + pf + "/jdk-" + data.version + "_" + pf + "_bin-zero." + jdk_suffix,
+                ],
+                subdir: jdk_subdir,
+                exploded: "images/jdk",
+            },
+            test: {
+                    local: "bundles/\\(jdk.*bin-tests.tar.gz\\)",
+                    remote: [
+                        "bundles/" + pf + "/jdk-" + data.version + "_" + pf + "_bin-zero-tests.tar.gz",
+                    ],
+                    exploded: "images/test"
+            },
+            jdk_symbols: {
+                    local: "bundles/\\(jdk.*bin-symbols.tar.gz\\)",
+                    remote: [
+                        "bundles/" + pf + "/jdk-" + data.version + "_" + pf + "_bin-zero-symbols.tar.gz",
+                    ],
+                    subdir: jdk_subdir,
+                    exploded: "images/jdk"
+                },
+            };
+    });
+
     buildJdkDep = input.build_os + "-" + input.build_cpu + ".jdk";
     docsProfiles = {
         "docs": {
@@ -1222,6 +1257,13 @@ var getJibProfilesDependencies = function (input, common) {
             ext: "tar.gz",
             revision: "1.8.1"
         },
+
+        libffi: {
+            organization: common.organization,
+            module: "libffi-" + input.build_platform,
+            ext: "tar.gz",
+            revision: "3.4.2+1.0"
+        },
     };
 
     return dependencies;
diff --git a/make/data/hotspot-symbols/symbols-shared b/make/data/hotspot-symbols/symbols-shared
index ab6adf06d4d..e3975f907cb 100644
--- a/make/data/hotspot-symbols/symbols-shared
+++ b/make/data/hotspot-symbols/symbols-shared
@@ -30,5 +30,6 @@ jio_vsnprintf
 JNI_CreateJavaVM
 JNI_GetCreatedJavaVMs
 JNI_GetDefaultJavaVMInitArgs
+JVM_IsForeignLinkerSupported
 JVM_FindClassFromBootLoader
 JVM_InitAgentProperties
diff --git a/make/devkit/createLibffiBundle.sh b/make/devkit/createLibffiBundle.sh
new file mode 100644
index 00000000000..62d714a5148
--- /dev/null
+++ b/make/devkit/createLibffiBundle.sh
@@ -0,0 +1,111 @@
+#!/bin/bash
+#
+# Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation.  Oracle designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Oracle in the LICENSE file that accompanied this code.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+
+# This script generates a libffi bundle. On linux by building it from source
+# using a devkit, which should match the devkit used to build the JDK.
+#
+# Set MAKE_ARGS to add parameters to make. Ex:
+#
+# $ MAKE_ARGS=-j32 bash createLibffiBundle.sh
+#
+# The script tries to behave well on multiple invocations, only performing steps
+# not already done. To redo a step, manually delete the target files from that
+# step.
+#
+# Note that the libtool and texinfo packages are needed to build libffi
+# $ sudo apt install libtool texinfo
+
+LIBFFI_VERSION=3.4.2
+
+BUNDLE_NAME=libffi-$LIBFFI_VERSION.tar.gz
+
+SCRIPT_FILE="$(basename $0)"
+SCRIPT_DIR="$(cd "$(dirname $0)" > /dev/null && pwd)"
+OUTPUT_DIR="${SCRIPT_DIR}/../../build/libffi"
+SRC_DIR="$OUTPUT_DIR/src"
+DOWNLOAD_DIR="$OUTPUT_DIR/download"
+INSTALL_DIR="$OUTPUT_DIR/install"
+IMAGE_DIR="$OUTPUT_DIR/image"
+
+USAGE="$0 <devkit dir>"
+
+if [ "$1" = "" ]; then
+    echo $USAGE
+    exit 1
+fi
+DEVKIT_DIR="$1"
+
+# Download source distros
+mkdir -p $DOWNLOAD_DIR
+cd $DOWNLOAD_DIR
+SOURCE_TAR=v$LIBFFI_VERSION.tar.gz
+if [ ! -f $SOURCE_TAR ]; then
+    wget https://github.com/libffi/libffi/archive/refs/tags/v$LIBFFI_VERSION.tar.gz
+fi
+
+# Unpack src
+mkdir -p $SRC_DIR
+cd $SRC_DIR
+LIBFFI_DIRNAME=libffi-$LIBFFI_VERSION
+LIBFFI_DIR=$SRC_DIR/$LIBFFI_DIRNAME
+if [ ! -d $LIBFFI_DIRNAME ]; then
+    echo "Unpacking $SOURCE_TAR"
+    tar xf $DOWNLOAD_DIR/$SOURCE_TAR
+fi
+
+# Build
+cd $LIBFFI_DIR
+if [ ! -e $LIBFFI_DIR/configure ]; then
+  bash ./autogen.sh
+fi
+bash ./configure --prefix=$INSTALL_DIR CC=$DEVKIT_DIR/bin/gcc CXX=$DEVKIT_DIR/bin/g++
+
+# Run with nice to keep system usable during build.
+nice make $MAKE_ARGS install
+
+mkdir -p $IMAGE_DIR
+# Extract what we need into an image
+if [ ! -e $IMAGE_DIR/lib/libffi.so ]; then
+  echo "Copying libffi.so* to image"
+  mkdir -p $IMAGE_DIR/lib
+  cp -a $INSTALL_DIR/lib64/libffi.so* $IMAGE_DIR/lib/
+fi
+if [ ! -e $IMAGE_DIR/include/ ]; then
+  echo "Copying include to image"
+  mkdir -p $IMAGE_DIR/include
+  cp -a $INSTALL_DIR/include/. $IMAGE_DIR/include/
+fi
+if [ ! -e $IMAGE_DIR/$SCRIPT_FILE ]; then
+  echo "Copying this script to image"
+  cp -a $SCRIPT_DIR/$SCRIPT_FILE $IMAGE_DIR/
+fi
+
+# Create bundle
+if [ ! -e $OUTPUT_DIR/$BUNDLE_NAME ]; then
+  echo "Creating $OUTPUT_DIR/$BUNDLE_NAME"
+  cd $IMAGE_DIR
+  tar zcf $OUTPUT_DIR/$BUNDLE_NAME *
+fi
diff --git a/make/modules/java.base/Lib.gmk b/make/modules/java.base/Lib.gmk
index 3b782577258..fb77d904297 100644
--- a/make/modules/java.base/Lib.gmk
+++ b/make/modules/java.base/Lib.gmk
@@ -217,3 +217,18 @@ $(eval $(call SetupJdkLibrary, BUILD_SYSLOOKUPLIB, \
 ))
 
 TARGETS += $(BUILD_SYSLOOKUPLIB)
+
+################################################################################
+# Create fallback linker lib
+
+ifeq ($(ENABLE_FALLBACK_LINKER), true)
+  $(eval $(call SetupJdkLibrary, BUILD_LIBFALLBACKLINKER, \
+      NAME := fallbackLinker, \
+      CFLAGS := $(CFLAGS_JDKLIB) $(LIBFFI_CFLAGS), \
+      LDFLAGS := $(LDFLAGS_JDKLIB) \
+                 $(call SET_SHARED_LIBRARY_ORIGIN), \
+      LIBS := $(LIBFFI_LIBS), \
+  ))
+
+  TARGETS += $(BUILD_LIBFALLBACKLINKER)
+endif
diff --git a/src/hotspot/cpu/aarch64/foreignGlobals_aarch64.cpp b/src/hotspot/cpu/aarch64/foreignGlobals_aarch64.cpp
index 90c1942c32d..f5b668b4dae 100644
--- a/src/hotspot/cpu/aarch64/foreignGlobals_aarch64.cpp
+++ b/src/hotspot/cpu/aarch64/foreignGlobals_aarch64.cpp
@@ -33,6 +33,10 @@
 #include "prims/vmstorage.hpp"
 #include "utilities/formatBuffer.hpp"
 
+bool ForeignGlobals::has_port() {
+  return true;
+}
+
 bool ABIDescriptor::is_volatile_reg(Register reg) const {
   return _integer_argument_registers.contains(reg)
     || _integer_additional_volatile_registers.contains(reg);
diff --git a/src/hotspot/cpu/arm/foreignGlobals_arm.cpp b/src/hotspot/cpu/arm/foreignGlobals_arm.cpp
index 5438cbe5cd6..e8a697667d5 100644
--- a/src/hotspot/cpu/arm/foreignGlobals_arm.cpp
+++ b/src/hotspot/cpu/arm/foreignGlobals_arm.cpp
@@ -29,6 +29,10 @@
 
 class MacroAssembler;
 
+bool ForeignGlobals::has_port() {
+  return false;
+}
+
 const ABIDescriptor ForeignGlobals::parse_abi_descriptor(jobject jabi) {
   Unimplemented();
   return {};
diff --git a/src/hotspot/cpu/ppc/foreignGlobals_ppc.cpp b/src/hotspot/cpu/ppc/foreignGlobals_ppc.cpp
index b685023656d..bf2ddc9de2b 100644
--- a/src/hotspot/cpu/ppc/foreignGlobals_ppc.cpp
+++ b/src/hotspot/cpu/ppc/foreignGlobals_ppc.cpp
@@ -29,6 +29,10 @@
 
 class MacroAssembler;
 
+bool ForeignGlobals::has_port() {
+  return false;
+}
+
 // Stubbed out, implement later
 const ABIDescriptor ForeignGlobals::parse_abi_descriptor(jobject jabi) {
   Unimplemented();
diff --git a/src/hotspot/cpu/riscv/foreignGlobals_riscv.cpp b/src/hotspot/cpu/riscv/foreignGlobals_riscv.cpp
index aefbb56a491..30cc6e1cf50 100644
--- a/src/hotspot/cpu/riscv/foreignGlobals_riscv.cpp
+++ b/src/hotspot/cpu/riscv/foreignGlobals_riscv.cpp
@@ -30,6 +30,10 @@
 
 class MacroAssembler;
 
+bool ForeignGlobals::has_port() {
+  return false;
+}
+
 const ABIDescriptor ForeignGlobals::parse_abi_descriptor(jobject jabi) {
   ShouldNotCallThis();
   return {};
diff --git a/src/hotspot/cpu/s390/foreignGlobals_s390.cpp b/src/hotspot/cpu/s390/foreignGlobals_s390.cpp
index 5438cbe5cd6..e8a697667d5 100644
--- a/src/hotspot/cpu/s390/foreignGlobals_s390.cpp
+++ b/src/hotspot/cpu/s390/foreignGlobals_s390.cpp
@@ -29,6 +29,10 @@
 
 class MacroAssembler;
 
+bool ForeignGlobals::has_port() {
+  return false;
+}
+
 const ABIDescriptor ForeignGlobals::parse_abi_descriptor(jobject jabi) {
   Unimplemented();
   return {};
diff --git a/src/hotspot/cpu/x86/foreignGlobals_x86_32.cpp b/src/hotspot/cpu/x86/foreignGlobals_x86_32.cpp
index 8a31955f4d1..cea03858f2c 100644
--- a/src/hotspot/cpu/x86/foreignGlobals_x86_32.cpp
+++ b/src/hotspot/cpu/x86/foreignGlobals_x86_32.cpp
@@ -28,6 +28,10 @@
 
 class MacroAssembler;
 
+bool ForeignGlobals::has_port() {
+  return false;
+}
+
 const ABIDescriptor ForeignGlobals::parse_abi_descriptor(jobject jabi) {
   Unimplemented();
   return {};
diff --git a/src/hotspot/cpu/x86/foreignGlobals_x86_64.cpp b/src/hotspot/cpu/x86/foreignGlobals_x86_64.cpp
index 74afbe4fd61..0a7d18b2689 100644
--- a/src/hotspot/cpu/x86/foreignGlobals_x86_64.cpp
+++ b/src/hotspot/cpu/x86/foreignGlobals_x86_64.cpp
@@ -30,6 +30,10 @@
 #include "runtime/sharedRuntime.hpp"
 #include "utilities/formatBuffer.hpp"
 
+bool ForeignGlobals::has_port() {
+  return true;
+}
+
 bool ABIDescriptor::is_volatile_reg(Register reg) const {
     return _integer_argument_registers.contains(reg)
         || _integer_additional_volatile_registers.contains(reg);
diff --git a/src/hotspot/cpu/zero/foreignGlobals_zero.cpp b/src/hotspot/cpu/zero/foreignGlobals_zero.cpp
index 7c35da7e3e0..32d548e2a48 100644
--- a/src/hotspot/cpu/zero/foreignGlobals_zero.cpp
+++ b/src/hotspot/cpu/zero/foreignGlobals_zero.cpp
@@ -28,6 +28,10 @@
 
 class MacroAssembler;
 
+bool ForeignGlobals::has_port() {
+  return false;
+}
+
 const ABIDescriptor ForeignGlobals::parse_abi_descriptor(jobject jabi) {
   ShouldNotCallThis();
   return {};
diff --git a/src/hotspot/cpu/zero/globalDefinitions_zero.hpp b/src/hotspot/cpu/zero/globalDefinitions_zero.hpp
index 9db2060b8dd..6e09da517b1 100644
--- a/src/hotspot/cpu/zero/globalDefinitions_zero.hpp
+++ b/src/hotspot/cpu/zero/globalDefinitions_zero.hpp
@@ -32,7 +32,7 @@
 
 #define SUPPORT_MONITOR_COUNT
 
-#ifndef FFI_GO_CLOSURES
+#ifdef __APPLE__
 #define FFI_GO_CLOSURES 0
 #endif
 
diff --git a/src/hotspot/share/include/jvm.h b/src/hotspot/share/include/jvm.h
index 98e0233887c..b33aea42071 100644
--- a/src/hotspot/share/include/jvm.h
+++ b/src/hotspot/share/include/jvm.h
@@ -174,6 +174,9 @@ JVM_IsPreviewEnabled(void);
 JNIEXPORT jboolean JNICALL
 JVM_IsContinuationsSupported(void);
 
+JNIEXPORT jboolean JNICALL
+JVM_IsForeignLinkerSupported(void);
+
 JNIEXPORT void JNICALL
 JVM_InitializeFromArchive(JNIEnv* env, jclass cls);
 
diff --git a/src/hotspot/share/prims/foreignGlobals.hpp b/src/hotspot/share/prims/foreignGlobals.hpp
index d0160f23226..9383756dcef 100644
--- a/src/hotspot/share/prims/foreignGlobals.hpp
+++ b/src/hotspot/share/prims/foreignGlobals.hpp
@@ -76,6 +76,8 @@ class ForeignGlobals {
   static void parse_register_array(objArrayOop jarray, StorageType type_index, GrowableArray<T>& array, T (*converter)(int));
 
 public:
+  static bool has_port();
+
   static const ABIDescriptor parse_abi_descriptor(jobject jabi);
   static const CallRegs parse_call_regs(jobject jconv);
   static VMStorage parse_vmstorage(oop storage);
diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp
index d79b31bdb30..7941b1f4878 100644
--- a/src/hotspot/share/prims/jvm.cpp
+++ b/src/hotspot/share/prims/jvm.cpp
@@ -63,6 +63,7 @@
 #include "oops/objArrayKlass.hpp"
 #include "oops/objArrayOop.inline.hpp"
 #include "oops/oop.inline.hpp"
+#include "prims/foreignGlobals.hpp"
 #include "prims/jvm_misc.hpp"
 #include "prims/jvmtiExport.hpp"
 #include "prims/jvmtiThreadState.inline.hpp"
@@ -3474,6 +3475,10 @@ JVM_LEAF(jboolean, JVM_IsContinuationsSupported(void))
   return VMContinuations ? JNI_TRUE : JNI_FALSE;
 JVM_END
 
+JVM_LEAF(jboolean, JVM_IsForeignLinkerSupported(void))
+  return ForeignGlobals::has_port() ? JNI_TRUE : JNI_FALSE;
+JVM_END
+
 // String support ///////////////////////////////////////////////////////////////////////////
 
 JVM_ENTRY(jstring, JVM_InternString(JNIEnv *env, jstring str))
diff --git a/src/java.base/share/classes/jdk/internal/foreign/CABI.java b/src/java.base/share/classes/jdk/internal/foreign/CABI.java
index 52718ff3de2..ec1722bae88 100644
--- a/src/java.base/share/classes/jdk/internal/foreign/CABI.java
+++ b/src/java.base/share/classes/jdk/internal/foreign/CABI.java
@@ -1,5 +1,5 @@
 /*
- *  Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved.
+ *  Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
  *  DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  *  This code is free software; you can redistribute it and/or modify it
@@ -25,6 +25,9 @@
  */
 package jdk.internal.foreign;
 
+import jdk.internal.foreign.abi.fallback.FallbackLinker;
+import jdk.internal.vm.ForeignLinkerSupport;
+
 import static java.lang.foreign.ValueLayout.ADDRESS;
 import static sun.security.action.GetPropertyAction.privilegedGetProperty;
 
@@ -33,45 +36,49 @@ public enum CABI {
     WIN_64,
     LINUX_AARCH_64,
     MAC_OS_AARCH_64,
-    WIN_AARCH_64;
+    WIN_AARCH_64,
+    FALLBACK,
+    UNSUPPORTED;
 
-    private static final CABI ABI;
-    private static final String ARCH;
-    private static final String OS;
-    private static final long ADDRESS_SIZE;
+    private static final CABI CURRENT = computeCurrent();
 
-    static {
-        ARCH = privilegedGetProperty("os.arch");
-        OS = privilegedGetProperty("os.name");
-        ADDRESS_SIZE = ADDRESS.bitSize();
-        // might be running in a 32-bit VM on a 64-bit platform.
-        // addressSize will be correctly 32
-        if ((ARCH.equals("amd64") || ARCH.equals("x86_64")) && ADDRESS_SIZE == 64) {
-            if (OS.startsWith("Windows")) {
-                ABI = WIN_64;
-            } else {
-                ABI = SYS_V;
-            }
-        } else if (ARCH.equals("aarch64")) {
-            if (OS.startsWith("Mac")) {
-                ABI = MAC_OS_AARCH_64;
-            } else if (OS.startsWith("Windows")) {
-                ABI = WIN_AARCH_64;
-            } else {
-                // The Linux ABI follows the standard AAPCS ABI
-                ABI = LINUX_AARCH_64;
+    private static CABI computeCurrent() {
+        String abi = privilegedGetProperty("jdk.internal.foreign.CABI");
+        if (abi != null) {
+            return CABI.valueOf(abi);
+        }
+
+        if (ForeignLinkerSupport.isSupported()) {
+            // figure out the ABI based on the platform
+            String arch = privilegedGetProperty("os.arch");
+            String os = privilegedGetProperty("os.name");
+            long addressSize = ADDRESS.bitSize();
+            // might be running in a 32-bit VM on a 64-bit platform.
+            // addressSize will be correctly 32
+            if ((arch.equals("amd64") || arch.equals("x86_64")) && addressSize == 64) {
+                if (os.startsWith("Windows")) {
+                    return WIN_64;
+                } else {
+                    return SYS_V;
+                }
+            } else if (arch.equals("aarch64")) {
+                if (os.startsWith("Mac")) {
+                    return MAC_OS_AARCH_64;
+                } else if (os.startsWith("Windows")) {
+                    return WIN_AARCH_64;
+                } else {
+                    // The Linux ABI follows the standard AAPCS ABI
+                    return LINUX_AARCH_64;
+                }
             }
-        } else {
-            // unsupported
-            ABI = null;
+        } else if (FallbackLinker.isSupported()) {
+            return FALLBACK; // fallback linker
         }
+
+        return UNSUPPORTED;
     }
 
     public static CABI current() {
-        if (ABI == null) {
-            throw new UnsupportedOperationException(
-                    "Unsupported os, arch, or address size: " + OS + ", " + ARCH + ", " + ADDRESS_SIZE);
-        }
-        return ABI;
+        return CURRENT;
     }
 }
diff --git a/src/java.base/share/classes/jdk/internal/foreign/SystemLookup.java b/src/java.base/share/classes/jdk/internal/foreign/SystemLookup.java
index a7677ba53b2..9410a2b7319 100644
--- a/src/java.base/share/classes/jdk/internal/foreign/SystemLookup.java
+++ b/src/java.base/share/classes/jdk/internal/foreign/SystemLookup.java
@@ -39,9 +39,12 @@
 import sun.security.action.GetPropertyAction;
 
 import static java.lang.foreign.ValueLayout.ADDRESS;
+import static sun.security.action.GetPropertyAction.privilegedGetProperty;
 
 public final class SystemLookup implements SymbolLookup {
 
+    private static final boolean IS_WINDOWS = privilegedGetProperty("os.name").startsWith("Windows");
+
     private SystemLookup() { }
 
     private static final SystemLookup INSTANCE = new SystemLookup();
@@ -57,10 +60,11 @@ private SystemLookup() { }
 
     private static SymbolLookup makeSystemLookup() {
         try {
-            return switch (CABI.current()) {
-                case SYS_V, LINUX_AARCH_64, MAC_OS_AARCH_64 -> libLookup(libs -> libs.load(jdkLibraryPath("syslookup")));
-                case WIN_64, WIN_AARCH_64 -> makeWindowsLookup(); // out of line to workaround javac crash
-            };
+            if (IS_WINDOWS) {
+                return makeWindowsLookup();
+            } else {
+                return libLookup(libs -> libs.load(jdkLibraryPath("syslookup")));
+            }
         } catch (Throwable ex) {
             // This can happen in the event of a library loading failure - e.g. if one of the libraries the
             // system lookup depends on cannot be loaded for some reason. In such extreme cases, rather than
@@ -118,10 +122,7 @@ private static SymbolLookup libLookup(Function<RawNativeLibraries, NativeLibrary
      */
     private static Path jdkLibraryPath(String name) {
         Path javahome = Path.of(GetPropertyAction.privilegedGetProperty("java.home"));
-        String lib = switch (CABI.current()) {
-            case SYS_V, LINUX_AARCH_64, MAC_OS_AARCH_64 -> "lib";
-            case WIN_64, WIN_AARCH_64 -> "bin";
-        };
+        String lib = IS_WINDOWS ? "bin" : "lib";
         String libname = System.mapLibraryName(name);
         return javahome.resolve(lib).resolve(libname);
     }
diff --git a/src/java.base/share/classes/jdk/internal/foreign/Utils.java b/src/java.base/share/classes/jdk/internal/foreign/Utils.java
index 1124a0a0440..e069963adda 100644
--- a/src/java.base/share/classes/jdk/internal/foreign/Utils.java
+++ b/src/java.base/share/classes/jdk/internal/foreign/Utils.java
@@ -29,11 +29,14 @@
 import java.lang.foreign.MemoryLayout;
 import java.lang.foreign.MemorySegment;
 import java.lang.foreign.SegmentAllocator;
+import java.lang.foreign.StructLayout;
 import java.lang.foreign.ValueLayout;
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodType;
 import java.lang.invoke.VarHandle;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Supplier;
@@ -202,4 +205,40 @@ public static void checkAllocationSizeAndAlign(long byteSize, long byteAlignment
             throw new IllegalArgumentException("Invalid alignment constraint : " + byteAlignment);
         }
     }
+
+    private static long computePadding(long offset, long align) {
+        boolean isAligned = offset == 0 || offset % align == 0;
+        if (isAligned) {
+            return 0;
+        } else {
+            long gap = offset % align;
+            return align - gap;
+        }
+    }
+
+    /**
+     * {@return return a struct layout constructed from the given elements, with padding computed automatically}
+     *
+     * @param elements the structs' fields
+     */
+    public static StructLayout computePaddedStructLayout(MemoryLayout... elements) {
+        long offset = 0L;
+        List<MemoryLayout> layouts = new ArrayList<>();
+        long align = 0;
+        for (MemoryLayout l : elements) {
+            long padding = computePadding(offset, l.bitAlignment());
+            if (padding != 0) {
+                layouts.add(MemoryLayout.paddingLayout(padding));
+                offset += padding;
+            }
+            layouts.add(l);
+            align = Math.max(align, l.bitAlignment());
+            offset += l.bitSize();
+        }
+        long padding = computePadding(offset, align);
+        if (padding != 0) {
+            layouts.add(MemoryLayout.paddingLayout(padding));
+        }
+        return MemoryLayout.structLayout(layouts.toArray(MemoryLayout[]::new));
+    }
 }
diff --git a/src/java.base/share/classes/jdk/internal/foreign/abi/AbstractLinker.java b/src/java.base/share/classes/jdk/internal/foreign/abi/AbstractLinker.java
index d97da7cf5c5..ea63ffb8e00 100644
--- a/src/java.base/share/classes/jdk/internal/foreign/abi/AbstractLinker.java
+++ b/src/java.base/share/classes/jdk/internal/foreign/abi/AbstractLinker.java
@@ -27,6 +27,7 @@
 import jdk.internal.foreign.SystemLookup;
 import jdk.internal.foreign.abi.aarch64.linux.LinuxAArch64Linker;
 import jdk.internal.foreign.abi.aarch64.macos.MacOsAArch64Linker;
+import jdk.internal.foreign.abi.fallback.FallbackLinker;
 import jdk.internal.foreign.abi.aarch64.windows.WindowsAArch64Linker;
 import jdk.internal.foreign.abi.x64.sysv.SysVx64Linker;
 import jdk.internal.foreign.abi.x64.windows.Windowsx64Linker;
@@ -44,7 +45,9 @@
 import java.util.Objects;
 
 public abstract sealed class AbstractLinker implements Linker permits LinuxAArch64Linker, MacOsAArch64Linker,
-                                                                      SysVx64Linker, WindowsAArch64Linker, Windowsx64Linker {
+                                                                      SysVx64Linker, WindowsAArch64Linker,
+                                                                      Windowsx64Linker, FallbackLinker {
+
 
     private record LinkRequest(FunctionDescriptor descriptor, LinkerOptions options) {}
     private final SoftReferenceCache<LinkRequest, MethodHandle> DOWNCALL_CACHE = new SoftReferenceCache<>();
diff --git a/src/java.base/share/classes/jdk/internal/foreign/abi/DowncallLinker.java b/src/java.base/share/classes/jdk/internal/foreign/abi/DowncallLinker.java
index 59343475406..8254e35682d 100644
--- a/src/java.base/share/classes/jdk/internal/foreign/abi/DowncallLinker.java
+++ b/src/java.base/share/classes/jdk/internal/foreign/abi/DowncallLinker.java
@@ -50,7 +50,6 @@ public class DowncallLinker {
     private static final JavaLangInvokeAccess JLIA = SharedSecrets.getJavaLangInvokeAccess();
 
     private static final MethodHandle MH_INVOKE_INTERP_BINDINGS;
-    private static final MethodHandle MH_CHECK_SYMBOL;
     private static final MethodHandle EMPTY_OBJECT_ARRAY_HANDLE = MethodHandles.constant(Object[].class, new Object[0]);
 
     static {
@@ -58,8 +57,6 @@ public class DowncallLinker {
             MethodHandles.Lookup lookup = MethodHandles.lookup();
             MH_INVOKE_INTERP_BINDINGS = lookup.findVirtual(DowncallLinker.class, "invokeInterpBindings",
                     methodType(Object.class, SegmentAllocator.class, Object[].class, InvocationData.class));
-            MH_CHECK_SYMBOL = lookup.findStatic(SharedUtils.class, "checkSymbol",
-                    methodType(void.class, MemorySegment.class));
         } catch (ReflectiveOperationException e) {
             throw new RuntimeException(e);
         }
@@ -111,7 +108,7 @@ public MethodHandle getBoundMethodHandle() {
 
         assert handle.type().parameterType(0) == SegmentAllocator.class;
         assert handle.type().parameterType(1) == MemorySegment.class;
-        handle = foldArguments(handle, 1, MH_CHECK_SYMBOL);
+        handle = foldArguments(handle, 1, SharedUtils.MH_CHECK_SYMBOL);
 
         handle = SharedUtils.swapArguments(handle, 0, 1); // normalize parameter order
 
diff --git a/src/java.base/share/classes/jdk/internal/foreign/abi/SharedUtils.java b/src/java.base/share/classes/jdk/internal/foreign/abi/SharedUtils.java
index 85b43a3b581..97d25bf178d 100644
--- a/src/java.base/share/classes/jdk/internal/foreign/abi/SharedUtils.java
+++ b/src/java.base/share/classes/jdk/internal/foreign/abi/SharedUtils.java
@@ -31,6 +31,7 @@
 import jdk.internal.foreign.abi.aarch64.linux.LinuxAArch64Linker;
 import jdk.internal.foreign.abi.aarch64.macos.MacOsAArch64Linker;
 import jdk.internal.foreign.abi.aarch64.windows.WindowsAArch64Linker;
+import jdk.internal.foreign.abi.fallback.FallbackLinker;
 import jdk.internal.foreign.abi.x64.sysv.SysVx64Linker;
 import jdk.internal.foreign.abi.x64.windows.Windowsx64Linker;
 import jdk.internal.vm.annotation.ForceInline;
@@ -40,7 +41,6 @@
 import java.lang.foreign.GroupLayout;
 import java.lang.foreign.MemoryLayout;
 import java.lang.foreign.MemorySegment;
-import java.lang.foreign.SegmentScope;
 import java.lang.foreign.SegmentAllocator;
 import java.lang.foreign.ValueLayout;
 import java.lang.invoke.MethodHandle;
@@ -51,9 +51,7 @@
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.Map;
-import java.util.NoSuchElementException;
 import java.util.Objects;
-import java.util.function.Consumer;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 
@@ -72,6 +70,7 @@ private SharedUtils() {
     private static final MethodHandle MH_ALLOC_BUFFER;
     private static final MethodHandle MH_BUFFER_COPY;
     private static final MethodHandle MH_REACHABILITY_FENCE;
+    public static final MethodHandle MH_CHECK_SYMBOL;
 
     static {
         try {
@@ -82,6 +81,8 @@ private SharedUtils() {
                     methodType(MemorySegment.class, MemorySegment.class, MemorySegment.class));
             MH_REACHABILITY_FENCE = lookup.findStatic(Reference.class, "reachabilityFence",
                     methodType(void.class, Object.class));
+            MH_CHECK_SYMBOL = lookup.findStatic(SharedUtils.class, "checkSymbol",
+                    methodType(void.class, MemorySegment.class));
         } catch (ReflectiveOperationException e) {
             throw new BootstrapMethodError(e);
         }
@@ -188,6 +189,8 @@ public static Linker getSystemLinker() {
             case LINUX_AARCH_64 -> LinuxAArch64Linker.getInstance();
             case MAC_OS_AARCH_64 -> MacOsAArch64Linker.getInstance();
             case WIN_AARCH_64 -> WindowsAArch64Linker.getInstance();
+            case FALLBACK -> FallbackLinker.getInstance();
+            case UNSUPPORTED -> throw new UnsupportedOperationException("Platform does not support native linker");
         };
     }
 
@@ -239,7 +242,7 @@ static MethodHandle mergeArguments(MethodHandle mh, int sourceIndex, int destInd
     }
 
 
-    static MethodHandle swapArguments(MethodHandle mh, int firstArg, int secondArg) {
+    public static MethodHandle swapArguments(MethodHandle mh, int firstArg, int secondArg) {
         MethodType mtype = mh.type();
         int[] perms = new int[mtype.parameterCount()];
         MethodType swappedType = MethodType.methodType(mtype.returnType());
@@ -257,7 +260,7 @@ private static MethodHandle reachabilityFenceHandle(Class<?> type) {
         return MH_REACHABILITY_FENCE.asType(MethodType.methodType(void.class, type));
     }
 
-    static void handleUncaughtException(Throwable t) {
+    public static void handleUncaughtException(Throwable t) {
         if (t != null) {
             t.printStackTrace();
             JLA.exit(1);
diff --git a/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/FFIABI.java b/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/FFIABI.java
new file mode 100644
index 00000000000..ff074ebe5c9
--- /dev/null
+++ b/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/FFIABI.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.internal.foreign.abi.fallback;
+
+/**
+ * enum which maps the {@code ffi_abi} enum
+ */
+enum FFIABI {
+    DEFAULT(LibFallback.DEFAULT_ABI);
+
+    private final int value;
+
+    FFIABI(int abi) {
+        this.value = abi;
+    }
+
+    int value() {
+        return value;
+    }
+}
diff --git a/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/FFIStatus.java b/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/FFIStatus.java
new file mode 100644
index 00000000000..5ac6a95efa6
--- /dev/null
+++ b/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/FFIStatus.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.internal.foreign.abi.fallback;
+
+/**
+ * See doc: <a href="https://github.com/libffi/libffi/blob/7611bb4cfe90884b55ad225e0166136a1d2cf22b/doc/libffi.texi#L159"></a>
+ * <p>
+ * typedef enum {
+ *   FFI_OK = 0,
+ *   FFI_BAD_TYPEDEF,
+ *   FFI_BAD_ABI,
+ *   FFI_BAD_ARGTYPE
+ * } ffi_status;
+ */
+enum FFIStatus {
+    FFI_OK,
+    FFI_BAD_TYPEDEF,
+    FFI_BAD_ABI,
+    FFI_BAD_ARGTYPE;
+
+    static FFIStatus of(int code) {
+        return switch (code) {
+            case 0 -> FFI_OK;
+            case 1 -> FFI_BAD_TYPEDEF;
+            case 2 -> FFI_BAD_ABI;
+            case 3 -> FFI_BAD_ARGTYPE;
+            default -> throw new IllegalArgumentException("Unknown status code: " + code);
+        };
+    }
+}
diff --git a/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/FFIType.java b/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/FFIType.java
new file mode 100644
index 00000000000..149c1a25e61
--- /dev/null
+++ b/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/FFIType.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.internal.foreign.abi.fallback;
+
+import jdk.internal.foreign.Utils;
+
+import java.lang.foreign.Arena;
+import java.lang.foreign.GroupLayout;
+import java.lang.foreign.MemoryLayout;
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.PaddingLayout;
+import java.lang.foreign.SegmentScope;
+import java.lang.foreign.SequenceLayout;
+import java.lang.foreign.StructLayout;
+import java.lang.foreign.UnionLayout;
+import java.lang.foreign.ValueLayout;
+import java.lang.invoke.VarHandle;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Predicate;
+
+import static java.lang.foreign.ValueLayout.ADDRESS;
+import static java.lang.foreign.ValueLayout.JAVA_INT;
+import static java.lang.foreign.ValueLayout.JAVA_LONG;
+import static java.lang.foreign.ValueLayout.JAVA_SHORT;
+
+/**
+ * typedef struct _ffi_type
+ * {
+ *   size_t size;
+ *   unsigned short alignment;
+ *   unsigned short type;
+ *   struct _ffi_type **elements;
+ * } ffi_type;
+ */
+class FFIType {
+    private static final ValueLayout SIZE_T = switch ((int) ADDRESS.bitSize()) {
+            case 64 -> JAVA_LONG;
+            case 32 -> JAVA_INT;
+            default -> throw new IllegalStateException("Address size not supported: " + ADDRESS.byteSize());
+        };
+    private static final ValueLayout UNSIGNED_SHORT = JAVA_SHORT;
+    private static final StructLayout LAYOUT = Utils.computePaddedStructLayout(
+            SIZE_T, UNSIGNED_SHORT, UNSIGNED_SHORT.withName("type"), ADDRESS.withName("elements"));
+
+    private static final VarHandle VH_TYPE = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("type"));
+    private static final VarHandle VH_ELEMENTS = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("elements"));
+    private static final VarHandle VH_SIZE_T_ARRAY = SIZE_T.arrayElementVarHandle();
+
+    private static MemorySegment make(List<MemoryLayout> elements, FFIABI abi, SegmentScope scope) {
+        MemorySegment elementsSeg = MemorySegment.allocateNative((elements.size() + 1) * ADDRESS.byteSize(), scope);
+        int i = 0;
+        for (; i < elements.size(); i++) {
+            MemoryLayout elementLayout = elements.get(i);
+            MemorySegment elementType = toFFIType(elementLayout, abi, scope);
+            elementsSeg.setAtIndex(ADDRESS, i, elementType);
+        }
+        // elements array is null-terminated
+        elementsSeg.setAtIndex(ADDRESS, i, MemorySegment.NULL);
+
+        MemorySegment ffiType = MemorySegment.allocateNative(LAYOUT, scope);
+        VH_TYPE.set(ffiType, LibFallback.STRUCT_TAG);
+        VH_ELEMENTS.set(ffiType, elementsSeg);
+
+        return ffiType;
+    }
+
+    private static final Map<Class<?>, MemorySegment> CARRIER_TO_TYPE = Map.of(
+        boolean.class, LibFallback.UINT8_TYPE,
+        byte.class, LibFallback.SINT8_TYPE,
+        short.class, LibFallback.SINT16_TYPE,
+        char.class, LibFallback.UINT16_TYPE,
+        int.class, LibFallback.SINT32_TYPE,
+        long.class, LibFallback.SINT64_TYPE,
+        float.class, LibFallback.FLOAT_TYPE,
+        double.class, LibFallback.DOUBLE_TYPE,
+        MemorySegment.class, LibFallback.POINTER_TYPE
+    );
+
+    static MemorySegment toFFIType(MemoryLayout layout, FFIABI abi, SegmentScope scope) {
+        if (layout instanceof GroupLayout grpl) {
+            if (grpl instanceof StructLayout strl) {
+                // libffi doesn't want our padding
+                List<MemoryLayout> filteredLayouts = strl.memberLayouts().stream()
+                        .filter(Predicate.not(PaddingLayout.class::isInstance))
+                        .toList();
+                MemorySegment structType = make(filteredLayouts, abi, scope);
+                verifyStructType(strl, filteredLayouts, structType, abi);
+                return structType;
+            }
+            assert grpl instanceof UnionLayout;
+            throw new UnsupportedOperationException("No unions (TODO)");
+        } else if (layout instanceof SequenceLayout sl) {
+            List<MemoryLayout> elements = Collections.nCopies(Math.toIntExact(sl.elementCount()), sl.elementLayout());
+            return make(elements, abi, scope);
+        }
+        return Objects.requireNonNull(CARRIER_TO_TYPE.get(((ValueLayout) layout).carrier()));
+    }
+
+    // verify layout against what libffi sets
+    private static void verifyStructType(StructLayout structLayout, List<MemoryLayout> filteredLayouts, MemorySegment structType,
+                                         FFIABI abi) {
+        try (Arena verifyArena = Arena.openConfined()) {
+            MemorySegment offsetsOut = verifyArena.allocate(SIZE_T.byteSize() * filteredLayouts.size());
+            LibFallback.getStructOffsets(structType, offsetsOut, abi);
+            long expectedOffset = 0;
+            int offsetIdx = 0;
+            for (MemoryLayout element : structLayout.memberLayouts()) {
+                if (!(element instanceof PaddingLayout)) {
+                    long ffiOffset = (long) VH_SIZE_T_ARRAY.get(offsetsOut, offsetIdx++);
+                    if (ffiOffset != expectedOffset) {
+                        throw new IllegalArgumentException("Invalid group layout." +
+                                " Offset of '" + element.name().orElse("<unnamed>")
+                                + "': " + expectedOffset + " != " + ffiOffset);
+                    }
+                }
+                expectedOffset += element.byteSize();
+            }
+        }
+    }
+}
diff --git a/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/FallbackLinker.java b/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/FallbackLinker.java
new file mode 100644
index 00000000000..c1362c2dc30
--- /dev/null
+++ b/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/FallbackLinker.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.internal.foreign.abi.fallback;
+
+import jdk.internal.foreign.AbstractMemorySegmentImpl;
+import jdk.internal.foreign.MemorySessionImpl;
+import jdk.internal.foreign.abi.AbstractLinker;
+import jdk.internal.foreign.abi.CapturableState;
+import jdk.internal.foreign.abi.LinkerOptions;
+import jdk.internal.foreign.abi.SharedUtils;
+
+import java.lang.foreign.Arena;
+import java.lang.foreign.FunctionDescriptor;
+import java.lang.foreign.GroupLayout;
+import java.lang.foreign.MemoryLayout;
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.SegmentAllocator;
+import java.lang.foreign.SegmentScope;
+import java.lang.foreign.ValueLayout;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.ref.Reference;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+import static java.lang.foreign.ValueLayout.ADDRESS;
+import static java.lang.foreign.ValueLayout.JAVA_LONG;
+import static java.lang.invoke.MethodHandles.foldArguments;
+
+public final class FallbackLinker extends AbstractLinker {
+
+    private static final MethodHandle MH_DO_DOWNCALL;
+    private static final MethodHandle MH_DO_UPCALL;
+
+    static {
+        try {
+            MH_DO_DOWNCALL = MethodHandles.lookup().findStatic(FallbackLinker.class, "doDowncall",
+                    MethodType.methodType(Object.class, SegmentAllocator.class, Object[].class, FallbackLinker.DowncallData.class));
+            MH_DO_UPCALL = MethodHandles.lookup().findStatic(FallbackLinker.class, "doUpcall",
+                    MethodType.methodType(void.class, MemorySegment.class, MemorySegment.class, UpcallData.class));
+        } catch (ReflectiveOperationException e) {
+            throw new ExceptionInInitializerError(e);
+        }
+    }
+
+    public static FallbackLinker getInstance() {
+        class Holder {
+            static final FallbackLinker INSTANCE = new FallbackLinker();
+        }
+        return Holder.INSTANCE;
+    }
+
+    public static boolean isSupported() {
+        return LibFallback.SUPPORTED;
+    }
+
+    @Override
+    protected MethodHandle arrangeDowncall(MethodType inferredMethodType, FunctionDescriptor function, LinkerOptions options) {
+        MemorySegment cif = makeCif(inferredMethodType, function, FFIABI.DEFAULT, SegmentScope.auto());
+
+        int capturedStateMask = options.capturedCallState()
+                .mapToInt(CapturableState::mask)
+                .reduce(0, (a, b) -> a | b);
+        DowncallData invData = new DowncallData(cif, function.returnLayout().orElse(null),
+                function.argumentLayouts(), capturedStateMask);
+
+        MethodHandle target = MethodHandles.insertArguments(MH_DO_DOWNCALL, 2, invData);
+
+        int leadingArguments = 1; // address
+        MethodType type = inferredMethodType.insertParameterTypes(0, SegmentAllocator.class, MemorySegment.class);
+        if (capturedStateMask != 0) {
+            leadingArguments++;
+            type = type.insertParameterTypes(2, MemorySegment.class);
+        }
+        target = target.asCollector(1, Object[].class, inferredMethodType.parameterCount() + leadingArguments);
+        target = target.asType(type);
+        target = foldArguments(target, 1, SharedUtils.MH_CHECK_SYMBOL);
+        target = SharedUtils.swapArguments(target, 0, 1); // normalize parameter order
+
+        return target;
+    }
+
+    @Override
+    protected MemorySegment arrangeUpcall(MethodHandle target, MethodType targetType, FunctionDescriptor function,
+                                          SegmentScope scope) {
+        MemorySegment cif = makeCif(targetType, function, FFIABI.DEFAULT, scope);
+
+        UpcallData invData = new UpcallData(target, function.returnLayout().orElse(null),
+                function.argumentLayouts());
+
+        MethodHandle doUpcallMH = MethodHandles.insertArguments(MH_DO_UPCALL, 2, invData);
+        return LibFallback.createClosure(cif, doUpcallMH, scope);
+    }
+
+    private static MemorySegment makeCif(MethodType methodType, FunctionDescriptor function, FFIABI abi, SegmentScope scope) {
+        MemorySegment argTypes = MemorySegment.allocateNative(function.argumentLayouts().size() * ADDRESS.byteSize(), scope);
+        List<MemoryLayout> argLayouts = function.argumentLayouts();
+        for (int i = 0; i < argLayouts.size(); i++) {
+            MemoryLayout layout = argLayouts.get(i);
+            argTypes.setAtIndex(ADDRESS, i, FFIType.toFFIType(layout, abi, scope));
+        }
+
+        MemorySegment returnType = methodType.returnType() != void.class
+                ? FFIType.toFFIType(function.returnLayout().orElseThrow(), abi, scope)
+                : LibFallback.VOID_TYPE;
+        return LibFallback.prepCif(returnType, argLayouts.size(), argTypes, abi, scope);
+    }
+
+    private record DowncallData(MemorySegment cif, MemoryLayout returnLayout, List<MemoryLayout> argLayouts,
+                                int capturedStateMask) {}
+
+    private static Object doDowncall(SegmentAllocator returnAllocator, Object[] args, DowncallData invData) {
+        List<MemorySessionImpl> acquiredSessions = new ArrayList<>();
+        try (Arena arena = Arena.openConfined()) {
+            int argStart = 0;
+
+            MemorySegment target = (MemorySegment) args[argStart++];
+            MemorySessionImpl targetImpl = ((AbstractMemorySegmentImpl) target).sessionImpl();
+            targetImpl.acquire0();
+            acquiredSessions.add(targetImpl);
+
+            MemorySegment capturedState = null;
+            if (invData.capturedStateMask() != 0) {
+                capturedState = (MemorySegment) args[argStart++];
+                MemorySessionImpl capturedStateImpl = ((AbstractMemorySegmentImpl) capturedState).sessionImpl();
+                capturedStateImpl.acquire0();
+                acquiredSessions.add(capturedStateImpl);
+            }
+
+            List<MemoryLayout> argLayouts = invData.argLayouts();
+            MemorySegment argPtrs = arena.allocate(argLayouts.size() * ADDRESS.byteSize());
+            for (int i = 0; i < argLayouts.size(); i++) {
+                Object arg = args[argStart + i];
+                MemoryLayout layout = argLayouts.get(i);
+                MemorySegment argSeg = arena.allocate(layout);
+                writeValue(arg, layout, argSeg, addr -> {
+                    MemorySessionImpl sessionImpl = ((AbstractMemorySegmentImpl) addr).sessionImpl();
+                    sessionImpl.acquire0();
+                    acquiredSessions.add(sessionImpl);
+                });
+                argPtrs.setAtIndex(ADDRESS, i, argSeg);
+            }
+
+            MemorySegment retSeg = null;
+            if (invData.returnLayout() != null) {
+                retSeg = (invData.returnLayout() instanceof GroupLayout ? returnAllocator : arena).allocate(invData.returnLayout);
+            }
+
+            LibFallback.doDowncall(invData.cif, target, retSeg, argPtrs, capturedState, invData.capturedStateMask());
+
+            Reference.reachabilityFence(invData.cif());
+
+            return readValue(retSeg, invData.returnLayout());
+        } finally {
+            for (MemorySessionImpl session : acquiredSessions) {
+                session.release0();
+            }
+        }
+    }
+
+    private record UpcallData(MethodHandle target, MemoryLayout returnLayout, List<MemoryLayout> argLayouts) {}
+
+    private static void doUpcall(MemorySegment retPtr, MemorySegment argPtrs, UpcallData data) throws Throwable {
+        List<MemoryLayout> argLayouts = data.argLayouts();
+        int numArgs = argLayouts.size();
+        MemoryLayout retLayout = data.returnLayout();
+        try (Arena upcallArena = Arena.openConfined()) {
+            MemorySegment argsSeg = MemorySegment.ofAddress(argPtrs.address(), numArgs * ADDRESS.byteSize(), upcallArena.scope());
+            MemorySegment retSeg = retLayout != null
+                ? MemorySegment.ofAddress(retPtr.address(), retLayout.byteSize(), upcallArena.scope())
+                : null;
+
+            Object[] args = new Object[numArgs];
+            for (int i = 0; i < numArgs; i++) {
+                MemoryLayout argLayout = argLayouts.get(i);
+                MemorySegment argPtr = MemorySegment.ofAddress(argsSeg.getAtIndex(JAVA_LONG, i), argLayout.byteSize(), upcallArena.scope());
+
+                args[i] = readValue(argPtr, argLayout);
+            }
+
+            Object result = data.target().invokeWithArguments(args);
+
+            writeValue(result, data.returnLayout(), retSeg);
+        }
+    }
+
+    // where
+    private static void writeValue(Object arg, MemoryLayout layout, MemorySegment argSeg) {
+        writeValue(arg, layout, argSeg, addr -> {});
+    }
+
+    private static void writeValue(Object arg, MemoryLayout layout, MemorySegment argSeg,
+                                   Consumer<MemorySegment> acquireCallback) {
+        if (layout instanceof ValueLayout.OfBoolean bl) {
+            argSeg.set(bl, 0, (Boolean) arg);
+        } else if (layout instanceof ValueLayout.OfByte bl) {
+            argSeg.set(bl, 0, (Byte) arg);
+        } else if (layout instanceof ValueLayout.OfShort sl) {
+            argSeg.set(sl, 0, (Short) arg);
+        } else if (layout instanceof ValueLayout.OfChar cl) {
+            argSeg.set(cl, 0, (Character) arg);
+        } else if (layout instanceof ValueLayout.OfInt il) {
+            argSeg.set(il, 0, (Integer) arg);
+        } else if (layout instanceof ValueLayout.OfLong ll) {
+            argSeg.set(ll, 0, (Long) arg);
+        } else if (layout instanceof ValueLayout.OfFloat fl) {
+            argSeg.set(fl, 0, (Float) arg);
+        } else if (layout instanceof ValueLayout.OfDouble dl) {
+            argSeg.set(dl, 0, (Double) arg);
+        } else if (layout instanceof ValueLayout.OfAddress al) {
+            MemorySegment addrArg = (MemorySegment) arg;
+            acquireCallback.accept(addrArg);
+            argSeg.set(al, 0, addrArg);
+        } else if (layout instanceof GroupLayout) {
+            argSeg.copyFrom((MemorySegment) arg); // by-value struct
+        } else {
+            assert layout == null;
+        }
+    }
+
+    private static Object readValue(MemorySegment seg, MemoryLayout layout) {
+        if (layout instanceof ValueLayout.OfBoolean bl) {
+            return seg.get(bl, 0);
+        } else if (layout instanceof ValueLayout.OfByte bl) {
+            return seg.get(bl, 0);
+        } else if (layout instanceof ValueLayout.OfShort sl) {
+            return seg.get(sl, 0);
+        } else if (layout instanceof ValueLayout.OfChar cl) {
+            return seg.get(cl, 0);
+        } else if (layout instanceof ValueLayout.OfInt il) {
+            return seg.get(il, 0);
+        } else if (layout instanceof ValueLayout.OfLong ll) {
+            return seg.get(ll, 0);
+        } else if (layout instanceof ValueLayout.OfFloat fl) {
+            return seg.get(fl, 0);
+        } else if (layout instanceof ValueLayout.OfDouble dl) {
+            return seg.get(dl, 0);
+        } else if (layout instanceof ValueLayout.OfAddress al) {
+            return seg.get(al, 0);
+        } else if (layout instanceof GroupLayout) {
+            return seg;
+        }
+        assert layout == null;
+        return null;
+    }
+}
diff --git a/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/LibFallback.java b/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/LibFallback.java
new file mode 100644
index 00000000000..30405d4cc55
--- /dev/null
+++ b/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/LibFallback.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.internal.foreign.abi.fallback;
+
+import jdk.internal.foreign.abi.SharedUtils;
+
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.SegmentScope;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodType;
+
+class LibFallback {
+    static final boolean SUPPORTED = tryLoadLibrary();
+
+    private static boolean tryLoadLibrary() {
+        try {
+            System.loadLibrary("fallbackLinker");
+        } catch (UnsatisfiedLinkError ule) {
+            return false;
+        }
+        init();
+        return true;
+    }
+
+    static final int DEFAULT_ABI = ffi_default_abi();
+
+    static final MemorySegment UINT8_TYPE = MemorySegment.ofAddress(ffi_type_uint8());
+    static final MemorySegment SINT8_TYPE = MemorySegment.ofAddress(ffi_type_sint8());
+    static final MemorySegment UINT16_TYPE = MemorySegment.ofAddress(ffi_type_uint16());
+    static final MemorySegment SINT16_TYPE = MemorySegment.ofAddress(ffi_type_sint16());
+    static final MemorySegment SINT32_TYPE = MemorySegment.ofAddress(ffi_type_sint32());
+    static final MemorySegment SINT64_TYPE = MemorySegment.ofAddress(ffi_type_sint64());
+    static final MemorySegment FLOAT_TYPE = MemorySegment.ofAddress(ffi_type_float());
+    static final MemorySegment DOUBLE_TYPE = MemorySegment.ofAddress(ffi_type_double());
+    static final MemorySegment POINTER_TYPE = MemorySegment.ofAddress(ffi_type_pointer());
+
+    static final MemorySegment VOID_TYPE = MemorySegment.ofAddress(ffi_type_void());
+    static final short STRUCT_TAG = ffi_type_struct();
+
+    private static final long SIZEOF_CIF = sizeofCif();
+
+    private static final MethodType UPCALL_TARGET_TYPE = MethodType.methodType(void.class, MemorySegment.class, MemorySegment.class);
+
+    /**
+     * Do a libffi based downcall. This method wraps the {@code ffi_call} function
+     *
+     * @param cif a pointer to a {@code ffi_cif} struct
+     * @param target the address of the target function
+     * @param retPtr a pointer to a buffer into which the return value shall be written, or {@code null} if the target
+     *               function does not return a value
+     * @param argPtrs a pointer to an array of pointers, which each point to an argument value
+     * @param capturedState a pointer to a buffer into which captured state is written, or {@code null} if no state is
+     *                      to be captured
+     * @param capturedStateMask the bit mask indicating which state to capture
+     *
+     * @see jdk.internal.foreign.abi.CapturableState
+     */
+    static void doDowncall(MemorySegment cif, MemorySegment target, MemorySegment retPtr, MemorySegment argPtrs,
+                                  MemorySegment capturedState, int capturedStateMask) {
+            doDowncall(cif.address(), target.address(),
+                    retPtr == null ? 0 : retPtr.address(), argPtrs.address(),
+                    capturedState == null ? 0 : capturedState.address(), capturedStateMask);
+    }
+
+    /**
+     * Wrapper for {@code ffi_prep_cif}
+     *
+     * @param returnType a pointer to an @{code ffi_type} describing the return type
+     * @param numArgs the number of arguments
+     * @param paramTypes a pointer to an array of pointers, which each point to an {@code ffi_type} describing a
+     *                parameter type
+     * @param abi the abi to be used
+     * @param scope the scope into which to allocate the returned {@code ffi_cif} struct
+     * @return a pointer to a prepared {@code ffi_cif} struct
+     *
+     * @throws IllegalStateException if the call to {@code ffi_prep_cif} returns a non-zero status code
+     */
+    static MemorySegment prepCif(MemorySegment returnType, int numArgs, MemorySegment paramTypes, FFIABI abi,
+                                         SegmentScope scope) throws IllegalStateException {
+        MemorySegment cif = MemorySegment.allocateNative(SIZEOF_CIF, scope);
+        checkStatus(ffi_prep_cif(cif.address(), abi.value(), numArgs, returnType.address(), paramTypes.address()));
+        return cif;
+    }
+
+    /**
+     * Create an upcallStub-style closure. This method wraps the {@code ffi_closure_alloc}
+     * and {@code ffi_prep_closure_loc} functions.
+     * <p>
+     * The closure will end up calling into {@link #doUpcall(long, long, MethodHandle)}
+     * <p>
+     * The target method handle should have the type {@code (MemorySegment, MemorySegment) -> void}. The first
+     * argument is a pointer to the buffer into which the native return value should be written. The second argument
+     * is a pointer to an array of pointers, which each point to a native argument value.
+     *
+     * @param cif a pointer to a {@code ffi_cif} struct
+     * @param target a method handle that points to the target function
+     * @param scope the scope to which to attach the created upcall stub
+     * @return the created upcall stub
+     *
+     * @throws IllegalStateException if the call to {@code ffi_prep_closure_loc} returns a non-zero status code
+     * @throws IllegalArgumentException if {@code target} does not have the right type
+     */
+    static MemorySegment createClosure(MemorySegment cif, MethodHandle target, SegmentScope scope)
+            throws IllegalStateException, IllegalArgumentException {
+        if (target.type() != UPCALL_TARGET_TYPE) {
+            throw new IllegalArgumentException("Target handle has wrong type: " + target.type() + " != " + UPCALL_TARGET_TYPE);
+        }
+
+        long[] ptrs = new long[3];
+        checkStatus(createClosure(cif.address(), target, ptrs));
+        long closurePtr = ptrs[0];
+        long execPtr = ptrs[1];
+        long globalTarget = ptrs[2];
+
+        return MemorySegment.ofAddress(execPtr, 0, scope, () -> freeClosure(closurePtr, globalTarget));
+    }
+
+    // the target function for a closure call
+    private static void doUpcall(long retPtr, long argPtrs, MethodHandle target) {
+        try {
+            target.invokeExact(MemorySegment.ofAddress(retPtr), MemorySegment.ofAddress(argPtrs));
+        } catch (Throwable t) {
+            SharedUtils.handleUncaughtException(t);
+        }
+    }
+
+    /**
+     * Wrapper for {@code ffi_get_struct_offsets}
+     *
+     * @param structType a pointer to an {@code ffi_type} representing a struct
+     * @param offsetsOut a pointer to an array of {@code size_t}, with one element for each element of the struct.
+     *                   This is an 'out' parameter that will be filled in by this call
+     * @param abi the abi to be used
+     *
+     * @throws IllegalStateException if the call to {@code ffi_get_struct_offsets} returns a non-zero status code
+     */
+    static void getStructOffsets(MemorySegment structType, MemorySegment offsetsOut, FFIABI abi)
+            throws IllegalStateException  {
+        checkStatus(ffi_get_struct_offsets(abi.value(), structType.address(), offsetsOut.address()));
+    }
+
+    private static void checkStatus(int code) {
+        FFIStatus status = FFIStatus.of(code);
+        if (status != FFIStatus.FFI_OK) {
+            throw new IllegalStateException("libffi call failed with status: " + status);
+        }
+    }
+
+    private static native void init();
+
+    private static native long sizeofCif();
+
+    private static native int createClosure(long cif, Object userData, long[] ptrs);
+    private static native void freeClosure(long closureAddress, long globalTarget);
+    private static native void doDowncall(long cif, long fn, long rvalue, long avalues, long capturedState, int capturedStateMask);
+
+    private static native int ffi_prep_cif(long cif, int abi, int nargs, long rtype, long atypes);
+    private static native int ffi_get_struct_offsets(int abi, long type, long offsets);
+
+    private static native int ffi_default_abi();
+    private static native short ffi_type_struct();
+
+    private static native long ffi_type_void();
+    private static native long ffi_type_uint8();
+    private static native long ffi_type_sint8();
+    private static native long ffi_type_uint16();
+    private static native long ffi_type_sint16();
+    private static native long ffi_type_uint32();
+    private static native long ffi_type_sint32();
+    private static native long ffi_type_uint64();
+    private static native long ffi_type_sint64();
+    private static native long ffi_type_float();
+    private static native long ffi_type_double();
+    private static native long ffi_type_pointer();
+}
diff --git a/src/java.base/share/classes/jdk/internal/vm/ForeignLinkerSupport.java b/src/java.base/share/classes/jdk/internal/vm/ForeignLinkerSupport.java
new file mode 100644
index 00000000000..5310b251bbd
--- /dev/null
+++ b/src/java.base/share/classes/jdk/internal/vm/ForeignLinkerSupport.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.internal.vm;
+
+/**
+ * Defines a static method to test if the VM has support for the foreign java.lang.foreign.Linker.
+ */
+public class ForeignLinkerSupport {
+    private static final boolean SUPPORTED = isSupported0();
+
+    private ForeignLinkerSupport() {
+    }
+
+    /**
+     * Return true if the VM has support for the foreign Linker.
+     */
+    public static boolean isSupported() {
+        return SUPPORTED;
+    }
+
+    /**
+     * Ensures that VM has support for the foreign Linker.
+     * @throws UnsupportedOperationException if not supported
+     */
+    public static void ensureSupported() {
+        if (!isSupported()) {
+            throw new UnsupportedOperationException("VM does not support linker");
+        }
+    }
+
+    private static native boolean isSupported0();
+}
diff --git a/src/java.base/share/native/libfallbackLinker/fallbackLinker.c b/src/java.base/share/native/libfallbackLinker/fallbackLinker.c
new file mode 100644
index 00000000000..761045b1f21
--- /dev/null
+++ b/src/java.base/share/native/libfallbackLinker/fallbackLinker.c
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "jdk_internal_foreign_abi_fallback_LibFallback.h"
+
+#include <ffi.h>
+
+#include <errno.h>
+#ifdef _WIN64
+#include <Windows.h>
+#include <Winsock2.h>
+#endif
+
+#include "jlong.h"
+
+static JavaVM* VM;
+static jclass LibFallback_class;
+static jmethodID LibFallback_doUpcall_ID;
+static const char* LibFallback_doUpcall_sig = "(JJLjava/lang/invoke/MethodHandle;)V";
+
+JNIEXPORT void JNICALL
+Java_jdk_internal_foreign_abi_fallback_LibFallback_init(JNIEnv* env, jclass cls) {
+  (*env)->GetJavaVM(env, &VM);
+  LibFallback_class = (*env)->FindClass(env, "jdk/internal/foreign/abi/fallback/LibFallback");
+  LibFallback_doUpcall_ID = (*env)->GetStaticMethodID(env,
+    LibFallback_class, "doUpcall", LibFallback_doUpcall_sig);
+}
+
+JNIEXPORT jlong JNICALL
+Java_jdk_internal_foreign_abi_fallback_LibFallback_sizeofCif(JNIEnv* env, jclass cls) {
+  return sizeof(ffi_cif);
+}
+
+JNIEXPORT jint JNICALL
+Java_jdk_internal_foreign_abi_fallback_LibFallback_ffi_1prep_1cif(JNIEnv* env, jclass cls, jlong cif, jint abi, jint nargs, jlong rtype, jlong atypes) {
+  return ffi_prep_cif(jlong_to_ptr(cif), (ffi_abi) abi, (unsigned int) nargs, jlong_to_ptr(rtype), jlong_to_ptr(atypes));
+}
+JNIEXPORT jint JNICALL
+Java_jdk_internal_foreign_abi_fallback_LibFallback_ffi_1get_1struct_1offsets(JNIEnv* env, jclass cls, jint abi, jlong type, jlong offsets) {
+  return ffi_get_struct_offsets((ffi_abi) abi, jlong_to_ptr(type), jlong_to_ptr(offsets));
+}
+
+static void do_capture_state(int32_t* value_ptr, int captured_state_mask) {
+    // keep in synch with jdk.internal.foreign.abi.CapturableState
+  enum PreservableValues {
+    NONE = 0,
+    GET_LAST_ERROR = 1,
+    WSA_GET_LAST_ERROR = 1 << 1,
+    ERRNO = 1 << 2
+  };
+#ifdef _WIN64
+  if (captured_state_mask & GET_LAST_ERROR) {
+    *value_ptr = GetLastError();
+    value_ptr++;
+  }
+  if (captured_state_mask & WSA_GET_LAST_ERROR) {
+    *value_ptr = WSAGetLastError();
+    value_ptr++;
+  }
+#endif
+  if (captured_state_mask & ERRNO) {
+    *value_ptr = errno;
+  }
+}
+
+JNIEXPORT void JNICALL
+Java_jdk_internal_foreign_abi_fallback_LibFallback_doDowncall(JNIEnv* env, jclass cls, jlong cif, jlong fn, jlong rvalue, jlong avalues, jlong jcaptured_state, jint captured_state_mask) {
+  ffi_call(jlong_to_ptr(cif), jlong_to_ptr(fn), jlong_to_ptr(rvalue), jlong_to_ptr(avalues));
+
+  if (captured_state_mask != 0) {
+    int32_t* captured_state = jlong_to_ptr(jcaptured_state);
+    do_capture_state(captured_state, captured_state_mask);
+  }
+}
+
+static void do_upcall(ffi_cif* cif, void* ret, void** args, void* user_data) {
+  // attach thread
+  JNIEnv* env;
+  jint result = (*VM)->AttachCurrentThreadAsDaemon(VM, (void**) &env, NULL);
+
+  // call into doUpcall in LibFallback
+  jobject upcall_data = (jobject) user_data;
+  (*env)->CallStaticVoidMethod(env, LibFallback_class, LibFallback_doUpcall_ID,
+    ptr_to_jlong(ret), ptr_to_jlong(args), upcall_data);
+
+  // always detach for now
+  (*VM)->DetachCurrentThread(VM);
+}
+
+static void free_closure(JNIEnv* env, void* closure, jobject upcall_data) {
+  ffi_closure_free(closure);
+  (*env)->DeleteGlobalRef(env, upcall_data);
+}
+
+JNIEXPORT jint JNICALL
+Java_jdk_internal_foreign_abi_fallback_LibFallback_createClosure(JNIEnv* env, jclass cls, jlong cif, jobject upcall_data, jlongArray jptrs) {
+  void* code;
+  void* closure = ffi_closure_alloc(sizeof(ffi_closure), &code);
+
+  jobject global_upcall_data = (*env)->NewGlobalRef(env, upcall_data);
+
+  ffi_status status = ffi_prep_closure_loc(closure, jlong_to_ptr(cif), &do_upcall, (void*) global_upcall_data, code);
+
+  if (status != FFI_OK) {
+    free_closure(env,closure, global_upcall_data);
+    return status;
+  }
+
+  jlong* ptrs = (*env)->GetLongArrayElements(env, jptrs, NULL);
+  ptrs[0] = ptr_to_jlong(closure);
+  ptrs[1] = ptr_to_jlong(code);
+  ptrs[2] = ptr_to_jlong(global_upcall_data);
+  (*env)->ReleaseLongArrayElements(env, jptrs, ptrs, JNI_COMMIT);
+
+  return status;
+}
+
+JNIEXPORT void JNICALL
+Java_jdk_internal_foreign_abi_fallback_LibFallback_freeClosure(JNIEnv* env, jclass cls, jlong closure, jlong upcall_data) {
+  free_closure(env, jlong_to_ptr(closure), jlong_to_ptr(upcall_data));
+}
+
+JNIEXPORT jint JNICALL
+Java_jdk_internal_foreign_abi_fallback_LibFallback_ffi_1default_1abi(JNIEnv* env, jclass cls) {
+  return (jint) FFI_DEFAULT_ABI;
+}
+
+JNIEXPORT jshort JNICALL
+Java_jdk_internal_foreign_abi_fallback_LibFallback_ffi_1type_1struct(JNIEnv* env, jclass cls) {
+  return (jshort) FFI_TYPE_STRUCT;
+}
+
+JNIEXPORT jlong JNICALL
+Java_jdk_internal_foreign_abi_fallback_LibFallback_ffi_1type_1void(JNIEnv* env, jclass cls) {
+  return ptr_to_jlong(&ffi_type_void);
+}
+
+JNIEXPORT jlong JNICALL
+Java_jdk_internal_foreign_abi_fallback_LibFallback_ffi_1type_1uint8(JNIEnv* env, jclass cls) {
+  return ptr_to_jlong(&ffi_type_uint8);
+}
+JNIEXPORT jlong JNICALL
+Java_jdk_internal_foreign_abi_fallback_LibFallback_ffi_1type_1sint8(JNIEnv* env, jclass cls) {
+  return ptr_to_jlong(&ffi_type_sint8);
+}
+JNIEXPORT jlong JNICALL
+Java_jdk_internal_foreign_abi_fallback_LibFallback_ffi_1type_1uint16(JNIEnv* env, jclass cls) {
+  return ptr_to_jlong(&ffi_type_uint16);
+}
+JNIEXPORT jlong JNICALL
+Java_jdk_internal_foreign_abi_fallback_LibFallback_ffi_1type_1sint16(JNIEnv* env, jclass cls) {
+  return ptr_to_jlong(&ffi_type_sint16);
+}
+JNIEXPORT jlong JNICALL
+Java_jdk_internal_foreign_abi_fallback_LibFallback_ffi_1type_1uint32(JNIEnv* env, jclass cls) {
+  return ptr_to_jlong(&ffi_type_uint32);
+}
+JNIEXPORT jlong JNICALL
+Java_jdk_internal_foreign_abi_fallback_LibFallback_ffi_1type_1sint32(JNIEnv* env, jclass cls) {
+  return ptr_to_jlong(&ffi_type_sint32);
+}
+JNIEXPORT jlong JNICALL
+Java_jdk_internal_foreign_abi_fallback_LibFallback_ffi_1type_1uint64(JNIEnv* env, jclass cls) {
+  return ptr_to_jlong(&ffi_type_uint64);
+}
+JNIEXPORT jlong JNICALL
+Java_jdk_internal_foreign_abi_fallback_LibFallback_ffi_1type_1sint64(JNIEnv* env, jclass cls) {
+  return ptr_to_jlong(&ffi_type_sint64);
+}
+JNIEXPORT jlong JNICALL
+Java_jdk_internal_foreign_abi_fallback_LibFallback_ffi_1type_1float(JNIEnv* env, jclass cls) {
+  return ptr_to_jlong(&ffi_type_float);
+}
+JNIEXPORT jlong JNICALL
+Java_jdk_internal_foreign_abi_fallback_LibFallback_ffi_1type_1double(JNIEnv* env, jclass cls) {
+  return ptr_to_jlong(&ffi_type_double);
+}
+JNIEXPORT jlong JNICALL
+Java_jdk_internal_foreign_abi_fallback_LibFallback_ffi_1type_1pointer(JNIEnv* env, jclass cls) {
+  return ptr_to_jlong(&ffi_type_pointer);
+}
diff --git a/src/java.base/share/native/libjava/ForeignLinkerSupport.c b/src/java.base/share/native/libjava/ForeignLinkerSupport.c
new file mode 100644
index 00000000000..9d989c09d3b
--- /dev/null
+++ b/src/java.base/share/native/libjava/ForeignLinkerSupport.c
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "jni.h"
+#include "jvm.h"
+
+#include "jdk_internal_vm_ForeignLinkerSupport.h"
+
+JNIEXPORT jboolean JNICALL
+Java_jdk_internal_vm_ForeignLinkerSupport_isSupported0(JNIEnv *env, jclass cls) {
+    return JVM_IsForeignLinkerSupported();
+}
diff --git a/test/jdk/java/foreign/TestUnsupportedLinker.java b/test/jdk/java/foreign/TestUnsupportedLinker.java
index 286aef17b63..7ebd83a8a99 100644
--- a/test/jdk/java/foreign/TestUnsupportedLinker.java
+++ b/test/jdk/java/foreign/TestUnsupportedLinker.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -25,7 +25,7 @@
  * @test
  * @enablePreview
  *
- * @run testng/othervm -Dos.arch=unknown -Dos.name=unknown --enable-native-access=ALL-UNNAMED TestUnsupportedLinker
+ * @run testng/othervm -Djdk.internal.foreign.CABI=UNSUPPORTED --enable-native-access=ALL-UNNAMED TestUnsupportedLinker
  */
 
 import java.lang.foreign.Linker;