Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correctly handle primitive VTs in System.arraycopy #17048

Merged
merged 1 commit into from May 17, 2023

Conversation

ehrenjulzert
Copy link

@ehrenjulzert ehrenjulzert commented Mar 28, 2023

  • Create VM_ValueTypeHelpers::copyFlattenableArray
    function and call it instead of
    VM_ArrayCopyHelpers::referenceArrayCopy when
    copying flattened or primitive arrays
  • Throw a NPE instead of an ASE in
    doReferenceArrayCopy when an attempt is made to
    copy a null object into an array of primitive
    value types

for #13182

@ehrenjulzert
Copy link
Author

One thing I'm unsure of is how/if impl_jitReferenceArrayCopy should be changed to throw a NPE, since it seems to not differentiate between different kinds of errors (always returns (void*)-1 when there's an exception). @hangshao0 do you know who I could ask about that?

@@ -75,7 +76,11 @@ typeCheckArrayStore(J9VMThread *vmThread, J9Object *object, J9IndexableObject *a
return (0 != instanceOfOrCheckCast(storedClazz, componentType));
}
}
} else if (J9_IS_J9CLASS_PRIMITIVE_VALUETYPE(componentType)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

The new code should be inside #if defined(J9VM_OPT_VALHALLA_VALUE_TYPES)

Copy link
Author

Choose a reason for hiding this comment

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

added in 92cb616


if (J9_IS_J9CLASS_PRIMITIVE_VALUETYPE(destComponentClass) &&
!J9_IS_J9CLASS_PRIMITIVE_VALUETYPE(srcComponentClass) &&
(NULL == J9JAVAARRAYOFOBJECT_LOAD(_currentThread, srcObject, srcStart))) {
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems that only one element in the array is loaded here and compared against NULL.

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 see the check of flatten vs not flatten on the srcComponentClass. Does the NPE apply to the flattened case ?

Copy link
Author

Choose a reason for hiding this comment

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

Only the first element being copied needs to be checked, because if the first element is both non-null and nullable then an ASE should be thrown instead (This is how it works on the RI)

Copy link
Author

Choose a reason for hiding this comment

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

Oh wait actually you're right, I didn't take into account the fact that primitive VTs could be inside an array of Objects. If you try to copy something like new Object[] {new VTInt(), null, null} into an array of VTInts then you will still get a NPE, whereas new Object[] {new Object(), null, null} will get you an ASE.

I can change the code to just check if the element at the index returned by referenceArrayCopy is null (since referenceArrayCopy returns the index at which an error occurred)

Copy link
Contributor

Choose a reason for hiding this comment

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

check if the element at the index returned by referenceArrayCopy is null

This should work.

Copy link
Author

Choose a reason for hiding this comment

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

Ok I fixed this issue in 92cb616

I also added a check to make sure the source array is not flattened

@@ -3030,6 +3043,7 @@ class INTERPRETER_CLASS
buildInternalNativeStackFrame(REGISTER_ARGS);
rc = THROW_ARRAY_STORE;
} else {

Copy link
Contributor

Choose a reason for hiding this comment

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

Please remove the empty line change.

Copy link
Author

Choose a reason for hiding this comment

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

fixed in 92cb616

J9Class *destComponentClass = ((J9ArrayClass *)J9OBJECT_CLAZZ(_currentThread, destObject))->componentType;

if (J9_IS_J9CLASS_PRIMITIVE_VALUETYPE(destComponentClass) &&
!J9_IS_J9CLASS_PRIMITIVE_VALUETYPE(srcComponentClass) &&
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to check if srcComponentClass is primitive VT ? It could be an array of Object and srcComponentClass is java.lang.Object.

Copy link
Author

Choose a reason for hiding this comment

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

Yeah I suppose that check is unnecessary since I'm already checking if the problem element is null on the next line

Copy link
Author

Choose a reason for hiding this comment

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

removed in 92cb616

@hangshao0 hangshao0 added comp:vm project:valhalla Used to track Project Valhalla related work labels Mar 28, 2023
@hangshao0
Copy link
Contributor

One thing I'm unsure of is how/if impl_jitReferenceArrayCopy should be changed to throw a NPE, since it seems to not differentiate between different kinds of errors (always returns (void*)-1 when there's an exception). @hangshao0 do you know who I could ask about that?

Do you want a different return code from the JIT helper impl_jitReferenceArrayCopy for the NPE case (when copying a null object into an array of primitive value types) ? @hzongaro @a7ehuo.

@@ -64,8 +64,9 @@ alwaysCallReferenceArrayCopyHelper(J9JavaVM *javaVM)
static bool
typeCheckArrayStore(J9VMThread *vmThread, J9Object *object, J9IndexableObject *arrayObj)
{
J9Class *componentType = ((J9ArrayClass *)J9OBJECT_CLAZZ(vmThread, arrayObj))->componentType;
Copy link
Contributor

Choose a reason for hiding this comment

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

For the case object and arrayObj are both NULL, this function returned true before this change. Now it will segfault.

Copy link
Author

Choose a reason for hiding this comment

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

I could add a bit at the top to return true if both object and arrayObj are null, but I feel like that's not intended behaviour. If the arrayObj is null then there shouldn't be any legal stores to it, so really I should be returning false in that case.

if (-1 != value) {
buildInternalNativeStackFrame(REGISTER_ARGS);
rc = THROW_ARRAY_STORE;
I_32 errorIndex = VM_ArrayCopyHelpers::referenceArrayCopy(_currentThread, srcObject, srcStart, destObject, destStart, elementCount);
Copy link
Contributor

Choose a reason for hiding this comment

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

I see the change here and in FastJNI_java_lang_System.cpp are duplicated. Can the change be made inside the VM helper VM_ArrayCopyHelpers::referenceArrayCopy() ?

Copy link
Author

Choose a reason for hiding this comment

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

Good point, I would have to change the return value of VM_ArrayCopyHelpers::referenceArrayCopy to a VM_BytecodeAction but I suppose it would be worth it.

@hangshao0
Copy link
Contributor

@dmitripivkine Can you review the change in the GC code ?

@amicic
Copy link
Contributor

amicic commented Mar 30, 2023

where did we actually fail during the copy in the existing code (I understand if we failed, that we unconditionally threw ASE)?

edit: I guess we actually succeeded in copying?

@dmitripivkine
Copy link
Contributor

I am not a VT expert, so please correct me in the case of misunderstanding.

  • I hope original claim that reference array of VTs can not have NULL elements is correct. I don't think it is relevant from GC point of view.
  • VT class can not have subclass and has depth 1 with j.l.Object as superclass. Am I reading code correctly that j.l.Objects elements cat not be copied to VT elements? If so, and VT->VT allowed only how source array can have NULLs? And if it a case check at element copy time is not necessary. Coping elements with types not allowed should be handled on front of copy operation
  • If somehow there is no other way to control delivery of NULL elements to VT array as in coping time the question is why this handling done for forwardReferenceArrayCopyWithCheckAndAlwaysWrtbarIndex() copy function only (where typeCheckArrayStore() is called) and rest of copy functions in the table are ignored? There is might be a case that this is only function can be used to copy VT array elements (happen to be or there is deterministic implementation logic?). In this case I think change can be accepted but should be more explicit explanation why so (at least comment)

@ehrenjulzert
Copy link
Author

@amicic Yes, in the original code we succeeded in copying, which was an error. Originally we always allowed null to be stored in any reference type array, which let us to actually do an illegal operation by storing null in an array of primitive value types. To fix this I added some code in typeCheckArrayStore that would return an error if we attempt to store null in an array of primitive value types, but unfortunately all errors thrown by typeCheckArrayStore are ASE exceptions, and in this specific case we need to throw a NPE. So to get it to throw a NPE I'm also changing how the errors are handled in doReferenceArrayCopy.

@ehrenjulzert
Copy link
Author

@dmitripivkine

I hope original claim that reference array of VTs can not have NULL elements is correct

Yup, this is correct. Primitive VTs are not nullable.

j.l.Objects elements can not be copied to VT elements

This isn't true, j.l.Object elements can be copied into a VT array of type T[], but only if they are not null and they can be downcast from Object to T. Consider the following 2 cases:

primitive static class VTInt {
  int i;
  VTInt(int i) {
    this.i = i;
  }
}

VTInt[] vtiAry = new VTInt[3];

// Case 1: this will work because you are allowed to copy Objects into vtiAry so long as they can be downcast to the type VTInt
Object[] oarr = new Object[] { new VTInt(1), new VTInt(1), new VTInt(1) };
System.arraycopy(oarr, 0, vtiAry, 0, 3);

// Case 2: this arraycopy call will throw a NPE because the last element in oarr is null
Object[] oarr = new Object[] { new VTInt(1), new VTInt(1), null };
System.arraycopy(oarr, 0, vtiAry, 0, 3);

why this handling done for forwardReferenceArrayCopyWithCheckAndAlwaysWrtbarIndex() copy function only

The table is defined here:

/* Forward copies with type check on each element (check for ArrayStoreException) */
table->forwardReferenceArrayCopyWithCheckIndex[j9gc_modron_wrtbar_illegal] = copyVariantUndefinedIndex;
table->forwardReferenceArrayCopyWithCheckIndex[j9gc_modron_wrtbar_none] = forwardReferenceArrayCopyWithCheckAndAlwaysWrtbarIndex;
table->forwardReferenceArrayCopyWithCheckIndex[j9gc_modron_wrtbar_always] = forwardReferenceArrayCopyWithCheckAndAlwaysWrtbarIndex;
table->forwardReferenceArrayCopyWithCheckIndex[j9gc_modron_wrtbar_oldcheck] = forwardReferenceArrayCopyWithCheckAndAlwaysWrtbarIndex;
table->forwardReferenceArrayCopyWithCheckIndex[j9gc_modron_wrtbar_cardmark] = forwardReferenceArrayCopyWithCheckAndAlwaysWrtbarIndex;
table->forwardReferenceArrayCopyWithCheckIndex[j9gc_modron_wrtbar_cardmark_incremental] = forwardReferenceArrayCopyWithCheckAndAlwaysWrtbarIndex;
table->forwardReferenceArrayCopyWithCheckIndex[j9gc_modron_wrtbar_cardmark_and_oldcheck] = forwardReferenceArrayCopyWithCheckAndAlwaysWrtbarIndex;
table->forwardReferenceArrayCopyWithCheckIndex[j9gc_modron_wrtbar_satb] = forwardReferenceArrayCopyWithCheckAndAlwaysWrtbarIndex;

The only two functions in the table are forwardReferenceArrayCopyWithCheckAndAlwaysWrtbarIndex and copyVariantUndefinedIndex. copyVariantUndefinedIndex does nothing except throw an error:

copyVariantUndefinedIndex(J9VMThread *vmThread, J9IndexableObject *srcObject, J9IndexableObject *destObject, I_32 srcIndex, I_32 destIndex, I_32 lengthInSlots)
{
Assert_MM_unreachable();
return srcIndex;
}

@hangshao0
Copy link
Contributor

@ehrenjulzert Does RI allow both System.arraycopy(oarr, 0, vtiAry, 0, 3) and System.arraycopy(vtiAry, 0, oarr, 0, 3), regardless of flattening (inlining) enabled or disabled ?

@ehrenjulzert
Copy link
Author

Yes, flattening doesn't matter

@hangshao0
Copy link
Contributor

hangshao0 commented Apr 3, 2023

Looks like RI allows copying between flattened array and object (non-flattened) array. Does OpenJ9 give the correct result if arrayCopy is done from object array to flattened array and from flattened array to object array ? If not, more change is needed here.

@ehrenjulzert
Copy link
Author

Does OpenJ9 give the correct result if arrayCopy is done from object array to flattened array and from flattened array to object array ?

Good catch, it doesn't work. When I was testing it earlier I was just looking at whether or not there was an exception, I wasn't looking at the values of the arrays themselves. When I try to copy the flattened array into the object array the values are treated as pointers, and vice versa. I'll have to add some logic to check if we are copying from a flattened to unflattened array or vice versa.

@hzongaro
Copy link
Member

hzongaro commented Apr 6, 2023

Do you want a different return code from the JIT helper impl_jitReferenceArrayCopy for the NPE case (when copying a null object into an array of primitive value types) ?

If I'm understanding correctly, some platforms currently check whether the result is non-zero, and if so, throw an ArrayStoreException, while others check whether the result is -1. We'll need to sort out how we'll want to handle the possibility of NullPointerException. I'll update you as soon as I have more information.

@ehrenjulzert
Copy link
Author

My update in 740909d removed all the GC code changes, and added flattened array handling to arraycopy using a new helper function, copyFlattenableArray.

destIndex--;

/* No type checks required since srcObject == destObject */
j9object_t copyObject = loadFlattenableArrayElement(currentThread, objectAccessBarrier, objectAllocate, srcObject, srcIndex, true);
Copy link
Contributor

Choose a reason for hiding this comment

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

It is possible that loadFlattenableArrayElement() failed in the fast path and the returned copyObject is NULL.

Copy link
Author

Choose a reason for hiding this comment

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

Added checks for this in 5db20fe

@hangshao0
Copy link
Contributor

Talked to @ehrenjulzert, there is a possible optimization when copying from flattened array to flattened array. There is no need to allocate the copyObject and check if the element in the array is null.

@hzongaro
Copy link
Member

Ehren @ehrenjulzert, just to follow up on your question:

Do you want a different return code from the JIT helper impl_jitReferenceArrayCopy for the NPE case (when copying a null object into an array of primitive value types) ?

Annabelle, Vijay, Daryl and I discussed this offline, and decided that, for now at least, the JIT will avoid using impl_jitReferenceArrayCopy in situations where a NullPointerException might be thrown - in such situations it will fall back to using the actual System.arraycopy. So there's no need for impl_jitReferenceArrayCopy to return a distinct failure result for now - it should continue to return -1.

@ehrenjulzert
Copy link
Author

In the latest commit I updated copyFlattenableArray to correctly handle allocation failures in loadFlattenableArrayElement, and also change the code to call primitiveArrayCopy when copying a flattened array into another flattened array (which should be much faster than calling copyFlattenableArray)

J9Class *destClazz = J9OBJECT_CLAZZ(_currentThread, destObject);
J9Class *destComponentClass = ((J9ArrayClass *)destClazz)->componentType;

if (J9_IS_J9CLASS_FLATTENED(srcClazz) && J9_IS_J9CLASS_FLATTENED(destClazz) && (srcClazz == destClazz)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

In the case srcClazz and destClazz are both flattened, we can directly throw Array Store Exception if srcClazz != destClazz. The current code will goes into referenceArrayCopy to throw the expectation, which is not obvious. I suggest moving (srcClazz == destClazz) to the next line like:

if (J9_IS_J9CLASS_FLATTENED(srcClazz) && J9_IS_J9CLASS_FLATTENED(destClazz) {
    if (srcClazz == destClazz) {

Throw the expectation if they are not the same class.

Copy link
Author

Choose a reason for hiding this comment

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

added in 25f3eed

j9object_t copyObject = loadFlattenableArrayElement(currentThread, objectAccessBarrier, objectAllocate, srcObject, srcIndex, true);

/* Check for an allocation failure (only possible if srcClazz is flattened) */
if ((NULL == copyObject) && J9_IS_J9CLASS_FLATTENED(srcClazz)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

If it is only possible if srcClazz is flattened, then you should assert J9_IS_J9CLASS_FLATTENED(srcClazz) is true inside if (NULL == copyObject)

Copy link
Author

Choose a reason for hiding this comment

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

What I mean by only possible if srcClazz is flattened is that when copyObject is NULL, 2 things are possible:

1: The flattenable array element at that index is actually NULL
Or
2: There was an allocation failure

Allocation failures are only possible when srcClazz is flattened, and so we need to make sure both (NULL == copyObject) and J9_IS_J9CLASS_FLATTENED(srcClazz) are true.

I guess I should update the comment to make that more clear?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, please update the comment.

Copy link
Author

Choose a reason for hiding this comment

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

updated in 25f3eed

runtime/vm/ValueTypeHelpers.hpp Show resolved Hide resolved
if (!VM_VMHelpers::objectArrayStoreAllowed(currentThread, destObject, copyObject)) {
return -1;
}
if (J9_IS_J9CLASS_PRIMITIVE_VALUETYPE(destComponentClass) && (NULL == copyObject)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I suggest to do the NPE check before the objectArrayStoreAllowed() check.

Copy link
Author

Choose a reason for hiding this comment

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

moved in 25f3eed

UDATA srcDepth = J9CLASS_DEPTH(srcClazz);
UDATA destDepth = J9CLASS_DEPTH(destClazz);
if ((srcDepth <= destDepth) || (destClazz->superclasses[srcDepth] != srcClazz)) {
typeChecksRequired = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

typeChecksRequired is only used at line 639, so this block to set typeChecksRequired can be moved into the else block at line 628.

Copy link
Author

Choose a reason for hiding this comment

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

moved in 25f3eed

@ehrenjulzert ehrenjulzert force-pushed the InlineTypeArray_fix branch 2 times, most recently from 7337a0e to 25f3eed Compare April 28, 2023 21:30

if (J9_IS_J9CLASS_FLATTENED(srcClazz) && J9_IS_J9CLASS_FLATTENED(destClazz)) {
if (srcClazz == destClazz) {
UDATA elementSize = J9ARRAYCLASS_GET_STRIDE(srcClazz);
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add a comment this only works for contiguous flattened arrays for now as GC does not support the discontiguous case.

Copy link
Author

Choose a reason for hiding this comment

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

added in 3eecee5

@hangshao0 hangshao0 requested a review from tajila May 1, 2023 17:53
@hangshao0
Copy link
Contributor

@dmitripivkine Can you review the change in the GC code ?

There is no GC change in this PR anymore. But you are still welcome to review if you want. @dmitripivkine

* Decide if type checks are required
* Type checks are not required only if both classes are the same, or the source class is a subclass of the dest class
*/
if (srcClazz != destClazz) {
Copy link
Contributor

Choose a reason for hiding this comment

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

can you use isSameOrSuperClassOf?

Copy link
Author

Choose a reason for hiding this comment

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

Yeah I can, good point

Copy link
Author

Choose a reason for hiding this comment

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

updated in e1d42a4

J9Class *destClazz = J9OBJECT_CLAZZ(_currentThread, destObject);
J9Class *destComponentClass = ((J9ArrayClass *)destClazz)->componentType;

if (J9_IS_J9CLASS_FLATTENED(srcClazz) && J9_IS_J9CLASS_FLATTENED(destClazz)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this has to be, if both are flattened and dont contain any object references. If there is an object ref, you need to do a barrier load/store

Copy link
Contributor

Choose a reason for hiding this comment

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

We should consider making a VM_ArrayCopyHelpers::vtArrayCopy that does a batch barrier. @dmitripivkine

Copy link
Contributor

Choose a reason for hiding this comment

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

You can check if the class has references between doing J9_ARE_ANY_BITS_SET(clazz->classFlags, J9ClassHasReferences)

Copy link
Author

Choose a reason for hiding this comment

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

Ok I updated the check in 9f31ca4

J9Class *srcComponentClass = ((J9ArrayClass *)srcClazz)->componentType;
J9Class *destComponentClass = ((J9ArrayClass *)destClazz)->componentType;

if (J9_IS_J9CLASS_FLATTENED(srcClazz) && J9_IS_J9CLASS_FLATTENED(destClazz) && !J9_ARE_ANY_BITS_SET(srcComponentClass->classFlags, J9ClassHasReferences) && !J9_ARE_ANY_BITS_SET(destComponentClass->classFlags, J9ClassHasReferences)) {
Copy link
Contributor

@tajila tajila May 15, 2023

Choose a reason for hiding this comment

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

sorry, I should have suggested J9_ARE_NO_BITS_SET(... so you dont have to add the !

Copy link
Author

Choose a reason for hiding this comment

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

updated in e2d43b1

Copy link
Contributor

@tajila tajila left a comment

Choose a reason for hiding this comment

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

Just one minor change

@hangshao0
Copy link
Contributor

The #if defined(J9VM_OPT_VALHALLA_VALUE_TYPES) in this PR should be changed to the new flag J9VM_OPT_VALHALLA_FLATTENABLE_VALUE_TYPES now as #17394 is merged.

@hangshao0
Copy link
Contributor

In the future, all the code related to value objects should be added inside #if defined(J9VM_OPT_VALHALLA_VALUE_TYPES). All the code related to primitive value types should be added inside #if defined(J9VM_OPT_VALHALLA_FLATTENABLE_VALUE_TYPES).

- Create VM_ValueTypeHelpers::copyFlattenableArray
function and call it instead of
VM_ArrayCopyHelpers::referenceArrayCopy when
copying flattened or primitive arrays
- Throw a NPE instead of an ASE in
doReferenceArrayCopy when an attempt is made to
copy a null object into an array of primitive
value types

for eclipse-openj9#13182

Signed-off-by: Ehren Julien-Neitzert <ehren.julien-neitzert@ibm.com>
@ehrenjulzert
Copy link
Author

Ok, I changed the #if defined to J9VM_OPT_VALHALLA_FLATTENABLE_VALUE_TYPES

@tajila
Copy link
Contributor

tajila commented May 16, 2023

Jenkins test sanity win jdk8

@tajila
Copy link
Contributor

tajila commented May 16, 2023

Jenkins test sanity,extended xlinuxval jdknext

@tajila tajila merged commit 7d5d62a into eclipse-openj9:master May 17, 2023
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
comp:vm project:valhalla Used to track Project Valhalla related work
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants