-
Bug
-
Resolution: Fixed
-
P2
-
22, 23
-
b23
-
Verified
Atomic operations (such as `VarHandle::getVolatile` and `VarHandle::setVolatile`) fail if derived from a memory layout enclosed in another composite layout (such as a StructLayout).
The reason for this is in `LayoutPath::dereferenceHandle`. Instead of just providing a layout with an alignment of 1, we need to consider both the enclosing layout(s) (transitively) and the current element.
Reproducer:
```
/*
* @test
* @run junit TestAtomicVarHandle
*/
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.lang.foreign.Arena;
import java.lang.foreign.MemoryLayout;
import java.lang.invoke.VarHandle;
import java.util.stream.Stream;
import static java.lang.foreign.ValueLayout.JAVA_INT;
import static org.junit.jupiter.api.Assertions.*;
final class TestAtomicVarHandle {
static final VarHandle INT_VH = JAVA_INT.varHandle();
static final VarHandle INT_IN_SUPER_ALIGNED_VH = MemoryLayout.structLayout(JAVA_INT.withName("i"))
.withByteAlignment(32)
.varHandle(MemoryLayout.PathElement.groupElement("i"));
@ParameterizedTest
@MethodSource("methodHandles")
void test(VarHandle vh, String name) {
try (Arena arena = Arena.ofConfined()) {
var segment = arena.allocate(1000, 64);
int value = (int)vh.getVolatile(segment, 0L);
assertEquals(0, value);
vh.set(segment, 0L, 42);
value = (int)vh.getVolatile(segment, 0L);
assertEquals(42, value);
}
}
private static Stream<Arguments> methodHandles() {
return Stream.of(
Arguments.of(INT_VH, "INT_VH"),
Arguments.of(INT_IN_SUPER_ALIGNED_VH, "INT_IN_SUPER_ALIGNED_VH")
);
}
}
STARTED TestAtomicVarHandle::test '[1] VarHandle[varType=int, coord=[interface java.lang.foreign.MemorySegment, long]], INT_VH'
SUCCESSFUL TestAtomicVarHandle::test '[1] VarHandle[varType=int, coord=[interface java.lang.foreign.MemorySegment, long]], INT_VH'
STARTED TestAtomicVarHandle::test '[2] VarHandle[varType=int, coord=[interface java.lang.foreign.MemorySegment, long]], INT_IN_SUPER_ALIGNED_VH'
java.lang.UnsupportedOperationException: Unsupported access mode for alignment: 1
at java.base/java.lang.invoke.VarHandleSegmentViewBase.newUnsupportedAccessModeForAlignment(VarHandleSegmentViewBase.java:63)
at java.base/java.lang.invoke.VarHandleSegmentAsInts.offsetNonPlain(VarHandleSegmentAsInts.java:88)
at java.base/java.lang.invoke.VarHandleSegmentAsInts.getVolatile(VarHandleSegmentAsInts.java:132)
at TestAtomicVarHandle.test(TestAtomicVarHandle.java:54)
```
Workaround:
```
static final VarHandle INDEX =
//HEADER.varHandle(PathElement.groupElement("index")); // Original
offset(JAVA_INT.varHandle(), HEADER.byteOffset(PathElement.groupElement("index"))); // <-- FIX
private static VarHandle offset(VarHandle target, long offset) {
MethodHandle mh = MethodHandles.insertArguments(MH_ADD, 0, offset);
return MethodHandles.collectCoordinates(target, 1, mh);
}
private static final MethodHandle MH_ADD;
static {
MethodHandles.Lookup lookup = MethodHandles.lookup();
try {
MH_ADD = lookup.findStatic(Long.class, "sum",
MethodType.methodType(long.class, long.class, long.class));
} catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
}
```
The reason for this is in `LayoutPath::dereferenceHandle`. Instead of just providing a layout with an alignment of 1, we need to consider both the enclosing layout(s) (transitively) and the current element.
Reproducer:
```
/*
* @test
* @run junit TestAtomicVarHandle
*/
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.lang.foreign.Arena;
import java.lang.foreign.MemoryLayout;
import java.lang.invoke.VarHandle;
import java.util.stream.Stream;
import static java.lang.foreign.ValueLayout.JAVA_INT;
import static org.junit.jupiter.api.Assertions.*;
final class TestAtomicVarHandle {
static final VarHandle INT_VH = JAVA_INT.varHandle();
static final VarHandle INT_IN_SUPER_ALIGNED_VH = MemoryLayout.structLayout(JAVA_INT.withName("i"))
.withByteAlignment(32)
.varHandle(MemoryLayout.PathElement.groupElement("i"));
@ParameterizedTest
@MethodSource("methodHandles")
void test(VarHandle vh, String name) {
try (Arena arena = Arena.ofConfined()) {
var segment = arena.allocate(1000, 64);
int value = (int)vh.getVolatile(segment, 0L);
assertEquals(0, value);
vh.set(segment, 0L, 42);
value = (int)vh.getVolatile(segment, 0L);
assertEquals(42, value);
}
}
private static Stream<Arguments> methodHandles() {
return Stream.of(
Arguments.of(INT_VH, "INT_VH"),
Arguments.of(INT_IN_SUPER_ALIGNED_VH, "INT_IN_SUPER_ALIGNED_VH")
);
}
}
STARTED TestAtomicVarHandle::test '[1] VarHandle[varType=int, coord=[interface java.lang.foreign.MemorySegment, long]], INT_VH'
SUCCESSFUL TestAtomicVarHandle::test '[1] VarHandle[varType=int, coord=[interface java.lang.foreign.MemorySegment, long]], INT_VH'
STARTED TestAtomicVarHandle::test '[2] VarHandle[varType=int, coord=[interface java.lang.foreign.MemorySegment, long]], INT_IN_SUPER_ALIGNED_VH'
java.lang.UnsupportedOperationException: Unsupported access mode for alignment: 1
at java.base/java.lang.invoke.VarHandleSegmentViewBase.newUnsupportedAccessModeForAlignment(VarHandleSegmentViewBase.java:63)
at java.base/java.lang.invoke.VarHandleSegmentAsInts.offsetNonPlain(VarHandleSegmentAsInts.java:88)
at java.base/java.lang.invoke.VarHandleSegmentAsInts.getVolatile(VarHandleSegmentAsInts.java:132)
at TestAtomicVarHandle.test(TestAtomicVarHandle.java:54)
```
Workaround:
```
static final VarHandle INDEX =
//HEADER.varHandle(PathElement.groupElement("index")); // Original
offset(JAVA_INT.varHandle(), HEADER.byteOffset(PathElement.groupElement("index"))); // <-- FIX
private static VarHandle offset(VarHandle target, long offset) {
MethodHandle mh = MethodHandles.insertArguments(MH_ADD, 0, offset);
return MethodHandles.collectCoordinates(target, 1, mh);
}
private static final MethodHandle MH_ADD;
static {
MethodHandles.Lookup lookup = MethodHandles.lookup();
try {
MH_ADD = lookup.findStatic(Long.class, "sum",
MethodType.methodType(long.class, long.class, long.class));
} catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
}
```