Skip to content

Commit 7cb043e

Browse files
committed
Lower segment-view VarHandle accesses in UnsafeFastPath in JDK 21+
UnsafeFastPath already converts jdk.internal.misc.Unsafe.get/put calls reached from the array, instance-field and static-field VarHandle operation methods into direct loads and stores. The MemorySegment view VarHandle operation classes: VarHandleSegmentAsBytes, AsChars, AsShorts, AsInts, AsLongs, AsFloats and AsDoubles, have a similar shape but were not recognized, so an access made through a segment-view VarHandle remained as an Unsafe call instead of folding to a direct access. This commit adds an umbrella recognizer, java_lang_invoke_VarHandleSegmentAsX_method, which is then used in UnsafeFastPath. A few notes: * The access base is null for a native segment and a primitive array for a heap segment. Both reduce to a single aladd(base, offset) load/store only while arrays are contiguous and on-heap, so the method is lowered only when arraylets and off-heap allocation are ruled out, the access is treated as a packed array element, and the base is not marked non-null (it may actually be null). * The Unsafe.get/putXUnaligned wrappers, used by the view classes for the unaligned element shapes, are reduced to a single load/store for packed-element callers on targets that allow misaligned accesses. This reduces the runtime overhead of MemorySegment accesses that reach the VarHandle path rather than the direct-lowering fast path mechanism that will be added in the near future. Signed-off-by: Nazim Bhuiyan <nubhuiyan@ibm.com>
1 parent 355d0aa commit 7cb043e

3 files changed

Lines changed: 107 additions & 5 deletions

File tree

runtime/compiler/codegen/J9RecognizedMethodsEnum.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,7 @@ FirstJ9Method = LastOMRMethod + 1,
695695

696696
java_lang_invoke_VarHandleByteArrayAsX_ArrayHandle_method,
697697
java_lang_invoke_VarHandleByteArrayAsX_ByteBufferHandle_method,
698+
java_lang_invoke_VarHandleSegmentAsX_method,
698699

699700
jdk_internal_foreign_layout_ValueLayouts_AbstractValueLayout_accessHandle,
700701
jdk_internal_foreign_AbstractMemorySegmentImpl_reinterpret, java_lang_foreign_MemorySegment_method,

runtime/compiler/env/j9method.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4540,6 +4540,11 @@ void TR_ResolvedJ9Method::construct()
45404540
&& !strncmp(className, "java/lang/invoke/VarHandleByteArrayAsShorts$ByteBufferHandle",
45414541
60))) {
45424542
setRecognizedMethodInfo(TR::java_lang_invoke_VarHandleByteArrayAsX_ByteBufferHandle_method);
4543+
} else if (classNameLen >= 39 && classNameLen <= 42
4544+
&& !strncmp(className, "java/lang/invoke/VarHandleSegmentAs", 35)) {
4545+
// The memory segment view VarHandle operation classes
4546+
// VarHandleSegmentAsBytes/Chars/Shorts/Ints/Longs/Floats/Doubles).
4547+
setRecognizedMethodInfo(TR::java_lang_invoke_VarHandleSegmentAsX_method);
45434548
}
45444549
} else if ((classNameLen == 31) && !strncmp(className, "java/lang/foreign/MemorySegment", 31)) {
45454550
if (nameLen >= 3 && (!strncmp(name, "get", 3) || !strncmp(name, "set", 3)))
@@ -5784,6 +5789,7 @@ bool TR_J9MethodBase::isVarHandleOperationMethod(TR::RecognizedMethod rm)
57845789
case TR::java_lang_invoke_VarHandleX_FieldStaticReadOnlyOrReadWrite_method:
57855790
case TR::java_lang_invoke_VarHandleByteArrayAsX_ArrayHandle_method:
57865791
case TR::java_lang_invoke_VarHandleByteArrayAsX_ByteBufferHandle_method:
5792+
case TR::java_lang_invoke_VarHandleSegmentAsX_method:
57875793
#else
57885794
case TR::java_lang_invoke_ArrayVarHandle_ArrayVarHandleOperations_OpMethod:
57895795
case TR::java_lang_invoke_StaticFieldVarHandle_StaticFieldVarHandleOperations_OpMethod:

runtime/compiler/optimizer/UnsafeFastPath.cpp

Lines changed: 100 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,26 @@
8080
//
8181
static bool isVarHandleOperationMethod(TR::RecognizedMethod rm)
8282
{
83-
return TR_J9MethodBase::isVarHandleOperationMethod(rm)
84-
&& rm != TR::java_lang_invoke_VarHandleByteArrayAsX_ByteBufferHandle_method;
83+
if (rm == TR::java_lang_invoke_VarHandleByteArrayAsX_ByteBufferHandle_method)
84+
return false;
85+
86+
#if defined(J9VM_OPT_OPENJDK_METHODHANDLE)
87+
// The memory segment view operation methods perform the access on a base that is
88+
// either null (NativeMemorySegmentImpl, raw native address) or a primitive array
89+
// (HeapMemorySegmentImpl). Both shapes can be lowered to the same aladd(base, offset)
90+
// load/store only while arrays are contiguous and on-heap, so give up when arraylets
91+
// or off-heap allocation are possible.
92+
if (rm == TR::java_lang_invoke_VarHandleSegmentAsX_method) {
93+
if (TR::Compiler->om.canGenerateArraylets())
94+
return false;
95+
#if defined(J9VM_GC_SPARSE_HEAP_ALLOCATION)
96+
if (TR::Compiler->om.isOffHeapAllocationEnabled())
97+
return false;
98+
#endif /* J9VM_GC_SPARSE_HEAP_ALLOCATION */
99+
}
100+
#endif /* J9VM_OPT_OPENJDK_METHODHANDLE */
101+
102+
return TR_J9MethodBase::isVarHandleOperationMethod(rm);
85103
}
86104

87105
static TR::RecognizedMethod getVarHandleAccessMethodFromInlinedCallStack(TR::Compilation *comp, TR::Node *node)
@@ -182,6 +200,7 @@ static bool isVarHandleOperationMethodOnArray(TR::RecognizedMethod rm)
182200

183201
case TR::java_lang_invoke_VarHandleX_Array_method:
184202
case TR::java_lang_invoke_VarHandleByteArrayAsX_ArrayHandle_method:
203+
case TR::java_lang_invoke_VarHandleSegmentAsX_method:
185204
#else
186205
case TR::java_lang_invoke_ArrayVarHandle_ArrayVarHandleOperations_OpMethod:
187206
case TR::java_lang_invoke_ByteArrayViewVarHandle_ByteArrayViewVarHandleOperations_OpMethod:
@@ -370,6 +389,63 @@ bool TR_UnsafeFastPath::tryTransformUnsafeAtomicCallInVarHandleAccessMethod(TR::
370389
return true;
371390
}
372391

392+
// The Unsafe.getXUnaligned(Object, long) / putXUnaligned(Object, long, X) wrappers are
393+
// plain Java methods whose bodies re-assemble the value byte- or short-wise when the
394+
// offset may be misaligned. On targets that support misaligned accesses they can be
395+
// lowered to a single load/store just like the corresponding aligned natives, but they
396+
// are not part of isUnsafeGetPutWithObjectArg(), so they get their own helpers here.
397+
static bool isUnalignedUnsafeGetPut(TR::RecognizedMethod rm)
398+
{
399+
switch (rm) {
400+
case TR::jdk_internal_misc_Unsafe_getCharUnaligned:
401+
case TR::jdk_internal_misc_Unsafe_getShortUnaligned:
402+
case TR::jdk_internal_misc_Unsafe_getIntUnaligned:
403+
case TR::jdk_internal_misc_Unsafe_getLongUnaligned:
404+
case TR::jdk_internal_misc_Unsafe_putCharUnaligned:
405+
case TR::jdk_internal_misc_Unsafe_putShortUnaligned:
406+
case TR::jdk_internal_misc_Unsafe_putIntUnaligned:
407+
case TR::jdk_internal_misc_Unsafe_putLongUnaligned:
408+
return true;
409+
default:
410+
return false;
411+
}
412+
return false;
413+
}
414+
415+
static bool isUnalignedUnsafePut(TR::RecognizedMethod rm)
416+
{
417+
switch (rm) {
418+
case TR::jdk_internal_misc_Unsafe_putCharUnaligned:
419+
case TR::jdk_internal_misc_Unsafe_putShortUnaligned:
420+
case TR::jdk_internal_misc_Unsafe_putIntUnaligned:
421+
case TR::jdk_internal_misc_Unsafe_putLongUnaligned:
422+
return true;
423+
default:
424+
return false;
425+
}
426+
return false;
427+
}
428+
429+
static TR::DataType dataTypeForUnalignedUnsafeGetPut(TR::RecognizedMethod rm)
430+
{
431+
switch (rm) {
432+
case TR::jdk_internal_misc_Unsafe_getCharUnaligned:
433+
case TR::jdk_internal_misc_Unsafe_getShortUnaligned:
434+
case TR::jdk_internal_misc_Unsafe_putCharUnaligned:
435+
case TR::jdk_internal_misc_Unsafe_putShortUnaligned:
436+
return TR::Int16;
437+
case TR::jdk_internal_misc_Unsafe_getIntUnaligned:
438+
case TR::jdk_internal_misc_Unsafe_putIntUnaligned:
439+
return TR::Int32;
440+
case TR::jdk_internal_misc_Unsafe_getLongUnaligned:
441+
case TR::jdk_internal_misc_Unsafe_putLongUnaligned:
442+
return TR::Int64;
443+
default:
444+
TR_ASSERT(false, "Method is not an unaligned unsafe get/put\n");
445+
}
446+
return TR::NoType;
447+
}
448+
373449
static bool needUnsignedConversion(TR::RecognizedMethod methodToReduce)
374450
{
375451
switch (methodToReduce) {
@@ -378,6 +454,7 @@ static bool needUnsignedConversion(TR::RecognizedMethod methodToReduce)
378454
case TR::com_ibm_jit_JITHelpers_getCharFromArrayVolatile:
379455
case TR::java_lang_StringUTF16_getChar:
380456
case TR::java_lang_StringUTF16_putChar:
457+
case TR::jdk_internal_misc_Unsafe_getCharUnaligned:
381458
return true;
382459

383460
default:
@@ -751,18 +828,27 @@ int32_t TR_UnsafeFastPath::perform()
751828
if (recognizedVarHandleOpMethod != TR::unknownMethod)
752829
callerMethod = recognizedVarHandleOpMethod;
753830
TR::RecognizedMethod calleeMethod = symbol->getRecognizedMethod();
754-
if (isKnownUnsafeCaller(callerMethod) && TR_J9MethodBase::isUnsafeGetPutWithObjectArg(calleeMethod)) {
831+
// The single load/store an unaligned wrapper reduces to uses the packed element
832+
// shape, so only callers known to access packed elements (byte array views and
833+
// memory segments) qualify, and only on targets that allow misaligned accesses.
834+
bool isKnownCaller = isKnownUnsafeCaller(callerMethod);
835+
bool isUnalignedCallee = isKnownCaller && isUnalignedUnsafeGetPut(calleeMethod)
836+
&& !comp()->cg()->getSupportsAlignedAccessOnly() && isUnsafeCallerAccessingArrayElement(callerMethod);
837+
if (isKnownCaller
838+
&& (TR_J9MethodBase::isUnsafeGetPutWithObjectArg(calleeMethod) || isUnalignedCallee)) {
755839
if (isUnsafeCallerAccessingStaticField(callerMethod))
756840
isStatic = true;
757841
if (isUnsafeCallerAccessingArrayElement(callerMethod))
758842
isArrayOperation = true;
759843

760-
if (isArrayOperation)
844+
if (isUnalignedCallee)
845+
type = dataTypeForUnalignedUnsafeGetPut(calleeMethod);
846+
else if (isArrayOperation)
761847
type = TR_J9MethodBase::unsafeDataTypeForArray(calleeMethod);
762848
else
763849
type = TR_J9MethodBase::unsafeDataTypeForObject(calleeMethod);
764850

765-
if (TR_J9MethodBase::isUnsafePut(calleeMethod))
851+
if (TR_J9MethodBase::isUnsafePut(calleeMethod) || isUnalignedUnsafePut(calleeMethod))
766852
value = node->getChild(3);
767853

768854
if (TR_J9MethodBase::isVolatileUnsafe(calleeMethod))
@@ -813,7 +899,16 @@ int32_t TR_UnsafeFastPath::perform()
813899
}
814900

815901
object = node->getChild(objectChild);
902+
#if defined(J9VM_OPT_OPENJDK_METHODHANDLE)
903+
// A MemorySegment access base is null for native segments, so it must not
904+
// be marked non-null. The address computation below is correct either way:
905+
// null base + absolute offset, or array base (non-null) + element offset.
906+
if (callerMethod != TR::java_lang_invoke_VarHandleSegmentAsX_method) {
907+
object->setIsNonNull(true);
908+
}
909+
#else /* J9VM_OPT_OPENJDK_METHODHANDLE */
816910
object->setIsNonNull(true);
911+
#endif /* J9VM_OPT_OPENJDK_METHODHANDLE */
817912
if (isStatic) {
818913
TR::Node *jlClass = object;
819914
TR::Node *j9Class = TR::Node::createWithSymRef(TR::aloadi, 1, 1, jlClass,

0 commit comments

Comments
 (0)