diff --git a/src/java.base/share/classes/jdk/internal/foreign/abi/BindingSpecializer.java b/src/java.base/share/classes/jdk/internal/foreign/abi/BindingSpecializer.java index bc5f63fd50ef7..6a5d6577e58e9 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/abi/BindingSpecializer.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/BindingSpecializer.java @@ -134,8 +134,6 @@ public class BindingSpecializer { private final MethodType leafType; private int[] leafArgSlots; - private int[] scopeSlots; - private int curScopeLocalIdx = -1; private int returnAllocatorIdx = -1; private int contextIdx = -1; private int returnBufferIdx = -1; @@ -266,21 +264,6 @@ private void specialize() { // allocator passed to us for allocating the return MS (downcalls only) if (callingSequence.forDowncall()) { returnAllocatorIdx = 0; // first param - - // for downcalls we also acquire/release scoped parameters before/after the call - // create a bunch of locals here to keep track of their scopes (to release later) - int[] initialScopeSlots = new int[callerMethodType.parameterCount()]; - int numScopes = 0; - for (int i = 0; i < callerMethodType.parameterCount(); i++) { - if (shouldAcquire(i)) { - int scopeLocal = cb.allocateLocal(REFERENCE); - initialScopeSlots[numScopes++] = scopeLocal; - cb.loadConstant(null); - cb.storeLocal(REFERENCE, scopeLocal); // need to initialize all scope locals here in case an exception occurs - } - } - scopeSlots = Arrays.copyOf(initialScopeSlots, numScopes); // fit to size - curScopeLocalIdx = 0; // used from emitGetInput } // create a Binding.Context for this call @@ -453,7 +436,13 @@ private boolean shouldAcquire(int paramIndex) { private void emitCleanup() { emitCloseContext(); if (callingSequence.forDowncall()) { - emitReleaseScopes(); + for (int paramIndex = 0 ; paramIndex < callerMethodType.parameterCount() ; paramIndex++) { + Class highLevelType = callerMethodType.parameterType(paramIndex); + if (shouldAcquire(paramIndex)) { + cb.loadLocal(TypeKind.from(highLevelType), cb.parameterSlot(paramIndex)); + emitReleaseScope(); + } + } } } @@ -498,43 +487,13 @@ private void emitGetInput() { private void emitAcquireScope() { cb.checkcast(CD_AbstractMemorySegmentImpl); cb.invokevirtual(CD_AbstractMemorySegmentImpl, "sessionImpl", MTD_SESSION_IMPL); - Label skipAcquire = cb.newLabel(); - Label end = cb.newLabel(); - - // start with 1 scope to maybe acquire on the stack - assert curScopeLocalIdx != -1; - boolean hasOtherScopes = curScopeLocalIdx != 0; - for (int i = 0; i < curScopeLocalIdx; i++) { - cb.dup(); // dup for comparison - cb.loadLocal(REFERENCE, scopeSlots[i]); - cb.if_acmpeq(skipAcquire); - } - - // 1 scope to acquire on the stack - cb.dup(); - int nextScopeLocal = scopeSlots[curScopeLocalIdx++]; - // call acquire first here. So that if it fails, we don't call release - cb.invokevirtual(CD_MemorySessionImpl, "acquire0", MTD_ACQUIRE0); // call acquire on the other - cb.storeLocal(REFERENCE, nextScopeLocal); // store off one to release later - - if (hasOtherScopes) { // avoid ASM generating a bunch of nops for the dead code - cb.goto_(end); - - cb.labelBinding(skipAcquire); - cb.pop(); // drop scope - } - - cb.labelBinding(end); + cb.invokevirtual(CD_MemorySessionImpl, "acquire0", MTD_ACQUIRE0); } - private void emitReleaseScopes() { - for (int scopeLocal : scopeSlots) { - cb.loadLocal(REFERENCE, scopeLocal); - cb.ifThen(Opcode.IFNONNULL, ifCb -> { - ifCb.loadLocal(REFERENCE, scopeLocal); - ifCb.invokevirtual(CD_MemorySessionImpl, "release0", MTD_RELEASE0); - }); - } + private void emitReleaseScope() { + cb.checkcast(CD_AbstractMemorySegmentImpl); + cb.invokevirtual(CD_AbstractMemorySegmentImpl, "sessionImpl", MTD_SESSION_IMPL); + cb.invokevirtual(CD_MemorySessionImpl, "release0", MTD_RELEASE0); } private void emitSaveReturnValue(Class storeType) { diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/FFMEscapeAnalysisTest.java b/test/micro/org/openjdk/bench/java/lang/foreign/FFMEscapeAnalysisTest.java new file mode 100644 index 0000000000000..c2e6fed7b2978 --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/foreign/FFMEscapeAnalysisTest.java @@ -0,0 +1,181 @@ +package org.openjdk.bench.java.lang.foreign; + +import org.openjdk.jmh.annotations.*; + +import java.lang.foreign.*; +import java.lang.invoke.*; +import java.util.concurrent.TimeUnit; + +/* +Windows 11, Ryzen 7950X3D, JDK 24-beta+20 (similar results on JDK 23) +Benchmark Mode Cnt Score Error Units +FFMEscapeAnalysisTest.noop_params0 avgt 3 3,133 ? 1,954 ns/op +FFMEscapeAnalysisTest.noop_params0:gc.alloc.rate avgt 3 0,007 ? 0,001 MB/sec +FFMEscapeAnalysisTest.noop_params0:gc.alloc.rate.norm avgt 3 ? 10?? B/op +FFMEscapeAnalysisTest.noop_params0:gc.count avgt 3 ? 0 counts +FFMEscapeAnalysisTest.noop_params1 avgt 3 3,051 ? 0,051 ns/op +FFMEscapeAnalysisTest.noop_params1:gc.alloc.rate avgt 3 0,007 ? 0,001 MB/sec +FFMEscapeAnalysisTest.noop_params1:gc.alloc.rate.norm avgt 3 ? 10?? B/op +FFMEscapeAnalysisTest.noop_params1:gc.count avgt 3 ? 0 counts +FFMEscapeAnalysisTest.noop_params2 avgt 3 3,048 ? 0,218 ns/op +FFMEscapeAnalysisTest.noop_params2:gc.alloc.rate avgt 3 0,007 ? 0,001 MB/sec +FFMEscapeAnalysisTest.noop_params2:gc.alloc.rate.norm avgt 3 ? 10?? B/op +FFMEscapeAnalysisTest.noop_params2:gc.count avgt 3 ? 0 counts +FFMEscapeAnalysisTest.noop_params3 avgt 3 3,110 ? 1,973 ns/op +FFMEscapeAnalysisTest.noop_params3:gc.alloc.rate avgt 3 2,368 ? 74,631 MB/sec +FFMEscapeAnalysisTest.noop_params3:gc.alloc.rate.norm avgt 3 0,008 ? 0,253 B/op +FFMEscapeAnalysisTest.noop_params3:gc.count avgt 3 ? 0 counts +FFMEscapeAnalysisTest.noop_params4 avgt 3 10,313 ? 3,615 ns/op +FFMEscapeAnalysisTest.noop_params4:gc.alloc.rate avgt 3 14796,598 ? 5131,809 MB/sec +FFMEscapeAnalysisTest.noop_params4:gc.alloc.rate.norm avgt 3 160,000 ? 0,001 B/op +FFMEscapeAnalysisTest.noop_params4:gc.count avgt 3 20,000 counts +FFMEscapeAnalysisTest.noop_params4:gc.time avgt 3 15,000 ms +FFMEscapeAnalysisTest.noop_params5 avgt 3 12,156 ? 4,828 ns/op +FFMEscapeAnalysisTest.noop_params5:gc.alloc.rate avgt 3 15692,588 ? 6152,349 MB/sec +FFMEscapeAnalysisTest.noop_params5:gc.alloc.rate.norm avgt 3 200,000 ? 0,001 B/op +FFMEscapeAnalysisTest.noop_params5:gc.count avgt 3 19,000 counts +FFMEscapeAnalysisTest.noop_params5:gc.time avgt 3 16,000 ms + */ +@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) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@Fork(value = 3, jvmArgsAppend = { "--enable-native-access=ALL-UNNAMED", "-Djava.library.path=micro/native" }) +public class FFMEscapeAnalysisTest { + + static { + System.loadLibrary("eaTest"); + } + + // A shared library that exports the functions below + private static final SymbolLookup LOOKUP = SymbolLookup.loaderLookup(); + + // void noop_params0() {} + private static final MethodHandle MH_NOOP_PARAMS0 = Linker.nativeLinker() + .downcallHandle(FunctionDescriptor.ofVoid()) + .bindTo(LOOKUP.find("noop_params0").orElseThrow()); + + // void noop_params1(void *param0) {} + private static final MethodHandle MH_NOOP_PARAMS1 = Linker.nativeLinker() + .downcallHandle(FunctionDescriptor.ofVoid( + ValueLayout.ADDRESS + )) + .bindTo(LOOKUP.find("noop_params1").orElseThrow()); + + // void noop_params2(void *param0, void *param1) {} + private static final MethodHandle MH_NOOP_PARAMS2 = Linker.nativeLinker() + .downcallHandle(FunctionDescriptor.ofVoid( + ValueLayout.ADDRESS, + ValueLayout.ADDRESS + )) + .bindTo(LOOKUP.find("noop_params2").orElseThrow()); + + // void noop_params3(void *param0, void *param1, void *param2) {} + private static final MethodHandle MH_NOOP_PARAMS3 = Linker.nativeLinker() + .downcallHandle(FunctionDescriptor.ofVoid( + ValueLayout.ADDRESS, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS + )) + .bindTo(LOOKUP.find("noop_params3").orElseThrow()); + + // void noop_params4(void *param0, void *param1, void *param2, void *param3) {} + private static final MethodHandle MH_NOOP_PARAMS4 = Linker.nativeLinker() + .downcallHandle(FunctionDescriptor.ofVoid( + ValueLayout.ADDRESS, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS + )) + .bindTo(LOOKUP.find("noop_params4").orElseThrow()); + + // void noop_params5(int param0, int param1, void *param2, void *param3, void *param4) {} + private static final MethodHandle MH_NOOP_PARAMS5 = Linker.nativeLinker() + .downcallHandle(FunctionDescriptor.ofVoid( + ValueLayout.ADDRESS, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS + )) + .bindTo(LOOKUP.find("noop_params5").orElseThrow()); + + @Benchmark + public void noop_params0() { + try { + MH_NOOP_PARAMS0.invokeExact(); + } catch (Throwable t) { + throw new AssertionError(t); + } + } + + @Benchmark + public void noop_params1() { + try { + MH_NOOP_PARAMS1.invokeExact( + MemorySegment.ofAddress(0L) + ); + } catch (Throwable t) { + throw new AssertionError(t); + } + } + + @Benchmark + public void noop_params2() { + try { + MH_NOOP_PARAMS2.invokeExact( + MemorySegment.ofAddress(0L), + MemorySegment.ofAddress(0L) + ); + } catch (Throwable t) { + throw new AssertionError(t); + } + } + + @Benchmark + public void noop_params3() { + try { + MH_NOOP_PARAMS3.invokeExact( + MemorySegment.ofAddress(0L), + MemorySegment.ofAddress(0L), + MemorySegment.ofAddress(0L) + ); + } catch (Throwable t) { + throw new AssertionError(t); + } + } + + @Benchmark + public void noop_params4() { + try { + MH_NOOP_PARAMS4.invokeExact( + MemorySegment.ofAddress(0L), + MemorySegment.ofAddress(0L), + MemorySegment.ofAddress(0L), + MemorySegment.ofAddress(0L) + ); + /*var p = MemorySegment.ofAddress(0L); + MH_NOOP_PARAMS4.invokeExact(p, p, p, p);*/ + } catch (Throwable t) { + throw new AssertionError(t); + } + } + + @Benchmark + public void noop_params5() { + try { + MH_NOOP_PARAMS5.invokeExact( + MemorySegment.ofAddress(0L), + MemorySegment.ofAddress(0L), + MemorySegment.ofAddress(0L), + MemorySegment.ofAddress(0L), + MemorySegment.ofAddress(0L) + ); + /*var p = MemorySegment.ofAddress(0L); + MH_NOOP_PARAMS5.invokeExact(p, p, p, p, p);*/ + } catch (Throwable t) { + throw new AssertionError(t); + } + } +} \ No newline at end of file diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/libeaTest.c b/test/micro/org/openjdk/bench/java/lang/foreign/libeaTest.c new file mode 100644 index 0000000000000..bd3bb052f063c --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/foreign/libeaTest.c @@ -0,0 +1,8 @@ +#include "export.h" + +EXPORT void noop_params0() {} +EXPORT void noop_params1(void *param0) {} +EXPORT void noop_params2(void *param0, void *param1) {} +EXPORT void noop_params3(void *param0, void *param1, void *param2) {} +EXPORT void noop_params4(void *param0, void *param1, void *param2, void *param3) {} +EXPORT void noop_params5(int param0, int param1, void *param2, void *param3, void *param4) {} \ No newline at end of file