-
Notifications
You must be signed in to change notification settings - Fork 728
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
Correctly handle primitive VTs in System.arraycopy #17048
Conversation
One thing I'm unsure of is how/if |
@@ -75,7 +76,11 @@ typeCheckArrayStore(J9VMThread *vmThread, J9Object *object, J9IndexableObject *a | |||
return (0 != instanceOfOrCheckCast(storedClazz, componentType)); | |||
} | |||
} | |||
} else if (J9_IS_J9CLASS_PRIMITIVE_VALUETYPE(componentType)) { |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added in 92cb616
runtime/vm/BytecodeInterpreter.hpp
Outdated
|
||
if (J9_IS_J9CLASS_PRIMITIVE_VALUETYPE(destComponentClass) && | ||
!J9_IS_J9CLASS_PRIMITIVE_VALUETYPE(srcComponentClass) && | ||
(NULL == J9JAVAARRAYOFOBJECT_LOAD(_currentThread, srcObject, srcStart))) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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 Object
s. If you try to copy something like new Object[] {new VTInt(), null, null}
into an array of VTInt
s 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)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
runtime/vm/BytecodeInterpreter.hpp
Outdated
@@ -3030,6 +3043,7 @@ class INTERPRETER_CLASS | |||
buildInternalNativeStackFrame(REGISTER_ARGS); | |||
rc = THROW_ARRAY_STORE; | |||
} else { | |||
|
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in 92cb616
runtime/vm/BytecodeInterpreter.hpp
Outdated
J9Class *destComponentClass = ((J9ArrayClass *)J9OBJECT_CLAZZ(_currentThread, destObject))->componentType; | ||
|
||
if (J9_IS_J9CLASS_PRIMITIVE_VALUETYPE(destComponentClass) && | ||
!J9_IS_J9CLASS_PRIMITIVE_VALUETYPE(srcComponentClass) && |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed in 92cb616
Do you want a different return code from the JIT helper |
bc29c33
to
92cb616
Compare
@@ -64,8 +64,9 @@ alwaysCallReferenceArrayCopyHelper(J9JavaVM *javaVM) | |||
static bool | |||
typeCheckArrayStore(J9VMThread *vmThread, J9Object *object, J9IndexableObject *arrayObj) | |||
{ | |||
J9Class *componentType = ((J9ArrayClass *)J9OBJECT_CLAZZ(vmThread, arrayObj))->componentType; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
runtime/vm/BytecodeInterpreter.hpp
Outdated
if (-1 != value) { | ||
buildInternalNativeStackFrame(REGISTER_ARGS); | ||
rc = THROW_ARRAY_STORE; | ||
I_32 errorIndex = VM_ArrayCopyHelpers::referenceArrayCopy(_currentThread, srcObject, srcStart, destObject, destStart, elementCount); |
There was a problem hiding this comment.
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()
?
There was a problem hiding this comment.
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.
@dmitripivkine Can you review the change in the GC code ? |
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? |
I am not a VT expert, so please correct me in the case of misunderstanding.
|
@amicic Yes, in the original code we succeeded in copying, which was an error. Originally we always allowed |
Yup, this is correct. Primitive VTs are not nullable.
This isn't true, j.l.Object elements can be copied into a VT array of type 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);
The table is defined here: openj9/runtime/gc_modron_startup/arrayCopy.cpp Lines 267 to 276 in e656d2e
The only two functions in the table are openj9/runtime/gc_modron_startup/arrayCopy.cpp Lines 241 to 245 in e656d2e
|
@ehrenjulzert Does RI allow both |
Yes, flattening doesn't matter |
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. |
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. |
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. |
92cb616
to
740909d
Compare
My update in 740909d removed all the GC code changes, and added flattened array handling to arraycopy using a new helper function, |
destIndex--; | ||
|
||
/* No type checks required since srcObject == destObject */ | ||
j9object_t copyObject = loadFlattenableArrayElement(currentThread, objectAccessBarrier, objectAllocate, srcObject, srcIndex, true); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
740909d
to
5db20fe
Compare
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. |
Ehren @ehrenjulzert, just to follow up on your question:
Annabelle, Vijay, Daryl and I discussed this offline, and decided that, for now at least, the JIT will avoid using |
5db20fe
to
392d059
Compare
In the latest commit I updated |
runtime/vm/BytecodeInterpreter.hpp
Outdated
J9Class *destClazz = J9OBJECT_CLAZZ(_currentThread, destObject); | ||
J9Class *destComponentClass = ((J9ArrayClass *)destClazz)->componentType; | ||
|
||
if (J9_IS_J9CLASS_FLATTENED(srcClazz) && J9_IS_J9CLASS_FLATTENED(destClazz) && (srcClazz == destClazz)) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)) { |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated in 25f3eed
if (!VM_VMHelpers::objectArrayStoreAllowed(currentThread, destObject, copyObject)) { | ||
return -1; | ||
} | ||
if (J9_IS_J9CLASS_PRIMITIVE_VALUETYPE(destComponentClass) && (NULL == copyObject)) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moved in 25f3eed
runtime/vm/ValueTypeHelpers.hpp
Outdated
UDATA srcDepth = J9CLASS_DEPTH(srcClazz); | ||
UDATA destDepth = J9CLASS_DEPTH(destClazz); | ||
if ((srcDepth <= destDepth) || (destClazz->superclasses[srcDepth] != srcClazz)) { | ||
typeChecksRequired = true; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moved in 25f3eed
7337a0e
to
25f3eed
Compare
|
||
if (J9_IS_J9CLASS_FLATTENED(srcClazz) && J9_IS_J9CLASS_FLATTENED(destClazz)) { | ||
if (srcClazz == destClazz) { | ||
UDATA elementSize = J9ARRAYCLASS_GET_STRIDE(srcClazz); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added in 3eecee5
25f3eed
to
3eecee5
Compare
There is no GC change in this PR anymore. But you are still welcome to review if you want. @dmitripivkine |
runtime/vm/ValueTypeHelpers.hpp
Outdated
* 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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you use isSameOrSuperClassOf
?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated in e1d42a4
3eecee5
to
e1d42a4
Compare
runtime/vm/BytecodeInterpreter.hpp
Outdated
J9Class *destClazz = J9OBJECT_CLAZZ(_currentThread, destObject); | ||
J9Class *destComponentClass = ((J9ArrayClass *)destClazz)->componentType; | ||
|
||
if (J9_IS_J9CLASS_FLATTENED(srcClazz) && J9_IS_J9CLASS_FLATTENED(destClazz)) { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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
e1d42a4
to
9f31ca4
Compare
runtime/vm/BytecodeInterpreter.hpp
Outdated
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)) { |
There was a problem hiding this comment.
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 !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated in e2d43b1
There was a problem hiding this 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
The |
In the future, all the code related to value objects should be added inside |
- 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 <[email protected]>
9f31ca4
to
e2d43b1
Compare
Ok, I changed the |
Jenkins test sanity win jdk8 |
Jenkins test sanity,extended xlinuxval jdknext |
function and call it instead of
VM_ArrayCopyHelpers::referenceArrayCopy when
copying flattened or primitive arrays
doReferenceArrayCopy when an attempt is made to
copy a null object into an array of primitive
value types
for #13182