Skip to content

Implement ROMMethodInfo caching support and stack walker integration#22918

Open
harship04 wants to merge 6 commits intoeclipse-openj9:masterfrom
harship04:romcache
Open

Implement ROMMethodInfo caching support and stack walker integration#22918
harship04 wants to merge 6 commits intoeclipse-openj9:masterfrom
harship04:romcache

Conversation

@harship04
Copy link

Implements a unified ROM method caching mechanism for consolidating ROM method metadata. Updates stack walking to use the cached information instead of repeated metadata lookups.

@harship04 harship04 force-pushed the romcache branch 9 times, most recently from 19d1ee8 to 5f11894 Compare November 11, 2025 15:38
@babsingh babsingh requested a review from gacholio November 11, 2025 16:19
@gacholio
Copy link
Contributor

Waiting for the changes we discussed before review (but at a glance, looks good so far).

@harship04 harship04 force-pushed the romcache branch 3 times, most recently from 24bce23 to 18af487 Compare November 15, 2025 12:33
@harship04
Copy link
Author

@gacholio , could you please review the PR

MARK_SLOT_AS_OBJECT(walkState, (j9object_t*) &(methodFrame->specialFrameFlags));
walkState->method = methodFrame->method;
if (NULL != walkState->method) {
initializeBasicROMMethodInfo(walkState, J9_ROM_METHOD_FROM_RAM_METHOD(walkState->method));
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think there should be any remaining callers of the basic init code (other than internally in mapcache.cpp) - they should all be replaced with the new ROM method call (other than the one for a bytecoded PC of course).

/* Always compute argument bits */
j9localmap_ArgBitsForPC0(romClass, romMethod, newInfo.argbits);

if (computeStackAndLocals && pc < (UDATA)J9_BYTECODE_SIZE_FROM_ROM_METHOD(romMethod)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Please bracket the second clause. In fact, why is this necessary? I think we can assume a running PC is withing the bytecode range of its method.

Copy link
Author

Choose a reason for hiding this comment

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

I removed the pc < (UDATA)J9_BYTECODE_SIZE_FROM_ROM_METHOD(romMethod) check in populateROMMethodInfo, but when I also remove the corresponding condition in getROMMethodInfoForBytecodePC—the one that returns if (pc <= J9SF_MAX_SPECIAL_FRAME_TYPE || pc >= (UDATA)J9_BYTECODE_SIZE_FROM_ROM_METHOD(romMethod)) then the JVM crashes with a segmentation faul

NULL);
}

/* Copy metadata */
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this not be a call to the basic init code?

@gacholio
Copy link
Contributor

Looks good in general. When computing the maps, you are not checking the size, checking for failure, or setting the bits indicating that the maps have been cached.

@gacholio
Copy link
Contributor

The argbits do not seem to be being computed at all.

@harship04 harship04 force-pushed the romcache branch 2 times, most recently from 495e826 to b581cfd Compare November 25, 2025 20:26
@harship04
Copy link
Author

The segmentation fault occurred because when pc == 0, the code returned early without initializing walkState->romMethodInfo; this was fixed by calling initializeBasicROMMethodInfo() for special-frame or out-of-bounds PC values.

@harship04
Copy link
Author

harship04 commented Dec 1, 2025

@gacholio

I added computePendingCountForBytecode() for bytecode-PC frames (argCount + tempCount, plus 1 for synchronized or non-empty methods).
Could you please confirm if this computation is correct, and whether the pending count should be updated inside populateROMMethodInfo() or only used when populating the cache

J9OSRFrame has pendingStackHeight and numberOfLocals, but J9ROMMethodInfo doesn’t store them. Currently, populateROMMethodInfo() receives these values for OSR frames but doesn’t save them. Should ROMMethodInfo eventually store them for caching, or are they only needed during map computation?

@harship04 harship04 force-pushed the romcache branch 3 times, most recently from e5bafb7 to 9ae4274 Compare January 8, 2026 14:17
@harship04
Copy link
Author

issue:
The original stack walker had two layers of protection preventing NULL constant pool dereferences. First, the constant pool was only accessed inside the J9_STACKWALK_ITERATE_O_SLOTS block, which is set only during GC root scanning and object slot enumeration. So for special frames (where constantPool == NULL), that entire block was skipped. Second, even when the flag was set, the code checked if (walkState->argCount), and special frames typically have argCount == 0, so the inner logic  was skipped again. This maintained the invariant that only frames safe for object slot enumeration ever dereference the constant pool.
In the new implementation, getROMMethodInfoForROMMethod() is called unconditionally, outside the J9_STACKWALK_ITERATE_O_SLOTS check. Since that function internally dereferences class/method data derived from the constant pool, it now runs even during walks of frames where the constant pool is NULL—breaking the original protection and causing the crash.

original code:

if (walkState->method) {
		J9ROMMethod * romMethod = J9_ROM_METHOD_FROM_RAM_METHOD(walkState->method);

		walkState->constantPool = UNTAGGED_METHOD_CP(walkState->method);
		walkState->argCount = J9_ARG_COUNT_FROM_ROM_METHOD(romMethod);

		if (walkState->flags & J9_STACKWALK_ITERATE_O_SLOTS) {
			WALK_METHOD_CLASS(walkState);

			if (walkState->argCount) {
				/* Max size as argCount always <= 255 */
				U_32 result[8];
				J9Class *methodClass = UNTAGGED_METHOD_CP(walkState->method)->ramClass;

#ifdef J9VM_INTERP_STACKWALK_TRACING
				swPrintf(walkState, 4, "\tUsing signature mapper\n");
#endif
				j9cached_ArgBitsForPC0(methodClass->romClass, romMethod, result, walkState->javaVM, methodClass->classLoader);

#ifdef J9VM_INTERP_STACKWALK_TRACING
				swPrintf(walkState, 4, "\tArguments starting at %p for %d slots\n", walkState->arg0EA, walkState->argCount);
#endif
				walkState->slotType = J9_STACKWALK_SLOT_TYPE_METHOD_LOCAL;
				walkState->slotIndex = 0;
				if (walkState->frameFlags & J9_SSF_JNI_REFS_REDIRECTED) {
					walkIndirectDescribedPushes(walkState, walkState->arg0EA, walkState->argCount, result);
				} else {
					walkDescribedPushes(walkState, walkState->arg0EA, walkState->argCount, result, walkState->argCount);
				}
			}
		}

the method causing the issue will null cp

method=0x7f952e7080a0 constantPool=(nil) pc=0x2 flags=0x1c0c0100 frameFlags=0x0
romMethod=0x7f952e70d4ec modifiers=0x0 argCount=0

It seems that the code never reached the line that dereferences the constant pool—J9Class *methodClass = UNTAGGED_METHOD_CP(walkState->method)->ramClass—because the frame never passes the checks if (walkState->flags & J9_STACKWALK_ITERATE_O_SLOTS) or if (walkState->argCount).

@harship04
Copy link
Author

harship04 commented Mar 9, 2026

@gacholio as we discussed, I’ve updated the code . Could you please review it.

To safely determine the class loader for caching, I deconstructed the J9_CLASS_FROM_METHOD macro by explicitly retrieving the constant pool using J9_CP_FROM_METHOD(method) and checking if it is NULL. If the constant pool is NULL, the bootstrap loader (vm->systemClassLoader) is used, since such methods are internal VM frames not loaded from class files.
In getROMMethodInfoCommon(), I added an argCount > 0 check so that the argument mapper is not called when there are no arguments. And after this change, can I proceed with checking for performance regressions?

/* Only compute argbits if method has arguments */
if (romMethodInfo->argCount > 0) {
J9ROMClass *romClass = J9_CLASS_FROM_METHOD(method)->romClass;
if (romMethodInfo->argCount <= (J9_ARGBITS_CACHE_SLOTS * 32)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

What does 32 mean here?

Copy link
Author

Choose a reason for hiding this comment

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

#define J9_STACKMAP_CACHE_SLOTS 2
#define J9_LOCALMAP_CACHE_SLOTS 2
#define J9_ARGBITS_CACHE_SLOTS 2

/* Flag values for J9ROMMethodInfo */
#define J9MAPCACHE_STACKMAP_CACHED 1
#define J9MAPCACHE_LOCALMAP_CACHED 2
#define J9MAPCACHE_ARGBITS_CACHED 4
#define J9MAPCACHE_METHOD_IS_CONSTRUCTOR 8
#define J9MAPCACHE_VALID 128

/* J9ROMMethodInfo must be a multiple of 8 bytes in size */
typedef struct J9ROMMethodInfo {
	void *key;
	U_32 stackmap[J9_STACKMAP_CACHE_SLOTS];
	U_32 localmap[J9_ARGBITS_CACHE_SLOTS];
	U_32 argbits[J9_ARGBITS_CACHE_SLOTS];
	U_32 modifiers;

32 represents the number of bits in a U_32. Since argbits is stored as an array of U_32, each element can hold 32 argument reference bits.The conditionromMethodInfo->argCount <= (J9_ARGBITS_CACHE_SLOTS * 32)checks whether the number of method arguments can fit in the argbits bitmap used for caching. The argbits field is an array of U_32, and each U_32 contains 32 bits, where each bit represents one argument slot. Since J9_ARGBITS_CACHE_SLOTS is 2, the bitmap can store up to 2 × 32 = 64 argument bits.

Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of J9_*_CACHE_SLOTS, perhaps we should have:

/* Cache slot sizes must all be multiples of 64 bits. */
#define J9_STACKMAP_CACHE_BITS 64
#define J9_LOCALMAP_CACHE_BITS 64
#define J9_ARGBITS_CACHE_BITS 64
typedef struct J9ROMMethodInfo {
	void *key;
	U_32 stackmap[J9_STACKMAP_CACHE_BITS / sizeof(U_32)];
	U_32 localmap[J9_ARGBITS_CACHE_BITS / sizeof(U_32)];
	U_32 argbits[J9_ARGBITS_CACHE_BITS / sizeof(U_32)];

Then line 130 (above) becomes:

		if (romMethodInfo->argCount <= J9_ARGBITS_CACHE_BITS) {

Copy link
Author

Choose a reason for hiding this comment

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

@keithc-ca ,
Based on my understanding, sizeof(U_32) returns 4 bytes, and sizeof(U_32) * 8 gives 32 bits. If we compute the array size using J9_ARGBITS_CACHE_BITS / sizeof(U_32), it becomes 64 / 4 which is 16.
and if we do this J9_STACKMAP_CACHE_BITS / (sizeof(U_32) * 8) then it will be 64/32 which will be 2 slots
Could you please confirm if this is correct, or if I should keep the original definition using J9_STACKMAP_CACHE_SLOTS 2 instead?

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry, you're right, those arrays should use / 32 instead of / sizeof(U_32):

	U_32 stackmap[J9_STACKMAP_CACHE_BITS / 32];
	U_32 localmap[J9_ARGBITS_CACHE_BITS / 32];
	U_32 argbits[J9_ARGBITS_CACHE_BITS / 32];

Copy link
Author

Choose a reason for hiding this comment

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

Done

@harship04 harship04 force-pushed the romcache branch 2 times, most recently from 37d7dad to 7629b32 Compare March 10, 2026 12:58
@harship04 harship04 requested a review from keithc-ca March 10, 2026 13:59
@harship04 harship04 marked this pull request as draft March 11, 2026 13:41
@harship04 harship04 marked this pull request as ready for review March 11, 2026 16:52
@harship04 harship04 marked this pull request as draft March 12, 2026 16:13
@harship04 harship04 force-pushed the romcache branch 2 times, most recently from d50ed41 to 4129a78 Compare March 17, 2026 06:11
@harship04
Copy link
Author

harship04 commented Mar 17, 2026

@gacholio, We initially assumed that there was no need to check the PC offset, since the stack walker already determines the frame type and calls the bytecode frame helper.
when I removed the following condition it resulted in a crash.

if (pcOffset >= (UDATA)J9_BYTECODE_SIZE_FROM_ROM_METHOD(romMethod)) {
    initializeBasicROMMethodInfo(walkState, romMethod);
    romMethodInfo->key = (void *)romMethod;
    return;
}

I observed that pcOffset can still be invalid ( very large and outside the bytecode range) because getROMMethodInfoForBytecodeFrame is invoked from jitWalkStackFrames in file runtime/codert_vm/jswalk.c even when the PC does not correspond to a valid bytecode location.

This leads to an out-of-bounds bytecodePC being computed and passed into the stackmap logic, which assumes a valid bytecode PC and results in a segmentation fault .

@harship04 harship04 marked this pull request as ready for review March 17, 2026 14:12
@harship04 harship04 requested a review from keithc-ca March 17, 2026 16:13
Comment on lines +1250 to +1252
U_32 stackmap[J9_STACKMAP_CACHE_BITS / 32];
U_32 localmap[J9_LOCALMAP_CACHE_BITS / 32];
U_32 argbits[J9_ARGBITS_CACHE_BITS / 32];
Copy link
Contributor

Choose a reason for hiding this comment

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

These fields should use name patterns that are consistent:

	U_32 stackMap[J9_STACKMAP_CACHE_BITS / 32];
	U_32 localMap[J9_LOCALMAP_CACHE_BITS / 32];
	U_32 argBits[J9_ARGBITS_CACHE_BITS / 32];

Copy link
Contributor

Choose a reason for hiding this comment

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

This has not been addressed.

Copy link
Contributor

Choose a reason for hiding this comment

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

This is a work in progress.

Copy link
Author

@harship04 harship04 Mar 21, 2026

Choose a reason for hiding this comment

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

Sorry for the delay, I was on leave.
@keithc-ca, Changes are now updated.

@gacholio
Copy link
Contributor

getROMMethodInfoForBytecodeFrame is invoked from jitWalkStackFrames

This is the issue - JIT frames are not bytecode frames. This call should be to the ROMMethod entrypoint (JIT frames do not use the computed stack/local maps - they only require the argbits). This lines up with the use of the ROM method as key for the other frames (native method, etc).

@gacholio gacholio marked this pull request as draft March 20, 2026 18:57
@harship04
Copy link
Author

getROMMethodInfoForBytecodeFrame is invoked from jitWalkStackFrames

This is the issue - JIT frames are not bytecode frames. This call should be to the ROMMethod entrypoint (JIT frames do not use the computed stack/local maps - they only require the argbits). This lines up with the use of the ROM method as key for the other frames (native method, etc).

@gacholio I’ve replaced the call with:

J9ROMMethod *romMethod = J9_ROM_METHOD_FROM_RAM_METHOD(walkState->method);
getROMMethodInfoForROMMethod(walkState, romMethod);

so that JIT frames now use the ROMMethod entrypoint instead of the bytecode frame helper.

@harship04 harship04 marked this pull request as ready for review March 21, 2026 04:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants