8354996: Reduce dynamic code generation for a single downcall

Reviewed-by: jvernee
This commit is contained in:
Chen Liang 2025-04-29 15:42:08 +00:00
parent fa2a9d1e10
commit 5d2d1ab574
7 changed files with 132 additions and 19 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -31,6 +31,8 @@
*/
package build.tools.classlist;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
@ -59,6 +61,7 @@ public class HelloClasslist {
private static final Logger LOGGER = Logger.getLogger("Hello");
@SuppressWarnings("restricted")
public static void main(String ... args) throws Throwable {
FileSystems.getDefault();
@ -141,6 +144,7 @@ public class HelloClasslist {
HelloClasslist.class.getMethod("staticMethod_V").invoke(null);
var obj = HelloClasslist.class.getMethod("staticMethod_L_L", Object.class).invoke(null, instance);
HelloClasslist.class.getField("field").get(instance);
MethodHandles.Lookup.ClassOption.class.getEnumConstants();
// A selection of trivial and relatively common MH operations
invoke(MethodHandles.identity(double.class), 1.0);
@ -160,6 +164,9 @@ public class HelloClasslist {
case B b -> b.b;
default -> 17;
};
// record run-time methods
o.equals(new B(5));
o.hashCode();
LOGGER.log(Level.FINE, "Value: " + value);
// The Striped64$Cell is loaded rarely only when there's a contention among
@ -167,6 +174,10 @@ public class HelloClasslist {
// an inconsistency in the classlist between builds (see JDK-8295951).
// To avoid the problem, load the class explicitly.
Class<?> striped64Class = Class.forName("java.util.concurrent.atomic.Striped64$Cell");
// Initialize FFM linkers
var signature = FunctionDescriptor.ofVoid();
Linker.nativeLinker().downcallHandle(signature);
}
public HelloClasslist() {}

View File

@ -859,10 +859,11 @@ public sealed interface Linker permits AbstractLinker {
* @see #captureStateLayout()
*/
static Option captureCallState(String... capturedState) {
Set<CapturableState> set = Stream.of(Objects.requireNonNull(capturedState))
int set = Stream.of(Objects.requireNonNull(capturedState))
.map(Objects::requireNonNull)
.map(CapturableState::forName)
.collect(Collectors.toSet());
.mapToInt(state -> 1 << state.ordinal())
.sum();
return new LinkerOptions.CaptureCallState(set);
}

View File

@ -71,7 +71,20 @@ public abstract sealed class AbstractLinker implements Linker permits LinuxAArch
MemorySegment makeStub(MethodHandle target, Arena arena);
}
private record LinkRequest(FunctionDescriptor descriptor, LinkerOptions options) {}
private record LinkRequest(FunctionDescriptor descriptor, LinkerOptions options) {
// Overrides for boot performance
@Override
public boolean equals(Object obj) {
return obj instanceof LinkRequest other &&
other.descriptor.equals(descriptor) &&
other.options.equals(options);
}
@Override
public int hashCode() {
return descriptor.hashCode() * 1237 + options.hashCode();
}
}
private final SoftReferenceCache<LinkRequest, MethodHandle> DOWNCALL_CACHE = new SoftReferenceCache<>();
private final SoftReferenceCache<LinkRequest, UpcallStubFactory> UPCALL_CACHE = new SoftReferenceCache<>();
private final Set<MemoryLayout> CANONICAL_LAYOUTS_CACHE = new HashSet<>(canonicalLayouts().values());
@ -308,22 +321,30 @@ public abstract sealed class AbstractLinker implements Linker permits LinuxAArch
case StructLayout sl -> MemoryLayout.structLayout(stripNames(sl.memberLayouts()));
case UnionLayout ul -> MemoryLayout.unionLayout(stripNames(ul.memberLayouts()));
case SequenceLayout sl -> MemoryLayout.sequenceLayout(sl.elementCount(), stripNames(sl.elementLayout()));
case AddressLayout al -> al.targetLayout()
.map(tl -> al.withoutName().withTargetLayout(stripNames(tl))) // restricted
.orElseGet(al::withoutName);
case AddressLayout al -> {
var stripped = al.withoutName();
var target = al.targetLayout();
if (target.isPresent())
stripped = stripped.withTargetLayout(stripNames(target.get()));
yield stripped;
}
default -> ml.withoutName(); // ValueLayout and PaddingLayout
};
}
private static MemoryLayout[] stripNames(List<MemoryLayout> layouts) {
return layouts.stream()
.map(AbstractLinker::stripNames)
.toArray(MemoryLayout[]::new);
var ret = new MemoryLayout[layouts.size()];
for (int i = 0; i < ret.length; i++) {
ret[i] = stripNames(layouts.get(i));
}
return ret;
}
private static FunctionDescriptor stripNames(FunctionDescriptor function) {
return function.returnLayout()
.map(rl -> FunctionDescriptor.of(stripNames(rl), stripNames(function.argumentLayouts())))
.orElseGet(() -> FunctionDescriptor.ofVoid(stripNames(function.argumentLayouts())));
var retLayout = function.returnLayout();
if (retLayout.isEmpty()) {
return FunctionDescriptor.ofVoid(stripNames(function.argumentLayouts()));
}
return FunctionDescriptor.of(stripNames(retLayout.get()), stripNames(function.argumentLayouts()));
}
}

View File

@ -30,6 +30,7 @@ import jdk.internal.util.OperatingSystem;
import java.lang.foreign.MemoryLayout;
import java.lang.foreign.StructLayout;
import java.lang.foreign.ValueLayout;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -42,6 +43,11 @@ public enum CapturableState {
public static final StructLayout LAYOUT = MemoryLayout.structLayout(
supportedStates().map(CapturableState::layout).toArray(MemoryLayout[]::new));
public static final List<CapturableState> BY_ORDINAL = List.of(values());
static {
assert (BY_ORDINAL.size() < Integer.SIZE); // Update LinkerOptions.CaptureCallState
}
private final String stateName;
private final ValueLayout layout;

View File

@ -26,6 +26,7 @@ package jdk.internal.foreign.abi;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@ -137,18 +138,57 @@ public class LinkerOptions {
throw new IllegalArgumentException("Index '" + index + "' not in bounds for descriptor: " + descriptor);
}
}
@Override
public int hashCode() {
return index;
}
@Override
public boolean equals(Object obj) {
return obj instanceof FirstVariadicArg that && index == that.index;
}
}
public record CaptureCallState(Set<CapturableState> saved) implements LinkerOptionImpl {
public record CaptureCallState(int compact) implements LinkerOptionImpl {
@Override
public void validateForDowncall(FunctionDescriptor descriptor) {
// done during construction
}
public Set<CapturableState> saved() {
var set = EnumSet.noneOf(CapturableState.class);
int mask = compact;
int i = 0;
while (mask != 0) {
if ((mask & 1) == 1) {
set.add(CapturableState.BY_ORDINAL.get(i));
}
mask >>= 1;
i++;
}
return set;
}
@Override
public boolean equals(Object obj) {
return obj instanceof CaptureCallState that && compact == that.compact;
}
@Override
public int hashCode() {
return compact;
}
}
public record Critical(boolean allowHeapAccess) implements LinkerOptionImpl {
public static Critical ALLOW_HEAP = new Critical(true);
public static Critical DONT_ALLOW_HEAP = new Critical(false);
public enum Critical implements LinkerOptionImpl {
ALLOW_HEAP,
DONT_ALLOW_HEAP;
public boolean allowHeapAccess() {
return ordinal() == 0; // this == ALLOW_HEAP
}
@Override
public void validateForDowncall(FunctionDescriptor descriptor) {

View File

@ -48,7 +48,29 @@ public class NativeEntryPoint {
private record CacheKey(MethodType methodType, ABIDescriptor abi,
List<VMStorage> argMoves, List<VMStorage> retMoves,
boolean needsReturnBuffer, int capturedStateMask,
boolean needsTransition) {}
boolean needsTransition) {
@Override
public boolean equals(Object o) {
if (!(o instanceof CacheKey other)) return false;
return methodType == other.methodType && abi == other.abi && capturedStateMask == other.capturedStateMask
&& needsTransition == other.needsTransition && needsReturnBuffer == other.needsReturnBuffer
&& argMoves.equals(other.argMoves) && retMoves.equals(other.retMoves);
}
@Override
public int hashCode() {
int result = System.identityHashCode(methodType);
result = 31 * result + abi.hashCode();
result = 31 * result + argMoves.hashCode();
result = 31 * result + retMoves.hashCode();
result = 31 * result + Boolean.hashCode(needsReturnBuffer);
result = 31 * result + capturedStateMask;
result = 31 * result + Boolean.hashCode(needsTransition);
return result;
}
}
private NativeEntryPoint(MethodType methodType, long downcallStubAddress) {
this.methodType = methodType;

View File

@ -24,6 +24,8 @@
*/
package jdk.internal.foreign.abi;
import java.util.Objects;
/**
*
* @param type the type of storage. e.g. stack, or which register type (GP, FP, vector)
@ -32,7 +34,7 @@ package jdk.internal.foreign.abi;
* @param indexOrOffset the index is either a register number within a type, or
* a stack offset in bytes if type = stack.
* (a particular platform might add a bias to this in generate code)
* @param debugName the debug name
* @param debugName the debug name, mostly derived from type
*/
public record VMStorage(byte type,
short segmentMaskOrSize,
@ -43,4 +45,14 @@ public record VMStorage(byte type,
this(type, segmentMaskOrSize, indexOrOffset, "Stack@" + indexOrOffset);
}
@Override
public int hashCode() {
return Objects.hash(type, segmentMaskOrSize, indexOrOffset);
}
@Override
public boolean equals(Object obj) {
return obj instanceof VMStorage that &&
type == that.type && segmentMaskOrSize == that.segmentMaskOrSize && indexOrOffset == that.indexOrOffset;
}
}