Skip to content

Improve x86 inline checkcast/instanceof sequences for array cast classes#23383

Open
0xdaryl wants to merge 2 commits intoeclipse-openj9:masterfrom
0xdaryl:arraycheckcast
Open

Improve x86 inline checkcast/instanceof sequences for array cast classes#23383
0xdaryl wants to merge 2 commits intoeclipse-openj9:masterfrom
0xdaryl:arraycheckcast

Conversation

@0xdaryl
Copy link
Contributor

@0xdaryl 0xdaryl commented Feb 20, 2026

This PR makes two contributions to JIT generated code on x86-64:

  1. Inline instanceof checks when cast class is [java/lang/Object

Extend the current x86 checkcast optimization when the cast class is a
java/lang/Object array to handle instanceof and isAssignableFrom() cases.

  1. Inline checkcast/instanceof checks when the cast class is a known array on x86

Essentially inlines the checkcast/instanceof/isAssignableFrom() sequence when the
cast class is an array that the VM implements [1], but specializes it for when the
cast class is an array known at compile-time. It performs:

  • An exact equality check
  • A check for a match in the cast class cache
  • An arity check followed by a subclass check on the array leaf components when the
    arities are the same
  • Updates the cast class cache on success/failure in the same manner that the VM
    implementation does

Any “unusual” cases are punted to the VM (e.g., mismatched arities, when the leaf
class is an interface), as well as to throw the CastClassException if required.

The opt can be disabled by setting TR_DisableInlineArrayExactCastClass.

[1]

inlineCheckCast(J9Class *instanceClass, J9Class *castClass, bool updateCache = true)

Extend the current x86 checkcast optimization when the cast class is a
java/lang/Object array to handle instanceof and isAssignableFrom() cases.

Signed-off-by: Daryl Maier <maier@ca.ibm.com>
…ay on x86

Essentially inlines the checkcast/instanceof/isAssignableFrom() sequence when the
cast class is an array that the VM implements [1], but specializes it for when the
cast class is an array known at compile-time.  It performs:

* An exact equality check
* A check for a match in the cast class cache
* An arity check followed by a subclass check on the array leaf components when the
  arities are the same
* Updates the cast class cache on success/failure in the same manner that the VM
  implementation does

Any “unusual” cases are punted to the VM (e.g., mismatched arities, when the leaf
class is an interface), as well as to throw the CastClassException if required.

The opt can be disabled by setting `TR_DisableInlineArrayExactCastClass`.

[1] https://github.com/eclipse-openj9/openj9/blob/9698be0c19fdc436c2fd6a0588dbafcfc0dd76fa/runtime/oti/VMHelpers.hpp#L589

Signed-off-by: Daryl Maier <maier@ca.ibm.com>
@0xdaryl
Copy link
Contributor Author

0xdaryl commented Feb 20, 2026

@a7ehuo : may I ask you to review this please? @vijaysun-omr FYI

Copy link
Contributor

@a7ehuo a7ehuo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is my review on the first commit. I'll review the second commit next Monday.

logprintf(comp->getOption(TR_TraceCG), comp->log(), "Inline checkcast for [jlO : node=%p", node);
if (reportInlineObjectArrayCheck)
{
OMR::CStdIOStreamLogger::Stdout->printf("XXXXX Inline checkcast for [jlO : isCheckCast=%d (icall=%d) : %s\n", isCheckCast, (node->getOpCodeValue() == TR::icall), comp->signature());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. What is XXXXX in the trace for? Should it be removed?

  2. Is OMR::CStdIOStreamLogger logging into verbose vlog, or onto the console, or into compilation log? The reason I'm asking is that if this information should be logged into the compilation log as well at line 4164 if comp->getOption(TR_TraceCG) is true.

  3. The message Inline checkcast for ... might be confusing since it could be instanceof or isAssignableFrom cases.

static char *disableInlineObjectArrayCheck = feGetEnv("TR_DisableInlineObjectArrayCheck");

if (!disableInlineObjectArrayCheckCast && isCheckCast && clazz && TR::Compiler->cls.isClassArray(comp, clazz))
bool isRelocatableCompile = comp->compileRelocatableCode() || comp->isOutOfProcessCompilation();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isRelocatableCompile is not used in this commit. It should be removed in this commit. (I assumed likely it'd be used in the next commit)

generateMemImmInstruction(TR::InstOpCode::TEST4MemImm4, node,
generateX86MemoryReference(romClassReg, offsetof(J9ROMClass, modifiers), cg), J9AccClassArray, cg);
generateLabelInstruction(TR::InstOpCode::JE4, node, outlinedCallLabel, cg);
generateLabelInstruction(TR::InstOpCode::JE4, node, outlinedHelperCallLabel, cg);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this line could be updated as below to avoid going to the helper if it is not array for instanceof and isAssignableFrom cases since resultReg has already been initialized as zero.

generateLabelInstruction(TR::InstOpCode::JE4, node, isCheckCast ? outlinedHelperCallLabel : fallThruLabel, cg);

generateMemImmInstruction(TR::InstOpCode::TEST4MemImm4, node,
generateX86MemoryReference(romClassReg, offsetof(J9ROMClass, modifiers), cg), J9AccClassInternalPrimitiveType, cg);
generateLabelInstruction(TR::InstOpCode::JNE4, node, outlinedCallLabel, cg);
generateLabelInstruction(TR::InstOpCode::JNE4, node, outlinedHelperCallLabel, cg);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this line could be updated as below to avoid going to the helper if it is primitive array for instanceof and isAssignableFrom cases since resultReg has already been initialized as zero.

generateLabelInstruction(TR::InstOpCode::JNE4, node, isCheckCast ? outlinedHelperCallLabel : fallThruLabel, cg);

Comment on lines 4200 to 4201
if (!objectNode->isNonNull())
{
Copy link
Contributor

@a7ehuo a7ehuo Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be a problem for isAssignableFrom(Class<?> cls)? If cls is null, NullPointerException (not return false) should be thrown, but the current behaviour would return 0/false by jumping to fallThruLabel.

         // If the object is NULL, no exception is thrown for a checkcast and a 0
         // is returned for an instanceof.
         //
         if (!objectNode->isNonNull())
            {
            generateRegRegInstruction(TR::InstOpCode::TESTRegReg(), node, objectReg, objectReg, cg);
            generateLabelInstruction(TR::InstOpCode::JE4, node, fallThruLabel, cg);
            }

@vijaysun-omr
Copy link
Contributor

Is it possible to show the sequence from a JIT trace log after instruction selection (to help with review) ? Thanks

Copy link
Contributor

@a7ehuo a7ehuo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is the second half of the review

OMR::CStdIOStreamLogger::Stdout->printf("YYYYY Found inlineArrayExactCastClass : isCheckCast=%d : %s\n", isCheckCast, comp->signature());
}

logprintf(comp->getOption(TR_TraceCG), comp->log(), "Inline instanceof/checkcast for const cast class array: node=%p", node);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could the compilation log be updated to include isCheckCast value and mention isAssignableFrom case?

//
if (reportInlineArrayExactCastClass)
{
OMR::CStdIOStreamLogger::Stdout->printf("YYYYY Found inlineArrayExactCastClass : isCheckCast=%d : %s\n", isCheckCast, comp->signature());
Copy link
Contributor

@a7ehuo a7ehuo Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does YYYYY refer to? Should it be removed or updated to something more meaningful?

Comment on lines +4392 to +4401
if (!objectNode->isNonNull())
{
generateRegRegInstruction(TR::InstOpCode::TESTRegReg(), node, objectReg, objectReg, cg);

// checkcast leaves the operand stack unaffected
// instanceof returns 0 if the objectRef is null
//
TR::LabelSymbol *nullTargetLabel = isCheckCast ? fallThruLabel : notCastableDoNotCacheLabel;
generateLabelInstruction(TR::InstOpCode::JE4, node, nullTargetLabel, cg);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does TR::icall corresponds directly to Java Class.isAssignableFrom? Will objectNode always be non null if the node is TR::icall? The reason I'm asking is that If cls is null, NullPointerException should be thrown.

TR::Register *scratchReg2 = NULL;
TR::Register *scratchReg3 = NULL;

bool use64BitClasses = cg->comp()->target().is64Bit() && !TR::Compiler->om.generateCompressedObjectHeaders();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The check cg->comp()->target().is64Bit() is redundant since this whole case 2 are wrapped under it

      else if (!disableInlineArrayExactCastClass && !isRelocatableCompile && cg->comp()->target().is64Bit())

if (!scratchReg)
scratchReg = cg->allocateRegister();

generateRegMemInstruction(TR::InstOpCode::LRegMem(), node, scratchReg,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it need to consider if use64BitClasses is true or false when generating TR::InstOpCode::LRegMem?

Comment on lines +4540 to +4550
if (IS_32BIT_SIGNED(componentClazzAddress))
{
// TODO: Need a relocation for componentClazz
generateRegImmInstruction(TR::InstOpCode::CMPRegImm4(), node, objectClassLeafReg, (int32_t)componentClazzAddress, cg);
}
else
{
// TODO: Need a relocation for componentClazz
generateRegImm64Instruction(TR::InstOpCode::MOV8RegImm64, node, scratchReg, componentClazzAddress, cg);
generateRegRegInstruction(TR::InstOpCode::CMPRegReg(), node, objectClassLeafReg, scratchReg, cg);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it explicitly consider use64BitClasses being true and false cases here?

Comment on lines +4495 to +4497
intptr_t castClassArity = ((J9ArrayClass*)clazz)->arity;

generateRegImmInstruction(TR::InstOpCode::CMPRegImm4(), node, scratchReg, (int32_t)castClassArity, cg);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it necessary to cast ((J9ArrayClass*)clazz)->arity from UDATA → intptr_t → int32_t? Should it be cast directly from UDATA → int32_t?

#if defined(J9VM_OPT_VALHALLA_FLATTENABLE_VALUE_TYPES)
if (J9_IS_J9ARRAYCLASS_NULL_RESTRICTED(castClass))
{
static_assert(J9ClassArrayIsNullRestricted == 0x2000000, "Cannot do simple bit test for J9ClassArrayIsNullRestricted");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The assert is currently written to require the value to be 0x2000000, but the assert message is unclear what is expected. Should it be updated to something like expect J9ClassArrayIsNullRestricted == 0x2000000 or J9ClassArrayIsNullRestricted must be 0x2000000 to be explicit?

TR::Register *objectClassReg,
uintptr_t clazzAddress,
bool use64BitClasses,
TR::Register *scratchReg,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scratchReg is not used in this function.

}
else
{
// TODO: process out of line, but could just throw a ClassCastException instead
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This TODO: process out of line ... comment sounds a bit ambiguous to me since processing out of line is already happening. Do you mean for checkcast failure, instead of punting to the full helper, generate a dedicated OOL to throw ClassCastException? If so, could the TODO comment be updated to be more accurate?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants