diff a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/abi/Binding.java b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/abi/Binding.java
--- a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/abi/Binding.java
+++ b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/abi/Binding.java
@@ -20,16 +20,20 @@
  * or visit www.oracle.com if you need additional information or have any
  * questions.
  */
 package jdk.internal.foreign.abi;
 
+import jdk.incubator.foreign.MemoryHandles;
 import jdk.incubator.foreign.MemoryLayout;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 
+import java.lang.invoke.VarHandle;
+import java.nio.ByteOrder;
+
 /**
  * The binding operators defined in the Binding class can be combined into argument and return value processing 'recipes'.
  *
  * The binding operators are interpreted using a stack-base interpreter. Operators can either consume operands from the
  * stack, or push them onto the stack.
@@ -362,10 +366,14 @@
 
         public Class<?> type() {
             return type;
         }
 
+        public VarHandle varHandle() {
+            return MemoryHandles.withOffset(MemoryHandles.varHandle(type, ByteOrder.nativeOrder()), offset);
+        }
+
         @Override
         public String toString() {
             return "Dereference{" +
                     "tag=" + tag() +
                     ", offset=" + offset +
diff a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/abi/BindingInterpreter.java b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/abi/BindingInterpreter.java
--- a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/abi/BindingInterpreter.java
+++ b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/abi/BindingInterpreter.java
@@ -21,48 +21,35 @@
  *  questions.
  */
 package jdk.internal.foreign.abi;
 
 import jdk.incubator.foreign.MemoryAddress;
-import jdk.incubator.foreign.MemoryHandles;
 import jdk.incubator.foreign.MemorySegment;
 import jdk.internal.foreign.MemoryAddressImpl;
 import jdk.internal.foreign.Utils;
 
-import java.lang.invoke.VarHandle;
-import java.nio.ByteOrder;
 import java.util.ArrayDeque;
 import java.util.Deque;
 import java.util.List;
-import java.util.function.Function;
 
 public class BindingInterpreter {
-    private static final VarHandle VH_BYTE = MemoryHandles.varHandle(byte.class, ByteOrder.nativeOrder());
-    private static final VarHandle VH_CHAR = MemoryHandles.varHandle(char.class, ByteOrder.nativeOrder());
-    private static final VarHandle VH_SHORT = MemoryHandles.varHandle(short.class, ByteOrder.nativeOrder());
-    private static final VarHandle VH_INT = MemoryHandles.varHandle(int.class, ByteOrder.nativeOrder());
-    private static final VarHandle VH_LONG = MemoryHandles.varHandle(long.class, ByteOrder.nativeOrder());
-    private static final VarHandle VH_FLOAT = MemoryHandles.varHandle(float.class, ByteOrder.nativeOrder());
-    private static final VarHandle VH_DOUBLE = MemoryHandles.varHandle(double.class, ByteOrder.nativeOrder());
 
-    static void unbox(Object arg, List<Binding> bindings, Function<VMStorage,
-            MemoryAddress> ptrFunction, List<? super MemorySegment> buffers) {
+    static void unbox(Object arg, List<Binding> bindings, StoreFunc storeFunc, List<? super MemorySegment> buffers) {
         Deque<Object> stack = new ArrayDeque<>();
         stack.push(arg);
         for (Binding b : bindings) {
             switch (b.tag()) {
                 case MOVE -> {
                     Binding.Move binding = (Binding.Move) b;
-                    MemoryAddress ptr = ptrFunction.apply(binding.storage());
-                    writeOverSized(ptr, binding.type(), stack.pop());
+                    storeFunc.store(binding.storage(), binding.type(), stack.pop());
                 }
                 case DEREFERENCE -> {
                     Binding.Dereference deref = (Binding.Dereference) b;
                     MemorySegment operand = (MemorySegment) stack.pop();
                     MemoryAddress baseAddress = operand.baseAddress();
                     MemoryAddress readAddress = baseAddress.addOffset(deref.offset());
-                    stack.push(read(readAddress, deref.type()));
+                    stack.push(SharedUtils.read(readAddress, deref.type()));
                 }
                 case COPY_BUFFER -> {
                     Binding.Copy binding = (Binding.Copy) b;
                     MemorySegment operand = (MemorySegment) stack.pop();
                     assert operand.byteSize() == binding.size() : "operand size mismatch";
@@ -82,26 +69,25 @@
                 default -> throw new IllegalArgumentException("Unsupported tag: " + b);
             }
         }
     }
 
-    static Object box(List<Binding> bindings, Function<VMStorage, MemoryAddress> ptrFunction) {
+    static Object box(List<Binding> bindings, LoadFunc loadFunc) {
         Deque<Object> stack = new ArrayDeque<>();
         for (Binding b : bindings) {
             switch (b.tag()) {
                 case MOVE -> {
                     Binding.Move binding = (Binding.Move) b;
-                    MemoryAddress ptr = ptrFunction.apply(binding.storage());
-                    stack.push(read(ptr, binding.type()));
+                    stack.push(loadFunc.load(binding.storage(), binding.type()));
                 }
                 case DEREFERENCE -> {
                     Binding.Dereference binding = (Binding.Dereference) b;
                     Object value = stack.pop();
                     MemorySegment operand = (MemorySegment) stack.pop();
                     MemoryAddress baseAddress = operand.baseAddress();
                     MemoryAddress writeAddress = baseAddress.addOffset(binding.offset());
-                    write(writeAddress, binding.type(), value);
+                    SharedUtils.write(writeAddress, binding.type(), value);
                 }
                 case COPY_BUFFER -> {
                     Binding.Copy binding = (Binding.Copy) b;
                     MemoryAddress operand = (MemoryAddress) stack.pop();
                     operand = MemoryAddressImpl.ofLongUnchecked(operand.toRawLongValue(), binding.size());
@@ -124,66 +110,13 @@
         }
 
        return stack.pop();
     }
 
-    private static void writeOverSized(MemoryAddress ptr, Class<?> type, Object o) {
-        // use VH_LONG for integers to zero out the whole register in the process
-        if (type == long.class) {
-            VH_LONG.set(ptr, (long) o);
-        } else if (type == int.class) {
-            VH_LONG.set(ptr, (long) (int) o);
-        } else if (type == short.class) {
-            VH_LONG.set(ptr, (long) (short) o);
-        } else if (type == char.class) {
-            VH_LONG.set(ptr, (long) (char) o);
-        } else if (type == byte.class) {
-            VH_LONG.set(ptr, (long) (byte) o);
-        } else if (type == float.class) {
-            VH_FLOAT.set(ptr, (float) o);
-        } else if (type == double.class) {
-            VH_DOUBLE.set(ptr, (double) o);
-        } else {
-            throw new IllegalArgumentException("Unsupported carrier: " + type);
-        }
+    interface StoreFunc {
+        void store(VMStorage storage, Class<?> type, Object o);
     }
 
-    private static void write(MemoryAddress ptr, Class<?> type, Object o) {
-        if (type == long.class) {
-            VH_LONG.set(ptr, (long) o);
-        } else if (type == int.class) {
-            VH_INT.set(ptr, (int) o);
-        } else if (type == short.class) {
-            VH_SHORT.set(ptr, (short) o);
-        } else if (type == char.class) {
-            VH_CHAR.set(ptr, (char) o);
-        } else if (type == byte.class) {
-            VH_BYTE.set(ptr, (byte) o);
-        } else if (type == float.class) {
-            VH_FLOAT.set(ptr, (float) o);
-        } else if (type == double.class) {
-            VH_DOUBLE.set(ptr, (double) o);
-        } else {
-            throw new IllegalArgumentException("Unsupported carrier: " + type);
-        }
-    }
-
-    private static Object read(MemoryAddress ptr, Class<?> type) {
-        if (type == long.class) {
-            return (long) VH_LONG.get(ptr);
-        } else if (type == int.class) {
-            return (int) VH_INT.get(ptr);
-        } else if (type == short.class) {
-            return (short) VH_SHORT.get(ptr);
-        } else if (type == char.class) {
-            return (char) VH_CHAR.get(ptr);
-        } else if (type == byte.class) {
-            return (byte) VH_BYTE.get(ptr);
-        } else if (type == float.class) {
-            return (float) VH_FLOAT.get(ptr);
-        } else if (type == double.class) {
-            return (double) VH_DOUBLE.get(ptr);
-        } else {
-            throw new IllegalArgumentException("Unsupported carrier: " + type);
-        }
+    interface LoadFunc {
+        Object load(VMStorage storage, Class<?> type);
     }
 }
diff a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/abi/CallingSequence.java b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/abi/CallingSequence.java
--- a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/abi/CallingSequence.java
+++ b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/abi/CallingSequence.java
@@ -24,10 +24,11 @@
 
 import jdk.incubator.foreign.FunctionDescriptor;
 
 import java.lang.invoke.MethodType;
 import java.util.List;
+import java.util.stream.IntStream;
 import java.util.stream.Stream;
 
 public class CallingSequence {
     private final MethodType mt;
     private final FunctionDescriptor desc;
@@ -41,25 +42,36 @@
         this.desc = desc;
         this.returnBindings = returnBindings;
         this.argumentBindings = argumentBindings;
     }
 
-    public Stream<Binding.Move> moveBindings() {
+    public Stream<Binding.Move> argMoveBindings() {
         return argumentBindings.stream()
                 .flatMap(List::stream)
                 .filter(Binding.Move.class::isInstance)
                 .map(Binding.Move.class::cast);
     }
 
+    public Stream<Binding.Move> retMoveBindings() {
+        return returnBindings()
+            .stream()
+            .filter(Binding.Move.class::isInstance)
+            .map(Binding.Move.class::cast);
+    }
+
     public int argumentCount() {
         return argumentBindings.size();
     }
 
     public List<Binding> argumentBindings(int i) {
         return argumentBindings.get(i);
     }
 
+    public Stream<Binding> argumentBindings() {
+        return argumentBindings.stream().flatMap(List::stream);
+    }
+
     public List<Binding> returnBindings() {
         return returnBindings;
     }
 
     public String asString() {
diff a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/abi/ProgrammableInvoker.java b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/abi/ProgrammableInvoker.java
--- a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/abi/ProgrammableInvoker.java
+++ b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/abi/ProgrammableInvoker.java
@@ -20,110 +20,334 @@
  * or visit www.oracle.com if you need additional information or have any
  * questions.
  */
 package jdk.internal.foreign.abi;
 
-import jdk.incubator.foreign.FunctionDescriptor;
 import jdk.incubator.foreign.MemoryAddress;
 import jdk.incubator.foreign.MemoryHandles;
 import jdk.incubator.foreign.MemorySegment;
+import jdk.incubator.foreign.NativeAllocationScope;
+import jdk.internal.access.JavaLangInvokeAccess;
+import jdk.internal.access.SharedSecrets;
 import jdk.internal.foreign.MemoryAddressImpl;
+import jdk.internal.foreign.Utils;
 
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodType;
 import java.lang.invoke.VarHandle;
 import java.nio.ByteOrder;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 
+import static java.lang.invoke.MethodHandles.collectArguments;
+import static java.lang.invoke.MethodHandles.dropArguments;
+import static java.lang.invoke.MethodHandles.empty;
+import static java.lang.invoke.MethodHandles.filterArguments;
+import static java.lang.invoke.MethodHandles.identity;
+import static java.lang.invoke.MethodHandles.insertArguments;
+import static java.lang.invoke.MethodHandles.permuteArguments;
+import static java.lang.invoke.MethodHandles.tryFinally;
+import static java.lang.invoke.MethodType.methodType;
 import static sun.security.action.GetBooleanAction.privilegedGetProperty;
 
 /**
  * This class implements native call invocation through a so called 'universal adapter'. A universal adapter takes
  * an array of longs together with a call 'recipe', which is used to move the arguments in the right places as
  * expected by the system ABI.
  */
 public class ProgrammableInvoker {
     private static final boolean DEBUG =
         privilegedGetProperty("jdk.internal.foreign.ProgrammableInvoker.DEBUG");
+    private static final boolean NO_SPEC =
+        privilegedGetProperty("jdk.internal.foreign.ProgrammableInvoker.NO_SPEC");
 
     private static final VarHandle VH_LONG = MemoryHandles.varHandle(long.class, ByteOrder.nativeOrder());
 
-    // Unbound MH for the invoke() method
-    private static final MethodHandle INVOKE_MH;
+    private static final MethodHandle MH_INVOKE_MOVES;
+    private static final MethodHandle MH_INVOKE_INTERP_BINDINGS;
+
+    private static final MethodHandle MH_UNBOX_ADDRESS;
+    private static final MethodHandle MH_BOX_ADDRESS;
+    private static final MethodHandle MH_BASE_ADDRESS;
+    private static final MethodHandle MH_COPY_BUFFER;
+    private static final MethodHandle MH_MAKE_ALLOCATOR;
+    private static final MethodHandle MH_CLOSE_ALLOCATOR;
+    private static final MethodHandle MH_ALLOCATE_BUFFER;
 
     private static final Map<ABIDescriptor, Long> adapterStubs = new ConcurrentHashMap<>();
 
     static {
         try {
-            INVOKE_MH = MethodHandles.lookup().findVirtual(ProgrammableInvoker.class, "invoke", MethodType.methodType(Object.class, Object[].class));
+            MethodHandles.Lookup lookup = MethodHandles.lookup();
+            MH_INVOKE_MOVES = lookup.findVirtual(ProgrammableInvoker.class, "invokeMoves",
+                    methodType(Object.class, Object[].class, Binding.Move[].class, Binding.Move[].class));
+            MH_INVOKE_INTERP_BINDINGS = lookup.findVirtual(ProgrammableInvoker.class, "invokeInterpBindings",
+                    methodType(Object.class, Object[].class, MethodHandle.class, Map.class, Map.class));
+            MH_UNBOX_ADDRESS = lookup.findStatic(ProgrammableInvoker.class, "toRawLongValue",
+                    methodType(long.class, MemoryAddress.class));
+            MH_BOX_ADDRESS = lookup.findStatic(ProgrammableInvoker.class, "ofLong",
+                    methodType(MemoryAddress.class, long.class));
+            MH_BASE_ADDRESS = lookup.findVirtual(MemorySegment.class, "baseAddress",
+                    methodType(MemoryAddress.class));
+            MH_COPY_BUFFER = lookup.findStatic(ProgrammableInvoker.class, "copyBuffer",
+                    methodType(MemorySegment.class, MemorySegment.class, long.class, long.class, NativeAllocationScope.class));
+            MH_MAKE_ALLOCATOR = lookup.findStatic(NativeAllocationScope.class, "boundedScope",
+                    methodType(NativeAllocationScope.class, long.class));
+            MH_CLOSE_ALLOCATOR = lookup.findVirtual(NativeAllocationScope.class, "close",
+                    methodType(void.class));
+            MH_ALLOCATE_BUFFER = lookup.findStatic(MemorySegment.class, "allocateNative",
+                    methodType(MemorySegment.class, long.class, long.class));
         } catch (ReflectiveOperationException e) {
             throw new RuntimeException(e);
         }
     }
 
     private final ABIDescriptor abi;
     private final BufferLayout layout;
     private final long stackArgsBytes;
 
-    private final MethodType type;
-    private final FunctionDescriptor function;
     private final CallingSequence callingSequence;
 
     private final MemoryAddress addr;
     private final long stubAddress;
 
+    private final long bufferCopySize;
+
     public ProgrammableInvoker(ABIDescriptor abi, MemoryAddress addr, CallingSequence callingSequence) {
         this.abi = abi;
         this.layout = BufferLayout.of(abi);
         this.stubAddress = adapterStubs.computeIfAbsent(abi, key -> generateAdapter(key, layout));
 
         this.addr = addr;
         this.callingSequence = callingSequence;
-        this.type = callingSequence.methodType();
-        this.function = callingSequence.functionDesc();
 
-        this.stackArgsBytes = callingSequence.moveBindings()
+        this.stackArgsBytes = callingSequence.argMoveBindings()
                 .map(Binding.Move::storage)
                 .filter(s -> abi.arch.isStackType(s.type()))
                 .count()
                 * abi.arch.typeSize(abi.arch.stackType());
+
+        this.bufferCopySize = bufferCopySize(callingSequence);
+    }
+
+    private static long bufferCopySize(CallingSequence callingSequence) {
+        // FIXME: > 16 bytes alignment might need extra space since the
+        // starting address of the allocator might be un-aligned.
+        long size = 0;
+        for (int i = 0; i < callingSequence.argumentCount(); i++) {
+            List<Binding> bindings = callingSequence.argumentBindings(i);
+            for (Binding b : bindings) {
+                if (b instanceof Binding.Copy) {
+                    Binding.Copy c = (Binding.Copy) b;
+                    size = Utils.alignUp(size, c.alignment());
+                    size += c.size();
+                }
+            }
+        }
+        return size;
     }
 
     public MethodHandle getBoundMethodHandle() {
-        return INVOKE_MH.bindTo(this).asCollector(Object[].class, type.parameterCount()).asType(type);
+        Binding.Move[] argMoves = callingSequence.argMoveBindings().toArray(Binding.Move[]::new);
+        Class<?>[] argMoveTypes = Arrays.stream(argMoves).map(Binding.Move::type).toArray(Class<?>[]::new);
+
+        Binding.Move[] retMoves = callingSequence.retMoveBindings().toArray(Binding.Move[]::new);
+        Class<?> returnType = retMoves.length == 0
+                ? void.class
+                : retMoves.length == 1
+                    ? retMoves[0].type()
+                    : Object[].class;
+
+        MethodType intrinsicType = methodType(returnType, argMoveTypes);
+
+        MethodHandle handle = insertArguments(MH_INVOKE_MOVES.bindTo(this), 1, argMoves, retMoves)
+                                            .asCollector(Object[].class, intrinsicType.parameterCount())
+                                            .asType(intrinsicType);
+
+        if (NO_SPEC || retMoves.length > 1) {
+            Map<VMStorage, Integer> argIndexMap = indexMap(argMoves);
+            Map<VMStorage, Integer> retIndexMap = indexMap(retMoves);
+
+            handle = insertArguments(MH_INVOKE_INTERP_BINDINGS.bindTo(this), 1, handle, argIndexMap, retIndexMap);
+            handle = handle.asCollector(Object[].class, callingSequence.methodType().parameterCount())
+                                             .asType(callingSequence.methodType());
+         } else {
+             handle = specialize(handle);
+         }
+
+        return handle;
     }
 
-    Object invoke(Object[] args) {
-        List<MemorySegment> tempBuffers = new ArrayList<>();
+    private MethodHandle specialize(MethodHandle intrinsicHandle) {
+        MethodType type = callingSequence.methodType();
+        MethodType intrinsicType = intrinsicHandle.type();
+
+        int insertPos = -1;
+        if (bufferCopySize > 0) {
+            intrinsicHandle = dropArguments(intrinsicHandle, 0, NativeAllocationScope.class);
+            insertPos++;
+        }
+        for (int i = 0; i < type.parameterCount(); i++) {
+            List<Binding> bindings = callingSequence.argumentBindings(i);
+            insertPos += bindings.stream().filter(Binding.Move.class::isInstance).count() + 1;
+            // We interpret the bindings in reverse since we have to construct a MethodHandle from the bottom up
+            for (int j = bindings.size() - 1; j >= 0; j--) {
+                Binding binding = bindings.get(j);
+                switch (binding.tag()) {
+                    case MOVE -> insertPos--; // handled by fallback
+                    case DUP ->
+                        intrinsicHandle = mergeArguments(intrinsicHandle, insertPos, insertPos + 1);
+                    case CONVERT_ADDRESS ->
+                        intrinsicHandle = filterArguments(intrinsicHandle, insertPos, MH_UNBOX_ADDRESS);
+                    case BASE_ADDRESS ->
+                        intrinsicHandle = filterArguments(intrinsicHandle, insertPos, MH_BASE_ADDRESS);
+                    case DEREFERENCE -> {
+                        Binding.Dereference deref = (Binding.Dereference) binding;
+                        MethodHandle filter = filterArguments(
+                            deref.varHandle()
+                            .toMethodHandle(VarHandle.AccessMode.GET)
+                            .asType(methodType(deref.type(), MemoryAddress.class)), 0, MH_BASE_ADDRESS);
+                        intrinsicHandle = filterArguments(intrinsicHandle, insertPos, filter);
+                    }
+                    case COPY_BUFFER -> {
+                        Binding.Copy copy = (Binding.Copy) binding;
+                        MethodHandle filter = insertArguments(MH_COPY_BUFFER, 1, copy.size(), copy.alignment());
+                        intrinsicHandle = collectArguments(intrinsicHandle, insertPos, filter);
+                        intrinsicHandle = mergeArguments(intrinsicHandle, 0, insertPos + 1);
+                    }
+                    default -> throw new IllegalArgumentException("Illegal tag: " + binding.tag());
+                }
+            }
+        }
+
+        if (type.returnType() != void.class) {
+            MethodHandle returnFilter = identity(type.returnType());
+            List<Binding> bindings = callingSequence.returnBindings();
+            for (int j = bindings.size() - 1; j >= 0; j--) {
+                Binding binding = bindings.get(j);
+                switch (binding.tag()) {
+                    case MOVE -> { /* handled by fallback */ }
+                    case CONVERT_ADDRESS ->
+                        returnFilter = filterArguments(returnFilter, 0, MH_BOX_ADDRESS);
+                    case DEREFERENCE -> {
+                        Binding.Dereference deref = (Binding.Dereference) binding;
+                        MethodHandle setter = deref.varHandle().toMethodHandle(VarHandle.AccessMode.SET);
+                        setter = filterArguments(
+                            setter.asType(methodType(void.class, MemoryAddress.class, deref.type())),
+                            0, MH_BASE_ADDRESS);
+                        returnFilter = collectArguments(returnFilter, returnFilter.type().parameterCount(), setter);
+                    }
+                    case DUP ->
+                        // FIXME assumes shape like: (MS, ..., MS, T) R, is that good enough?
+                        returnFilter = mergeArguments(returnFilter, 0, returnFilter.type().parameterCount() - 2);
+                    case ALLOC_BUFFER -> {
+                        Binding.Allocate alloc = (Binding.Allocate) binding;
+                        returnFilter = collectArguments(returnFilter, 0,
+                                insertArguments(MH_ALLOCATE_BUFFER, 0, alloc.size(), alloc.alignment()));
+                    }
+                    default ->
+                        throw new IllegalArgumentException("Illegal tag: " + binding.tag());
+                }
+            }
+
+            intrinsicHandle = MethodHandles.filterReturnValue(intrinsicHandle, returnFilter);
+        }
+
+        if (bufferCopySize > 0) {
+            MethodHandle closer = intrinsicType.returnType() == void.class
+                  // (Throwable, NativeAllocationScope) -> void
+                ? collectArguments(empty(methodType(void.class, Throwable.class)), 1, MH_CLOSE_ALLOCATOR)
+                  // (Throwable, V, NativeAllocationScope) -> V
+                : collectArguments(dropArguments(identity(intrinsicHandle.type().returnType()), 0, Throwable.class),
+                                   2, MH_CLOSE_ALLOCATOR);
+            intrinsicHandle = tryFinally(intrinsicHandle, closer);
+            intrinsicHandle = collectArguments(intrinsicHandle, 0, insertArguments(MH_MAKE_ALLOCATOR, 0, bufferCopySize));
+        }
+        return intrinsicHandle;
+    }
+
+    private static MethodHandle mergeArguments(MethodHandle mh, int sourceIndex, int destIndex) {
+        MethodType oldType = mh.type();
+        Class<?> sourceType = oldType.parameterType(sourceIndex);
+        Class<?> destType = oldType.parameterType(destIndex);
+        if (sourceType != destType) {
+            // TODO meet?
+            throw new IllegalArgumentException("Parameter types differ: " + sourceType + " != " + destType);
+        }
+        MethodType newType = oldType.dropParameterTypes(destIndex, destIndex + 1);
+        int[] reorder = new int[oldType.parameterCount()];
+        assert destIndex > sourceIndex;
+        for (int i = 0, index = 0; i < reorder.length; i++) {
+            if (i != destIndex) {
+                reorder[i] = index++;
+            } else {
+                reorder[i] = sourceIndex;
+            }
+        }
+        return permuteArguments(mh, newType, reorder);
+    }
+
+    private static MemorySegment copyBuffer(MemorySegment operand, long size, long alignment,
+                                    NativeAllocationScope allocator) {
+        assert operand.byteSize() == size : "operand size mismatch";
+        MemorySegment copy = allocator.allocate(size, alignment).segment();
+        copy.copyFrom(operand.asSlice(0, size));
+        return copy;
+    }
+
+    private static long toRawLongValue(MemoryAddress address) {
+        return address.toRawLongValue(); // Workaround for JDK-8239083
+    }
+
+    private static MemoryAddress ofLong(long address) {
+        return MemoryAddress.ofLong(address); // Workaround for JDK-8239083
+    }
+
+    private Map<VMStorage, Integer> indexMap(Binding.Move[] moves) {
+        return IntStream.range(0, moves.length)
+                        .boxed()
+                        .collect(Collectors.toMap(i -> moves[i].storage(), i -> i));
+    }
+
+    /**
+     * Does a native invocation by moving primitive values from the arg array into an intermediate buffer
+     * and calling the assembly stub that forwards arguments from the buffer to the target function
+     *
+     * @param args an array of primitive values to be copied in to the buffer
+     * @param argBindings Binding.Move values describing how arguments should be copied
+     * @param returnBindings Binding.Move values describing how return values should be copied
+     * @return null, a single primitive value, or an Object[] of primitive values
+     */
+    Object invokeMoves(Object[] args, Binding.Move[] argBindings, Binding.Move[] returnBindings) {
+        MemorySegment stackArgsSeg = null;
         try (MemorySegment argBuffer = MemorySegment.allocateNative(layout.size, 64)) {
             MemoryAddress argsPtr = argBuffer.baseAddress();
             MemoryAddress stackArgs;
             if (stackArgsBytes > 0) {
-                MemorySegment stackArgsSeg = MemorySegment.allocateNative(stackArgsBytes, 8);
-                tempBuffers.add(stackArgsSeg);
+                stackArgsSeg = MemorySegment.allocateNative(stackArgsBytes, 8);
                 stackArgs = stackArgsSeg.baseAddress();
             } else {
                 stackArgs = MemoryAddressImpl.NULL;
             }
 
             VH_LONG.set(argsPtr.addOffset(layout.arguments_next_pc), addr.toRawLongValue());
             VH_LONG.set(argsPtr.addOffset(layout.stack_args_bytes), stackArgsBytes);
             VH_LONG.set(argsPtr.addOffset(layout.stack_args), stackArgs.toRawLongValue());
 
-            for (int i = 0; i < args.length; i++) {
-                Object arg = args[i];
-                jdk.internal.foreign.abi.BindingInterpreter.unbox(arg, callingSequence.argumentBindings(i),
-                        s -> {
-                            if (abi.arch.isStackType(s.type())) {
-                                return stackArgs.addOffset(s.index() * abi.arch.typeSize(abi.arch.stackType()));
-                            }
-                            return argsPtr.addOffset(layout.argOffset(s));
-                        }, tempBuffers);
+            for (int i = 0; i < argBindings.length; i++) {
+                Binding.Move binding = argBindings[i];
+                VMStorage storage = binding.storage();
+                MemoryAddress ptr = abi.arch.isStackType(storage.type())
+                    ? stackArgs.addOffset(storage.index() * abi.arch.typeSize(abi.arch.stackType()))
+                    : argsPtr.addOffset(layout.argOffset(storage));
+                SharedUtils.writeOverSized(ptr, binding.type(), args[i]);
             }
 
             if (DEBUG) {
                 System.err.println("Buffer state before:");
                 layout.dump(abi.arch, argsPtr, System.err);
@@ -134,14 +358,60 @@
             if (DEBUG) {
                 System.err.println("Buffer state after:");
                 layout.dump(abi.arch, argsPtr, System.err);
             }
 
-            return function.returnLayout().isEmpty()
-                    ? null
-                    : jdk.internal.foreign.abi.BindingInterpreter.box(callingSequence.returnBindings(),
-                    s -> argsPtr.addOffset(layout.retOffset(s))); // buffers are leaked
+            if (returnBindings.length == 0) {
+                return null;
+            } else if (returnBindings.length == 1) {
+                Binding.Move move = returnBindings[0];
+                VMStorage storage = move.storage();
+                return SharedUtils.read(argsPtr.addOffset(layout.retOffset(storage)), move.type());
+            } else { // length > 1
+                Object[] returns = new Object[returnBindings.length];
+                for (int i = 0; i < returnBindings.length; i++) {
+                    Binding.Move move = returnBindings[i];
+                    VMStorage storage = move.storage();
+                    returns[i] = SharedUtils.read(argsPtr.addOffset(layout.retOffset(storage)), move.type());
+                }
+                return returns;
+            }
+        } finally {
+            if (stackArgsSeg != null) {
+                stackArgsSeg.close();
+            }
+        }
+    }
+
+    Object invokeInterpBindings(Object[] args, MethodHandle leaf,
+                                Map<VMStorage, Integer> argIndexMap,
+                                Map<VMStorage, Integer> retIndexMap) throws Throwable {
+        List<MemorySegment> tempBuffers = new ArrayList<>();
+        try {
+            // do argument processing, get Object[] as result
+            Object[] moves = new Object[leaf.type().parameterCount()];
+            for (int i = 0; i < args.length; i++) {
+                Object arg = args[i];
+                BindingInterpreter.unbox(arg, callingSequence.argumentBindings(i),
+                        (storage, type, value) -> {
+                            moves[argIndexMap.get(storage)] = value;
+                        }, tempBuffers);
+            }
+
+            // call leaf
+            Object o = leaf.invokeWithArguments(moves);
+
+            // return value processing
+            if (o == null) {
+                return null;
+            } else if (o instanceof Object[]) {
+                Object[] oArr = (Object[]) o;
+                return BindingInterpreter.box(callingSequence.returnBindings(),
+                        (storage, type) -> oArr[retIndexMap.get(storage)]);
+            } else {
+                return BindingInterpreter.box(callingSequence.returnBindings(), (storage, type) -> o);
+            }
         } finally {
             tempBuffers.forEach(MemorySegment::close);
         }
     }
 
diff a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/abi/ProgrammableUpcallHandler.java b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/abi/ProgrammableUpcallHandler.java
--- a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/abi/ProgrammableUpcallHandler.java
+++ b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/abi/ProgrammableUpcallHandler.java
@@ -87,14 +87,17 @@
 
             MemoryAddress bufferBase = MemoryAddressImpl.ofLongUnchecked(buffer.toRawLongValue(), layout.size);
             MemoryAddress stackArgsBase = MemoryAddressImpl.ofLongUnchecked((long)VH_LONG.get(buffer.rebase(bufferBase.segment()).addOffset(layout.stack_args)));
             Object[] args = new Object[type.parameterCount()];
             for (int i = 0 ; i < type.parameterCount() ; i++) {
-                args[i] = jdk.internal.foreign.abi.BindingInterpreter.box(callingSequence.argumentBindings(i),
-                        s -> abi.arch.isStackType(s.type())
-                            ? stackArgsBase.addOffset(s.index() * abi.arch.typeSize(abi.arch.stackType()))
-                            : bufferBase.addOffset(layout.argOffset(s)));
+                args[i] = BindingInterpreter.box(callingSequence.argumentBindings(i),
+                        (storage, type) -> {
+                            MemoryAddress ptr = abi.arch.isStackType(storage.type())
+                                ? stackArgsBase.addOffset(storage.index() * abi.arch.typeSize(abi.arch.stackType()))
+                                : bufferBase.addOffset(layout.argOffset(storage));
+                            return SharedUtils.read(ptr, type);
+                        });
             }
 
             if (DEBUG) {
                 System.err.println("Java arguments:");
                 System.err.println(Arrays.toString(args).indent(2));
@@ -106,12 +109,15 @@
                 System.err.println("Java return:");
                 System.err.println(Objects.toString(o).indent(2));
             }
 
             if (mh.type().returnType() != void.class) {
-                jdk.internal.foreign.abi.BindingInterpreter.unbox(o,
-                        callingSequence.returnBindings(), s -> bufferBase.addOffset(layout.retOffset(s)), new ArrayList<>());
+                BindingInterpreter.unbox(o, callingSequence.returnBindings(),
+                        (storage, type, value) -> {
+                            MemoryAddress ptr = bufferBase.addOffset(layout.retOffset(storage));
+                            SharedUtils.writeOverSized(ptr, type, value);
+                        }, new ArrayList<>());
             }
 
             if (DEBUG) {
                 System.err.println("Buffer state after:");
                 layout.dump(abi.arch, buffer, System.err);
diff a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/abi/SharedUtils.java b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/abi/SharedUtils.java
--- a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/abi/SharedUtils.java
+++ b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/abi/SharedUtils.java
@@ -43,10 +43,11 @@
 
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodType;
 import java.lang.invoke.VarHandle;
+import java.nio.ByteOrder;
 import java.util.List;
 import java.util.function.Consumer;
 import java.util.stream.IntStream;
 
 import static java.lang.invoke.MethodHandles.collectArguments;
@@ -60,10 +61,18 @@
 
     private static final MethodHandle MH_ALLOC_BUFFER;
     private static final MethodHandle MH_BASEADDRESS;
     private static final MethodHandle MH_BUFFER_COPY;
 
+    private static final VarHandle VH_BYTE = MemoryHandles.varHandle(byte.class, ByteOrder.nativeOrder());
+    private static final VarHandle VH_CHAR = MemoryHandles.varHandle(char.class, ByteOrder.nativeOrder());
+    private static final VarHandle VH_SHORT = MemoryHandles.varHandle(short.class, ByteOrder.nativeOrder());
+    private static final VarHandle VH_INT = MemoryHandles.varHandle(int.class, ByteOrder.nativeOrder());
+    private static final VarHandle VH_LONG = MemoryHandles.varHandle(long.class, ByteOrder.nativeOrder());
+    private static final VarHandle VH_FLOAT = MemoryHandles.varHandle(float.class, ByteOrder.nativeOrder());
+    private static final VarHandle VH_DOUBLE = MemoryHandles.varHandle(double.class, ByteOrder.nativeOrder());
+
     static {
         try {
             var lookup = MethodHandles.lookup();
             MH_ALLOC_BUFFER = lookup.findStatic(SharedUtils.class, "allocateNative",
                     methodType(MemorySegment.class, MemoryLayout.class));
@@ -390,6 +399,67 @@
         @Override
         public MemoryAddress address() {
             return address;
         }
     }
+
+    static void writeOverSized(MemoryAddress ptr, Class<?> type, Object o) {
+        // use VH_LONG for integers to zero out the whole register in the process
+        if (type == long.class) {
+            VH_LONG.set(ptr, (long) o);
+        } else if (type == int.class) {
+            VH_LONG.set(ptr, (long) (int) o);
+        } else if (type == short.class) {
+            VH_LONG.set(ptr, (long) (short) o);
+        } else if (type == char.class) {
+            VH_LONG.set(ptr, (long) (char) o);
+        } else if (type == byte.class) {
+            VH_LONG.set(ptr, (long) (byte) o);
+        } else if (type == float.class) {
+            VH_FLOAT.set(ptr, (float) o);
+        } else if (type == double.class) {
+            VH_DOUBLE.set(ptr, (double) o);
+        } else {
+            throw new IllegalArgumentException("Unsupported carrier: " + type);
+        }
+    }
+
+    static void write(MemoryAddress ptr, Class<?> type, Object o) {
+        if (type == long.class) {
+            VH_LONG.set(ptr, (long) o);
+        } else if (type == int.class) {
+            VH_INT.set(ptr, (int) o);
+        } else if (type == short.class) {
+            VH_SHORT.set(ptr, (short) o);
+        } else if (type == char.class) {
+            VH_CHAR.set(ptr, (char) o);
+        } else if (type == byte.class) {
+            VH_BYTE.set(ptr, (byte) o);
+        } else if (type == float.class) {
+            VH_FLOAT.set(ptr, (float) o);
+        } else if (type == double.class) {
+            VH_DOUBLE.set(ptr, (double) o);
+        } else {
+            throw new IllegalArgumentException("Unsupported carrier: " + type);
+        }
+    }
+
+    static Object read(MemoryAddress ptr, Class<?> type) {
+        if (type == long.class) {
+            return (long) VH_LONG.get(ptr);
+        } else if (type == int.class) {
+            return (int) VH_INT.get(ptr);
+        } else if (type == short.class) {
+            return (short) VH_SHORT.get(ptr);
+        } else if (type == char.class) {
+            return (char) VH_CHAR.get(ptr);
+        } else if (type == byte.class) {
+            return (byte) VH_BYTE.get(ptr);
+        } else if (type == float.class) {
+            return (float) VH_FLOAT.get(ptr);
+        } else if (type == double.class) {
+            return (double) VH_DOUBLE.get(ptr);
+        } else {
+            throw new IllegalArgumentException("Unsupported carrier: " + type);
+        }
+    }
 }
diff a/test/jdk/java/foreign/TestDowncall.java b/test/jdk/java/foreign/TestDowncall.java
--- a/test/jdk/java/foreign/TestDowncall.java
+++ b/test/jdk/java/foreign/TestDowncall.java
@@ -28,11 +28,17 @@
  *          jdk.incubator.foreign/jdk.internal.foreign
  *          jdk.incubator.foreign/jdk.internal.foreign.abi
  *          java.base/sun.security.action
  * @build NativeTestHelper CallGeneratorHelper TestDowncall
  *
- * @run testng/othervm -Dforeign.restricted=permit TestDowncall
+ * @run testng/othervm
+ *   -Dforeign.restricted=permit
+ *   TestDowncall
+ * @run testng/othervm
+ *   -Dforeign.restricted=permit
+ *   -Djdk.internal.foreign.ProgrammableInvoker.NO_SPEC=true
+ *   TestDowncall
  */
 
 import jdk.incubator.foreign.CSupport;
 import jdk.incubator.foreign.FunctionDescriptor;
 import jdk.incubator.foreign.LibraryLookup;
diff a/test/jdk/java/foreign/TestUpcall.java b/test/jdk/java/foreign/TestUpcall.java
--- a/test/jdk/java/foreign/TestUpcall.java
+++ b/test/jdk/java/foreign/TestUpcall.java
@@ -28,11 +28,17 @@
  *          jdk.incubator.foreign/jdk.internal.foreign
  *          jdk.incubator.foreign/jdk.internal.foreign.abi
  *          java.base/sun.security.action
  * @build NativeTestHelper CallGeneratorHelper TestUpcall
  *
- * @run testng/othervm -Dforeign.restricted=permit TestUpcall
+ * @run testng/othervm
+ *   -Dforeign.restricted=permit
+ *   TestUpcall
+ * @run testng/othervm
+ *   -Dforeign.restricted=permit
+ *   -Djdk.internal.foreign.ProgrammableInvoker.NO_SPEC=true
+ *   TestUpcall
  */
 
 import jdk.incubator.foreign.CSupport;
 import jdk.incubator.foreign.FunctionDescriptor;
 import jdk.incubator.foreign.GroupLayout;
@@ -136,11 +142,10 @@
         }
         args[params.size()] = makeCallback(ret, params, fields, checks, argChecks);
         return args;
     }
 
-    @SuppressWarnings("unchecked")
     static MemoryAddress makeCallback(Ret ret, List<ParamType> params, List<StructFieldType> fields, List<Consumer<Object>> checks, List<Consumer<Object[]>> argChecks) {
         if (params.isEmpty()) {
             return dummyAddress;
         }
 
@@ -154,22 +159,22 @@
             Class<?> carrier = paramCarrier(layout);
             mh = mh.asType(mh.type().changeParameterType(i, carrier));
 
             final int finalI = i;
             if (carrier == MemorySegment.class) {
-                argChecks.add(o -> assertStructEquals((MemorySegment) o[finalI], (MemorySegment) box.get()[finalI], layout));
+                argChecks.add(o -> assertStructEquals((MemorySegment) box.get()[finalI], (MemorySegment) o[finalI], layout));
             } else {
-                argChecks.add(o -> assertEquals(o[finalI], box.get()[finalI]));
+                argChecks.add(o -> assertEquals(box.get()[finalI], o[finalI]));
             }
         }
 
         ParamType firstParam = params.get(0);
         MemoryLayout firstlayout = firstParam.layout(fields);
         Class<?> firstCarrier = paramCarrier(firstlayout);
 
         if (firstCarrier == MemorySegment.class) {
-            checks.add(o -> assertStructEquals((MemorySegment) o, (MemorySegment) box.get()[0], firstlayout));
+            checks.add(o -> assertStructEquals((MemorySegment) box.get()[0], (MemorySegment) o, firstlayout));
         } else {
             checks.add(o -> assertEquals(o, box.get()[0]));
         }
 
         mh = mh.asType(mh.type().changeReturnType(ret == Ret.VOID ? void.class : firstCarrier));
@@ -180,17 +185,17 @@
                 : FunctionDescriptor.ofVoid(paramLayouts);
         MemoryAddress stub = abi.upcallStub(mh, func).baseAddress();
         return stub;
     }
 
-    private static void assertStructEquals(MemorySegment s1, MemorySegment s2, MemoryLayout layout) {
-        assertEquals(s1.byteSize(), s2.byteSize());
+    private static void assertStructEquals(MemorySegment actual, MemorySegment expected, MemoryLayout layout) {
+        assertEquals(actual.byteSize(), expected.byteSize());
         GroupLayout g = (GroupLayout) layout;
         for (MemoryLayout field : g.memberLayouts()) {
             if (field instanceof ValueLayout) {
                 VarHandle vh = g.varHandle(vhCarrier(field), MemoryLayout.PathElement.groupElement(field.name().orElseThrow()));
-                assertEquals(vh.get(s1.baseAddress()), vh.get(s2.baseAddress()));
+                assertEquals(vh.get(actual.baseAddress()), vh.get(expected.baseAddress()));
             }
         }
     }
 
     private static Class<?> vhCarrier(MemoryLayout layout) {
diff a/test/micro/org/openjdk/bench/jdk/incubator/foreign/CallOverhead.java b/test/micro/org/openjdk/bench/jdk/incubator/foreign/CallOverhead.java
--- a/test/micro/org/openjdk/bench/jdk/incubator/foreign/CallOverhead.java
+++ b/test/micro/org/openjdk/bench/jdk/incubator/foreign/CallOverhead.java
@@ -80,15 +80,27 @@
     @Benchmark
     public void panama_blank() throws Throwable {
         func.invokeExact();
     }
 
+    @Benchmark
+    @Fork(jvmArgsAppend = "-Djdk.internal.foreign.ProgrammableInvoker.NO_SPEC=true")
+    public void panama_blank_NO_SPEC() throws Throwable {
+        func.invokeExact();
+    }
+
     @Benchmark
     public int jni_identity() throws Throwable {
         return identity(10);
     }
 
     @Benchmark
     public int panama_identity() throws Throwable {
         return (int) identity.invokeExact(10);
     }
+
+    @Benchmark
+    @Fork(jvmArgsAppend = "-Djdk.internal.foreign.ProgrammableInvoker.NO_SPEC=true")
+    public int panama_identity_NO_SPEC() throws Throwable {
+        return (int) identity.invokeExact(10);
+    }
 }
