Skip to content

Commit 8c1b915

Browse files
committed
8356126: Duplication handling and optimization of CaptureCallState
Reviewed-by: jvernee
1 parent 3f6b177 commit 8c1b915

File tree

7 files changed

+84
-91
lines changed

7 files changed

+84
-91
lines changed

src/hotspot/share/prims/downcallLinker.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232

3333
// We call this from _thread_in_native, right after a downcall
3434
JVM_LEAF(void, DowncallLinker::capture_state(int32_t* value_ptr, int captured_state_mask))
35-
// keep in synch with jdk.internal.foreign.abi.PreservableValues
35+
// keep in synch with jdk.internal.foreign.abi.CapturableState
3636
enum PreservableValues {
3737
NONE = 0,
3838
GET_LAST_ERROR = 1,

src/java.base/share/classes/java/lang/foreign/Linker.java

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,7 @@
3434

3535
import java.lang.invoke.MethodHandle;
3636
import java.util.Map;
37-
import java.util.Objects;
38-
import java.util.Set;
3937
import java.util.function.Consumer;
40-
import java.util.stream.Collectors;
41-
import java.util.stream.Stream;
4238

4339
/**
4440
* A linker provides access to foreign functions from Java code, and access to Java code
@@ -859,12 +855,11 @@ static Option firstVariadicArg(int index) {
859855
* @see #captureStateLayout()
860856
*/
861857
static Option captureCallState(String... capturedState) {
862-
int set = Stream.of(Objects.requireNonNull(capturedState))
863-
.map(Objects::requireNonNull)
864-
.map(CapturableState::forName)
865-
.mapToInt(state -> 1 << state.ordinal())
866-
.sum();
867-
return new LinkerOptions.CaptureCallState(set);
858+
int mask = 0;
859+
for (var state : capturedState) {
860+
mask |= CapturableState.maskFromName(state);
861+
}
862+
return new LinkerOptions.CaptureCallState(mask);
868863
}
869864

870865
/**

src/java.base/share/classes/jdk/internal/foreign/abi/CallingSequence.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,7 @@ public boolean hasReturnBindings() {
185185
}
186186

187187
public int capturedStateMask() {
188-
return linkerOptions.capturedCallState()
189-
.mapToInt(CapturableState::mask)
190-
.reduce(0, (a, b) -> a | b);
188+
return linkerOptions.capturedCallStateMask();
191189
}
192190

193191
public boolean needsTransition() {
Lines changed: 52 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -29,67 +29,68 @@
2929

3030
import java.lang.foreign.MemoryLayout;
3131
import java.lang.foreign.StructLayout;
32-
import java.lang.foreign.ValueLayout;
33-
import java.util.List;
34-
import java.util.stream.Collectors;
35-
import java.util.stream.Stream;
32+
import java.util.ArrayList;
33+
import java.util.Map;
3634

3735
import static java.lang.foreign.ValueLayout.JAVA_INT;
3836

39-
public enum CapturableState {
40-
GET_LAST_ERROR ("GetLastError", JAVA_INT, 1 << 0, OperatingSystem.isWindows()),
41-
WSA_GET_LAST_ERROR("WSAGetLastError", JAVA_INT, 1 << 1, OperatingSystem.isWindows()),
42-
ERRNO ("errno", JAVA_INT, 1 << 2, true);
37+
/**
38+
* Utility class for the call states to capture.
39+
*/
40+
public final class CapturableState {
4341

44-
public static final StructLayout LAYOUT = MemoryLayout.structLayout(
45-
supportedStates().map(CapturableState::layout).toArray(MemoryLayout[]::new));
46-
public static final List<CapturableState> BY_ORDINAL = List.of(values());
42+
public static final StructLayout LAYOUT;
43+
// Keep in synch with DowncallLinker::capture_state in downcallLinker.cpp
44+
private static final Map<String, Integer> MASKS;
4745

4846
static {
49-
assert (BY_ORDINAL.size() < Integer.SIZE); // Update LinkerOptions.CaptureCallState
50-
}
51-
52-
private final String stateName;
53-
private final ValueLayout layout;
54-
private final int mask;
55-
private final boolean isSupported;
56-
57-
CapturableState(String stateName, ValueLayout layout, int mask, boolean isSupported) {
58-
this.stateName = stateName;
59-
this.layout = layout.withName(stateName);
60-
this.mask = mask;
61-
this.isSupported = isSupported;
62-
}
63-
64-
private static Stream<CapturableState> supportedStates() {
65-
return Stream.of(values()).filter(CapturableState::isSupported);
66-
}
67-
68-
public static CapturableState forName(String name) {
69-
return Stream.of(values())
70-
.filter(stl -> stl.stateName().equals(name))
71-
.filter(CapturableState::isSupported)
72-
.findAny()
73-
.orElseThrow(() -> new IllegalArgumentException(
74-
"Unknown name: " + name +", must be one of: "
75-
+ supportedStates()
76-
.map(CapturableState::stateName)
77-
.collect(Collectors.joining(", "))));
78-
}
79-
80-
public String stateName() {
81-
return stateName;
47+
if (OperatingSystem.isWindows()) {
48+
LAYOUT = MemoryLayout.structLayout(
49+
JAVA_INT.withName("GetLastError"),
50+
JAVA_INT.withName("WSAGetLastError"),
51+
JAVA_INT.withName("errno"));
52+
MASKS = Map.of(
53+
"GetLastError", 1 << 0,
54+
"WSAGetLastError", 1 << 1,
55+
"errno", 1 << 2
56+
);
57+
} else {
58+
LAYOUT = MemoryLayout.structLayout(
59+
JAVA_INT.withName("errno"));
60+
MASKS = Map.of(
61+
"errno", 1 << 2
62+
);
63+
}
8264
}
8365

84-
public ValueLayout layout() {
85-
return layout;
66+
private CapturableState() {
8667
}
8768

88-
public int mask() {
89-
return mask;
69+
/**
70+
* Returns the mask for a supported capturable state, or throw an
71+
* IllegalArgumentException if no supported state with this name exists.
72+
*/
73+
public static int maskFromName(String name) {
74+
var ret = MASKS.get(name);
75+
if (ret == null) {
76+
throw new IllegalArgumentException(
77+
"Unknown name: " + name + ", must be one of: "
78+
+ MASKS.keySet());
79+
}
80+
return ret;
9081
}
9182

92-
public boolean isSupported() {
93-
return isSupported;
83+
/**
84+
* Returns a collection-like display string for a captured state mask.
85+
* Enclosed with brackets.
86+
*/
87+
public static String displayString(int mask) {
88+
var displayList = new ArrayList<>(); // unordered
89+
for (var e : MASKS.entrySet()) {
90+
if ((mask & e.getValue()) != 0) {
91+
displayList.add(e.getKey());
92+
}
93+
}
94+
return displayList.toString();
9495
}
9596
}

src/java.base/share/classes/jdk/internal/foreign/abi/LinkerOptions.java

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -84,9 +84,9 @@ public boolean hasCapturedCallState() {
8484
return getOption(CaptureCallState.class) != null;
8585
}
8686

87-
public Stream<CapturableState> capturedCallState() {
87+
public int capturedCallStateMask() {
8888
CaptureCallState stl = getOption(CaptureCallState.class);
89-
return stl == null ? Stream.empty() : stl.saved().stream();
89+
return stl == null ? 0 : stl.mask();
9090
}
9191

9292
public boolean isVariadicFunction() {
@@ -150,35 +150,25 @@ public boolean equals(Object obj) {
150150
}
151151
}
152152

153-
public record CaptureCallState(int compact) implements LinkerOptionImpl {
153+
public record CaptureCallState(int mask) implements LinkerOptionImpl {
154154
@Override
155155
public void validateForDowncall(FunctionDescriptor descriptor) {
156156
// done during construction
157157
}
158158

159-
public Set<CapturableState> saved() {
160-
var set = EnumSet.noneOf(CapturableState.class);
161-
int mask = compact;
162-
int i = 0;
163-
while (mask != 0) {
164-
if ((mask & 1) == 1) {
165-
set.add(CapturableState.BY_ORDINAL.get(i));
166-
}
167-
mask >>= 1;
168-
i++;
169-
}
170-
return set;
171-
}
172-
173-
174159
@Override
175160
public boolean equals(Object obj) {
176-
return obj instanceof CaptureCallState that && compact == that.compact;
161+
return obj instanceof CaptureCallState that && mask == that.mask;
177162
}
178163

179164
@Override
180165
public int hashCode() {
181-
return compact;
166+
return mask;
167+
}
168+
169+
@Override
170+
public String toString() {
171+
return "CaptureCallState" + CapturableState.displayString(mask);
182172
}
183173
}
184174

src/java.base/share/classes/jdk/internal/foreign/abi/fallback/FallbackLinker.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,7 @@ protected MethodHandle arrangeDowncall(MethodType inferredMethodType, FunctionDe
8484
assertNotEmpty(function);
8585
MemorySegment cif = makeCif(inferredMethodType, function, options, Arena.ofAuto());
8686

87-
int capturedStateMask = options.capturedCallState()
88-
.mapToInt(CapturableState::mask)
89-
.reduce(0, (a, b) -> a | b);
87+
int capturedStateMask = options.capturedCallStateMask();
9088
DowncallData invData = new DowncallData(cif, function.returnLayout().orElse(null),
9189
function.argumentLayouts(), capturedStateMask, options.allowsHeapAccess());
9290

test/jdk/java/foreign/capturecallstate/TestCaptureCallState.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
/*
2525
* @test
26+
* @bug 8356126
2627
* @library ../ /test/lib
2728
* @run testng/othervm/native --enable-native-access=ALL-UNNAMED TestCaptureCallState
2829
*/
@@ -47,8 +48,7 @@
4748
import static java.lang.foreign.ValueLayout.JAVA_DOUBLE;
4849
import static java.lang.foreign.ValueLayout.JAVA_INT;
4950
import static java.lang.foreign.ValueLayout.JAVA_LONG;
50-
import static org.testng.Assert.assertEquals;
51-
import static org.testng.Assert.assertTrue;
51+
import static org.testng.Assert.*;
5252

5353
public class TestCaptureCallState extends NativeTestHelper {
5454

@@ -61,6 +61,17 @@ public class TestCaptureCallState extends NativeTestHelper {
6161
}
6262
}
6363

64+
// Basic sanity tests around Java API contracts
65+
@Test
66+
public void testApiContracts() {
67+
assertThrows(IllegalArgumentException.class, () -> Linker.Option.captureCallState("Does not exist"));
68+
var duplicateOpt = Linker.Option.captureCallState("errno", "errno"); // duplicates
69+
var noDuplicateOpt = Linker.Option.captureCallState("errno");
70+
assertEquals(duplicateOpt, noDuplicateOpt, "auto deduplication");
71+
var display = duplicateOpt.toString();
72+
assertTrue(display.contains("errno"), "toString should contain state name 'errno': " + display);
73+
}
74+
6475
private record SaveValuesCase(String nativeTarget, FunctionDescriptor nativeDesc, String threadLocalName,
6576
Consumer<Object> resultCheck, boolean critical) {}
6677

0 commit comments

Comments
 (0)