Skip to content

Z: Inline any length 2d array allocation - #22548

Merged
r30shah merged 1 commit into
eclipse-openj9:masterfrom
ehsankianifar:Z_inlineMultiANewArray
Oct 8, 2025
Merged

Z: Inline any length 2d array allocation#22548
r30shah merged 1 commit into
eclipse-openj9:masterfrom
ehsankianifar:Z_inlineMultiANewArray

Conversation

@ehsankianifar

Copy link
Copy Markdown
Contributor

Adding instructions to inline allocation of two dimensional arrays with any length. Before this change, we only inline 2d arrays if at least one dimension length is zero.

@ehsankianifar
ehsankianifar marked this pull request as draft September 3, 2025 17:28
@ehsankianifar
ehsankianifar force-pushed the Z_inlineMultiANewArray branch 2 times, most recently from 1e70d33 to 46eb3fc Compare September 3, 2025 17:48
@ehsankianifar
ehsankianifar marked this pull request as ready for review September 5, 2025 19:31
@ehsankianifar

Copy link
Copy Markdown
Contributor Author

@r30shah this PR is ready for review
the selected instructions are as flowing:

LA      GPR9, Auto[<auto slot -1>] 112(GPR5)
LHI     GPR0,0x2	    *Number of dimensions
LGFI    GPR10,8707328	*Class register
 
Label L0067:	# (Start of internal control flow)	
   *** In case length1 or length2 are zero, we use this as object size
LGHI    GPR7,0x10	 ; Load discontinuous array size.
LTGF    GPR8,#466 4(GPR9) ; Load 1st dim length.
BRC     VGNOP(0x8), Label L0069 ***If the first dim length is zero
BRC     BM(0x4), Label L0064    ***If dimension length is negative

LTGF    GPR12,#467 0(GPR9) ; Load 2st dim length.
BRC     VGNOP(0x8), Label L0070 ***If the second dim length is zero
   ***convert second dim length to aligned leaf array size
SLA     GPR12,3
BRC     B(0xd), Label L0064
AGHI    GPR12,0x8	
LGR     GPR7,GPR12	
Label L0070:	                ***If the second dim length is zero
LR      GPR12,GPR7	
MSGR    GPR7,GPR8      *** number of leaf arrays * size of leaf array
   ***convert first dim length to aligned array size
SLLG    GPR8,GPR8,2
AGHI    GPR8,0xf	
NILF    GPR8,-8	
AGR     GPR7,GPR8	 ; Total size in sizeReg.
BRC     B(0xd), Label L0064
Label L0069:	                ***If the first dim length is zero
LG      GPR6,#468 96(GPR13) ; Set result reg.
ALGR    GPR7,GPR6	
BRC     MASK12(0xb), Label L0064
CLG     GPR7,#469 104(GPR13)
BRC     BL(0x2), Label L0064  ***If heap top test fails

STG     GPR7,#470 96(GPR13) ; Heap top test pass. Update heap alloc.
LG      GPR3,#471 0(GPR9) ; Start first dim alloc
ST      GPR10,#472 0(GPR6) ***Store class
ST      GPR3,#473 4(GPR6)  ***Store length
CLIJ    GPR3,Label L0066,0,BH(mask=0x8), 	***If the first dim length is zero, we are done!
   ***Get ready to allocate leaf arrays
AGR     GPR8,GPR6	 ; dim1SizeReg points to the fist leaf array.
LG      GPR10,#474 92(GPR10)
L       GPR10,#475 0(GPR9)
RISBG   GPR12,GPR12,0,31,30	 ; Compressed dim2 size in higher 32bits.
RISBG   GPR3,GPR8,0,31,30	 ; Compressed dim2 reference in higher 32 bits.
LGHI    GPR7,0x8
   ***	Allocate leaf arrays in a loop
Label L0068:	
STG     GPR10,#476 0(GPR8) ; Start second dim alloc ***Store Class and length
STFH    GPR3,#477 0(GPR7,GPR6) ***Store compressed leaf address to first dim array
AGFR    GPR8,GPR12	***Next leaf array
AGHI    GPR7,0x4	***Next element of first dim array
AHHHR   GPR3,GPR12,GPR3	***Bump compressed reference address
BRCT    GPR3,Label L0068 ***Next leaf

BRC     BRU(0xf), Label L0066  ***Done! go to the end
Label L0064:	***Jump to slow path helper in OOL 
BRC     BRU(0xf), Outlined Label L0065
assocreg
Label L0066:	# (End of internal control flow)	
LGR     GPR7,GPR6	 ; Copy tmp result to final result.

I upload a full compilation logs with compressed and non compressed references here:
NoCompRefs.log
CompRefs.log

@ehsankianifar
ehsankianifar force-pushed the Z_inlineMultiANewArray branch 2 times, most recently from 6863661 to 0e9c5ae Compare September 5, 2025 19:38

@r30shah r30shah left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I am going to load this PR and do in more depth review, some initial comments.

Comment thread runtime/compiler/z/codegen/J9TreeEvaluator.cpp Outdated
Comment thread runtime/compiler/z/codegen/J9TreeEvaluator.cpp
Comment thread runtime/compiler/z/codegen/J9TreeEvaluator.cpp Outdated
@ehsankianifar

Copy link
Copy Markdown
Contributor Author

@r30shah I addressed the change requests. Please take another look when you have time. Thanks!

{
generateRRInstruction(cg, TR::InstOpCode::LGR, node, temp1Reg, targetReg);
generateRILInstruction(cg, TR::InstOpCode::AGFI, node, temp1Reg, zeroArraySizeAligned);
traceMsg(comp, "Inline allocation for multianewarray with element size:%d and leaf component size:%d", elementSize, componentSize);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
traceMsg(comp, "Inline allocation for multianewarray with element size:%d and leaf component size:%d", elementSize, componentSize);
traceMsg(comp, "Inline allocation for multianewarray with element size: %d and leaf component size: %d", elementSize, componentSize);

#endif /* J9VM_GC_SPARSE_HEAP_ALLOCATION */
TR::LabelSymbol *controlFlowStartLabel = generateLabelSymbol(cg);
controlFlowStartLabel->setStartInternalControlFlow();
generateS390LabelInstruction(cg, TR::InstOpCode::label, node, controlFlowStartLabel);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Internal control flow starts after LTGF - not here.

int32_t alignmentConstant = TR::Compiler->om.getObjectAlignmentInBytes();
bool useCompRefs = TR::Compiler->om.compressObjectReferences();
// Skip alignment if both header and elements are aligned.
bool alignFirstDim = !OMR::aligned(headerSize, alignmentConstant) || !OMR::aligned(elementSize, alignmentConstant);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

First use if the alignFirstDim is at line 4915 - is there specific reason - it is defined here?
Also it would have been easier to read is you have something like following,

Suggested change
bool alignFirstDim = !OMR::aligned(headerSize, alignmentConstant) || !OMR::aligned(elementSize, alignmentConstant);
bool alignFirstDim = !(OMR::aligned(headerSize, alignmentConstant) && OMR::aligned(elementSize, alignmentConstant));

This is self-explanatory, you would not need the comment above.

bool useCompRefs = TR::Compiler->om.compressObjectReferences();
// Skip alignment if both header and elements are aligned.
bool alignFirstDim = !OMR::aligned(headerSize, alignmentConstant) || !OMR::aligned(elementSize, alignmentConstant);
bool alignSecondDim = !OMR::aligned(headerSize, alignmentConstant) || !OMR::aligned(componentSize, alignmentConstant);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Similar comment as previous comment.

// Skip alignment if both header and elements are aligned.
bool alignFirstDim = !OMR::aligned(headerSize, alignmentConstant) || !OMR::aligned(elementSize, alignmentConstant);
bool alignSecondDim = !OMR::aligned(headerSize, alignmentConstant) || !OMR::aligned(componentSize, alignmentConstant);
// If any dimension length is zero, resulting array have discontiguous array size

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think the comment is bit unclear - please elaborate a bit.

Comment thread runtime/compiler/z/codegen/J9TreeEvaluator.cpp
* \details
* Allocate 2 dimensional multi new arrays of any type with inline instructions if the size is within the range of TLH.
* This helper needs the following conditions to work properly and the caller should check these before calling:
* 1. Must be a 64bit system.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think having Asserts for these conditions makes more sense (Rather than having it in caller.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I used simple ASSERT to emphasize the conditions. I do not expect to get invalid data since the caller check those. Please let me know if you prefer ASSERT_FATAL

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I would prefer FATAL assert, it is not so much as current callers, this asserts are there to prevent in future using this generate helper function with the conditions with which current function is implemented

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I converted to fatal assert

// Need dimReg for helper.
TR::Register *dimReg = cg->evaluate(node->getSecondChild());
TR::Register *classReg = cg->gprClobberEvaluate(node->getThirdChild());
TR::Register *vmThreadReg = cg->getMethodMetaDataRealRegister();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Move declaration and definition of this register closer to where it is used (It is not needed for deps as well)

@ehsankianifar
ehsankianifar force-pushed the Z_inlineMultiANewArray branch 2 times, most recently from dc27cf3 to bc0f923 Compare September 17, 2025 15:34

@r30shah r30shah left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think I am 70% done with review - Only need to go through code that deals with the allocation / bookkeeping of second dimension arrays - but some minor comments / questions / suggestions based on the review done so far. Just cosmetic changes, no structural changes.

///////////////////////////////////////////////////////////////////////////////////////
// Generate code for multianewarray
// Checks the number of dimensions. For 1 dimensional arrays call the helper, for >1 call
// Checks the number of dimensions. For 1 dimensional arrays call the helper, for 2 call

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think a better comment here would need some more details. We are generating the inlined code for 2 dimensions. For all other cases we generate helper call, it is not only the 1 dimensional array.

Also out of curiosity - Why can't we generate inline code for 1 dimensional array, can we have multianewarray for 1-D array ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

since the number of dimensions is static, It would be very odd to receive a call to allocate one dimension array here! anewarray would gets called for those cases.

TR_ASSERT_FATAL(secondChild->getOpCodeValue() == TR::iconst, "dims of multianewarray must be iconst");

// Only generate inline code if nDims > 1
uint32_t nDims = secondChild->get32bitIntegralValue();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Though, it was already there, suggestion,

Suggested change
uint32_t nDims = secondChild->get32bitIntegralValue();
uint32_t nDims = static_cast<uint32_t>(secondChild->get32bitIntegralValue());

static TR::Register * generateMultianewArrayWithInlineAllocators(TR::Node *node, TR::CodeGenerator *cg)
/**
* \brief
* Inline allocation of two dimensional new array.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The name and brief do not match - I am ok to simply use multi dimensional array here. We can mention here that the helper currently only supports two dimensional array.

* Inline allocation of two dimensional new array.
*
* \details
* Allocate 2 dimensional multi new arrays of any type with inline instructions if the size is within the range of TLH.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Name of the function and the comment (brief and details) is bit mismatching. I would convey the message in the details that this helper only supports multianew array with 2 dimensions. Also would spin the conditions you listed as current support statement (Mode definitive - compare to what you have right not (work properly))

* This helper allocate any non negative size 2D array if it fits inside the TLH.
*
* \param node
* The node.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

multianewarray node


cursor = generateS390LabelInstruction(cg, TR::InstOpCode::label, node, zeroSecondDimLabel, cursor);
// If jumping from zero second dim match, set second dim size to discontiguous. Otherwise this wont change values.
cursor = generateRRInstruction(cg, TR::InstOpCode::LR, node, dim2SizeReg, sizeReg, cursor);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why LR and not LGR - I assume the sizeReg here is the discontiguous size which you loaded at the beginning in the evaluator. Eventhough this would not cause any functional issue given the dim2SizeReg is loaded with LTGF - Please use LGR here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

the purpuse of LTGF is to zero the higher 32 bits of the 64bit register. Since we know that this size is less than 32bits and the higher 32bit is already zero, we can use LR here with is a 4byte instruction. LGR is a 8byte instruction.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This kind of assumptions are the one that could cause code to become difficult to understand / manage and may cause bug if something changes. What different does LR/LGR makes ? In your evaluator, operand is 64-bit - Why do we make assumption that it will never have upper half with some value so only load the 32-bit value.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I was trying to reduce code size by using LR but if you think LGR makes the code more readable, I'll use that.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's more about the kind of assumptions - that later on leads to issues with upper half containing some stale values - you have written the code so you know about the assumption, I have reviewed the code so I know. But in future due to some issues if there is some change happens before this LR/LGR - this kind of change is so implicit that it is easy to forget. We would have the code in - and in rare highly intermittent case we would hit some unexpected result. This is the reason, we should try to work with the operand type as much as possible.

// If jumping from zero second dim match, set second dim size to discontiguous. Otherwise this wont change values.
cursor = generateRRInstruction(cg, TR::InstOpCode::LR, node, dim2SizeReg, sizeReg, cursor);
// Total size = (second dim size * number of second dim arrays) + first dim size.
// Max possible value is uint32.max * int32.max which will not overflow.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Which operand is uint32? I think this comment is bit misleading - Please add more context with the assumption that you are taking - which is max number of bytes would not overflow 64-bit range.

if (alignFirstDim)
cursor = generateRILInstruction(cg, TR::InstOpCode::NILF, node, dim1SizeReg, -alignmentConstant, cursor);

cursor = generateRREInstruction(cg, TR::InstOpCode::AGR, node, sizeReg, dim1SizeReg, cursor);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If we reach here, do we expect to be dealing with negative values ? What would be the consequences of using ALGR?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We already checked for negatives and max possible value is guaranteed to be a positive int64 value so both AGR and ALGR work fine here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

So can you use ALGR? that would guarantee to not cause fixed point overflow.

cursor = generateRREInstruction(cg, TR::InstOpCode::AGR, node, sizeReg, dim1SizeReg, cursor);
iComment("Total size in sizeReg.");
// Only a positive number with no overflow is acceptable.
cursor = generateS390BranchInstruction(cg, TR::InstOpCode::BRC, TR::InstOpCode::COND_BRNP, node, inlineAllocFailLabel, cursor);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Given the zero length and negative length is already dealt - can we have 0/negative outcome ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No it is not possible to have negative or zero. We only need to check for overflow here but since it has no cost, I used COND_BRNP to emphasize that the size after this branch is a positive integer!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please adjust the condition based on the instruction change recommended in #22548 (comment)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I remove this branch since this adding can not overflow.

// Update heap alloc.
cursor = generateRXInstruction(cg, TR::InstOpCode::STG, node, sizeReg, generateS390MemoryReference(vmThreadReg, heapAllocOffset, cg), cursor);
iComment("Heap top test pass. Update heap alloc.");
TR::Register *miscellaneousReg = cg->allocateRegister();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think scratchReg is better suitable name.

@ehsankianifar
ehsankianifar force-pushed the Z_inlineMultiANewArray branch 2 times, most recently from 5708636 to 56ef740 Compare September 23, 2025 14:05
@ehsankianifar

Copy link
Copy Markdown
Contributor Author

Thanks @r30shah for your review. I addressed all the changes and the only thing left is the SLA vs SLLG instruction.
the problem is by using SLLG, we won't know if the shift result is larger than 32 bits. it could be large enough that overflow the multiply single instruction. I experimented with a few options like explicitly checking the higher 32bits of the size register after SLLG or using 128bit multiplication but it adds too much complexity. Maybe I can add more detailed explanation in the code and keep using SLA to mitigate the readability issue.

@ehsankianifar

Copy link
Copy Markdown
Contributor Author

I get some failures in my jdk21 testing on Jenkins. I investigate to see if it is related to my changes.

Comment thread runtime/compiler/z/codegen/J9TreeEvaluator.cpp
if (componentSize == 1)
{
// Make sure second dim length is not negative.
cursor = generateS390BranchInstruction(cg, TR::InstOpCode::BRC, TR::InstOpCode::COND_BL, node, inlineAllocFailLabel, cursor);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

So my point about using the appropriate instruction for the data type was to ensure that we do not run into issues where some other change that happens causes incompatibility with your change down the road with such implicit assumptions like using SLA to only working with 32-bit signed length (# bytes) and assuming that we would never have the overflow with MSGR because different paths in your ICF and set of instructions you selected does not let the case which can have an overflow for long multiplication.

I do see the current implementation you have works on z196 or newer. We have instruction that is available in z14 or newer (Multiply Single) that you can use which can set the condition code that you can use. Second option is using multiply and check upper 64-bit with zero by using CGIJ instruction.

Bit hesitant with current logic you have though it simplifies the instruction you generated - it puts limitation on the size of the array you are allocating and that restriction is implicit - not something documented / written in the comment, and also some third person reading the code would not find easy to make enhancement / change and may introduce the bugs, but it you really think that this is the best sequence we can have and in field even with large TLH size, restricting a dimension length to the value you have due to very limited cases, I am ok with current change, and in that case, I would recommend you to put a detailed comments so that it becomes clear when someone looks/modifies the code.

TR::Node::recreate(node, TR::acall);
TR::Register *targetReg2 = TR::TreeEvaluator::performCall(node, false, cg);
TR::Node *secondChild = node->getSecondChild();
if (secondChild->getOpCode().isLoadConst() || secondChild->getOpCodeValue() == TR::iRegLoad)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@r30shah No sure if this is necessary to convert to long. It doesn't cause any issue but I am wondering is having garbage data in the higher32 bits of the parameter register can cause any issues in any case.
Let me know if I should revert this change.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I do not think you need to do anything here. The child will be evaluated into parameter register by the parameter type that a helper call expects - Is there specific reason you need this change?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks, A few tests started to fail and I saw wrong parameter value and thought maybe it is because of the higher half of the parameter register. Later I find that the register was evaluated after the branching to the helper therefore the value was wrong but I decided to make sure about this.

TR::Node::recreate(node, TR::acall);
TR::Register *targetReg2 = TR::TreeEvaluator::performCall(node, false, cg);
TR::Node *secondChild = node->getSecondChild();
if (secondChild->getOpCode().isLoadConst() || secondChild->getOpCodeValue() == TR::iRegLoad)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I do not think you need to do anything here. The child will be evaluated into parameter register by the parameter type that a helper call expects - Is there specific reason you need this change?

@ehsankianifar

Copy link
Copy Markdown
Contributor Author

Sanity tests are passing.

TR::Register *targetReg = cg->allocateRegister();
TR::Instruction *cursor = NULL;
int32_t elementSize = TR::Compiler->om.sizeofReferenceField();
TR_ASSERT_FATAL(comp->target().is64Bit(), "multianewArrayEvaluator is only supported on 64-bit JVMs!");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It is getting bit crowded here. Can we add some spacing between ASSERTs? to make each ASSERT message and condition clear?

generateS390LabelInstruction(cg, TR::InstOpCode::label, node, cFlowRegionStart);
/********************************************* Register setup *********************************************/
TR::Register *dimsPtrReg = cg->evaluate(node->getFirstChild());
TR::Register *classReg = cg->gprClobberEvaluate(node->getThirdChild());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Going to let this one pass - but this variable is used bit late.

cursor = generateRXInstruction(cg, TR::InstOpCode::LGF, node, firstDimLenReg, generateS390MemoryReference(dimsPtrReg, 4, cg));
if (node->getSecondChild()->getReferenceCount() > 1)
{
// If dimensions child is referenced elsewhere, evaluate it before ICF.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I do not think this comment it needed. I believe this is pretty self explanatory (Given you are doing ref count check)

// Load int32 second dim length to a 64 bit register.
cursor = generateRXInstruction(cg, TR::InstOpCode::LGF, node, dim2SizeReg, generateS390MemoryReference(dimsPtrReg, 0, cg), cursor);
iComment("Load 2st dim length.");
// To guarantee no overflow in size calculation, the `length * componentSize` should not be larger than int32.max. Additionaly, the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Here are the points I want to make clear here so that in future if we decide to go with using the other multiplication version or investigate why we fail allocating inline even when we set large enough TLH.

  • Dimension length type is Int32. For given component, if total number of bytes are larger than INT_MAX, there is a possibility that total bytes to allocate may overflow 64-bit range.
  • By using SLA instruction, even though we put a cap on allocating inline array way below the range that can cause 64-bit overflow, it will simplify the instructions you are generating and avoid the overflow check after multiplying dimension 1 bytes and dimension 2 bytes as well as at places which contributes to total bytes calculation.
  • So add the assumption that we are making with the dimension length.

Need to be clear on what we are choosing here - Choosing the simplicity / platform compatibility over the allocation limit (Which I do expect to be very rare)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Also this is source code that is going to be opened in the IDE or browser which will not be rendered for the mark down - using the special characters for markdown would not help at all - Looks bit odd.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated the comment

cursor = generateS390BranchInstruction(cg, TR::InstOpCode::BRC, TR::InstOpCode::COND_MASK5, node, inlineAllocFailLabel, cursor);

int32_t headerSize= TR::Compiler->om.contiguousArrayHeaderSizeInBytes();
// `size = (size + alignmentConstant - 1) & -alignmentConstant` equation round up the size to a factor of alignmentConstant.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No need to put `` in the source code.

// Fist dim class and length:
bool useCompRefs = TR::Compiler->om.compressObjectReferences();
cursor = generateRXInstruction(cg, useCompRefs ? TR::InstOpCode::ST : TR::InstOpCode::STG, node, classReg,
generateS390MemoryReference(resultReg, (int32_t)TR::Compiler->om.offsetOfObjectVftField(), cg), cursor);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please use static_cast<int32_t>... - Also if other places in this evaluator does use C++ casting, please change that.

cursor = generateRXInstruction(cg, TR::InstOpCode::LA, node, dim1SizeReg, generateS390MemoryReference(dim1SizeReg, resultReg, 0, cg), cursor);
iComment("dim1SizeReg points to the fist leaf array.");
// Load component class to class register. In case of comp refs, class is in high order!
cursor = generateRXInstruction(cg, TR::InstOpCode::LG, node, classReg, generateS390MemoryReference(classReg, offsetof(J9ArrayClass, componentType)+(useCompRefs ? 4 : 0), cg), cursor);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
cursor = generateRXInstruction(cg, TR::InstOpCode::LG, node, classReg, generateS390MemoryReference(classReg, offsetof(J9ArrayClass, componentType)+(useCompRefs ? 4 : 0), cg), cursor);
cursor = generateRXInstruction(cg, TR::InstOpCode::LG, node, classReg, generateS390MemoryReference(classReg, offsetof(J9ArrayClass, componentType) + (useCompRefs ? 4 : 0), cg), cursor);


cursor = generateRXInstruction(cg, TR::InstOpCode::LA, node, dim1SizeReg, generateS390MemoryReference(dim1SizeReg, resultReg, 0, cg), cursor);
iComment("dim1SizeReg points to the fist leaf array.");
// Load component class to class register. In case of comp refs, class is in high order!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What is high order ? Class is in the lower half of the double word right.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thats correct, made the comment more clear.

// dataAddr field. secondDimLenReg is expected to be NULL at this point.
cursor = generateRXInstruction(cg, TR::InstOpCode::STG, node, firstDimLenReg, generateS390MemoryReference(targetReg, fej9->getOffsetOfDiscontiguousDataAddrField(), cg));
iComment("Clear 1st dim dataAddr field.");
// Keep a compressed version of dim2 size in higher 32 bits.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This seems like an odd comment. It suggest compressed version of dim2 size not the compressed refs. Please update the comment to make it clear. Also having some layout information here would enhance the comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated the comment and added diagram

generateRRInstruction(cg, TR::InstOpCode::LGR, node, temp2Reg, targetReg);
generateRRInstruction(cg, TR::InstOpCode::AGR, node, temp2Reg, temp1Reg);
if (cg->comp()->target().cpu.isAtLeast(OMR_PROCESSOR_S390_Z196))
// Load the address of the first element of the first dimension array in sizeReg.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can you add some comment on the layout here to share how some of the initialization you are doing is done here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added comments to summarize the second dim allocation plan

@r30shah r30shah left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@ehsankianifar I think we are closer, some more changes.

cursor = generateS390CompareAndBranchInstruction(cg, TR::InstOpCode::CL, node, scratchReg, 0, TR::InstOpCode::COND_BE, controlFlowEndLabel, false /* needsCC */, cursor);

cursor = generateRXInstruction(cg, TR::InstOpCode::LA, node, dim1SizeReg, generateS390MemoryReference(dim1SizeReg, resultReg, 0, cg), cursor);
iComment("dim1SizeReg points to the fist leaf array.");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Typo here.


cursor = generateRXInstruction(cg, TR::InstOpCode::LA, node, dim1SizeReg, generateS390MemoryReference(dim1SizeReg, resultReg, 0, cg), cursor);
iComment("dim1SizeReg points to the fist leaf array.");
// Load component class to class register. In case of comp refs, the calss is 32bits long so using this register for leaf array length value as well.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Another typo, and please do not shorten compressedrefs to comp refs (We do not use that throughout our code and also comp is used throughout for the compiler object). Class pointer is is 32-bit wide (Opposite to long which is more suitable for array).

// Load component class to class register. In case of comp refs, the calss is 32bits long so using this register for leaf array length value as well.
// ClassReg when non-comp refs: | class |
// ClassReg when comp refs: | class | length |
cursor = generateRXInstruction(cg, TR::InstOpCode::LG, node, classReg, generateS390MemoryReference(classReg, offsetof(J9ArrayClass, componentType) + (useCompRefs ? 4 : 0), cg), cursor);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hmm, I do get your intention to smartly combine the field to reduce number of operations we need to do. It does work now but is not easily extensible if for any reason object layout changes or in future we can not guarantee J9Class* to be 32-bit wide for compressed refs. So do not do that.

This is also bug/error prune - We are loading double word from middle of the field offset for componentType which will also load upper half of itable into the register which you are later updating by loading the length register.

So please revert to old way of updating the header.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It save us one store operation in the leaf allocation loop. It is very easy to change but should't we change it if the layout changes in future?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

My point is that - If the layout changes or compressed ref address for J9Class is not guaranteed to fit 32-bits, kind of changes needed would be a lot, and it wouldn't be that simple.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You are also doing an extra operation to fit both class and length in one register, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Chenged to simple load/store

cursor = generateRXInstruction(cg, TR::InstOpCode::L, node, classReg, generateS390MemoryReference(dimsPtrReg, 0, cg), cursor);
if (shiftAmount > 0)
{
// Keep a compressed dim2 size in higher 32 bits.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I do not think you addressed my previous comment, what is compressed dim2 size? Keep a compressed dim2 size in higher 32-bits has completely different meaning then For compressedref, dim2 size in higher 32-bits

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I compressed the size by shifting to the right. So we have the actual size in lower 32bits and the shifted size in higher 32 bits. This way I can add this value to the compressed reference of the leaf array and get the compressed reference of the next leaf.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It is not termed as compressed size. You are shifting the size not compressing it.

// dim2SizeReg when shift amount is 0: | unused | leaf array size |
// dim2SizeReg when shift amount is > 0: | compressed leaf array size | leaf array size |
cursor = generateRIEInstruction(cg, TR::InstOpCode::RISBG, node, dim2SizeReg, dim2SizeReg, 0, 31, 32-shiftAmount, cursor);
iComment("Compressed dim2 size in higher 32bits.");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fix this comment. You are not compressing the dim2 size.

// scratchReg when shift amount is 0: | leaf array length | number of leaf arrays |
// scratchReg when shift amount is > 0: | compressed leaf array address | number of leaf arrays |
cursor = generateRIEInstruction(cg, TR::InstOpCode::RISBG, node, scratchReg, dim1SizeReg, 0, 31, 32-shiftAmount, cursor);
iComment("Compressed dim2 reference in higher 32 bits.");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fix this as well.

// Start setting second dim:
TR::LabelSymbol *secondDimLabel = generateLabelSymbol(cg);
cursor = generateS390LabelInstruction(cg, TR::InstOpCode::label, node, secondDimLabel, cursor);
// Store the class field. In case of comp refs, store the class and length fields.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please update the short for comp refs to compressedrefs throughout this evaluator.

cursor = generateS390BranchInstruction(cg, TR::InstOpCode::BRC, TR::InstOpCode::COND_B, node, controlFlowEndLabel, cursor);
iComment("Allocation done!");

/********************************************* Unreachable zone *********************************************/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What do you mean by unreachable zone ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Any code here will not be executed. I put it here to make it clear why the EXRL target is here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ok, so I do not like having two XC instruction to perform the clearing and having EXRL separate target. We should be able to generate better loop for that. In any case, this comment is not that clear. Can we rewrite the loop so that we do not need extra XC to be generated ?

static bool disableBatchClear = feGetEnv("TR_DisableBatchClear") != NULL;
bool needInitialization = disableBatchClear && !node->canSkipZeroInitialization();
TR::LabelSymbol *memoryInitializationExrlTargetLabel = NULL;
if (needInitialization)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hmm, Did you change the login for memclear ? Usually we would use XC and for residue, use the EXRL. Current implementation have two XC generated. We should simplify this code it does look messy.

@r30shah r30shah left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@ehsankianifar Some more suggestions, I think we are closer.

@ehsankianifar
ehsankianifar force-pushed the Z_inlineMultiANewArray branch 3 times, most recently from ab0751c to 1c51da8 Compare October 1, 2025 17:06
@ehsankianifar

Copy link
Copy Markdown
Contributor Author

@ehsankianifar Some more suggestions, I think we are closer.

Thanks @r30shah,
The last push address the comments.

@r30shah

r30shah commented Oct 1, 2025

Copy link
Copy Markdown
Contributor

Thanks @ehsankianifar - Just confirming before I go for final review, have you addressed comment in #22548 (comment) ?

@ehsankianifar

Copy link
Copy Markdown
Contributor Author

@r30shah I changed the order of memory initialization instructions.

@ehsankianifar
ehsankianifar force-pushed the Z_inlineMultiANewArray branch 4 times, most recently from db78475 to c7f5f89 Compare October 2, 2025 21:02
@ehsankianifar

Copy link
Copy Markdown
Contributor Author

Thanks @ehsankianifar - Just confirming before I go for final review, have you addressed comment in #22548 (comment) ?

I changed the order of operations in my last commit so I clean 256 byte chunks first the clean the residual bytes. Also moved the EXRL target next to EXRL instruction.

@ehsankianifar
ehsankianifar force-pushed the Z_inlineMultiANewArray branch from c7f5f89 to 91a89d2 Compare October 3, 2025 13:46

@r30shah r30shah left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@ehsankianifar Looks OK to me, but will skim over once again to make sure if there is anything left to address. One comment I had - Please not it is comment, I do not expect you to make change, but just an answer. I am going to launch test.


generateRRInstruction(cg, TR::InstOpCode::LGR, node, targetReg, targetReg2);
generateS390BranchInstruction(cg, TR::InstOpCode::BRC, TR::InstOpCode::COND_BRC, node, cFlowRegionDone);
generateRRInstruction(cg, TR::InstOpCode::LGR, node, resultReg, slowResultReg);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Asking, would it be possible to copy this to collectedResultReg ? so that we do not perform copy again in the mainline?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I was afraid using the collected reference register in the slow path may add it to the GC point but never tested to see what happens.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ok. It would be good to understand if this can be issue or not. I do see other evaluators using the allocated collected reference register throughout the evaluator (Take a look at VMNew). Now it may be possible that these register is added to dependency list which is merged in OOL with the dependency list generated for the helper call and it is ok (It won't be spilled), in which case I think current way in multianewarray is fine, but something to look into in future.

@r30shah

r30shah commented Oct 7, 2025

Copy link
Copy Markdown
Contributor

Can you squash commits?

Adding instructions to inline allocation of two dimensional arrays with any length.
Before this change, we only inline 2d arrays if at least one dimension length is zero.

signed-off-by: Ehsan Kiani Far <ehsan.kianifar@gmail.com>
@ehsankianifar
ehsankianifar force-pushed the Z_inlineMultiANewArray branch from 91a89d2 to 9452d2d Compare October 7, 2025 20:00
@ehsankianifar

Copy link
Copy Markdown
Contributor Author

Can you squash commits?

Thanks for the review. Comments squashed!

@r30shah

r30shah commented Oct 7, 2025

Copy link
Copy Markdown
Contributor

jenkins test sanity zlinux jdk8,jdk21

@r30shah r30shah left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All tests have passed, and changes LGTM. I am merging this one.

@r30shah
r30shah merged commit 6267a60 into eclipse-openj9:master Oct 8, 2025
9 checks passed
@ehsankianifar
ehsankianifar deleted the Z_inlineMultiANewArray branch October 8, 2025 15:19
ehsankianifar added a commit to ehsankianifar/openj9 that referenced this pull request Oct 9, 2025
multianewarray opcode instructions was updated in eclipse-openj9#22548 and the
 evaluation of the second child was changed to evaluate only if
 reference count is > 1. This caused the last multianewarray tree to
 have wrong dims register child and some failures. Remove the condition
 for evaluating children.

Fix: eclipse-openj9#22757
signed-off-by: Ehsan Kiani Far <ehsan.kianifar@gmail.com>
ehsankianifar added a commit to ehsankianifar/openj9 that referenced this pull request Oct 10, 2025
multianewarray opcode instructions was updated in eclipse-openj9#22548 and the
 evaluation of the second child was changed to evaluate only if
 reference count is > 1. This caused the last multianewarray tree to
 have wrong dims register child and some failures. Remove the condition
 for evaluating children.

Fix: eclipse-openj9#22757
signed-off-by: Ehsan Kiani Far <ehsan.kianifar@gmail.com>
ehsankianifar added a commit to ehsankianifar/openj9 that referenced this pull request Oct 15, 2025
multianewarray opcode instructions was updated in eclipse-openj9#22548 and the
 evaluation of the second child was changed to evaluate only if
 reference count is > 1. This caused the last multianewarray tree to
 have wrong dims register child and some failures. Remove the condition
 for evaluating children.

Fix: eclipse-openj9#22757
signed-off-by: Ehsan Kiani Far <ehsan.kianifar@gmail.com>
ehsankianifar added a commit to ehsankianifar/openj9 that referenced this pull request Oct 15, 2025
multianewarray opcode instructions was updated in eclipse-openj9#22548 and the
evaluation of the second child was changed to evaluate only if
reference count is > 1. This caused the last multianewarray tree to
have wrong dims register child and some failures. Remove the condition
for evaluating children.

Fix: eclipse-openj9#22757
signed-off-by: Ehsan Kiani Far <ehsan.kianifar@gmail.com>
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