Iterative implementation for isSubstitutable#23445
Iterative implementation for isSubstitutable#23445AditiS11 wants to merge 1 commit intoeclipse-openj9:masterfrom
Conversation
|
Creating a single helper function for field iteration would require two-passes of the stack, first collecting all fields into a stack, then performing the comparison. I thought that this would increase the memory overhead. |
|
|
||
| struct FieldComparisonFrame | ||
| { | ||
| j9object_t lhs; |
There was a problem hiding this comment.
The value of lhs and rhs appear to be the same for each entry so I don't think they need to be saved. state is not used.
| UDATA startOffset, | ||
| J9Class *clazz) | ||
| { | ||
| #if defined(J9VM_OPT_VALHALLA_VALUE_TYPES) |
There was a problem hiding this comment.
I think it would make more sense to move this flag around isSubstitutable. Your helper code should be under J9VM_OPT_VALHALLA_VALUE_TYPES as well.
| Assert_VM_notNull(lhs); | ||
| Assert_VM_notNull(rhs); | ||
| Assert_VM_true(J9OBJECT_CLAZZ(currentThread, lhs) == J9OBJECT_CLAZZ(currentThread, rhs)); | ||
| rc = compareValueType(currentThread, objectAccessBarrier, lhs, rhs, startOffset, clazz); |
There was a problem hiding this comment.
You can directly return the result of compareValueType instead of creating a variable.
| static bool | ||
| isSubstitutable(J9VMThread *currentThread, MM_ObjectAccessBarrierAPI objectAccessBarrier, j9object_t lhs, j9object_t rhs, UDATA startOffset, J9Class *clazz) | ||
| * Iteratively traverse the fields and determine if they are equal. | ||
| * Use depth-first search with a stack to handle nested flattened value types. |
There was a problem hiding this comment.
I think a BFS approach would make more sense for this method so you don't need to store iterators.
| goto done; | ||
| j9object_t lhsFieldObject = objectAccessBarrier.inlineMixedObjectReadObject(currentThread, frame.lhs, fieldOffset); | ||
| j9object_t rhsFieldObject = objectAccessBarrier.inlineMixedObjectReadObject(currentThread, frame.rhs, fieldOffset); | ||
| bool rc = VM_ValueTypeHelpers::acmp(currentThread, objectAccessBarrier, lhsFieldObject, rhsFieldObject); |
There was a problem hiding this comment.
There is a circular call here. acmp() calls isSubstitutable.
There was a problem hiding this comment.
Although acmp() calls isSubstitutable(), it starts a new traversal frame on the referenced object. I think it will not go into an infinite recursion.
There was a problem hiding this comment.
There could still be many method calls added to the stack if an object field has another object field.
There was a problem hiding this comment.
I think it will not will not go into an infinite recursion
Please elaborate: Why are cycles involving value objects and non-value objects impossible?
There was a problem hiding this comment.
In my previous approach, acmp() was called from isSubstitutable() to compare reference fields. isSubstitutable() had a check to push value type objects into a queue so that their fields could be compared.
Hence it wouldn't be an infinite recursion.
However in the current implementation I have removed the call to acmp() in isSubstitutable() completely as Theresa suggested.
14eedce to
ab51a40
Compare
| U_32 walkFlags = J9VM_FIELD_OFFSET_WALK_INCLUDE_INSTANCE; | ||
| J9ROMFieldOffsetWalkState state; | ||
|
|
||
| std::queue<FieldComparisonFrame> queue; |
There was a problem hiding this comment.
We do not use STL. You can implement a queue using an array.
0bd0a32 to
885375f
Compare
| U_32 walkFlags = J9VM_FIELD_OFFSET_WALK_INCLUDE_INSTANCE; | ||
| J9ROMFieldOffsetWalkState state; | ||
|
|
||
| const UDATA MAX_QUEUE_SIZE = 512; |
There was a problem hiding this comment.
Defined the max queue size as 512. Not sure if this is sufficient to support the behaviour of valuetypes.
There was a problem hiding this comment.
512 should be sufficient.
There was a problem hiding this comment.
Why is this sufficient? There could still be any number of fields.
There was a problem hiding this comment.
The description says Use DFS so I assumed it is the depth.
Seems that the implementation is changed to do BFS, so 512 is not guaranteed to be big enough.
You can start with an array on stack with a default initial size (like 128). In the case of overflow, you can switch to a new array (2X size of old array) allocated from malloc and copy the data into the new array.
There was a problem hiding this comment.
I had suggested to switch to BFS since the DFS approach required saving much more information including field iterators in each queue entry. If the memory outlook for DFS is potentially better we can do that instead.
There was a problem hiding this comment.
I don't have a strong preference between DFS or BFS.
| #include "ObjectAccessBarrierAPI.hpp" | ||
| #include "VMHelpers.hpp" | ||
| #include "vm_api.h" | ||
| #include <queue> |
There was a problem hiding this comment.
This library is not used.
885375f to
891a23d
Compare
theresa-m
left a comment
There was a problem hiding this comment.
Please update the pull request description to reflect that this is now BFS.
| UDATA queueSize = initialQueueSize; | ||
| FieldComparisonFrame *queue = (FieldComparisonFrame *)j9mem_allocate_memory(sizeof(FieldComparisonFrame) * queueSize, OMRMEM_CATEGORY_VM); | ||
| if (NULL == queue) { | ||
| return false; |
There was a problem hiding this comment.
There should be an out of memory error thrown if memory allocation fails. You can modify the return value to an integer to return different types of results like is done in copyFlattenableArray.
| U_64 rhsValue = objectAccessBarrier.inlineMixedObjectReadU64(currentThread, frame.rhs, fieldOffset); | ||
|
|
||
| if (lhsValue != rhsValue) { | ||
| j9mem_free_memory(queue); |
There was a problem hiding this comment.
I suggest jumping to a label that will free the queue memory since this is done in a lot of places.
| if (flattened) { | ||
| if (tail >= queueSize) { | ||
| UDATA newQueueSize = queueSize * 2; | ||
| FieldComparisonFrame *newQueue = (FieldComparisonFrame *)j9mem_allocate_memory(newQueueSize * sizeof(FieldComparisonFrame), OMRMEM_CATEGORY_VM); |
There was a problem hiding this comment.
I suggest creating a helper method to create and resize the queue since a lot of this code is duplicated.
82e4078 to
11dd004
Compare
| * Use breadth-first search with a queue to handle nested flattened value types. | ||
| * | ||
| * @param[in] currentThread the current thread | ||
| * @param[in] lhs the lhs object address |
There was a problem hiding this comment.
objectAccessBarrier is missing from the param list.
| FieldComparisonFrame frame = queue[head++]; | ||
| if (J9_ARE_ALL_BITS_SET(frame.clazz->classFlags, J9ClassCanSupportFastSubstitutability)) { | ||
| bool compare = objectAccessBarrier.structuralFlattenedCompareObjects(currentThread, frame.clazz, frame.lhs, frame.rhs, frame.offset); | ||
| if ( false == compare) { |
There was a problem hiding this comment.
Extra space between ( and f.
| UDATA queueSize = initialQueueSize; | ||
| UDATA head = 0; | ||
| UDATA tail = 0; | ||
| FieldComparisonFrame *queue = (FieldComparisonFrame *)j9mem_allocate_memory(sizeof(FieldComparisonFrame) * queueSize, OMRMEM_CATEGORY_VM); |
There was a problem hiding this comment.
To avoid allocating unnecessary memory I think this queue should only be created if we run into a field that needs to be stored.
| } | ||
| done: | ||
| if (-1 == result) { | ||
| vm->internalVMFunctions->setHeapOutOfMemoryError(currentThread); |
There was a problem hiding this comment.
Since this is only called from the interpreter the stack will also need to be setup like https://github.com/eclipse-openj9/openj9/blob/master/runtime/vm/BytecodeInterpreter.hpp#L11718-L11723. I suggest to pass this error through acmp and handle the exception throwing in the interpreter instead.
4578055 to
9309b5d
Compare
| _vm->internalVMFunctions->setHeapOutOfMemoryError(_currentThread); | ||
| rc = GOTO_THROW_CURRENT_EXCEPTION; | ||
| return rc; | ||
| } |
There was a problem hiding this comment.
This can be an else if. Same for line 8835. Also please compare cmp to its value in the next line since the result is no longer bool.
| I_32 cmp = VM_ValueTypeHelpers::acmp(_currentThread, _objectAccessBarrier, lhs, rhs); | ||
| if (-1 == cmp) { | ||
| _vm->internalVMFunctions->setHeapOutOfMemoryError(_currentThread); | ||
| rc = GOTO_THROW_CURRENT_EXCEPTION; |
There was a problem hiding this comment.
You could use THROW_HEAP_OOM and follow the same pattern as other bytecodes by jumping to the line 8847 to return. Same for the other acmp use.
| UDATA startOffset, | ||
| J9Class *clazz) | ||
| { | ||
| Assert_VM_notNull(lhs); |
There was a problem hiding this comment.
These asserts are redundant now since isSubstitutable is only called by acmp. I think compareValueType method can just be named isSubstitutable.
| goto done; | ||
| } | ||
| case 'L': { | ||
| goto nextFrame; |
There was a problem hiding this comment.
I don't see this defined.
There was a problem hiding this comment.
Sorry, I had added it in an earlier version, but I have now wrapped the entire field walk in the else block. Hence there is no need for the nextFrame label.
| UDATA head = 0; | ||
| UDATA tail = 0; | ||
| FieldComparisonFrame *queue = NULL; | ||
| FieldComparisonFrame frame = { lhs, rhs, clazz, startOffset}; |
There was a problem hiding this comment.
There is an extra space between { and l or a missing space between t and }.
| flattened = J9_IS_FIELD_FLATTENED(fieldClass, walkResult->field); | ||
| } | ||
| if (flattened) { | ||
| if (queue == NULL) { |
There was a problem hiding this comment.
NULL == queue, same with 237.
5357d9b to
7364525
Compare
| rc = GOTO_BRANCH_WITH_ASYNC_CHECK; | ||
| } else if (-1 == cmp) { | ||
| rc = THROW_HEAP_OOM; | ||
| goto done; |
There was a problem hiding this comment.
Sorry I think my last review was confusing, this goto isn't needed.
| * Data members | ||
| */ | ||
| private: | ||
| struct FieldComparisonFrame |
There was a problem hiding this comment.
This should be under J9VM_OPT_VALHALLA_VALUE_TYPES.
7364525 to
9bcda6e
Compare
9bcda6e to
47097c1
Compare
| JIT_RETURN_UDATA(currentThread->javaVM->internalVMFunctions->valueTypeCapableAcmp(currentThread, lhs, rhs)); | ||
| I_32 cmp = currentThread->javaVM->internalVMFunctions->valueTypeCapableAcmp(currentThread, lhs, rhs); | ||
| if (-1 == cmp) { | ||
| JIT_RETURN_UDATA(setHeapOutOfMemoryErrorFromJIT(currentThread)); |
There was a problem hiding this comment.
j9mem_allocate_memory() is native memory allocation, so this should be setNativeOutOfMemoryErrorFromJIT.
Similarly in BytecodeInterpreter.hpp, the j9mem_allocate_memory() allocation failure (from isSubstitutable()) should result in NativeOutOfMemoryError.
47097c1 to
fd62634
Compare
fd62634 to
4cdcbad
Compare
|
Please use parentheses here and in the commit message to clarify that the references is to a function: |
901fdd8 to
96634be
Compare
96634be to
3cea4ae
Compare
3cea4ae to
ce3853e
Compare
ce3853e to
a5eef68
Compare
|
Any more review comments ? @keithc-ca |
|
I'll have another look at this later today. |
|
@keithc-ca @hzongaro Review comments ? |
keithc-ca
left a comment
There was a problem hiding this comment.
This should be rebased to avoid implicit merges for
runtime/oti/j9nonbuilder.hruntime/oti/vm_api.hruntime/vm/BytecodeInterpreter.hpp
Replace recursive approach to traverse fields. Modify isSubstitutable() to iteratively traverse the fields. Use BFS with a queue to handle nested flattened value types. Related: eclipse-openj9#15768 Signed-off-by: Aditi Srinivas M <Aditi.Srini@ibm.com>
Replace recursive approach to traverse fields.
Modify isSubstitutable to iteratively traverse the fields.
Use BFS with a queue to handle nested flattened value types.
Related: #15768