RFR: 8359235: C1 compilation fails with "assert(is_single_stack() && !is_virtual()) failed: type check"
Guanqiang Han
ghan at openjdk.org
Fri Jul 25 03:25:53 UTC 2025
On Thu, 24 Jul 2025 18:41:13 GMT, Dean Long <dlong at openjdk.org> wrote:
>> I'm able to consistently reproduce the problem using the following command line and test program :
>>
>> java -Xcomp -XX:TieredStopAtLevel=1 -XX:C1MaxInlineSize=200 Test.java
>>
>> import java.util.Arrays;
>> public class Test{
>> public static void main(String[] args) {
>> System.out.println("begin");
>> byte[] arr1 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
>> byte[] arr2 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
>> System.out.println(Arrays.equals(arr1, arr2));
>> System.out.println("end");
>> }
>> }
>>
>> From my analysis, the root cause appears to be a mismatch in operand handling between T_ADDRESS and T_LONG in LIR_Assembler::stack2reg, especially when the source is marked as double stack (e.g., T_LONG) and the destination as single CPU register (e.g., T_ADDRESS), leading to assertion failures like assert(is_single_stack())(because T_LONG is double_size).
>>
>> In the test program above , the call chain is: Arrays.equals → ArraysSupport.vectorizedMismatch → LIRGenerator::do_vectorizedMismatch
>> Within the do_vectorizedMismatch() method, a move instruction constructs an LIR_Op1. During LIR to machine code generation, LIR_Assembler::stack2reg was called.
>>
>> In this case, the src operand has type T_LONG and the dst operand has type T_ADDRESS. This combination triggers an assert in stack2reg, due to a mismatch between the stack slot type and register type handling.
>>
>> Importantly, this path ( LIR_Assembler::stack2reg was called ) is only taken when src is forced onto the stack. To reliably trigger this condition, the test is run with the -Xcomp option to force compilation and increase register pressure.
>>
>> A reference to the relevant code paths is provided below :
>> <img width="1260" height="720" alt="image1" src="https://github.com/user-attachments/assets/d0d6a8e2-4316-4475-86a6-58f5f274682c" />
>> <img width="598" height="206" alt="image2" src="https://github.com/user-attachments/assets/90d1bcdd-c9fa-4598-b8a6-101101caad9c" />
>>
>> On 64-bit platforms, although T_ADDRESS is classified as single_size, it is in fact 64 bits wide ,represent a single 64-bit general-purpose register and it can hold a T_LONG value, which is also 64 bits.
>>
>> However, T_LONG is defined as double_size, requiring two local variable slots or a pair of registers in the JVM's abstract model. This mismatch stems from the fact that T_ADDRESS is platform-dependent: it's 32 bits on 32-bit platforms, and 64 bits on 64-bit platforms — yet its size class...
>
> I think it is good to detect mismatches between T_LONG and T_ADDRESS, so I'd rather not relax the checks. Why not fix
> do_vectorizedMismatch() to use new_register(T_ADDRESS)? And maybe file a separate RFE to cleanup this confusion that new_pointer_register() causes.
@dean-long Thanks for the feedback!
Initially, I also considered modifying do_vectorizedMismatch() to use new_register(T_ADDRESS), as you suggested. However, I found that this change would trigger a series of follow-up modifications. as shown below:
<img width="847" height="179" alt="image3" src="https://github.com/user-attachments/assets/3dcbc5b7-5239-4154-a2f0-0c5f2d2ae5ac" />
<img width="783" height="190" alt="image4" src="https://github.com/user-attachments/assets/83fbce3f-9845-4ec1-a731-a1c6c88d1820" />
That’s why I opted for a more localized fix . I believe this is still a reasonable compromise. On 64-bit platforms, both T_ADDRESS and T_LONG are 64-bit wide, and general-purpose registers are capable of holding either type. Moreover, the code already uses movptr for moving 64-bit wide data , as shown below:
<img width="1016" height="346" alt="image5" src="https://github.com/user-attachments/assets/fce9a083-aa4f-4d8b-a77f-9683d1f761b6" />
So semantically, this modification in PR seems safe and practical in this context.
That said, I fully agree that the current treatment of new_pointer_register() is a bit confusing, If you, or other experts familiar with this area, believe the RFE is reasonable and it gets opened, I’d be happy to take on the implementation.
Thanks again for your insights, and I look forward to your feedback.
-------------
PR Comment: https://git.openjdk.org/jdk/pull/26462#issuecomment-3116255535
More information about the hotspot-compiler-dev
mailing list