You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[JitStress] VectorTableLookupExtension fails in jitstress1 #84696
Looking at the result and inferring, it seems that the input operand's vector register's upper half are zeroed out. With that only indices between 0 ~ 7 and 16 ~ 23 are returning correct result while other inbound indices are returning 0. Out of bound indices are still correct and returns the correct value. Seems something corrupts the input registers during upper vector restore. Will continue investigation.
This seems to be an odd existing problem that was surfaced with the consecutive work because this is the first time that we started using FieldList() for non-call nodes.
For table operand of VectorTableLookup() we create FIELD_LIST to represent it. The actual rows (or vectors) goes inside the FIELD_LIST . Below, [000476] and [000494] are 2 vectors that build the [000333] field list. The field list is used as operand of intrinsic node [000331]. However, between the definition of field list ([000333]) and actual use of it (``[000331]), there are various nodes, including a CALL node [000337]` .
When we build RefPosition for intrinsic node [000331], we might have to build RefPosition to restore the upper half vectors of those vectors that are part of the field_list. For example, here is the list of refpositions created:
#386 : Restore upper-half vector for the 1st operand of VectorTableLookupExtension
#387 : Actual use of the 1st operand.
#388: Restore upper-half vector for the 1st field of our field list which is the 2nd operand of VectorTableLookupExtension. This basically corresponds to [000476] in the above tree node dump.
#389 : Actual use of the 1st field of our field list.
#390 : Restore upper-half vector for the 2nd field of our field list. This basically corresponds to [000494] in the above tree node dump.
#391 : Actual use of the 2st field of our field list.
Note that, Refposition #388 corresponds to the use node [000476] and does not correspond to the actual use of field_list [000331]. As a result, when we have to restore the upper-half vector for this value, we end up restoring it before [000476]. Likewise, it happens for #390, whose upper half vector restore happens at [000494] instead of at [000331].
This leads to multiple problems, some of them can be seen in this example:
Since 1st field of the field_list was the last use, we restored it properly at [000476] , but did not feel the need to save the upper-half again before the CALL at [000337]. Because of the call, the upper half of the 1st field is wiped out.
The save for 2nd field of the field_list happens before the CALL at [000337], but its restore happens at [000494], which happened to be located before the place where the save happened. Thus, we ended up restoring before even saving the upper half of a vector.
Ideally, we need to carry some semantics to tell that for such situations, consider the use as the field_list instead of individual fields use. I will continue investigating if that will help and feasible.
I have discovered another problem related to the ordering and seems to be pre-existing for upper vector restore and copy registers. The arrangement of refpositions for consecutive registers exposed it. This will be a long post, but it is worth describing the problem in detail.
Typically, if there is a use of a partially-saved vector (vector whose upper-half was saved before the call), we first restore it and then use it. There arises an interesting scenario in presence of FIELD_LIST, where we want to use multiple vector registers in succession, and all should be restored before the use (because all the vector registers were partially-spilled). For example, if there were 3 elements in the FIELD_LIST, the allocation ordering is something like this:
restore upper-half of V07 from r15into r5use V07 present in r5restore upper-half of V08 from r8into r6use V08 present in r6restore upper-half of V11 from r11into r7use V11 present in r7
The way we insert the restores is by appending them one after another before the use tree node, in our case, FIELD_LIST or the HWINTRINSIC node and end up something like this:
restore upper-half of V07 from r15into r5restore upper-half of V08 from r8into r6restore upper-half of V11 from r11into r7use V07 present in r5use V08 present in r6use V11 present in r7
Having such arrangement can create problems, but they can get worse and hit easily if we had to add copyRegs for each of those variables before use. copyRegs happens if a variable was assigned a register X, but at the current location, we want a different register Y, we need to insert a copy from X to Y to copy the value of variable present in register X into Y before we use the variable value from Y.
For above example, imagine for V07, V08 and V11, they were restored into a different register we need to copy them to the appropriate registers before use, we will generate copyRegs as seen below:
restore upper-half of V07 from r12intor15restore upper-half of V08 from r8into r18restore upper-half of V11 from r11intor14copy V07 from r15 to r5use V07 present in r5copy V07 from r18 to r6use V08 present in r6copy V07 from r14 to r7use V11 present in r7
Now let's see an example when a problem can arise because of this. Consider the following order in which registers are allocated for set of refpositions.
[r9] : Register from where V07 restore happen into home location r8[r13] : Use V07, but since it was present inr8, generate `copy r8 to r13`[r11] : Register from where V08 restore happen into home location r10[r14] : Use V08, but since it was present inr10, generate `copy r10 to r14` ; Note - Below, we can assign r8 because it is dead after copyReg for V07[r8] : Register from where V11 restore happen into home location r12[r15] : Use V11, but since it was present inr8, generate `copy r12 to r15`
Below is the pseudo-code of how the code should look like ideally.
restore upper-half of V07 from r9intor8copy V07 from r8 to r13use V07 present inr13 ; consecutive 1restore upper-half of V08 from r11intor10copy V08 from r10 to r14use V08 present inr14 ; consecutive 2; load value from stack into r8restore upper-half of V11 from r8intor12copy V11 from r12 to r15use V11 present inr15 ; consecutive 3
However, since we insert restores one after another, we end up with this:
restore upper-half of V07 from r9intor8restore upper-half of V08 from r11intor10; load value from stack into r8restore upper-half of V11 from r8intor12copy V07 from r8 to r13 ; <--- PROBLEM. r8 held the value of V11 but we thought we are just copying the value of V08use V07 present inr13 ; consecutive 1copy V08 from r10 to r14use V08 present inr14 ; consecutive 2copy V11 from r12 to r15use V11 present inr15 ; consecutive 3
I will check if I can tweak the algorithm to assign the same consecutive register to upper-half positions too, to avoid extra copies. I already added TODO when working on consecutive registers:
// TODO-CQ: We could technically assign RefTypeUpperVectorRestore and its RefTypeUse same register, but
// during register selection, it might get tricky to know which of the busy registers are assigned to
// RefTypeUpperVectorRestore positions of corresponding variables for which (another criteria)
// we are trying to find consecutive registers.
Another possibility is (at least for consecutive register scenarios), mark the registers into which we are restoring the values, and that way they will not used while we are processing the refpositions. In above example, we will basically mark r8 and r10 busy and hence r8 won't be selected to "restore from" register of V11.
Other option is to rearrange the restores properly so they happen right before the actual use instead of arranging them next to each other. This seems a viable option, specially for consecutive registers.
Activity
tannergooding commentedon Apr 12, 2023
CC. @kunalspathak
kunalspathak commentedon Apr 12, 2023
Thanks, I will take a look. Most likely this is happening because delay reg frees are not getting tracked properly.
kunalspathak commentedon Apr 13, 2023
Looking at the result and inferring, it seems that the input operand's vector register's upper half are zeroed out. With that only indices between
0 ~ 7and16 ~ 23are returning correct result while other inbound indices are returning0. Out of bound indices are still correct and returns the correct value. Seems something corrupts the input registers during upper vector restore. Will continue investigation.kunalspathak commentedon Apr 13, 2023
Pasting the assembly here.
v17andv18are the table registers and are copied fromv10andv12respectively whose upper-half was restored in the past.Assembly
kunalspathak commentedon Apr 14, 2023
This seems to be an odd existing problem that was surfaced with the consecutive work because this is the first time that we started using
FieldList()for non-call nodes.For
tableoperand ofVectorTableLookup()we createFIELD_LISTto represent it. The actual rows (or vectors) goes inside theFIELD_LIST. Below,[000476]and[000494]are 2 vectors that build the[000333]field list. The field list is used as operand of intrinsic node[000331]. However, between the definition of field list ([000333]) and actual use of it (``[000331]), there are various nodes, including a CALL node[000337]` .When we build
RefPositionfor intrinsic node[000331], we might have to buildRefPositionto restore the upper half vectors of those vectors that are part of the field_list. For example, here is the list of refpositions created:#386: Restore upper-half vector for the 1st operand ofVectorTableLookupExtension#387: Actual use of the 1st operand.#388: Restore upper-half vector for the 1st field of our field list which is the 2nd operand ofVectorTableLookupExtension. This basically corresponds to[000476]in the above tree node dump.#389: Actual use of the 1st field of our field list.#390: Restore upper-half vector for the 2nd field of our field list. This basically corresponds to[000494]in the above tree node dump.#391: Actual use of the 2st field of our field list.#392: Actual use of the 3rd operand.#393: Result ofVectorTableLookupExtension.Note that, Refposition
#388corresponds to the use node[000476]and does not correspond to the actual use of field_list[000331]. As a result, when we have to restore the upper-half vector for this value, we end up restoring it before[000476]. Likewise, it happens for#390, whose upper half vector restore happens at[000494]instead of at[000331].This leads to multiple problems, some of them can be seen in this example:
[000476], but did not feel the need to save the upper-half again before theCALLat[000337]. Because of the call, the upper half of the 1st field is wiped out.CALLat[000337], but its restore happens at[000494], which happened to be located before the place where the save happened. Thus, we ended up restoring before even saving the upper half of a vector.Ideally, we need to carry some semantics to tell that for such situations, consider the use as the field_list instead of individual fields use. I will continue investigating if that will help and feasible.
kunalspathak commentedon Apr 15, 2023
I have discovered another problem related to the ordering and seems to be pre-existing for upper vector restore and copy registers. The arrangement of refpositions for consecutive registers exposed it. This will be a long post, but it is worth describing the problem in detail.
Typically, if there is a use of a partially-saved vector (vector whose upper-half was saved before the call), we first restore it and then use it. There arises an interesting scenario in presence of
FIELD_LIST, where we want to use multiple vector registers in succession, and all should be restored before the use (because all the vector registers were partially-spilled). For example, if there were 3 elements in theFIELD_LIST, the allocation ordering is something like this:The way we insert the restores is by appending them one after another before the use tree node, in our case,
FIELD_LISTor theHWINTRINSICnode and end up something like this:Having such arrangement can create problems, but they can get worse and hit easily if we had to add
copyRegsfor each of those variables before use.copyRegshappens if a variable was assigned a register X, but at the current location, we want a different register Y, we need to insert acopy from X to Yto copy the value of variable present in register X into Y before we use the variable value from Y.For above example, imagine for
V07,V08andV11, they were restored into a different register we need to copy them to the appropriate registers before use, we will generatecopyRegsas seen below:Now let's see an example when a problem can arise because of this. Consider the following order in which registers are allocated for set of refpositions.
Below is the pseudo-code of how the code should look like ideally.
However, since we insert restores one after another, we end up with this:
I will check if I can tweak the algorithm to assign the same consecutive register to upper-half positions too, to avoid extra copies. I already added TODO when working on consecutive registers:
runtime/src/coreclr/jit/lsraarm64.cpp
Lines 93 to 96 in 72586a7
Another possibility is (at least for consecutive register scenarios), mark the registers into which we are restoring the values, and that way they will not used while we are processing the refpositions. In above example, we will basically mark r8 and r10 busy and hence r8 won't be selected to "restore from" register of
V11.Other option is to rearrange the restores properly so they happen right before the actual use instead of arranging them next to each other. This seems a viable option, specially for consecutive registers.
kunalspathak commentedon Apr 15, 2023
A snapshot from the Jitdump for one of the example that has problem I described above:
kunalspathak commentedon Apr 20, 2023
Fixed by #84824