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,19 +20,35 @@
  * or visit www.oracle.com if you need additional information or have any
  * questions.
  */
 package jdk.internal.foreign.abi;
 
+import jdk.incubator.foreign.MemoryAddress;
 import jdk.incubator.foreign.MemoryHandles;
 import jdk.incubator.foreign.MemoryLayout;
+import jdk.incubator.foreign.MemorySegment;
+import jdk.incubator.foreign.NativeScope;
+import jdk.internal.foreign.MemoryAddressImpl;
 
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
 import java.util.ArrayList;
+import java.util.Deque;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
 import java.lang.invoke.VarHandle;
 import java.nio.ByteOrder;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static java.lang.invoke.MethodHandles.collectArguments;
+import static java.lang.invoke.MethodHandles.filterArguments;
+import static java.lang.invoke.MethodHandles.insertArguments;
+import static java.lang.invoke.MethodHandles.permuteArguments;
+import static java.lang.invoke.MethodType.methodType;
 
 /**
  * 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
@@ -184,10 +200,34 @@
  * none
  *
  * --------------------
  */
 public abstract class Binding {
+    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_ALLOCATE_BUFFER;
+
+    static {
+        try {
+            MethodHandles.Lookup lookup = MethodHandles.lookup();
+            MH_UNBOX_ADDRESS = lookup.findVirtual(MemoryAddress.class, "toRawLongValue",
+                    methodType(long.class));
+            MH_BOX_ADDRESS = lookup.findStatic(MemoryAddress.class, "ofLong",
+                    methodType(MemoryAddress.class, long.class));
+            MH_BASE_ADDRESS = lookup.findVirtual(MemorySegment.class, "baseAddress",
+                    methodType(MemoryAddress.class));
+            MH_COPY_BUFFER = lookup.findStatic(Binding.class, "copyBuffer",
+                    methodType(MemorySegment.class, MemorySegment.class, long.class, long.class, NativeScope.class));
+            MH_ALLOCATE_BUFFER = lookup.findStatic(MemorySegment.class, "allocateNative",
+                    methodType(MemorySegment.class, long.class, long.class));
+        } catch (ReflectiveOperationException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
     enum Tag {
         MOVE,
         DEREFERENCE,
         COPY_BUFFER,
         ALLOC_BUFFER,
@@ -204,15 +244,51 @@
 
     public Tag tag() {
         return tag;
     }
 
+    public abstract void verifyUnbox(Deque<Class<?>> stack);
+    public abstract void verifyBox(Deque<Class<?>> stack);
+
+    public abstract void unbox(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc, NativeScope scope);
+    public abstract void box(Deque<Object> stack, BindingInterpreter.LoadFunc loadFunc);
+
+    public abstract MethodHandle specializeUnbox(MethodHandle specializedHandle, int insertPos);
+    public abstract MethodHandle specializeBox(MethodHandle returnFilter);
+
+    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 void checkType(Class<?> type) {
         if (!type.isPrimitive() || type == void.class || type == boolean.class)
             throw new IllegalArgumentException("Illegal type: " + type);
     }
 
+    private static MemorySegment copyBuffer(MemorySegment operand, long size, long alignment, NativeScope allocator) {
+        MemorySegment copy = allocator.allocate(size, alignment).segment();
+        copy.copyFrom(operand.asSlice(0, size));
+        return copy;
+    }
+
     public static Move move(VMStorage storage, Class<?> type) {
         checkType(type);
         return new Move(storage, type);
     }
 
@@ -338,10 +414,42 @@
 
         @Override
         public int hashCode() {
             return Objects.hash(tag(), storage, type);
         }
+
+        @Override
+        public void verifyUnbox(Deque<Class<?>> stack) {
+            Class<?> actualType = stack.pop();
+            Class<?> expectedType = type;
+            SharedUtils.checkType(actualType, expectedType);
+        }
+
+        @Override
+        public void verifyBox(Deque<Class<?>> stack) {
+            stack.push(type);
+        }
+
+        @Override
+        public void unbox(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc, NativeScope scope) {
+            storeFunc.store(storage, type, stack.pop());
+        }
+
+        @Override
+        public void box(Deque<Object> stack, BindingInterpreter.LoadFunc loadFunc) {
+            stack.push(loadFunc.load(storage, type));
+        }
+
+        @Override
+        public MethodHandle specializeUnbox(MethodHandle specializedHandle, int insertPos) {
+            return specializedHandle; // no-op
+        }
+
+        @Override
+        public MethodHandle specializeBox(MethodHandle returnFilter) {
+            return returnFilter; // no-op
+        }
     }
 
     /**
      * DEREFERENCE([offset into memory region], [type])
      *   When unboxing: pops a MemorySegment from the operand stack,
@@ -366,14 +474,10 @@
 
         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 +
@@ -392,10 +496,65 @@
 
         @Override
         public int hashCode() {
             return Objects.hash(tag(), offset, type);
         }
+
+        @Override
+        public void verifyUnbox(Deque<Class<?>> stack) {
+            Class<?> actualType = stack.pop();
+            SharedUtils.checkType(actualType, MemorySegment.class);
+            Class<?> newType = type;
+            stack.push(newType);
+        }
+
+        @Override
+        public void verifyBox(Deque<Class<?>> stack) {
+            Class<?> storeType = stack.pop();
+            SharedUtils.checkType(storeType, type);
+            Class<?> segmentType = stack.pop();
+            SharedUtils.checkType(segmentType, MemorySegment.class);
+        }
+
+        @Override
+        public void unbox(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc, NativeScope scope) {
+            MemorySegment operand = (MemorySegment) stack.pop();
+            MemoryAddress baseAddress = operand.baseAddress();
+            MemoryAddress readAddress = baseAddress.addOffset(offset);
+            stack.push(SharedUtils.read(readAddress, type));
+        }
+
+        @Override
+        public void box(Deque<Object> stack, BindingInterpreter.LoadFunc loadFunc) {
+            Object value = stack.pop();
+            MemorySegment operand = (MemorySegment) stack.pop();
+            MemoryAddress baseAddress = operand.baseAddress();
+            MemoryAddress writeAddress = baseAddress.addOffset(offset);
+            SharedUtils.write(writeAddress, type, value);
+        }
+
+        private VarHandle varHandle() {
+            return MemoryHandles.withOffset(MemoryHandles.varHandle(type, ByteOrder.nativeOrder()), offset);
+        }
+
+        @Override
+        public MethodHandle specializeUnbox(MethodHandle specializedHandle, int insertPos) {
+            MethodHandle filter = filterArguments(
+                varHandle()
+                    .toMethodHandle(VarHandle.AccessMode.GET)
+                    .asType(methodType(type, MemoryAddress.class)), 0, MH_BASE_ADDRESS);
+            return filterArguments(specializedHandle, insertPos, filter);
+        }
+
+        @Override
+        public MethodHandle specializeBox(MethodHandle returnFilter) {
+            MethodHandle setter = varHandle().toMethodHandle(VarHandle.AccessMode.SET);
+            setter = filterArguments(
+                setter.asType(methodType(void.class, MemoryAddress.class, type)),
+                0, MH_BASE_ADDRESS);
+            return collectArguments(returnFilter, returnFilter.type().parameterCount(), setter);
+        }
     }
 
     /**
      * COPY([size], [alignment])
      *   Creates a new MemorySegment with the given [size] and [alignment],
@@ -440,10 +599,53 @@
 
         @Override
         public int hashCode() {
             return Objects.hash(tag(), size, alignment);
         }
+
+        @Override
+        public void verifyUnbox(Deque<Class<?>> stack) {
+            Class<?> actualType = stack.pop();
+            SharedUtils.checkType(actualType, MemorySegment.class);
+            stack.push(MemorySegment.class);
+        }
+
+        @Override
+        public void verifyBox(Deque<Class<?>> stack) {
+            Class<?> actualType = stack.pop();
+            SharedUtils.checkType(actualType, MemoryAddress.class);
+            stack.push(MemorySegment.class);
+        }
+
+        @Override
+        public void unbox(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc, NativeScope scope) {
+            MemorySegment operand = (MemorySegment) stack.pop();
+            MemorySegment copy = scope.allocate(size, alignment).segment();
+            copy.copyFrom(operand.asSlice(0, size));
+            stack.push(copy);
+        }
+
+        @Override
+        public void box(Deque<Object> stack, BindingInterpreter.LoadFunc loadFunc) {
+            MemoryAddress operand = (MemoryAddress) stack.pop();
+            operand = MemoryAddressImpl.ofLongUnchecked(operand.toRawLongValue(), size);
+            MemorySegment copy = MemorySegment.allocateNative(size, alignment);
+            copy.copyFrom(operand.segment().asSlice(0, size));
+            stack.push(copy); // leaked
+        }
+
+        @Override
+        public MethodHandle specializeUnbox(MethodHandle specializedHandle, int insertPos) {
+            MethodHandle filter = insertArguments(MH_COPY_BUFFER, 1, size, alignment);
+            specializedHandle = collectArguments(specializedHandle, insertPos, filter);
+            return mergeArguments(specializedHandle, 0, insertPos + 1);
+        }
+
+        @Override
+        public MethodHandle specializeBox(MethodHandle returnFilter) {
+            throw new UnsupportedOperationException();
+        }
     }
 
     /**
      * ALLOCATE([size], [alignment])
      *   Creates a new MemorySegment with the give [size] and [alignment], and pushes it onto the operand stack.
@@ -486,10 +688,40 @@
 
         @Override
         public int hashCode() {
             return Objects.hash(tag(), size, alignment);
         }
+
+        @Override
+        public void verifyUnbox(Deque<Class<?>> stack) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void verifyBox(Deque<Class<?>> stack) {
+            stack.push(MemorySegment.class);
+        }
+
+        @Override
+        public void unbox(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc, NativeScope scope) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void box(Deque<Object> stack, BindingInterpreter.LoadFunc loadFunc) {
+            stack.push(MemorySegment.allocateNative(size, alignment));
+        }
+
+        @Override
+        public MethodHandle specializeUnbox(MethodHandle specializedHandle, int insertPos) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public MethodHandle specializeBox(MethodHandle returnFilter) {
+            return collectArguments(returnFilter, 0, insertArguments(MH_ALLOCATE_BUFFER, 0, size, alignment));
+        }
     }
 
     /**
      * CONVERT_ADDRESS()
      *   When unboxing: pops a 'MemoryAddress' from the operand stack, converts it to a 'long',
@@ -518,10 +750,44 @@
         @Override
         public boolean equals(Object o) {
             if (this == o) return true;
             return o != null && getClass() == o.getClass();
         }
+
+        @Override
+        public void verifyUnbox(Deque<Class<?>> stack) {
+            Class<?> actualType = stack.pop();
+            SharedUtils.checkType(actualType, MemoryAddress.class);
+            stack.push(long.class);
+        }
+
+        @Override
+        public void verifyBox(Deque<Class<?>> stack) {
+            Class<?> actualType = stack.pop();
+            SharedUtils.checkType(actualType, long.class);
+            stack.push(MemoryAddress.class);
+        }
+
+        @Override
+        public void unbox(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc, NativeScope scope) {
+            stack.push(((MemoryAddress) stack.pop()).toRawLongValue());
+        }
+
+        @Override
+        public void box(Deque<Object> stack, BindingInterpreter.LoadFunc loadFunc) {
+            stack.push(MemoryAddress.ofLong((long) stack.pop()));
+        }
+
+        @Override
+        public MethodHandle specializeUnbox(MethodHandle specializedHandle, int insertPos) {
+            return filterArguments(specializedHandle, insertPos, MH_UNBOX_ADDRESS);
+        }
+
+        @Override
+        public MethodHandle specializeBox(MethodHandle returnFilter) {
+            return filterArguments(returnFilter, 0, MH_BOX_ADDRESS);
+        }
     }
 
     /**
      * BASE_ADDRESS()
      *   Pops a MemorySegment from the operand stack, and takes the base address of the segment
@@ -548,10 +814,44 @@
         @Override
         public boolean equals(Object o) {
             if (this == o) return true;
             return o != null && getClass() == o.getClass();
         }
+
+        @Override
+        public void verifyUnbox(Deque<Class<?>> stack) {
+            Class<?> actualType = stack.pop();
+            SharedUtils.checkType(actualType, MemorySegment.class);
+            stack.push(MemoryAddress.class);
+        }
+
+        @Override
+        public void verifyBox(Deque<Class<?>> stack) {
+            Class<?> actualType = stack.pop();
+            SharedUtils.checkType(actualType, MemorySegment.class);
+            stack.push(MemoryAddress.class);
+        }
+
+        @Override
+        public void unbox(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc, NativeScope scope) {
+            stack.push(((MemorySegment) stack.pop()).baseAddress());
+        }
+
+        @Override
+        public void box(Deque<Object> stack, BindingInterpreter.LoadFunc loadFunc) {
+            stack.push(((MemorySegment) stack.pop()).baseAddress());
+        }
+
+        @Override
+        public MethodHandle specializeUnbox(MethodHandle specializedHandle, int insertPos) {
+            return filterArguments(specializedHandle, insertPos, MH_BASE_ADDRESS);
+        }
+
+        @Override
+        public MethodHandle specializeBox(MethodHandle returnFilter) {
+            throw new UnsupportedOperationException();
+        }
     }
 
     /**
      * DUP()
      *   Duplicates the value on the top of the operand stack (without popping it!),
@@ -578,7 +878,73 @@
         @Override
         public boolean equals(Object o) {
             if (this == o) return true;
             return o != null && getClass() == o.getClass();
         }
+
+        @Override
+        public void verifyUnbox(Deque<Class<?>> stack) {
+            stack.push(stack.peekLast());
+        }
+
+        @Override
+        public void verifyBox(Deque<Class<?>> stack) {
+            stack.push(stack.peekLast());
+        }
+
+        @Override
+        public void unbox(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc, NativeScope scope) {
+            stack.push(stack.peekLast());
+        }
+
+        @Override
+        public void box(Deque<Object> stack, BindingInterpreter.LoadFunc loadFunc) {
+            stack.push(stack.peekLast());
+        }
+
+        /*
+         * Fixes up Y-shaped data graphs (produced by DEREFERENCE):
+         *
+         * 1. DUP()
+         * 2. DEREFERENCE(0, int.class)
+         * 3. MOVE  (ignored)
+         * 4. DEREFERENCE(4, int.class)
+         * 5. MOVE  (ignored)
+         *
+         * (specialized in reverse!)
+         *
+         * 5. (int, int) -> void                       insertPos = 1
+         * 4. (MemorySegment, int) -> void             insertPos = 1
+         * 3. (MemorySegment, int) -> void             insertPos = 0
+         * 2. (MemorySegment, MemorySegment) -> void   insertPos = 0
+         * 1. (MemorySegment) -> void                  insertPos = 0
+         *
+         */
+        @Override
+        public MethodHandle specializeUnbox(MethodHandle specializedHandle, int insertPos) {
+            return mergeArguments(specializedHandle, insertPos, insertPos + 1);
+        }
+
+        /*
+         * Fixes up Y-shaped data graphs (produced by DEREFERENCE):
+         *
+         * 1. ALLOCATE_BUFFER(4, 4)
+         * 2. DUP
+         * 3. MOVE  (ignored)
+         * 4. DEREFERNCE(0, int.class)
+         *
+         * (specialized in reverse!)
+         *
+         * input: (MemorySegment) -> MemorySegment (identity function of high-level return)
+         * 4. (MemorySegment, MemorySegment, int) -> MemorySegment
+         * 3. (MemorySegment, MemorySegment, int) -> MemorySegment
+         * 2. (MemorySegment, int) -> MemorySegment
+         * 1. (int) -> MemorySegment
+         *
+         */
+        @Override
+        public MethodHandle specializeBox(MethodHandle returnFilter) {
+            // assumes shape like: (MS, ..., MS, T) R
+            return mergeArguments(returnFilter, 0, returnFilter.type().parameterCount() - 2);
+        }
     }
 }
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
@@ -20,97 +20,31 @@
  *  or visit www.oracle.com if you need additional information or have any
  *  questions.
  */
 package jdk.internal.foreign.abi;
 
-import jdk.incubator.foreign.MemoryAddress;
-import jdk.incubator.foreign.MemorySegment;
-import jdk.internal.foreign.MemoryAddressImpl;
-import jdk.internal.foreign.Utils;
+import jdk.incubator.foreign.NativeScope;
 
 import java.util.ArrayDeque;
 import java.util.Deque;
 import java.util.List;
 
 public class BindingInterpreter {
 
-    static void unbox(Object arg, List<Binding> bindings, StoreFunc storeFunc, List<? super MemorySegment> buffers) {
+    static void unbox(Object arg, List<Binding> bindings, StoreFunc storeFunc, NativeScope scope) {
         Deque<Object> stack = new ArrayDeque<>();
         stack.push(arg);
         for (Binding b : bindings) {
-            switch (b.tag()) {
-                case MOVE -> {
-                    Binding.Move binding = (Binding.Move) b;
-                    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(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";
-                    MemorySegment copy = MemorySegment.allocateNative(binding.size(), binding.alignment());
-                    copy.copyFrom(operand.asSlice(0, binding.size()));
-                    buffers.add(copy);
-                    stack.push(copy);
-                }
-                case ALLOC_BUFFER ->
-                    throw new UnsupportedOperationException();
-                case CONVERT_ADDRESS ->
-                    stack.push(((MemoryAddress) stack.pop()).toRawLongValue());
-                case BASE_ADDRESS ->
-                    stack.push(((MemorySegment) stack.pop()).baseAddress());
-                case DUP ->
-                    stack.push(stack.peekLast());
-                default -> throw new IllegalArgumentException("Unsupported tag: " + b);
-            }
+            b.unbox(stack, storeFunc, scope);
         }
     }
 
     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;
-                    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());
-                    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());
-                    MemorySegment copy = MemorySegment.allocateNative(binding.size(), binding.alignment());
-                    copy.copyFrom(operand.segment().asSlice(0, binding.size()));
-                    stack.push(copy); // leaked
-                }
-                case ALLOC_BUFFER -> {
-                    Binding.Allocate binding = (Binding.Allocate) b;
-                    stack.push(MemorySegment.allocateNative(binding.size(), binding.alignment()));
-                }
-                case CONVERT_ADDRESS ->
-                    stack.push(MemoryAddress.ofLong((long) stack.pop()));
-                case BASE_ADDRESS ->
-                    stack.push(((MemorySegment) stack.pop()).baseAddress());
-                case DUP ->
-                    stack.push(stack.peekLast());
-                default -> throw new IllegalArgumentException("Unsupported tag: " + b);
-            }
+            b.box(stack, loadFunc);
         }
-
        return stack.pop();
     }
 
     interface StoreFunc {
         void store(VMStorage storage, Class<?> type, Object o);
diff a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/abi/CallingSequenceBuilder.java b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/abi/CallingSequenceBuilder.java
--- a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/abi/CallingSequenceBuilder.java
+++ b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/abi/CallingSequenceBuilder.java
@@ -21,13 +21,11 @@
  *  questions.
  */
 package jdk.internal.foreign.abi;
 
 import jdk.incubator.foreign.FunctionDescriptor;
-import jdk.incubator.foreign.MemoryAddress;
 import jdk.incubator.foreign.MemoryLayout;
-import jdk.incubator.foreign.MemorySegment;
 import sun.security.action.GetPropertyAction;
 
 import java.lang.invoke.MethodType;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
@@ -79,104 +77,33 @@
                 verifyUnboxBindings(carrier, bindings);
             }
         }
     }
 
-    private static void checkType(Class<?> actualType, Class<?> expectedType) {
-        if (expectedType != actualType) {
-            throw new IllegalArgumentException(
-                    String.format("Invalid operand type: %s. %s expected", actualType, expectedType));
-        }
-    }
-
     private static void verifyUnboxBindings(Class<?> inType, List<Binding> bindings) {
         Deque<Class<?>> stack = new ArrayDeque<>();
         stack.push(inType);
 
         for (Binding b : bindings) {
-            switch (b.tag()) {
-                case MOVE -> {
-                    Class<?> actualType = stack.pop();
-                    Class<?> expectedType = ((Binding.Move) b).type();
-                    checkType(actualType, expectedType);
-                }
-                case DEREFERENCE -> {
-                    Class<?> actualType = stack.pop();
-                    checkType(actualType, MemorySegment.class);
-                    Class<?> newType = ((Binding.Dereference) b).type();
-                    stack.push(newType);
-                }
-                case BASE_ADDRESS -> {
-                    Class<?> actualType = stack.pop();
-                    checkType(actualType, MemorySegment.class);
-                    stack.push(MemoryAddress.class);
-                }
-                case CONVERT_ADDRESS -> {
-                    Class<?> actualType = stack.pop();
-                    checkType(actualType, MemoryAddress.class);
-                    stack.push(long.class);
-                }
-                case ALLOC_BUFFER ->
-                    throw new UnsupportedOperationException();
-                case COPY_BUFFER -> {
-                    Class<?> actualType = stack.pop();
-                    checkType(actualType, MemorySegment.class);
-                    stack.push(MemorySegment.class);
-                }
-                case DUP ->
-                    stack.push(stack.peekLast());
-                default -> throw new IllegalArgumentException("Unknown binding: " + b);
-            }
+            b.verifyUnbox(stack);
         }
 
         if (!stack.isEmpty()) {
             throw new IllegalArgumentException("Stack must be empty after recipe");
         }
     }
 
-    private static void verifyBoxBindings(Class<?> outType, List<Binding> bindings) {
+    private static void verifyBoxBindings(Class<?> expectedReturnType, List<Binding> bindings) {
         Deque<Class<?>> stack = new ArrayDeque<>();
 
         for (Binding b : bindings) {
-            switch (b.tag()) {
-                case MOVE -> {
-                    Class<?> newType = ((Binding.Move) b).type();
-                    stack.push(newType);
-                }
-                case DEREFERENCE -> {
-                    Class<?> storeType = stack.pop();
-                    checkType(storeType, ((Binding.Dereference) b).type());
-                    Class<?> segmentType = stack.pop();
-                    checkType(segmentType, MemorySegment.class);
-                }
-                case CONVERT_ADDRESS -> {
-                    Class<?> actualType = stack.pop();
-                    checkType(actualType, long.class);
-                    stack.push(MemoryAddress.class);
-                }
-                case BASE_ADDRESS -> {
-                    Class<?> actualType = stack.pop();
-                    checkType(actualType, MemorySegment.class);
-                    stack.push(MemoryAddress.class);
-                }
-                case ALLOC_BUFFER -> {
-                    stack.push(MemorySegment.class);
-                }
-                case COPY_BUFFER -> {
-                    Class<?> actualType = stack.pop();
-                    checkType(actualType, MemoryAddress.class);
-                    stack.push(MemorySegment.class);
-                }
-                case DUP ->
-                    stack.push(stack.peekLast());
-                default -> throw new IllegalArgumentException("Unknown binding: " + b);
-            }
+            b.verifyBox(stack);
         }
 
         if (stack.size() != 1) {
             throw new IllegalArgumentException("Stack must contain exactly 1 value");
         }
 
         Class<?> actualReturnType = stack.pop();
-        checkType(actualReturnType, outType);
+        SharedUtils.checkType(actualReturnType, expectedReturnType);
     }
 }
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
@@ -23,13 +23,11 @@
 package jdk.internal.foreign.abi;
 
 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.incubator.foreign.NativeScope;
 import jdk.internal.foreign.MemoryAddressImpl;
 import jdk.internal.foreign.Utils;
 
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
@@ -69,41 +67,26 @@
     private static final VarHandle VH_LONG = MemoryHandles.varHandle(long.class, ByteOrder.nativeOrder());
 
     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 {
             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",
+            MH_MAKE_ALLOCATOR = lookup.findStatic(NativeScope.class, "boundedScope",
+                    methodType(NativeScope.class, long.class));
+            MH_CLOSE_ALLOCATOR = lookup.findVirtual(NativeScope.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);
         }
     }
 
@@ -161,15 +144,15 @@
                 ? void.class
                 : retMoves.length == 1
                     ? retMoves[0].type()
                     : Object[].class;
 
-        MethodType intrinsicType = methodType(returnType, argMoveTypes);
+        MethodType leafType = methodType(returnType, argMoveTypes);
 
         MethodHandle handle = insertArguments(MH_INVOKE_MOVES.bindTo(this), 1, argMoves, retMoves)
-                                            .asCollector(Object[].class, intrinsicType.parameterCount())
-                                            .asType(intrinsicType);
+                                            .asCollector(Object[].class, leafType.parameterCount())
+                                            .asType(leafType);
 
         if (NO_SPEC || retMoves.length > 1) {
             Map<VMStorage, Integer> argIndexMap = indexMap(argMoves);
             Map<VMStorage, Integer> retIndexMap = indexMap(retMoves);
 
@@ -181,133 +164,57 @@
          }
 
         return handle;
     }
 
-    private MethodHandle specialize(MethodHandle intrinsicHandle) {
-        MethodType type = callingSequence.methodType();
-        MethodType intrinsicType = intrinsicHandle.type();
+    private MethodHandle specialize(MethodHandle leafHandle) {
+        MethodType highLevelType = callingSequence.methodType();
+        MethodType leafType = leafHandle.type();
+
+        MethodHandle specializedHandle = leafHandle; // initial
 
         int insertPos = -1;
         if (bufferCopySize > 0) {
-            intrinsicHandle = dropArguments(intrinsicHandle, 0, NativeAllocationScope.class);
+            specializedHandle = dropArguments(specializedHandle, 0, NativeScope.class);
             insertPos++;
         }
-        for (int i = 0; i < type.parameterCount(); i++) {
+        for (int i = 0; i < highLevelType.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 (binding.tag() == Binding.Tag.MOVE) {
+                    insertPos--;
+                } else {
+                    specializedHandle = binding.specializeUnbox(specializedHandle, insertPos);
                 }
             }
         }
 
-        if (type.returnType() != void.class) {
-            MethodHandle returnFilter = identity(type.returnType());
+        if (highLevelType.returnType() != void.class) {
+            MethodHandle returnFilter = identity(highLevelType.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());
-                }
+                returnFilter = binding.specializeBox(returnFilter);
             }
-
-            intrinsicHandle = MethodHandles.filterReturnValue(intrinsicHandle, returnFilter);
+            specializedHandle = MethodHandles.filterReturnValue(specializedHandle, returnFilter);
         }
 
         if (bufferCopySize > 0) {
-            MethodHandle closer = intrinsicType.returnType() == void.class
-                  // (Throwable, NativeAllocationScope) -> void
+            // insert try-finally to close the NativeScope used for Binding.Copy
+            MethodHandle closer = leafType.returnType() == void.class
+                  // (Throwable, NativeScope) -> 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),
+                  // (Throwable, V, NativeScope) -> V
+                : collectArguments(dropArguments(identity(specializedHandle.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;
-            }
+            specializedHandle = tryFinally(specializedHandle, closer);
+            specializedHandle = collectArguments(specializedHandle, 0, insertArguments(MH_MAKE_ALLOCATOR, 0, bufferCopySize));
         }
-        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
+        return specializedHandle;
     }
 
     private Map<VMStorage, Integer> indexMap(Binding.Move[] moves) {
         return IntStream.range(0, moves.length)
                         .boxed()
@@ -383,20 +290,20 @@
     }
 
     Object invokeInterpBindings(Object[] args, MethodHandle leaf,
                                 Map<VMStorage, Integer> argIndexMap,
                                 Map<VMStorage, Integer> retIndexMap) throws Throwable {
-        List<MemorySegment> tempBuffers = new ArrayList<>();
+        NativeScope scope = bufferCopySize != 0 ? NativeScope.boundedScope(bufferCopySize) : null;
         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);
+                        }, scope);
             }
 
             // call leaf
             Object o = leaf.invokeWithArguments(moves);
 
@@ -409,11 +316,13 @@
                         (storage, type) -> oArr[retIndexMap.get(storage)]);
             } else {
                 return BindingInterpreter.box(callingSequence.returnBindings(), (storage, type) -> o);
             }
         } finally {
-            tempBuffers.forEach(MemorySegment::close);
+            if (scope != null) {
+                scope.close();
+            }
         }
     }
 
     //natives
 
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
@@ -113,11 +113,11 @@
             if (mh.type().returnType() != void.class) {
                 BindingInterpreter.unbox(o, callingSequence.returnBindings(),
                         (storage, type, value) -> {
                             MemoryAddress ptr = bufferBase.addOffset(layout.retOffset(storage));
                             SharedUtils.writeOverSized(ptr, type, value);
-                        }, new ArrayList<>());
+                        }, null);
             }
 
             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
@@ -319,10 +319,17 @@
             }
         }
         return handle;
     }
 
+    static void checkType(Class<?> actualType, Class<?> expectedType) {
+        if (expectedType != actualType) {
+            throw new IllegalArgumentException(
+                    String.format("Invalid operand type: %s. %s expected", actualType, expectedType));
+        }
+    }
+
     public static class SimpleVaArg {
         public final Class<?> carrier;
         public final MemoryLayout layout;
         public final Object value;
 
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
@@ -24,10 +24,13 @@
 
 import jdk.incubator.foreign.CSupport;
 import jdk.incubator.foreign.FunctionDescriptor;
 import jdk.incubator.foreign.LibraryLookup;
 import jdk.incubator.foreign.ForeignLinker;
+import jdk.incubator.foreign.MemoryAddress;
+import jdk.incubator.foreign.MemoryLayout;
+import jdk.incubator.foreign.MemorySegment;
 import org.openjdk.jmh.annotations.Benchmark;
 import org.openjdk.jmh.annotations.BenchmarkMode;
 import org.openjdk.jmh.annotations.Fork;
 import org.openjdk.jmh.annotations.Measurement;
 import org.openjdk.jmh.annotations.Mode;
@@ -37,11 +40,14 @@
 
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodType;
 import java.util.concurrent.TimeUnit;
 
+import static jdk.incubator.foreign.CSupport.C_DOUBLE;
 import static jdk.incubator.foreign.CSupport.C_INT;
+import static jdk.incubator.foreign.CSupport.C_LONGLONG;
+import static jdk.incubator.foreign.CSupport.C_POINTER;
 
 @BenchmarkMode(Mode.AverageTime)
 @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS)
 @Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
 @State(org.openjdk.jmh.annotations.Scope.Thread)
@@ -50,10 +56,20 @@
 public class CallOverhead {
 
     static final ForeignLinker abi = CSupport.getSystemLinker();
     static final MethodHandle func;
     static final MethodHandle identity;
+    static final MethodHandle identity_struct;
+    static final MethodHandle identity_memory_address;
+    static final MethodHandle args5;
+    static final MethodHandle args10;
+
+    static final MemoryLayout POINT_LAYOUT = MemoryLayout.ofStruct(
+        C_LONGLONG, C_LONGLONG
+    );
+
+    static final MemorySegment point = MemorySegment.allocateNative(POINT_LAYOUT);
 
     static {
         System.loadLibrary("CallOverheadJNI");
 
         try {
@@ -62,10 +78,24 @@
                     MethodType.methodType(void.class),
                     FunctionDescriptor.ofVoid());
             identity = abi.downcallHandle(ll.lookup("identity"),
                     MethodType.methodType(int.class, int.class),
                     FunctionDescriptor.of(C_INT, C_INT));
+            identity_struct = abi.downcallHandle(ll.lookup("identity_struct"),
+                    MethodType.methodType(MemorySegment.class, MemorySegment.class),
+                    FunctionDescriptor.of(POINT_LAYOUT, POINT_LAYOUT));
+            identity_memory_address = abi.downcallHandle(ll.lookup("identity_memory_address"),
+                    MethodType.methodType(MemoryAddress.class, MemoryAddress.class),
+                    FunctionDescriptor.of(C_POINTER, C_POINTER));
+            args5 = abi.downcallHandle(ll.lookup("args5"), // just reuse identity
+                    MethodType.methodType(void.class, long.class, double.class, long.class, double.class, long.class),
+                    FunctionDescriptor.ofVoid(C_LONGLONG, C_DOUBLE, C_LONGLONG, C_DOUBLE, C_LONGLONG));
+            args10 = abi.downcallHandle(ll.lookup("args10"),
+                    MethodType.methodType(void.class, long.class, double.class, long.class, double.class, long.class,
+                                                      double.class, long.class, double.class, long.class, double.class),
+                    FunctionDescriptor.ofVoid(C_LONGLONG, C_DOUBLE, C_LONGLONG, C_DOUBLE, C_LONGLONG,
+                                              C_DOUBLE, C_LONGLONG, C_DOUBLE, C_LONGLONG, C_DOUBLE));
         } catch (NoSuchMethodException e) {
             throw new BootstrapMethodError(e);
         }
     }
 
@@ -101,6 +131,52 @@
     @Benchmark
     @Fork(jvmArgsAppend = "-Djdk.internal.foreign.ProgrammableInvoker.NO_SPEC=true")
     public int panama_identity_NO_SPEC() throws Throwable {
         return (int) identity.invokeExact(10);
     }
+
+    @Benchmark
+    public MemorySegment panama_identity_struct() throws Throwable {
+        return (MemorySegment) identity_struct.invokeExact(point);
+    }
+
+    @Benchmark
+    @Fork(jvmArgsAppend = "-Djdk.internal.foreign.ProgrammableInvoker.NO_SPEC=true")
+    public MemorySegment panama_identity_struct_NO_SPEC() throws Throwable {
+        return (MemorySegment) identity_struct.invokeExact(point);
+    }
+
+    @Benchmark
+    public MemoryAddress panama_identity_memory_address() throws Throwable {
+        return (MemoryAddress) identity_memory_address.invokeExact(MemoryAddress.NULL);
+    }
+
+    @Benchmark
+    @Fork(jvmArgsAppend = "-Djdk.internal.foreign.ProgrammableInvoker.NO_SPEC=true")
+    public MemoryAddress panama_identity_memory_address_NO_SPEC() throws Throwable {
+        return (MemoryAddress) identity_memory_address.invokeExact(MemoryAddress.NULL);
+    }
+
+    @Benchmark
+    public void panama_args5() throws Throwable {
+        args5.invokeExact(10L, 11D, 12L, 13D, 14L);
+    }
+
+    @Benchmark
+    @Fork(jvmArgsAppend = "-Djdk.internal.foreign.ProgrammableInvoker.NO_SPEC=true")
+    public void panama_args5_NO_SPEC() throws Throwable {
+        args5.invokeExact(10L, 11D, 12L, 13D, 14L);
+    }
+    
+    @Benchmark
+    public void panama_args10() throws Throwable {
+        args10.invokeExact(10L, 11D, 12L, 13D, 14L,
+                           15D, 16L, 17D, 18L, 19D);
+    }
+
+    @Benchmark
+    @Fork(jvmArgsAppend = "-Djdk.internal.foreign.ProgrammableInvoker.NO_SPEC=true")
+    public void panama_args10_NO_SPEC() throws Throwable {
+        args10.invokeExact(10L, 11D, 12L, 13D, 14L,
+                           15D, 16L, 17D, 18L, 19D);
+    }
 }
diff a/test/micro/org/openjdk/bench/jdk/incubator/foreign/libCallOverhead.c b/test/micro/org/openjdk/bench/jdk/incubator/foreign/libCallOverhead.c
--- a/test/micro/org/openjdk/bench/jdk/incubator/foreign/libCallOverhead.c
+++ b/test/micro/org/openjdk/bench/jdk/incubator/foreign/libCallOverhead.c
@@ -30,5 +30,22 @@
 EXPORT void func() {}
 
 EXPORT int identity(int x) {
   return x;
 }
+
+typedef struct {
+    long long x;
+    long long y;
+} Point;
+
+EXPORT Point identity_struct(Point p) {
+    return p;
+}
+
+EXPORT void* identity_memory_address(void* p) {
+    return p;
+}
+
+EXPORT void args5(long long a0, double a1, long long a2, double a3, long long a4) {}
+EXPORT void args10(long long a0, double a1, long long a2, double a3, long long a4,
+                   double a5, long long a6, double a7, long long a8, double a9) {}
